Skip to content

Commit a071af9

Browse files
feat: namedExport
1 parent fabc58a commit a071af9

15 files changed

+191
-104
lines changed

README.md

+53-52
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,6 @@ 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 |
122121

123122
### `url`
124123

@@ -532,8 +531,10 @@ module.exports = {
532531
mode: 'local',
533532
exportGlobals: true,
534533
localIdentName: '[path][name]__[local]--[hash:base64:5]',
534+
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,56 @@ 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+
**styles.css**
928+
929+
```css
930+
.foo-baz {
931+
color: red;
932+
}
933+
.bar {
934+
color: blue;
935+
}
936+
```
937+
938+
**index.js**
939+
940+
```js
941+
import { fooBaz, bar } from './styles.css';
942+
943+
console.log(fooBaz, bar);
944+
```
945+
946+
You can enable a ES module named export using:
947+
948+
**webpack.config.js**
949+
950+
```js
951+
module.exports = {
952+
module: {
953+
rules: [
954+
{
955+
test: /\.css$/i,
956+
loader: 'css-loader',
957+
options: {
958+
esModule: true,
959+
modules: {
960+
namedExport: true,
961+
},
962+
},
963+
},
964+
],
965+
},
966+
};
967+
```
968+
918969
### `sourceMap`
919970

920971
Type: `Boolean`
@@ -1036,56 +1087,6 @@ module.exports = {
10361087
};
10371088
```
10381089

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-
10891090
## Examples
10901091

10911092
### Assets

src/index.js

+23-5
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,25 @@ 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 (
53+
Boolean(modulesOptions.namedExport) &&
54+
Boolean(modulesOptions.namedExport) !== Boolean(esModule)
55+
) {
56+
this.emitError(
57+
new Error(
58+
'`Options.module.namedExport` cannot be used without `options.esModule`'
59+
)
60+
);
61+
}
62+
4963
plugins.push(...getModulesPlugins(modulesOptions, this));
5064

5165
const icssResolver = this.getResolve({
@@ -177,18 +191,22 @@ export default function loader(content, map, meta) {
177191
);
178192
});
179193

180-
const esModule =
181-
typeof options.esModule !== 'undefined' ? options.esModule : false;
182-
183-
const importCode = getImportCode(this, exportType, imports, esModule);
194+
const importCode = getImportCode(
195+
this,
196+
exportType,
197+
imports,
198+
esModule,
199+
modulesOptions
200+
);
184201
const moduleCode = getModuleCode(
185202
result,
186203
exportType,
187204
sourceMap,
188205
apiImports,
189206
urlReplacements,
190207
icssReplacements,
191-
esModule
208+
esModule,
209+
modulesOptions
192210
);
193211
const exportCode = getExportCode(
194212
exports,

src/utils.js

+11-9
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 (
@@ -269,7 +270,7 @@ function getImportCode(
269270
exportType,
270271
imports,
271272
esModule,
272-
namedExport
273+
modulesOptions
273274
) {
274275
let code = '';
275276

@@ -288,7 +289,7 @@ function getImportCode(
288289
const { importName, url, icss } = item;
289290

290291
code += esModule
291-
? icss && namedExport
292+
? icss && modulesOptions.namedExport
292293
? `import ${importName}, * as ${importName}_NAMED___ from ${url};\n`
293294
: `import ${importName} from ${url};\n`
294295
: `var ${importName} = require(${url});\n`;
@@ -305,7 +306,7 @@ function getModuleCode(
305306
urlReplacements,
306307
icssReplacements,
307308
esModule,
308-
namedExport
309+
modulesOptions
309310
) {
310311
if (exportType !== 'full') {
311312
return 'var ___CSS_LOADER_EXPORT___ = {};\n';
@@ -355,7 +356,7 @@ function getModuleCode(
355356
const { replacementName, importName, localName } = replacement;
356357

357358
code = code.replace(new RegExp(replacementName, 'g'), () =>
358-
namedExport
359+
modulesOptions.namedExport
359360
? `" + ${importName}_NAMED___[${JSON.stringify(
360361
camelCase(localName)
361362
)}] + "`
@@ -377,8 +378,7 @@ function getExportCode(
377378
exportType,
378379
icssReplacements,
379380
esModule,
380-
modulesOptions,
381-
namedExport
381+
modulesOptions
382382
) {
383383
let code = '';
384384
let localsCode = '';
@@ -391,8 +391,10 @@ function getExportCode(
391391

392392
localsCode += `\t${JSON.stringify(name)}: ${JSON.stringify(value)}`;
393393

394-
if (namedExport) {
395-
namedCode += `export const ${name} = ${JSON.stringify(value)};\n`;
394+
if (modulesOptions.namedExport) {
395+
namedCode += `export const ${camelCase(name)} = ${JSON.stringify(
396+
value
397+
)};\n`;
396398
}
397399
};
398400

@@ -441,7 +443,7 @@ function getExportCode(
441443
() => `" + ${importName}.locals[${JSON.stringify(localName)}] + "`
442444
);
443445

444-
if (namedExport) {
446+
if (modulesOptions.namedExport) {
445447
namedCode = namedCode.replace(
446448
new RegExp(replacementName, 'g'),
447449
() =>

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

+19-19
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,16 @@ File was processed with these loaders:",
99

1010
exports[`"esModule" option should emit error when class has unsupported name: warnings 1`] = `Array []`;
1111

12-
exports[`"esModule" option should emit error when exportNamed true && esModule false: errors 1`] = `
12+
exports[`"esModule" option should emit error when namedExport true && esModule false: errors 1`] = `
1313
Array [
1414
"ModuleError: Module Error (from \`replaced original path\`):
1515
\`Options.module.namedExport\` cannot be used without \`options.esModule\`",
1616
]
1717
`;
1818

19-
exports[`"esModule" option should work js template with "exportNamed" option: errors 1`] = `Array []`;
19+
exports[`"esModule" option should work js template with "namedExport" option: errors 1`] = `Array []`;
2020

21-
exports[`"esModule" option should work js template with "exportNamed" option: module 1`] = `
21+
exports[`"esModule" option should work js template with "namedExport" option: module 1`] = `
2222
"// Imports
2323
import ___CSS_LOADER_API_IMPORT___ from \\"../../../../../src/runtime/api.js\\";
2424
var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(false);
@@ -33,7 +33,7 @@ export default ___CSS_LOADER_EXPORT___;
3333
"
3434
`;
3535

36-
exports[`"esModule" option should work js template with "exportNamed" option: result 1`] = `
36+
exports[`"esModule" option should work js template with "namedExport" option: result 1`] = `
3737
Object {
3838
"css": Array [
3939
Array [
@@ -61,7 +61,7 @@ Object {
6161
}
6262
`;
6363
64-
exports[`"esModule" option should work js template with "exportNamed" option: warnings 1`] = `Array []`;
64+
exports[`"esModule" option should work js template with "namedExport" option: warnings 1`] = `Array []`;
6565
6666
exports[`"esModule" option should work when not specified: errors 1`] = `Array []`;
6767
@@ -109,25 +109,25 @@ Array [
109109
110110
exports[`"esModule" option should work when not specified: warnings 1`] = `Array []`;
111111
112-
exports[`"esModule" option should work with "exportNamed" option with nested import: errors 1`] = `Array []`;
112+
exports[`"esModule" option should work with "namedExport" option with nested import: errors 1`] = `Array []`;
113113
114-
exports[`"esModule" option should work with "exportNamed" option with nested import: module 1`] = `
114+
exports[`"esModule" option should work with "namedExport" option with nested import: module 1`] = `
115115
"// Imports
116116
import ___CSS_LOADER_API_IMPORT___ from \\"../../../../../src/runtime/api.js\\";
117117
import ___CSS_LOADER_ICSS_IMPORT_0___, * as ___CSS_LOADER_ICSS_IMPORT_0____NAMED___ from \\"-!../../../../../src/index.js??[ident]!../../../modules/composes/values.css\\";
118118
var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(false);
119119
___CSS_LOADER_EXPORT___.i(___CSS_LOADER_ICSS_IMPORT_0___, \\"\\", true);
120120
// Module
121-
___CSS_LOADER_EXPORT___.push([module.id, \\".qwCT06AE1ZDvQtiz0EQJ8 {\\\\n color: \\" + ___CSS_LOADER_ICSS_IMPORT_0____NAMED___[\\"vDef\\"] + \\";\\\\n}\\\\n\\", \\"\\"]);
121+
___CSS_LOADER_EXPORT___.push([module.id, \\".lOpALu8t_iv2-GccTMbIq {\\\\n color: \\" + ___CSS_LOADER_ICSS_IMPORT_0____NAMED___[\\"vDef\\"] + \\";\\\\n}\\\\n\\", \\"\\"]);
122122
// Exports
123123
export const vDef = \\"\\" + ___CSS_LOADER_ICSS_IMPORT_0____NAMED___[\\"vDef\\"] + \\"\\";
124-
export const ghi = \\"qwCT06AE1ZDvQtiz0EQJ8\\";
124+
export const ghi = \\"lOpALu8t_iv2-GccTMbIq\\";
125125
126126
export default ___CSS_LOADER_EXPORT___;
127127
"
128128
`;
129129
130-
exports[`"esModule" option should work with "exportNamed" option with nested import: result 1`] = `
130+
exports[`"esModule" option should work with "namedExport" option with nested import: result 1`] = `
131131
Array [
132132
Array [
133133
"../../src/index.js?[ident]!./modules/composes/values.css",
@@ -137,7 +137,7 @@ Array [
137137
],
138138
Array [
139139
"./es-module/named/nested/index.css",
140-
".qwCT06AE1ZDvQtiz0EQJ8 {
140+
".lOpALu8t_iv2-GccTMbIq {
141141
color: red;
142142
}
143143
",
@@ -146,28 +146,28 @@ Array [
146146
]
147147
`;
148148
149-
exports[`"esModule" option should work with "exportNamed" option with nested import: warnings 1`] = `Array []`;
149+
exports[`"esModule" option should work with "namedExport" option with nested import: warnings 1`] = `Array []`;
150150
151-
exports[`"esModule" option should work with "exportNamed" option: errors 1`] = `Array []`;
151+
exports[`"esModule" option should work with "namedExport" option: errors 1`] = `Array []`;
152152
153-
exports[`"esModule" option should work with "exportNamed" option: module 1`] = `
153+
exports[`"esModule" option should work with "namedExport" option: module 1`] = `
154154
"// Imports
155155
import ___CSS_LOADER_API_IMPORT___ from \\"../../../../../src/runtime/api.js\\";
156156
var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(false);
157157
// Module
158-
___CSS_LOADER_EXPORT___.push([module.id, \\"._1Cb_30DnkF22nebwtzVBFY {\\\\n color: red;\\\\n}\\\\n\\\\n.bar {\\\\n color: red;\\\\n}\\\\n\\", \\"\\"]);
158+
___CSS_LOADER_EXPORT___.push([module.id, \\".jSf5EjnYI1bvqKHBrOPz6 {\\\\n color: red;\\\\n}\\\\n\\\\n.bar {\\\\n color: red;\\\\n}\\\\n\\", \\"\\"]);
159159
// Exports
160-
export const barBaz = \\"_1Cb_30DnkF22nebwtzVBFY\\";
160+
export const barBaz = \\"jSf5EjnYI1bvqKHBrOPz6\\";
161161
162162
export default ___CSS_LOADER_EXPORT___;
163163
"
164164
`;
165165
166-
exports[`"esModule" option should work with "exportNamed" option: result 1`] = `
166+
exports[`"esModule" option should work with "namedExport" option: result 1`] = `
167167
Array [
168168
Array [
169169
"./es-module/named/base/index.css",
170-
"._1Cb_30DnkF22nebwtzVBFY {
170+
".jSf5EjnYI1bvqKHBrOPz6 {
171171
color: red;
172172
}
173173
@@ -180,7 +180,7 @@ Array [
180180
]
181181
`;
182182
183-
exports[`"esModule" option should work with "exportNamed" option: warnings 1`] = `Array []`;
183+
exports[`"esModule" option should work with "namedExport" option: warnings 1`] = `Array []`;
184184
185185
exports[`"esModule" option should work with a value equal to "false": errors 1`] = `Array []`;
186186

0 commit comments

Comments
 (0)