Skip to content

Commit d139ec1

Browse files
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 += `${

0 commit comments

Comments
 (0)