Skip to content

Commit

Permalink
feat: support named exports with any characters
Browse files Browse the repository at this point in the history
  • Loading branch information
laverdet committed Jan 23, 2024
1 parent f9192ee commit 6f43929
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 23 deletions.
6 changes: 3 additions & 3 deletions CHANGELOG.md
Expand Up @@ -7,7 +7,7 @@ All notable changes to this project will be documented in this file. See [standa

### Bug Fixes

* css nesting support
* css nesting support
* `@scope` at-rule support

## [6.9.0](https://github.com/webpack-contrib/css-loader/compare/v6.8.1...v6.9.0) (2024-01-09)
Expand Down Expand Up @@ -170,7 +170,7 @@ All notable changes to this project will be documented in this file. See [standa
* `new URL()` syntax used for `url()`, only when the `esModule` option is enabled (enabled by default), it means you can bundle CSS for libraries
* [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)
* 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)
* `[ext]` placeholder don't need `.` (dot) before for the `localIdentName` option, i.e. please change `.[ext]` on `[ext]` (no dot before)
* `[ext]` placeholder don't need `.` (dot) before for the `localIdentName` option, i.e. please change `.[ext]` on `[ext]` (no dot before)
* `[folder]` placeholder was removed without replacement for the `localIdentName` option, please use a custom function if you need complex logic
* `[emoji]` placeholder was removed without replacement for the `localIdentName` option, please use a custom function if you need complex logic
* the `localIdentHashPrefix` was removed in favor the `localIdentHashSalt` option
Expand All @@ -189,7 +189,7 @@ All notable changes to this project will be documented in this file. See [standa

### Notes

* **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)**
* **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)**

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

Expand Down
22 changes: 12 additions & 10 deletions README.md
Expand Up @@ -1119,11 +1119,15 @@ Enables/disables ES modules named export for locals.

> **Warning**
>
> Names of locals are converted to camelcase, i.e. the `exportLocalsConvention` option has `camelCaseOnly` value by default.
> Names of locals are converted to camelcase, i.e. the `exportLocalsConvention` option has
> `camelCaseOnly` value by default. You can set this back to any other valid option but selectors
> which are not valid JavaScript identifiers may run into problems which do not implement the entire
> modules specification.
> **Warning**
>
> It is not allowed to use JavaScript reserved words in css class names.
> It is not allowed to use JavaScript reserved words in css class names unless
> `exportLocalsConvention` is `"asIs"`.
**styles.css**

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

```js
import { fooBaz, bar } from "./styles.css";
import * as styles from "./styles.css";

console.log(fooBaz, bar);
console.log(styles.fooBaz, styles.bar);
// or if using `exportLocalsConvention: "asIs"`:
console.log(styles["foo-baz"], styles.bar);
```

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

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

> **Warning**
>
> Only `camelCaseOnly` value allowed if you set the `namedExport` value to `true`.
| Name | Type | Description |
| :-------------------: | :------: | :----------------------------------------------------------------------------------------------- |
| **`'asIs'`** | `string` | Class names will be exported as is. |
Expand Down Expand Up @@ -1739,7 +1741,7 @@ With the help of the `/* webpackIgnore: true */`comment, it is possible to disab
.class {
/* Disabled url handling for the first url in the 'background' declaration */
color: red;
background:
background:
/* webpackIgnore: true */ url("./url/img.png"), url("./url/img.png");
}

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

Expand Down
25 changes: 15 additions & 10 deletions src/utils.js
Expand Up @@ -594,6 +594,7 @@ function getModulesOptions(rawOptions, exportType, loaderContext) {
: "asIs",
exportOnlyLocals: false,
...rawModulesOptions,
useExportsAs: rawModulesOptions.exportLocalsConvention === "asIs",
};

