Skip to content

Commit fabc58a

Browse files
feat: esModule export named
1 parent cb80db0 commit fabc58a

File tree

8 files changed

+330
-12
lines changed

8 files changed

+330
-12
lines changed

README.md

+51-1
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ module.exports = {
118118
| **[`importLoaders`](#importloaders)** | `{Number}` | `0` | Enables/Disables or setups number of loaders applied before CSS loader |
119119
| **[`onlyLocals`](#onlylocals)** | `{Boolean}` | `false` | Export only locals |
120120
| **[`esModule`](#esmodule)** | `{Boolean}` | `false` | Use ES modules syntax |
121+
| **[`namedExport`](#namedExport)** | `{Boolean}` | `false` | Use ES modules named export |
121122

122123
### `url`
123124

@@ -531,7 +532,6 @@ module.exports = {
531532
mode: 'local',
532533
exportGlobals: true,
533534
localIdentName: '[path][name]__[local]--[hash:base64:5]',
534-
localsConvention: 'camelCase',
535535
context: path.resolve(__dirname, 'src'),
536536
hashPrefix: 'my-custom-hash',
537537
},
@@ -1036,6 +1036,56 @@ module.exports = {
10361036
};
10371037
```
10381038

1039+
### `namedExport`
1040+
1041+
Type: `Boolean`
1042+
Default: `false`
1043+
1044+
Enable/disable ES modules named export for css classes.
1045+
Names of exported classes are converted to camelCase.
1046+
1047+
**styles.css**
1048+
1049+
```css
1050+
.foo-baz {
1051+
color: red;
1052+
}
1053+
.bar {
1054+
color: blue;
1055+
}
1056+
```
1057+
1058+
**index.js**
1059+
1060+
```js
1061+
import { fooBaz, bar } from './styles.css';
1062+
1063+
console.log(fooBaz, bar);
1064+
```
1065+
1066+
You can enable a ES module named export using:
1067+
1068+
**webpack.config.js**
1069+
1070+
```js
1071+
module.exports = {
1072+
module: {
1073+
rules: [
1074+
{
1075+
test: /\.css$/i,
1076+
loader: 'css-loader',
1077+
options: {
1078+
esModule: true,
1079+
modules: {
1080+
namedExport: true,
1081+
},
1082+
},
1083+
},
1084+
],
1085+
},
1086+
};
1087+
```
1088+
10391089
## Examples
10401090

10411091
### Assets

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

+39-9
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,13 @@ function getPreRequester({ loaders, loaderIndex }) {
264264
};
265265
}
266266

267-
function getImportCode(loaderContext, exportType, imports, esModule) {
267+
function getImportCode(
268+
loaderContext,
269+
exportType,
270+
imports,
271+
esModule,
272+
namedExport
273+
) {
268274
let code = '';
269275

270276
if (exportType === 'full') {
@@ -279,10 +285,12 @@ function getImportCode(loaderContext, exportType, imports, esModule) {
279285
}
280286

281287
for (const item of imports) {
282-
const { importName, url } = item;
288+
const { importName, url, icss } = item;
283289

284290
code += esModule
285-
? `import ${importName} from ${url};\n`
291+
? icss && namedExport
292+
? `import ${importName}, * as ${importName}_NAMED___ from ${url};\n`
293+
: `import ${importName} from ${url};\n`
286294
: `var ${importName} = require(${url});\n`;
287295
}
288296

@@ -296,7 +304,8 @@ function getModuleCode(
296304
apiImports,
297305
urlReplacements,
298306
icssReplacements,
299-
esModule
307+
esModule,
308+
namedExport
300309
) {
301310
if (exportType !== 'full') {
302311
return 'var ___CSS_LOADER_EXPORT___ = {};\n';
@@ -345,9 +354,12 @@ function getModuleCode(
345354
for (const replacement of icssReplacements) {
346355
const { replacementName, importName, localName } = replacement;
347356

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

@@ -365,17 +377,23 @@ function getExportCode(
365377
exportType,
366378
icssReplacements,
367379
esModule,
368-
modulesOptions
380+
modulesOptions,
381+
namedExport
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 (namedExport) {
395+
namedCode += `export const ${name} = ${JSON.stringify(value)};\n`;
396+
}
379397
};
380398

381399
for (const { name, value } of exports) {
@@ -422,10 +440,22 @@ function getExportCode(
422440
new RegExp(replacementName, 'g'),
423441
() => `" + ${importName}.locals[${JSON.stringify(localName)}] + "`
424442
);
443+
444+
if (namedExport) {
445+
namedCode = namedCode.replace(
446+
new RegExp(replacementName, 'g'),
447+
() =>
448+
`" + ${importName}_NAMED___[${JSON.stringify(
449+
camelCase(localName)
450+
)}] + "`
451+
);
452+
}
425453
}
426454

427455
if (localsCode) {
428-
code += `___CSS_LOADER_EXPORT___.locals = {\n${localsCode}\n};\n`;
456+
code += namedCode
457+
? `${namedCode}\n`
458+
: `___CSS_LOADER_EXPORT___.locals = {\n${localsCode}\n};\n`;
429459
}
430460

431461
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 exportNamed 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 "exportNamed" option: errors 1`] = `Array []`;
20+
21+
exports[`"esModule" option should work js template with "exportNamed" 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 "exportNamed" 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 "exportNamed" 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 "exportNamed" option with nested import: errors 1`] = `Array []`;
113+
114+
exports[`"esModule" option should work with "exportNamed" 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, \\".qwCT06AE1ZDvQtiz0EQJ8 {\\\\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 = \\"qwCT06AE1ZDvQtiz0EQJ8\\";
125+
126+
export default ___CSS_LOADER_EXPORT___;
127+
"
128+
`;
129+
130+
exports[`"esModule" option should work with "exportNamed" 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+
".qwCT06AE1ZDvQtiz0EQJ8 {
141+
color: red;
142+
}
143+
",
144+
"",
145+
],
146+
]
147+
`;
148+
149+
exports[`"esModule" option should work with "exportNamed" option with nested import: warnings 1`] = `Array []`;
150+
151+
exports[`"esModule" option should work with "exportNamed" option: errors 1`] = `Array []`;
152+
153+
exports[`"esModule" option should work with "exportNamed" 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, \\"._1Cb_30DnkF22nebwtzVBFY {\\\\n color: red;\\\\n}\\\\n\\\\n.bar {\\\\n color: red;\\\\n}\\\\n\\", \\"\\"]);
159+
// Exports
160+
export const barBaz = \\"_1Cb_30DnkF22nebwtzVBFY\\";
161+
162+
export default ___CSS_LOADER_EXPORT___;
163+
"
164+
`;
165+
166+
exports[`"esModule" option should work with "exportNamed" option: result 1`] = `
167+
Array [
168+
Array [
169+
"./es-module/named/base/index.css",
170+
"._1Cb_30DnkF22nebwtzVBFY {
171+
color: red;
172+
}
173+
174+
.bar {
175+
color: red;
176+
}
177+
",
178+
"",
179+
],
180+
]
181+
`;
182+
183+
exports[`"esModule" option should work with "exportNamed" 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`] = `

0 commit comments

Comments
 (0)