Skip to content

Commit 6f43929

Browse files
authored
feat: support named exports with any characters
1 parent f9192ee commit 6f43929

File tree

7 files changed

+72
-23
lines changed

7 files changed

+72
-23
lines changed

CHANGELOG.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ All notable changes to this project will be documented in this file. See [standa
77

88
### Bug Fixes
99

10-
* css nesting support
10+
* css nesting support
1111
* `@scope` at-rule support
1212

1313
## [6.9.0](https://github.com/webpack-contrib/css-loader/compare/v6.8.1...v6.9.0) (2024-01-09)
@@ -170,7 +170,7 @@ All notable changes to this project will be documented in this file. See [standa
170170
* `new URL()` syntax used for `url()`, only when the `esModule` option is enabled (enabled by default), it means you can bundle CSS for libraries
171171
* [data URI](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) are handling in `url()`, it means you can register loaders for them, [example](https://webpack.js.org/configuration/module/#rulescheme)
172172
* aliases with `false` value for `url()` now generate empty data URI (i.e. `data:0,`), only when the `esModule` option is enabled (enabled by default)
173-
* `[ext]` placeholder don't need `.` (dot) before for the `localIdentName` option, i.e. please change `.[ext]` on `[ext]` (no dot before)
173+
* `[ext]` placeholder don't need `.` (dot) before for the `localIdentName` option, i.e. please change `.[ext]` on `[ext]` (no dot before)
174174
* `[folder]` placeholder was removed without replacement for the `localIdentName` option, please use a custom function if you need complex logic
175175
* `[emoji]` placeholder was removed without replacement for the `localIdentName` option, please use a custom function if you need complex logic
176176
* the `localIdentHashPrefix` was removed in favor the `localIdentHashSalt` option
@@ -189,7 +189,7 @@ All notable changes to this project will be documented in this file. See [standa
189189

190190
### Notes
191191

192-
* **we strongly recommend not to add `.css` to `resolve.extensions`, it reduces performance and in most cases it is simply not necessary, alternative you can set resolve options [by dependency](https://webpack.js.org/configuration/resolve/#resolvebydependency)**
192+
* **we strongly recommend not to add `.css` to `resolve.extensions`, it reduces performance and in most cases it is simply not necessary, alternative you can set resolve options [by dependency](https://webpack.js.org/configuration/resolve/#resolvebydependency)**
193193

194194
### [5.2.7](https://github.com/webpack-contrib/css-loader/compare/v5.2.6...v5.2.7) (2021-07-13)
195195

README.md

+12-10
Original file line numberDiff line numberDiff line change
@@ -1119,11 +1119,15 @@ Enables/disables ES modules named export for locals.
11191119

11201120
> **Warning**
11211121
>
1122-
> Names of locals are converted to camelcase, i.e. the `exportLocalsConvention` option has `camelCaseOnly` value by default.
1122+
> Names of locals are converted to camelcase, i.e. the `exportLocalsConvention` option has
1123+
> `camelCaseOnly` value by default. You can set this back to any other valid option but selectors
1124+
> which are not valid JavaScript identifiers may run into problems which do not implement the entire
1125+
> modules specification.
11231126
11241127
> **Warning**
11251128
>
1126-
> It is not allowed to use JavaScript reserved words in css class names.
1129+
> It is not allowed to use JavaScript reserved words in css class names unless
1130+
> `exportLocalsConvention` is `"asIs"`.
11271131
11281132
**styles.css**
11291133

@@ -1139,9 +1143,11 @@ Enables/disables ES modules named export for locals.
11391143
**index.js**
11401144

11411145
```js
1142-
import { fooBaz, bar } from "./styles.css";
1146+
import * as styles from "./styles.css";
11431147

1144-
console.log(fooBaz, bar);
1148+
console.log(styles.fooBaz, styles.bar);
1149+
// or if using `exportLocalsConvention: "asIs"`:
1150+
console.log(styles["foo-baz"], styles.bar);
11451151
```
11461152

11471153
You can enable a ES module named export using:
@@ -1224,10 +1230,6 @@ Style of exported class names.
12241230

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

1227-
> **Warning**
1228-
>
1229-
> Only `camelCaseOnly` value allowed if you set the `namedExport` value to `true`.
1230-
12311233
| Name | Type | Description |
12321234
| :-------------------: | :------: | :----------------------------------------------------------------------------------------------- |
12331235
| **`'asIs'`** | `string` | Class names will be exported as is. |
@@ -1739,7 +1741,7 @@ With the help of the `/* webpackIgnore: true */`comment, it is possible to disab
17391741
.class {
17401742
/* Disabled url handling for the first url in the 'background' declaration */
17411743
color: red;
1742-
background:
1744+
background:
17431745
/* webpackIgnore: true */ url("./url/img.png"), url("./url/img.png");
17441746
}
17451747

@@ -1755,7 +1757,7 @@ With the help of the `/* webpackIgnore: true */`comment, it is possible to disab
17551757
/* Disabled url handling for the second url in the 'background' declaration */
17561758
color: red;
17571759
background: url("./url/img.png"),
1758-
/* webpackIgnore: true */
1760+
/* webpackIgnore: true */
17591761
url("./url/img.png");
17601762
}
17611763

src/utils.js

+15-10
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,7 @@ function getModulesOptions(rawOptions, exportType, loaderContext) {
594594
: "asIs",
595595
exportOnlyLocals: false,
596596
...rawModulesOptions,
597+
useExportsAs: rawModulesOptions.exportLocalsConvention === "asIs",
597598
};
598599

599600
let exportLocalsConventionType;
@@ -679,6 +680,7 @@ function getModulesOptions(rawOptions, exportType, loaderContext) {
679680

680681
if (
681682
typeof exportLocalsConventionType === "string" &&
683+
exportLocalsConventionType !== "asIs" &&
682684
exportLocalsConventionType !== "camelCaseOnly" &&
683685
exportLocalsConventionType !== "dashesOnly"
684686
) {
@@ -1158,29 +1160,32 @@ function getExportCode(
11581160

11591161
if (icssPluginUsed) {
11601162
let localsCode = "";
1163+
let identifierId = 0;
11611164

11621165
const addExportToLocalsCode = (names, value) => {
11631166
const normalizedNames = Array.isArray(names)
11641167
? new Set(names)
11651168
: new Set([names]);
11661169

11671170
for (const name of normalizedNames) {
1171+
const serializedValue = isTemplateLiteralSupported
1172+
? convertToTemplateLiteral(value)
1173+
: JSON.stringify(value);
11681174
if (options.modules.namedExport) {
1169-
localsCode += `export var ${name} = ${
1170-
isTemplateLiteralSupported
1171-
? convertToTemplateLiteral(value)
1172-
: JSON.stringify(value)
1173-
};\n`;
1175+
if (options.modules.useExportsAs) {
1176+
identifierId += 1;
1177+
const id = `_${identifierId.toString(16)}`;
1178+
localsCode += `var ${id} = ${serializedValue};\n`;
1179+
localsCode += `export { ${id} as ${JSON.stringify(name)} };\n`;
1180+
} else {
1181+
localsCode += `export var ${name} = ${serializedValue};\n`;
1182+
}
11741183
} else {
11751184
if (localsCode) {
11761185
localsCode += `,\n`;
11771186
}
11781187

1179-
localsCode += `\t${JSON.stringify(name)}: ${
1180-
isTemplateLiteralSupported
1181-
? convertToTemplateLiteral(value)
1182-
: JSON.stringify(value)
1183-
}`;
1188+
localsCode += `\t${JSON.stringify(name)}: ${serializedValue}`;
11841189
}
11851190
}
11861191
};

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

+13
Original file line numberDiff line numberDiff line change
@@ -8801,6 +8801,19 @@ Object {
88018801

88028802
exports[`"modules" option should work with "exportOnlyLocals" and "esModule" with "true" value options: warnings 1`] = `Array []`;
88038803

8804+
exports[`"modules" option should work with "exportOnlyLocals" and "exportLocalsConvention": "asIs": errors 1`] = `Array []`;
8805+
8806+
exports[`"modules" option should work with "exportOnlyLocals" and "exportLocalsConvention": "asIs": module 1`] = `
8807+
"// Exports
8808+
var _1 = \`Sl3D7kVfPwS7_QdqSTVq\`;
8809+
export { _1 as \\"class\\" };
8810+
var _2 = \`tHyHTECdn65WISyToGeV\`;
8811+
export { _2 as \\"class-name\\" };
8812+
"
8813+
`;
8814+
8815+
exports[`"modules" option should work with "exportOnlyLocals" and "exportLocalsConvention": "asIs": warnings 1`] = `Array []`;
8816+
88048817
exports[`"modules" option should work with "exportOnlyLocals" and "namedExport" option: errors 1`] = `Array []`;
88058818

88068819
exports[`"modules" option should work with "exportOnlyLocals" and "namedExport" option: module 1`] = `
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
:local(.class) {
2+
color: red;
3+
}
4+
5+
:local(.class-name) {
6+
color: red;
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import * as css from './exportsAs.css';
2+
3+
export const _class = css['class'];
4+
export const _className = css['class-name'];

test/modules-option.test.js

+18
Original file line numberDiff line numberDiff line change
@@ -1837,6 +1837,24 @@ describe('"modules" option', () => {
18371837
expect(getErrors(stats)).toMatchSnapshot("errors");
18381838
});
18391839

1840+
it('should work with "exportOnlyLocals" and "exportLocalsConvention": "asIs"', async () => {
1841+
const compiler = getCompiler("./modules/namedExport/exportsAs/index.js", {
1842+
esModule: true,
1843+
modules: {
1844+
namedExport: true,
1845+
exportLocalsConvention: "asIs",
1846+
exportOnlyLocals: true,
1847+
},
1848+
});
1849+
const stats = await compile(compiler);
1850+
1851+
expect(
1852+
getModuleSource("./modules/namedExport/exportsAs/exportsAs.css", stats)
1853+
).toMatchSnapshot("module");
1854+
expect(getWarnings(stats)).toMatchSnapshot("warnings");
1855+
expect(getErrors(stats, true)).toMatchSnapshot("errors");
1856+
});
1857+
18401858
it('should work with "url" and "namedExport"', async () => {
18411859
const compiler = getCompiler("./modules/url/source.js", {
18421860
modules: {

0 commit comments

Comments
 (0)