let exportLocalsConventionType;
Expand Down Expand Up @@ -679,6 +680,7 @@ function getModulesOptions(rawOptions, exportType, loaderContext) {

if (
typeof exportLocalsConventionType === "string" &&
exportLocalsConventionType !== "asIs" &&
exportLocalsConventionType !== "camelCaseOnly" &&
exportLocalsConventionType !== "dashesOnly"
) {
Expand Down Expand Up @@ -1158,29 +1160,32 @@ function getExportCode(

if (icssPluginUsed) {
let localsCode = "";
let identifierId = 0;

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

for (const name of normalizedNames) {
const serializedValue = isTemplateLiteralSupported
? convertToTemplateLiteral(value)
: JSON.stringify(value);
if (options.modules.namedExport) {
localsCode += `export var ${name} = ${
isTemplateLiteralSupported
? convertToTemplateLiteral(value)
: JSON.stringify(value)
};\n`;
if (options.modules.useExportsAs) {
identifierId += 1;
const id = `_${identifierId.toString(16)}`;
localsCode += `var ${id} = ${serializedValue};\n`;
localsCode += `export { ${id} as ${JSON.stringify(name)} };\n`;
} else {
localsCode += `export var ${name} = ${serializedValue};\n`;
}
} else {
if (localsCode) {
localsCode += `,\n`;
}

localsCode += `\t${JSON.stringify(name)}: ${
isTemplateLiteralSupported
? convertToTemplateLiteral(value)
: JSON.stringify(value)
}`;
localsCode += `\t${JSON.stringify(name)}: ${serializedValue}`;
}
}
};
Expand Down
13 changes: 13 additions & 0 deletions test/__snapshots__/modules-option.test.js.snap
Expand Up @@ -8801,6 +8801,19 @@ Object {

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

exports[`"modules" option should work with "exportOnlyLocals" and "exportLocalsConvention": "asIs": errors 1`] = `Array []`;

exports[`"modules" option should work with "exportOnlyLocals" and "exportLocalsConvention": "asIs": module 1`] = `
"// Exports
var _1 = \`Sl3D7kVfPwS7_QdqSTVq\`;
export { _1 as \\"class\\" };
var _2 = \`tHyHTECdn65WISyToGeV\`;
export { _2 as \\"class-name\\" };
"
`;

exports[`"modules" option should work with "exportOnlyLocals" and "exportLocalsConvention": "asIs": warnings 1`] = `Array []`;

exports[`"modules" option should work with "exportOnlyLocals" and "namedExport" option: errors 1`] = `Array []`;

exports[`"modules" option should work with "exportOnlyLocals" and "namedExport" option: module 1`] = `
Expand Down
7 changes: 7 additions & 0 deletions test/fixtures/modules/namedExport/exportsAs/exportsAs.css
@@ -0,0 +1,7 @@
:local(.class) {
color: red;
}

:local(.class-name) {
color: red;
}
4 changes: 4 additions & 0 deletions test/fixtures/modules/namedExport/exportsAs/index.js
@@ -0,0 +1,4 @@
import * as css from './exportsAs.css';

export const _class = css['class'];
export const _className = css['class-name'];
18 changes: 18 additions & 0 deletions test/modules-option.test.js
Expand Up @@ -1837,6 +1837,24 @@ describe('"modules" option', () => {
expect(getErrors(stats)).toMatchSnapshot("errors");
});

it('should work with "exportOnlyLocals" and "exportLocalsConvention": "asIs"', async () => {
const compiler = getCompiler("./modules/namedExport/exportsAs/index.js", {
esModule: true,
modules: {
namedExport: true,
exportLocalsConvention: "asIs",
exportOnlyLocals: true,
},
});
const stats = await compile(compiler);

expect(
getModuleSource("./modules/namedExport/exportsAs/exportsAs.css", stats)
).toMatchSnapshot("module");
expect(getWarnings(stats)).toMatchSnapshot("warnings");
expect(getErrors(stats, true)).toMatchSnapshot("errors");
});

it('should work with "url" and "namedExport"', async () => {
const compiler = getCompiler("./modules/url/source.js", {
modules: {
Expand Down

0 comments on commit 6f43929

Please sign in to comment.