Skip to content

Commit d139ec1

Browse files
authoredJul 16, 2020
feat: named export for locals (#1108)
1 parent cb80db0 commit d139ec1

19 files changed

+435
-31
lines changed
 

‎README.md

+54-1
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,7 @@ module.exports = {
534534
localsConvention: 'camelCase',
535535
context: path.resolve(__dirname, 'src'),
536536
hashPrefix: 'my-custom-hash',
537+
namedExport: true,
537538
},
538539
},
539540
},
@@ -756,7 +757,7 @@ module.exports = {
756757
};
757758
```
758759

759-
### `localsConvention`
760+
##### `localsConvention`
760761

761762
Type: `String`
762763
Default: `'asIs'`
@@ -915,6 +916,58 @@ module.exports = {
915916
};
916917
```
917918

919+
##### `namedExport`
920+
921+
Type: `Boolean`
922+
Default: `false`
923+
924+
Enable/disable ES modules named export for css classes.
925+
Names of exported classes are converted to camelCase.
926+
927+
> i It is not allowed to use JavaScript reserved words in css class names
928+
929+
**styles.css**
930+
931+
```css
932+
.foo-baz {
933+
color: red;
934+
}
935+
.bar {
936+
color: blue;
937+
}
938+
```
939+
940+
**index.js**
941+
942+
```js
943+
import { fooBaz, bar } from './styles.css';
944+
945+
console.log(fooBaz, bar);
946+
```
947+
948+
You can enable a ES module named export using:
949+
950+
**webpack.config.js**
951+
952+
```js
953+
module.exports = {
954+
module: {
955+
rules: [
956+
{
957+
test: /\.css$/i,
958+
loader: 'css-loader',
959+
options: {
960+
esModule: true,
961+
modules: {
962+
namedExport: true,
963+
},
964+
},
965+
},
966+
],
967+
},
968+
};
969+
```
970+
918971
### `sourceMap`
919972

920973
Type: `Boolean`

‎src/index.js

+20-5
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,22 @@ export default function loader(content, map, meta) {
4141
const urlHandler = (url) =>
4242
stringifyRequest(this, preRequester(options.importLoaders) + url);
4343

44+
const esModule =
45+
typeof options.esModule !== 'undefined' ? options.esModule : false;
46+
4447
let modulesOptions;
4548

4649
if (shouldUseModulesPlugins(options.modules, this.resourcePath)) {
4750
modulesOptions = getModulesOptions(options, this);
4851

52+
if (modulesOptions.namedExport === true && esModule === false) {
53+
this.emitError(
54+
new Error(
55+
'`Options.module.namedExport` cannot be used without `options.esModule`'
56+
)
57+
);
58+
}
59+
4960
plugins.push(...getModulesPlugins(modulesOptions, this));
5061

5162
const icssResolver = this.getResolve({
@@ -177,18 +188,22 @@ export default function loader(content, map, meta) {
177188
);
178189
});
179190

180-
const esModule =
181-
typeof options.esModule !== 'undefined' ? options.esModule : false;
182-
183-
const importCode = getImportCode(this, exportType, imports, esModule);
191+
const importCode = getImportCode(
192+
this,
193+
exportType,
194+
imports,
195+
esModule,
196+
modulesOptions
197+
);
184198
const moduleCode = getModuleCode(
185199
result,
186200
exportType,
187201
sourceMap,
188202
apiImports,
189203
urlReplacements,
190204
icssReplacements,
191-
esModule
205+
esModule,
206+
modulesOptions
192207
);
193208
const exportCode = getExportCode(
194209
exports,

‎src/options.json

+4
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@
100100
"instanceof": "Function"
101101
}
102102
]
103+
},
104+
"namedExport": {
105+
"description": "Use the named export ES modules.",
106+
"type": "boolean"
103107
}
104108
}
105109
}

‎src/plugins/postcss-icss-parser.js

