Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support named exports with any characters #1549

Merged
merged 2 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
9 changes: 7 additions & 2 deletions test/exportType.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ describe("'exportType' option", () => {
it("should work with 'string' value and CSS modules", async () => {
const compiler = getCompiler("./basic-string-css-modules.js", {
exportType: "string",
modules: true,
modules: {
exportLocalsConvention: "camelCaseOnly",
},
});
const stats = await compile(compiler);

Expand Down Expand Up @@ -185,7 +187,9 @@ describe("'exportType' option", () => {
"./modules/composes/composes-css-style-sheet.js",
{
exportType: "css-style-sheet",
modules: true,
modules: {
exportLocalsConvention: "camelCaseOnly",
},
}
);
const stats = await compile(compiler);
Expand All @@ -206,6 +210,7 @@ describe("'exportType' option", () => {
{
exportType: "css-style-sheet",
modules: {
exportLocalsConvention: "camelCaseOnly",
exportOnlyLocals: true,
},
}
Expand Down
7 changes: 7 additions & 0 deletions test/fixtures/modules/namedExport/exportsAs/exportsAs.css
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import * as css from './exportsAs.css';

export const _class = css['class'];
export const _className = css['class-name'];
25 changes: 25 additions & 0 deletions test/modules-option.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1566,6 +1566,7 @@ describe('"modules" option', () => {
it('should work with the "namedExport" option', async () => {
const compiler = getCompiler("./modules/namedExport/base/index.js", {
modules: {
exportLocalsConvention: "camelCaseOnly",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why it was added? Sounds like a breaking change

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mentioned this in the comment above. The changes didn't have any effect on the unit test. I will remove.

namedExport: true,
},
});
Expand Down Expand Up @@ -1605,6 +1606,7 @@ describe('"modules" option', () => {
const compiler = getCompiler("./modules/namedExport/nested/index.js", {
esModule: true,
modules: {
exportLocalsConvention: "camelCaseOnly",
namedExport: true,
},
});
Expand All @@ -1624,6 +1626,7 @@ describe('"modules" option', () => {
const compiler = getCompiler("./modules/namedExport/template/index.js", {
esModule: true,
modules: {
exportLocalsConvention: "camelCaseOnly",
localIdentName: "[local]",
namedExport: true,
},
Expand Down Expand Up @@ -1821,6 +1824,7 @@ describe('"modules" option', () => {
mode: "local",
localIdentName: "_[local]",
namedExport: true,
exportLocalsConvention: "camelCaseOnly",
exportOnlyLocals: true,
},
esModule: true,
Expand All @@ -1837,9 +1841,28 @@ 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: {
exportLocalsConvention: "camelCaseOnly",
namedExport: true,
},
});
Expand All @@ -1860,6 +1883,7 @@ describe('"modules" option', () => {
"./modules/url/source.js",
{
modules: {
exportLocalsConvention: "camelCaseOnly",
namedExport: true,
},
},
Expand Down Expand Up @@ -2036,6 +2060,7 @@ describe('"modules" option', () => {
modules: {
mode: "icss",
namedExport: true,
exportLocalsConvention: "camelCaseOnly",
},
}
);
Expand Down