@@ -3,10 +3,10 @@ import { types as t } from "@babel/core";
3
3
import type babel from "@babel/core" ;
4
4
import chalk from "chalk" ;
5
5
6
- import { getImportInfo , getTemplFromStrCls } from "./transforms.js" ;
6
+ import { transformClassNames , transformImport } from "./transforms.js" ;
7
7
import { CSSModuleError } from "./utils.js" ;
8
8
9
- function ImportDeclaration ( path : NodePath < t . ImportDeclaration > , state : PluginPass ) {
9
+ function ImportDeclaration ( path : NodePath < t . ImportDeclaration > , { pluginState } : PluginPass ) {
10
10
// we're only interested in scss/sass/css imports
11
11
if ( ! / .m o d u l e .( s [ a c ] s s | c s s ) ( : .* ) ? $ / iu. test ( path . node . source . value ) ) {
12
12
return ;
@@ -15,90 +15,67 @@ function ImportDeclaration(path: NodePath<t.ImportDeclaration>, state: PluginPas
15
15
// saving path for error messages
16
16
CSSModuleError . path = path ;
17
17
18
- if ( path . node . specifiers . length > 1 && ! t . isImportDefaultSpecifier ( path . node . specifiers [ 0 ] ) ) {
19
- // Syntax: import { classA, classB } from "./m1.module.css"
20
- throw new CSSModuleError ( `Import CSS-Module as a default import on '${ chalk . cyan ( path . node . source . value ) } '` ) ;
21
- }
22
- if ( path . node . specifiers . length > 1 ) {
23
- // Syntax: import style, { classA, classB } from "./m1.module.css"
24
- throw new CSSModuleError ( `More than one import found on '${ chalk . cyan ( path . node . source . value ) } '` ) ;
25
- }
26
-
27
- let moduleInfo = getImportInfo ( path . node ) ;
28
- if ( moduleInfo . hasSpecifier ) {
29
- let importSpecifier = path . node . specifiers [ 0 ] . local ;
30
- if ( importSpecifier . name in state . pluginState . modules . namedModules ) {
31
- throw new CSSModuleError ( `CSS-Module ${ chalk . yellow ( `'${ importSpecifier . name } '` ) } has already been declared` ) ;
32
- }
18
+ // 1. Transform import declaration
19
+ const idGenerator = ( hint : string ) => path . scope . generateUidIdentifier ( hint ) ;
20
+ const res = transformImport ( path . node , idGenerator ) ;
21
+ path . replaceWith ( res . transformedNode ) ;
22
+ path . skip ( ) ;
33
23
34
- // saving new module
35
- state . pluginState . modules . namedModules [ importSpecifier . name ] = importSpecifier . name ;
36
- } else if ( moduleInfo . default ) {
37
- if ( state . pluginState . modules . defaultModule ) {
38
- throw new CSSModuleError ( `Only one default css-module import is allowed. Provide names for all except the default module` ) ;
24
+ // 2. Add CSS module to the list
25
+ const importSpecifier = res . transformedNode . specifiers [ 0 ] . local . name ;
26
+ if ( res . generatedSpecifier ) {
27
+ if ( res . moduleLabel ) {
28
+ addCheckedModule ( res . moduleLabel , importSpecifier , pluginState . modules ) ;
29
+ } else {
30
+ // this is a default module
31
+ addCheckedDefaultModule ( importSpecifier , pluginState . modules ) ;
39
32
}
40
-
41
- let importSpecifier = path . scope . generateUidIdentifier ( "style" ) ;
42
- let newSpecifiers = [ t . importDefaultSpecifier ( importSpecifier ) ] ;
43
- let newImportDeclaration = t . importDeclaration ( newSpecifiers , t . stringLiteral ( path . node . source . value ) ) ;
44
- path . replaceWith < t . ImportDeclaration > ( newImportDeclaration ) ;
45
-
46
- // saving this module as the default module for the current translation unit.
47
- state . pluginState . modules . defaultModule = importSpecifier . name ;
48
33
} else {
49
- if ( moduleInfo . moduleName in state . pluginState . modules . namedModules ) {
50
- throw new CSSModuleError ( `CSS-Module ${ chalk . yellow ( `'${ moduleInfo . moduleName } '` ) } has already been declared` ) ;
34
+ // Verify that the module label is unique.
35
+ // Prevents scenarios where the same value is used as both a module
36
+ // label and an import specifier in different import declarations.
37
+ addCheckedModule ( importSpecifier , importSpecifier , pluginState . modules ) ;
38
+
39
+ if ( res . moduleLabel && res . moduleLabel != importSpecifier ) {
40
+ // Make module label an alias to the provided specifier
41
+ addCheckedModule ( res . moduleLabel , importSpecifier , pluginState . modules ) ;
51
42
}
52
-
53
- let importSpecifier = path . scope . generateUidIdentifier ( moduleInfo . moduleName ) ;
54
- let newSpecifiers = [ t . importDefaultSpecifier ( importSpecifier ) ] ;
55
- let newImportDeclaration = t . importDeclaration ( newSpecifiers , t . stringLiteral ( path . node . source . value ) ) ;
56
- path . replaceWith < t . ImportDeclaration > ( newImportDeclaration ) ;
57
-
58
- // saving new module
59
- state . pluginState . modules . namedModules [ moduleInfo . moduleName ] = importSpecifier . name ;
60
43
}
61
-
62
- // strips away module name from the source
63
- path . node . source . value = moduleInfo . moduleSource ; // this inplace replacment does not causes any problem with the ast
64
- path . skip ( ) ;
65
44
}
66
45
67
- function JSXAttribute ( path : NodePath < t . JSXAttribute > , state : PluginPass ) {
68
- const firstNamedModule = getFirstNamedModule ( state . pluginState . modules . namedModules ) ;
46
+ function JSXAttribute ( path : NodePath < t . JSXAttribute > , { pluginState } : PluginPass ) {
47
+ const firstNamedModule = getFirstNamedModule ( pluginState . modules . namedModules ) ;
69
48
70
49
// we only support className attribute having a string value
71
- if ( path . node . name . name != "className" || ! t . isStringLiteral ( path . node . value ) ) {
50
+ if ( path . node . name . name != "className" || ! path . node . value || ! t . isStringLiteral ( path . node . value ) ) {
72
51
return ;
73
52
}
74
53
// className values should be transformed only if we ever found a css module.
75
54
// FirstNamedModule signifies that we found at least one named css module.
76
- if ( ! state . pluginState . modules . defaultModule && ! firstNamedModule ) {
55
+ if ( ! pluginState . modules . defaultModule && ! firstNamedModule ) {
77
56
return ;
78
57
}
79
58
80
59
// saving path for error messages
81
60
CSSModuleError . path = path ;
82
61
83
62
// if no default modules is available, make the first modules as default
84
- if ( ! state . pluginState . modules . defaultModule ) {
63
+ if ( ! pluginState . modules . defaultModule ) {
85
64
if ( firstNamedModule ) {
86
- state . pluginState . modules . defaultModule = state . pluginState . modules . namedModules [ firstNamedModule ] ;
65
+ pluginState . modules . defaultModule = pluginState . modules . namedModules [ firstNamedModule ] ;
87
66
}
88
67
}
89
68
90
- let fileCSSModules = state . pluginState . modules ;
91
- let templateLiteral = getTemplFromStrCls ( path . node . value . value , fileCSSModules ) ;
69
+ let classNames = path . node . value . value ;
70
+ let templateLiteral = transformClassNames ( classNames , pluginState . modules ) ;
92
71
let jsxExpressionContainer = t . jsxExpressionContainer ( templateLiteral ) ;
93
72
let newJSXAttr = t . jsxAttribute ( t . jsxIdentifier ( "className" ) , jsxExpressionContainer ) ;
94
73
path . replaceWith ( newJSXAttr ) ;
95
74
path . skip ( ) ;
96
75
}
97
76
98
77
function API ( ) : PluginObj < PluginPass > {
99
- /**
100
- * Sets up the initial state of the plugin
101
- */
78
+ // Set up the initial state for the plugin
102
79
function pre ( this : PluginPass ) : void {
103
80
this . pluginState = {
104
81
modules : {
@@ -116,16 +93,32 @@ function API(): PluginObj<PluginPass> {
116
93
} ;
117
94
}
118
95
96
+ function addCheckedModule ( moduleLabel : string , module : string , modules : Modules ) {
97
+ if ( moduleLabel in modules . namedModules ) {
98
+ throw new CSSModuleError ( `Duplicate CSS module '${ chalk . yellow ( module ) } ' found` ) ;
99
+ }
100
+ modules . namedModules [ moduleLabel ] = module ;
101
+ }
102
+
103
+ function addCheckedDefaultModule ( module : string , modules : Modules ) {
104
+ if ( modules . defaultModule ) {
105
+ throw new CSSModuleError ( `Only one default css-module import is allowed. Provide names for all except the default module` ) ;
106
+ }
107
+ modules . defaultModule = module ;
108
+ }
109
+
119
110
export default API ;
120
111
121
112
function getFirstNamedModule ( namedModules : Modules [ "namedModules" ] ) : string | null {
122
113
for ( let module in namedModules ) return module ;
123
114
return null ;
124
115
}
125
116
117
+ type CSSModuleLabel = string ;
118
+ type CSSModuleIdentifier = string ;
126
119
export type Modules = {
127
120
defaultModule ?: string ;
128
- namedModules : { [ moduleName : string ] : string } ;
121
+ namedModules : { [ moduleLabel : CSSModuleLabel ] : CSSModuleIdentifier } ;
129
122
} ;
130
123
131
124
type PluginState = {
0 commit comments