Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit d737aa6

Browse files
committedJul 15, 2020
feat: esModule export named
1 parent 56c0427 commit d737aa6

10 files changed

+380
-30
lines changed
 

‎README.md

+51
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ module.exports = {
119119
| **[`localsConvention`](#localsconvention)** | `{String}` | `'asIs'` | Style of exported classnames |
120120
| **[`onlyLocals`](#onlylocals)** | `{Boolean}` | `false` | Export only locals |
121121
| **[`esModule`](#esmodule)** | `{Boolean}` | `false` | Use ES modules syntax |
122+
| **[`namedExport`](#namedExport)** | `{Boolean}` | `false` | Use ES modules named export |
122123

123124
### `url`
124125

@@ -1035,6 +1036,56 @@ module.exports = {
10351036
};
10361037
```
10371038

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+
10381089
## Examples
10391090

10401091
### Assets

‎src/index.js

+29-4
Original file line numberDiff line numberDiff line change
@@ -171,26 +171,51 @@ export default function loader(content, map, meta) {
171171
);
172172
});
173173

174-
const { localsConvention } = options;
174+
const namedExport =
175+
typeof options.modules === 'object' &&
176+
typeof options.modules.namedExport !== 'undefined'
177+
? options.modules.namedExport
178+
: false;
179+
180+
const { localsConvention } = namedExport
181+
? { localsConvention: 'camelCaseOnly' }
182+
: options;
183+
175184
const esModule =
176185
typeof options.esModule !== 'undefined' ? options.esModule : false;
177186

178-
const importCode = getImportCode(this, exportType, imports, esModule);
187+
if (Boolean(namedExport) && Boolean(namedExport) !== Boolean(esModule)) {
188+
this.emitError(
189+
new Error(
190+
'`Options.module.namedExport` cannot be used without `options.esModule`'
191+
)
192+
);
193+
}
194+
195+
const importCode = getImportCode(
196+
this,
197+
exportType,
198+
imports,
199+
esModule,
200+
namedExport
201+
);
179202
const moduleCode = getModuleCode(
180203
result,
181204
exportType,
182205
sourceMap,
183206
apiImports,
184207
urlReplacements,
185208
icssReplacements,
186-
esModule
209+
esModule,
210+
namedExport
187211
);
188212
const exportCode = getExportCode(
189213
exports,
190214
exportType,
191215
localsConvention,
192216
icssReplacements,
193-
esModule
217+
esModule,
218+
namedExport
194219
);
195220

196221
return callback(null, `${importCode}${moduleCode}${exportCode}`);

‎src/options.json

+4
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@
9090
"instanceof": "Function"
9191
}
9292
]
93+
},
94+
"namedExport": {
95+
"description": "Use the named export ES modules.",
96+
"type": "boolean"
9397
}
9498
}
9599
}

‎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
@@ -258,7 +258,13 @@ function getPreRequester({ loaders, loaderIndex }) {
258258
};
259259
}
260260

261-
function getImportCode(loaderContext, exportType, imports, esModule) {
261+
function getImportCode(
262+
loaderContext,
263+
exportType,
264+
imports,
265+
esModule,
266+
namedExport
267+
) {
262268
let code = '';
263269

264270
if (exportType === 'full') {
@@ -273,10 +279,12 @@ function getImportCode(loaderContext, exportType, imports, esModule) {
273279
}
274280

275281
for (const item of imports) {
276-
const { importName, url } = item;
282+
const { importName, url, icss } = item;
277283

278284
code += esModule
279-
? `import ${importName} from ${url};\n`
285+
? icss && namedExport
286+
? `import ${importName}, * as ${importName}_NAMED___ from ${url};\n`
287+
: `import ${importName} from ${url};\n`
280288
: `var ${importName} = require(${url});\n`;
281289
}
282290

@@ -290,7 +298,8 @@ function getModuleCode(
290298
apiImports,
291299
urlReplacements,
292300
icssReplacements,
293-
esModule
301+
esModule,
302+
namedExport
294303
) {
295304
if (exportType !== 'full') {
296305
return 'var ___CSS_LOADER_EXPORT___ = {};\n';
@@ -339,9 +348,12 @@ function getModuleCode(
339348
for (const replacement of icssReplacements) {
340349
const { replacementName, importName, localName } = replacement;
341350

342-
code = code.replace(
343-
new RegExp(replacementName, 'g'),
344-
() => `" + ${importName}.locals[${JSON.stringify(localName)}] + "`
351+
code = code.replace(new RegExp(replacementName, 'g'), () =>
352+
namedExport
353+
? `" + ${importName}_NAMED___[${JSON.stringify(
354+
camelCase(localName)
355+
)}] + "`
356+
: `" + ${importName}.locals[${JSON.stringify(localName)}] + "`
345357
);
346358
}
347359

@@ -359,17 +371,23 @@ function getExportCode(
359371
exportType,
360372
localsConvention,
361373
icssReplacements,
362-
esModule
374+
esModule,
375+
namedExport
363376
) {
364377
let code = '';
365378
let localsCode = '';
379+
let namedCode = '';
366380

367381
const addExportToLocalsCode = (name, value) => {
368382
if (localsCode) {
369383
localsCode += `,\n`;
370384
}
371385

372386
localsCode += `\t${JSON.stringify(name)}: ${JSON.stringify(value)}`;
387+
388+
if (namedExport) {
389+
namedCode += `export const ${name} = ${JSON.stringify(value)};\n`;
390+
}
373391
};
374392

375393
for (const { name, value } of exports) {
@@ -416,10 +434,22 @@ function getExportCode(
416434
new RegExp(replacementName, 'g'),
417435
() => `" + ${importName}.locals[${JSON.stringify(localName)}] + "`
418436
);
437+
438+
if (namedExport) {
439+
namedCode = namedCode.replace(
440+
new RegExp(replacementName, 'g'),
441+
() =>
442+
`" + ${importName}_NAMED___[${JSON.stringify(
443+
camelCase(localName)
444+
)}] + "`
445+
);
446+
}
419447
}
420448

421449
if (localsCode) {
422-
code += `___CSS_LOADER_EXPORT___.locals = {\n${localsCode}\n};\n`;
450+
code += namedCode
451+
? `${namedCode}\n`
452+
: `___CSS_LOADER_EXPORT___.locals = {\n${localsCode}\n};\n`;
423453
}
424454

425455
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)
Please sign in to comment.