Skip to content

Commit 791fff3

Browse files
refactor: named export (#1125)
1 parent 01e8c76 commit 791fff3

17 files changed

+327
-258
lines changed

README.md

+6-3
Original file line numberDiff line numberDiff line change
@@ -844,9 +844,10 @@ Type: `Boolean`
844844
Default: `false`
845845

846846
Enables/disables ES modules named export for locals.
847-
Names of locals are converted to camelCase.
848847

849-
> i It is not allowed to use JavaScript reserved words in css class names
848+
> ⚠ Names of locals are converted to camelcase, i.e. the `exportLocalsConvention` option has `camelCaseOnly` value by default.
849+
850+
> ⚠ It is not allowed to use JavaScript reserved words in css class names.
850851
851852
**styles.css**
852853

@@ -920,12 +921,14 @@ module.exports = {
920921
##### `exportlocalsConvention`
921922

922923
Type: `String`
923-
Default: `'asIs'`
924+
Default: based on the `modules.namedExport` option value, if `true` - `camelCaseOnly`, otherwise `asIs`
924925

925926
Style of exported class names.
926927

927928
By default, the exported JSON keys mirror the class names (i.e `asIs` value).
928929

930+
> ⚠ Only `camelCaseOnly` value allowed if you set the `namedExport` value to `true`.
931+
929932
| Name | Type | Description |
930933
| :-------------------: | :--------: | :----------------------------------------------------------------------------------------------- |
931934
| **`'asIs'`** | `{String}` | Class names will be exported as is. |

src/index.js

+11-3
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,17 @@ export default async function loader(content, map, meta) {
3636
});
3737

3838
const plugins = [];
39-
const options = normalizeOptions(rawOptions, this);
39+
const callback = this.async();
40+
41+
let options;
42+
43+
try {
44+
options = normalizeOptions(rawOptions, this);
45+
} catch (error) {
46+
callback(error);
47+
48+
return;
49+
}
4050

4151
if (shouldUseModulesPlugins(options)) {
4252
const icssResolver = this.getResolve({
@@ -117,8 +127,6 @@ export default async function loader(content, map, meta) {
117127
}
118128
}
119129

120-
const callback = this.async();
121-
122130
let result;
123131

124132
try {

src/utils.js

+33-28
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,13 @@ function getModulesOptions(rawOptions, loaderContext) {
163163
return false;
164164
}
165165
}
166+
167+
if (
168+
rawOptions.modules.namedExport === true &&
169+
typeof rawOptions.modules.exportLocalsConvention === 'undefined'
170+
) {
171+
modulesOptions.exportLocalsConvention = 'camelCaseOnly';
172+
}
166173
}
167174

168175
modulesOptions = { ...modulesOptions, ...(rawOptions.modules || {}) };
@@ -172,12 +179,18 @@ function getModulesOptions(rawOptions, loaderContext) {
172179
modulesOptions.mode = modulesOptions.mode(loaderContext.resourcePath);
173180
}
174181

175-
if (modulesOptions.namedExport === true && rawOptions.esModule === false) {
176-
loaderContext.emitError(
177-
new Error(
178-
'`Options.module.namedExport` cannot be used without `options.esModule`'
179-
)
180-
);
182+
if (modulesOptions.namedExport === true) {
183+
if (rawOptions.esModule === false) {
184+
throw new Error(
185+
'The "modules.namedExport" option requires the "esModules" option to be enabled'
186+
);
187+
}
188+
189+
if (modulesOptions.exportLocalsConvention !== 'camelCaseOnly') {
190+
throw new Error(
191+
'The "modules.namedExport" option requires the "modules.exportLocalsConvention" option to be "camelCaseOnly"'
192+
);
193+
}
181194
}
182195

183196
return modulesOptions;
@@ -411,19 +424,18 @@ function dashesCamelCase(str) {
411424
function getExportCode(exports, icssReplacements, options) {
412425
let code = '';
413426
let localsCode = '';
414-
let namedCode = '';
415427

416428
const addExportToLocalsCode = (name, value) => {
417-
if (localsCode) {
418-
localsCode += `,\n`;
419-
}
420-
421-
localsCode += `\t${JSON.stringify(name)}: ${JSON.stringify(value)}`;
422-
423429
if (options.modules.namedExport) {
424-
namedCode += `export const ${camelCase(name)} = ${JSON.stringify(
430+
localsCode += `export const ${camelCase(name)} = ${JSON.stringify(
425431
value
426432
)};\n`;
433+
} else {
434+
if (localsCode) {
435+
localsCode += `,\n`;
436+
}
437+
438+
localsCode += `\t${JSON.stringify(name)}: ${JSON.stringify(value)}`;
427439
}
428440
};
429441

@@ -467,25 +479,18 @@ function getExportCode(exports, icssReplacements, options) {
467479
for (const replacement of icssReplacements) {
468480
const { replacementName, importName, localName } = replacement;
469481

470-
localsCode = localsCode.replace(
471-
new RegExp(replacementName, 'g'),
472-
() => `" + ${importName}.locals[${JSON.stringify(localName)}] + "`
473-
);
474-
475-
if (options.modules.namedExport) {
476-
namedCode = namedCode.replace(
477-
new RegExp(replacementName, 'g'),
478-
() =>
479-
`" + ${importName}_NAMED___[${JSON.stringify(
482+
localsCode = localsCode.replace(new RegExp(replacementName, 'g'), () =>
483+
options.modules.namedExport
484+
? `" + ${importName}_NAMED___[${JSON.stringify(
480485
camelCase(localName)
481486
)}] + "`
482-
);
483-
}
487+
: `" + ${importName}.locals[${JSON.stringify(localName)}] + "`
488+
);
484489
}
485490

486491
if (localsCode) {
487-
code += namedCode
488-
? `${namedCode}\n`
492+
code += options.modules.namedExport
493+
? `${localsCode}`
489494
: `___CSS_LOADER_EXPORT___.locals = {\n${localsCode}\n};\n`;
490495
}
491496

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

-136
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,5 @@
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-
663
exports[`"esModule" option should work when not specified: errors 1`] = `Array []`;
674

685
exports[`"esModule" option should work when not specified: module 1`] = `
@@ -109,79 +46,6 @@ Array [
10946

11047
exports[`"esModule" option should work when not specified: warnings 1`] = `Array []`;
11148

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, \\"._1yYSY3W2VgnkKdMmuxCIL1 {\\\\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 = \\"_1yYSY3W2VgnkKdMmuxCIL1\\";
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-
"._1yYSY3W2VgnkKdMmuxCIL1 {
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, \\"._3yAjbn27wJ9GVH2M-pj-hs {\\\\n color: red;\\\\n}\\\\n\\\\n.bar {\\\\n color: red;\\\\n}\\\\n\\", \\"\\"]);
159-
// Exports
160-
export const barBaz = \\"_3yAjbn27wJ9GVH2M-pj-hs\\";
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-
"._3yAjbn27wJ9GVH2M-pj-hs {
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-
18549
exports[`"esModule" option should work with a value equal to "false": errors 1`] = `Array []`;
18650

18751
exports[`"esModule" option should work with a value equal to "false": module 1`] = `

0 commit comments

Comments
 (0)