+1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ export default postcss.plugin(
8383
value: {
8484
// 'CSS_LOADER_ICSS_IMPORT'
8585
order: 0,
86+
icss: true,
8687
importName,
8788
url: options.urlHandler(resolvedUrl),
8889
index: currentIndex,

‎src/utils.js

+40-8
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ function getModulesOptions(options, loaderContext) {
146146
getLocalIdent,
147147
hashPrefix: '',
148148
exportGlobals: false,
149+
namedExport: false,
149150
};
150151

151152
if (
@@ -264,7 +265,13 @@ function getPreRequester({ loaders, loaderIndex }) {
264265
};
265266
}
266267

267-
function getImportCode(loaderContext, exportType, imports, esModule) {
268+
function getImportCode(
269+
loaderContext,
270+
exportType,
271+
imports,
272+
esModule,
273+
modulesOptions
274+
) {
268275
let code = '';
269276

270277
if (exportType === 'full') {
@@ -279,10 +286,12 @@ function getImportCode(loaderContext, exportType, imports, esModule) {
279286
}
280287

281288
for (const item of imports) {
282-
const { importName, url } = item;
289+
const { importName, url, icss } = item;
283290

284291
code += esModule
285-
? `import ${importName} from ${url};\n`
292+
? icss && modulesOptions.namedExport
293+
? `import ${importName}, * as ${importName}_NAMED___ from ${url};\n`
294+
: `import ${importName} from ${url};\n`
286295
: `var ${importName} = require(${url});\n`;
287296
}
288297

@@ -296,7 +305,8 @@ function getModuleCode(
296305
apiImports,
297306
urlReplacements,
298307
icssReplacements,
299-
esModule
308+
esModule,
309+
modulesOptions
300310
) {
301311
if (exportType !== 'full') {
302312
return 'var ___CSS_LOADER_EXPORT___ = {};\n';
@@ -345,9 +355,12 @@ function getModuleCode(
345355
for (const replacement of icssReplacements) {
346356
const { replacementName, importName, localName } = replacement;
347357

348-
code = code.replace(
349-
new RegExp(replacementName, 'g'),
350-
() => `" + ${importName}.locals[${JSON.stringify(localName)}] + "`
358+
code = code.replace(new RegExp(replacementName, 'g'), () =>
359+
modulesOptions.namedExport
360+
? `" + ${importName}_NAMED___[${JSON.stringify(
361+
camelCase(localName)
362+
)}] + "`
363+
: `" + ${importName}.locals[${JSON.stringify(localName)}] + "`
351364
);
352365
}
353366

@@ -369,13 +382,20 @@ function getExportCode(
369382
) {
370383
let code = '';
371384
let localsCode = '';
385+
let namedCode = '';
372386

373387
const addExportToLocalsCode = (name, value) => {
374388
if (localsCode) {
375389
localsCode += `,\n`;
376390
}
377391

378392
localsCode += `\t${JSON.stringify(name)}: ${JSON.stringify(value)}`;
393+
394+
if (modulesOptions.namedExport) {
395+
namedCode += `export const ${camelCase(name)} = ${JSON.stringify(
396+
value
397+
)};\n`;
398+
}
379399
};
380400

381401
for (const { name, value } of exports) {
@@ -422,10 +442,22 @@ function getExportCode(
422442
new RegExp(replacementName, 'g'),
423443
() => `" + ${importName}.locals[${JSON.stringify(localName)}] + "`
424444
);
445+
446+
if (modulesOptions.namedExport) {
447+
namedCode = namedCode.replace(
448+
new RegExp(replacementName, 'g'),
449+
() =>
450+
`" + ${importName}_NAMED___[${JSON.stringify(
451+
camelCase(localName)
452+
)}] + "`
453+
);
454+
}
425455
}
426456

427457
if (localsCode) {
428-
code += `___CSS_LOADER_EXPORT___.locals = {\n${localsCode}\n};\n`;
458+
code += namedCode
459+
? `${namedCode}\n`
460+
: `___CSS_LOADER_EXPORT___.locals = {\n${localsCode}\n};\n`;
429461
}
430462

431463
code += `${

‎test/__snapshots__/esModule-option.test.js.snap

+136
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,68 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`"esModule" option should emit error when class has unsupported name: errors 1`] = `
4+
Array [
5+
"ModuleParseError: Module parse failed: Unexpected keyword 'class' (7:13)
6+
File was processed with these loaders:",
7+
]
8+
`;
9+
10+
exports[`"esModule" option should emit error when class has unsupported name: warnings 1`] = `Array []`;
11+
12+
exports[`"esModule" option should emit error when namedExport true && esModule false: errors 1`] = `
13+
Array [
14+
"ModuleError: Module Error (from \`replaced original path\`):
15+
\`Options.module.namedExport\` cannot be used without \`options.esModule\`",
16+
]
17+
`;
18+
19+
exports[`"esModule" option should work js template with "namedExport" option: errors 1`] = `Array []`;
20+
21+
exports[`"esModule" option should work js template with "namedExport" option: module 1`] = `
22+
"// Imports
23+
import ___CSS_LOADER_API_IMPORT___ from \\"../../../../../src/runtime/api.js\\";
24+
var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(false);
25+
// Module
26+
___CSS_LOADER_EXPORT___.push([module.id, \\".header-baz {\\\\n color: red;\\\\n}\\\\n\\\\n.body {\\\\n color: coral;\\\\n}\\\\n\\\\n.footer {\\\\n color: blue;\\\\n}\\\\n\\", \\"\\"]);
27+
// Exports
28+
export const headerBaz = \\"header-baz\\";
29+
export const body = \\"body\\";
30+
export const footer = \\"footer\\";
31+
32+
export default ___CSS_LOADER_EXPORT___;
33+
"
34+
`;
35+
36+
exports[`"esModule" option should work js template with "namedExport" option: result 1`] = `
37+
Object {
38+
"css": Array [
39+
Array [
40+
"./es-module/named/template/index.css",
41+
".header-baz {
42+
color: red;
43+
}
44+
45+
.body {
46+
color: coral;
47+
}
48+
49+
.footer {
50+
color: blue;
51+
}
52+
",
53+
"",
54+
],
55+
],
56+
"html": "
57+
<div class=\\"header-baz\\">
58+
<div class=\\"body\\">
59+
<div class=\\"footer\\">
60+
",
61+
}
62+
`;
63+
64+
exports[`"esModule" option should work js template with "namedExport" option: warnings 1`] = `Array []`;
65+
366
exports[`"esModule" option should work when not specified: errors 1`] = `Array []`;
467
568
exports[`"esModule" option should work when not specified: module 1`] = `
@@ -46,6 +109,79 @@ Array [
46109
47110
exports[`"esModule" option should work when not specified: warnings 1`] = `Array []`;
48111
112+
exports[`"esModule" option should work with "namedExport" option with nested import: errors 1`] = `Array []`;
113+
114+
exports[`"esModule" option should work with "namedExport" option with nested import: module 1`] = `
115+
"// Imports
116+
import ___CSS_LOADER_API_IMPORT___ from \\"../../../../../src/runtime/api.js\\";
117+
import ___CSS_LOADER_ICSS_IMPORT_0___, * as ___CSS_LOADER_ICSS_IMPORT_0____NAMED___ from \\"-!../../../../../src/index.js??[ident]!../../../modules/composes/values.css\\";
118+
var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(false);
119+
___CSS_LOADER_EXPORT___.i(___CSS_LOADER_ICSS_IMPORT_0___, \\"\\", true);
120+
// Module
121+
___CSS_LOADER_EXPORT___.push([module.id, \\".lOpALu8t_iv2-GccTMbIq {\\\\n color: \\" + ___CSS_LOADER_ICSS_IMPORT_0____NAMED___[\\"vDef\\"] + \\";\\\\n}\\\\n\\", \\"\\"]);
122+
// Exports
123+
export const vDef = \\"\\" + ___CSS_LOADER_ICSS_IMPORT_0____NAMED___[\\"vDef\\"] + \\"\\";
124+
export const ghi = \\"lOpALu8t_iv2-GccTMbIq\\";
125+
126+
export default ___CSS_LOADER_EXPORT___;
127+
"
128+
`;
129+
130+
exports[`"esModule" option should work with "namedExport" option with nested import: result 1`] = `
131+
Array [
132+
Array [
133+
"../../src/index.js?[ident]!./modules/composes/values.css",
134+
"
135+
",
136+
"",
137+
],
138+
Array [
139+
"./es-module/named/nested/index.css",
140+
".lOpALu8t_iv2-GccTMbIq {
141+
color: red;
142+
}
143+
",
144+
"",
145+
],
146+
]
147+
`;
148+
149+
exports[`"esModule" option should work with "namedExport" option with nested import: warnings 1`] = `Array []`;
150+
151+
exports[`"esModule" option should work with "namedExport" option: errors 1`] = `Array []`;
152+
153+
exports[`"esModule" option should work with "namedExport" option: module 1`] = `
154+
"// Imports
155+
import ___CSS_LOADER_API_IMPORT___ from \\"../../../../../src/runtime/api.js\\";
156+
var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(false);
157+
// Module
158+
___CSS_LOADER_EXPORT___.push([module.id, \\".jSf5EjnYI1bvqKHBrOPz6 {\\\\n color: red;\\\\n}\\\\n\\\\n.bar {\\\\n color: red;\\\\n}\\\\n\\", \\"\\"]);
159+
// Exports
160+
export const barBaz = \\"jSf5EjnYI1bvqKHBrOPz6\\";
161+
162+
export default ___CSS_LOADER_EXPORT___;
163+
"
164+
`;
165+
166+
exports[`"esModule" option should work with "namedExport" option: result 1`] = `
167+
Array [
168+
Array [
169+
"./es-module/named/base/index.css",
170+
".jSf5EjnYI1bvqKHBrOPz6 {
171+
color: red;
172+
}
173+
174+
.bar {
175+
color: red;
176+
}
177+
",
178+
"",
179+
],
180+
]
181+
`;
182+
183+
exports[`"esModule" option should work with "namedExport" option: warnings 1`] = `Array []`;
184+
49185
exports[`"esModule" option should work with a value equal to "false": errors 1`] = `Array []`;
50186
51187
exports[`"esModule" option should work with a value equal to "false": module 1`] = `

‎test/__snapshots__/validate-options.test.js.snap

+21-15
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ exports[`validate options should throw an error on the "importLoaders" option wi
3939
exports[`validate options should throw an error on the "modules" option with "{"auto":"invalid"}" value 1`] = `
4040
"Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema.
4141
- options.modules should be one of these:
42-
boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent? }
42+
boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent?, namedExport? }
4343
-> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules).
4444
Details:
4545
* options.modules.auto should be one of these:
@@ -63,7 +63,7 @@ exports[`validate options should throw an error on the "modules" option with "{"
6363
exports[`validate options should throw an error on the "modules" option with "{"getLocalIdent":[]}" value 1`] = `
6464
"Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema.
6565
- options.modules should be one of these:
66-
boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent? }
66+
boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent?, namedExport? }
6767
-> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules).
6868
Details:
6969
* options.modules.getLocalIdent should be one of these:
@@ -86,7 +86,7 @@ exports[`validate options should throw an error on the "modules" option with "{"
8686
exports[`validate options should throw an error on the "modules" option with "{"localIdentRegExp":true}" value 1`] = `
8787
"Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema.
8888
- options.modules should be one of these:
89-
boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent? }
89+
boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent?, namedExport? }
9090
-> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules).
9191
Details:
9292
* options.modules.localIdentRegExp should be one of these:
@@ -106,7 +106,7 @@ exports[`validate options should throw an error on the "modules" option with "{"
106106
exports[`validate options should throw an error on the "modules" option with "{"mode":"globals"}" value 1`] = `
107107
"Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema.
108108
- options.modules should be one of these:
109-
boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent? }
109+
boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent?, namedExport? }
110110
-> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules).
111111
Details:
112112
* options.modules.mode should be one of these:
@@ -120,7 +120,7 @@ exports[`validate options should throw an error on the "modules" option with "{"
120120
exports[`validate options should throw an error on the "modules" option with "{"mode":"locals"}" value 1`] = `
121121
"Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema.
122122
- options.modules should be one of these:
123-
boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent? }
123+
boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent?, namedExport? }
124124
-> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules).
125125
Details:
126126
* options.modules.mode should be one of these:
@@ -134,7 +134,7 @@ exports[`validate options should throw an error on the "modules" option with "{"
134134
exports[`validate options should throw an error on the "modules" option with "{"mode":"pures"}" value 1`] = `
135135
"Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema.
136136
- options.modules should be one of these:
137-
boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent? }
137+
boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent?, namedExport? }
138138
-> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules).
139139
Details:
140140
* options.modules.mode should be one of these:
@@ -148,7 +148,7 @@ exports[`validate options should throw an error on the "modules" option with "{"
148148
exports[`validate options should throw an error on the "modules" option with "{"mode":true}" value 1`] = `
149149
"Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema.
150150
- options.modules should be one of these:
151-
boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent? }
151+
boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent?, namedExport? }
152152
-> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules).
153153
Details:
154154
* options.modules.mode should be one of these:
@@ -159,56 +159,62 @@ exports[`validate options should throw an error on the "modules" option with "{"
159159
* options.modules.mode should be an instance of function."
160160
`;
161161
162+
exports[`validate options should throw an error on the "modules" option with "{"namedExport":"invalid"}" value 1`] = `
163+
"Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema.
164+
- options.modules.namedExport should be a boolean.
165+
-> Use the named export ES modules."
166+
`;
167+
162168
exports[`validate options should throw an error on the "modules" option with "globals" value 1`] = `
163169
"Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema.
164170
- options.modules should be one of these:
165-
boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent? }
171+
boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent?, namedExport? }
166172
-> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules).
167173
Details:
168174
* options.modules should be a boolean.
169175
* options.modules should be one of these:
170176
\\"local\\" | \\"global\\" | \\"pure\\"
171177
* options.modules should be an object:
172-
object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent? }"
178+
object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent?, namedExport? }"
173179
`;
174180
175181
exports[`validate options should throw an error on the "modules" option with "locals" value 1`] = `
176182
"Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema.
177183
- options.modules should be one of these:
178-
boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent? }
184+
boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent?, namedExport? }
179185
-> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules).
180186
Details:
181187
* options.modules should be a boolean.
182188
* options.modules should be one of these:
183189
\\"local\\" | \\"global\\" | \\"pure\\"
184190
* options.modules should be an object:
185-
object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent? }"
191+
object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent?, namedExport? }"
186192
`;
187193
188194
exports[`validate options should throw an error on the "modules" option with "pures" value 1`] = `
189195
"Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema.
190196
- options.modules should be one of these:
191-
boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent? }
197+
boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent?, namedExport? }
192198
-> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules).
193199
Details:
194200
* options.modules should be a boolean.
195201
* options.modules should be one of these:
196202
\\"local\\" | \\"global\\" | \\"pure\\"
197203
* options.modules should be an object:
198-
object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent? }"
204+
object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent?, namedExport? }"
199205
`;
200206
201207
exports[`validate options should throw an error on the "modules" option with "true" value 1`] = `
202208
"Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema.
203209
- options.modules should be one of these:
204-
boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent? }
210+
boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent?, namedExport? }
205211
-> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules).
206212
Details:
207213
* options.modules should be a boolean.
208214
* options.modules should be one of these:
209215
\\"local\\" | \\"global\\" | \\"pure\\"
210216
* options.modules should be an object:
211-
object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent? }"
217+
object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent?, namedExport? }"
212218
`;
213219
214220
exports[`validate options should throw an error on the "onlyLocals" option with "true" value 1`] = `

‎test/esModule-option.test.js

+83
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,87 @@ describe('"esModule" option', () => {
100100
expect(getWarnings(stats)).toMatchSnapshot('warnings');
101101
expect(getErrors(stats)).toMatchSnapshot('errors');
102102
});
103+
104+
it('should work with "namedExport" option', async () => {
105+
const compiler = getCompiler('./es-module/named/base/index.js', {
106+
esModule: true,
107+
modules: {
108+
namedExport: true,
109+
},
110+
});
111+
const stats = await compile(compiler);
112+
113+
expect(
114+
getModuleSource('./es-module/named/base/index.css', stats)
115+
).toMatchSnapshot('module');
116+
expect(getExecutedCode('main.bundle.js', compiler, stats)).toMatchSnapshot(
117+
'result'
118+
);
119+
expect(getWarnings(stats)).toMatchSnapshot('warnings');
120+
expect(getErrors(stats)).toMatchSnapshot('errors');
121+
});
122+
123+
it('should work with "namedExport" option with nested import', async () => {
124+
const compiler = getCompiler('./es-module/named/nested/index.js', {
125+
esModule: true,
126+
modules: {
127+
namedExport: true,
128+
},
129+
});
130+
const stats = await compile(compiler);
131+
132+
expect(
133+
getModuleSource('./es-module/named/nested/index.css', stats)
134+
).toMatchSnapshot('module');
135+
expect(getExecutedCode('main.bundle.js', compiler, stats)).toMatchSnapshot(
136+
'result'
137+
);
138+
expect(getWarnings(stats)).toMatchSnapshot('warnings');
139+
expect(getErrors(stats)).toMatchSnapshot('errors');
140+
});
141+
142+
it('should work js template with "namedExport" option', async () => {
143+
const compiler = getCompiler('./es-module/named/template/index.js', {
144+
esModule: true,
145+
modules: {
146+
localIdentName: '[local]',
147+
namedExport: true,
148+
},
149+
});
150+
const stats = await compile(compiler);
151+
152+
expect(
153+
getModuleSource('./es-module/named/template/index.css', stats)
154+
).toMatchSnapshot('module');
155+
expect(getExecutedCode('main.bundle.js', compiler, stats)).toMatchSnapshot(
156+
'result'
157+
);
158+
expect(getWarnings(stats)).toMatchSnapshot('warnings');
159+
expect(getErrors(stats)).toMatchSnapshot('errors');
160+
});
161+
162+
it('should emit error when class has unsupported name', async () => {
163+
const compiler = getCompiler('./es-module/named/broken/index.js', {
164+
esModule: true,
165+
modules: {
166+
namedExport: true,
167+
},
168+
});
169+
const stats = await compile(compiler);
170+
171+
expect(getWarnings(stats)).toMatchSnapshot('warnings');
172+
expect(getErrors(stats, true)).toMatchSnapshot('errors');
173+
});
174+
175+
it('should emit error when namedExport true && esModule false', async () => {
176+
const compiler = getCompiler('./es-module/named/base/index.js', {
177+
esModule: false,
178+
modules: {
179+
namedExport: true,
180+
},
181+
});
182+
const stats = await compile(compiler);
183+
184+
expect(getErrors(stats)).toMatchSnapshot('errors');
185+
});
103186
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
:local(.bar-baz) {
2+
color: red;
3+
}
4+
5+
:global(.bar) {
6+
color: red;
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import css from './index.css';
2+
3+
__export__ = css;
4+
5+
export default css;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:local(.class) {
2+
color: red;
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import css from './index.css';
2+
3+
__export__ = css;
4+
5+
export default css;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@value v-def from '../../../modules/composes/values.css';
2+
3+
.ghi {
4+
color: v-def;
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import css from './index.css';
2+
3+
__export__ = css;
4+
5+
export default css;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
:local(.header-baz) {
2+
color: red;
3+
}
4+
5+
:local(.body) {
6+
color: coral;
7+
}
8+
9+
:local(.footer) {
10+
color: blue;
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import css from './index.css';
2+
import html from './template.js';
3+
4+
const result = {css, html};
5+
6+
__export__ = result;
7+
8+
export default result;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { headerBaz, body, footer } from './index.css';
2+
3+
let html = '\n';
4+
5+
html += `<div class="${headerBaz}">\n`;
6+
html += `<div class="${body}">\n`;
7+
html += `<div class="${footer}">\n`;
8+
9+
__export__ = html;
10+
11+
export default html;

‎test/fixtures/simple.css

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
.class {
1+
.some-class {
22
color: red;
33
}

‎test/validate-options.test.js

+15-1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ describe('validate options', () => {
3737
{ localsConvention: 'camelCaseOnly' },
3838
{ localsConvention: 'dashes' },
3939
{ localsConvention: 'dashesOnly' },
40+
{ namedExport: true },
41+
{ namedExport: false },
4042
],
4143
failure: [
4244
'true',
@@ -55,6 +57,7 @@ describe('validate options', () => {
5557
{ exportGlobals: 'invalid' },
5658
{ auto: 'invalid' },
5759
{ localsConvention: 'unknown' },
60+
{ namedExport: 'invalid' },
5861
],
5962
},
6063
sourceMap: {
@@ -94,7 +97,18 @@ describe('validate options', () => {
9497
it(`should ${
9598
type === 'success' ? 'successfully validate' : 'throw an error on'
9699
} the "${key}" option with "${stringifyValue(value)}" value`, async () => {
97-
const compiler = getCompiler('simple.js', { [key]: value });
100+
const options = { [key]: value };
101+
102+
if (
103+
key === 'modules' &&
104+
typeof value === 'object' &&
105+
value.namedExport === true
106+
) {
107+
options.esModule = true;
108+
}
109+
110+
const compiler = getCompiler('simple.js', options);
111+
98112
let stats;
99113

100114
try {

0 commit comments

Comments
 (0)
Please sign in to comment.