From d6c31a17a0dde3e2bcf2b5e55f913c3645d32868 Mon Sep 17 00:00:00 2001 From: Alexander Akait <4567934+alexander-akait@users.noreply.github.com> Date: Wed, 10 Apr 2024 18:11:59 +0300 Subject: [PATCH] fix: automatically rename class `default` to `_default` when named export is enabled (#1590) --- README.md | 8 ++- src/utils.js | 6 +- .../__snapshots__/modules-option.test.js.snap | 68 +++++++++++++++++++ test/fixtures/modules/issue-1589/source.css | 3 + test/fixtures/modules/issue-1589/source.js | 5 ++ test/modules-option.test.js | 35 ++++++++++ 6 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 test/fixtures/modules/issue-1589/source.css create mode 100644 test/fixtures/modules/issue-1589/source.js diff --git a/README.md b/README.md index 4c8b8bbe..c9e1f447 100644 --- a/README.md +++ b/README.md @@ -1156,7 +1156,7 @@ Enables/disables ES modules named export for locals. > **Warning** > -> It is not allowed to use the `default` reserved word in css classes. +> Because it is not allowed to use the `default` class in CSS when the `namedExport` is `true` (since ECMA modules have a reserved keyword `default` for default export), it will be automatically renamed to the `_default` class. **styles.css** @@ -1167,6 +1167,9 @@ Enables/disables ES modules named export for locals. .bar { color: blue; } +.default { + color: green; +} ``` **index.js** @@ -1179,6 +1182,9 @@ console.log(styles["foo-baz"], styles.bar); // If using `exportLocalsConvention: "camel-case-only"`: console.log(styles.fooBaz, styles.bar); + +// For the `default` classname +console.log(styles["_default"]); ``` You can enable a ES module named export using: diff --git a/src/utils.js b/src/utils.js index 57adc01c..0ccc6dbd 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1184,12 +1184,16 @@ function getExportCode( ? new Set(names) : new Set([names]); - for (const name of normalizedNames) { + for (let name of normalizedNames) { const serializedValue = isTemplateLiteralSupported ? convertToTemplateLiteral(value) : JSON.stringify(value); if (options.modules.namedExport) { + if (name === "default") { + name = `_${name}`; + } + if (!validIdentifier.test(name) || keywords.has(name)) { identifierId += 1; diff --git a/test/__snapshots__/modules-option.test.js.snap b/test/__snapshots__/modules-option.test.js.snap index 8300e020..37c78af1 100644 --- a/test/__snapshots__/modules-option.test.js.snap +++ b/test/__snapshots__/modules-option.test.js.snap @@ -12317,6 +12317,74 @@ exports[`"modules" option should work with \`@scope\` at-rule: result 1`] = ` exports[`"modules" option should work with \`@scope\` at-rule: warnings 1`] = `[]`; +exports[`"modules" option should work with \`default\` class and with named export: errors 1`] = `[]`; + +exports[`"modules" option should work with \`default\` class and with named export: module 1`] = ` +"// Imports +import ___CSS_LOADER_API_NO_SOURCEMAP_IMPORT___ from "../../../../src/runtime/noSourceMaps.js"; +import ___CSS_LOADER_API_IMPORT___ from "../../../../src/runtime/api.js"; +var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(___CSS_LOADER_API_NO_SOURCEMAP_IMPORT___); +// Module +___CSS_LOADER_EXPORT___.push([module.id, \`.VP7CYSvMVRONwmJxbckO { + background: red +} +\`, ""]); +// Exports +export var _default = \`VP7CYSvMVRONwmJxbckO\`; +export default ___CSS_LOADER_EXPORT___; +" +`; + +exports[`"modules" option should work with \`default\` class and with named export: result 1`] = ` +[ + [ + "./modules/issue-1589/source.css", + ".VP7CYSvMVRONwmJxbckO { + background: red +} +", + "", + ], +] +`; + +exports[`"modules" option should work with \`default\` class and with named export: warnings 1`] = `[]`; + +exports[`"modules" option should work with \`default\` class and without named export: errors 1`] = `[]`; + +exports[`"modules" option should work with \`default\` class and without named export: module 1`] = ` +"// Imports +import ___CSS_LOADER_API_NO_SOURCEMAP_IMPORT___ from "../../../../src/runtime/noSourceMaps.js"; +import ___CSS_LOADER_API_IMPORT___ from "../../../../src/runtime/api.js"; +var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(___CSS_LOADER_API_NO_SOURCEMAP_IMPORT___); +// Module +___CSS_LOADER_EXPORT___.push([module.id, \`.VP7CYSvMVRONwmJxbckO { + background: red +} +\`, ""]); +// Exports +___CSS_LOADER_EXPORT___.locals = { + "default": \`VP7CYSvMVRONwmJxbckO\` +}; +export default ___CSS_LOADER_EXPORT___; +" +`; + +exports[`"modules" option should work with \`default\` class and without named export: result 1`] = ` +[ + [ + "./modules/issue-1589/source.css", + ".VP7CYSvMVRONwmJxbckO { + background: red +} +", + "", + ], +] +`; + +exports[`"modules" option should work with \`default\` class and without named export: warnings 1`] = `[]`; + exports[`"modules" option should work with CSS nesting: errors 1`] = `[]`; exports[`"modules" option should work with CSS nesting: module 1`] = ` diff --git a/test/fixtures/modules/issue-1589/source.css b/test/fixtures/modules/issue-1589/source.css new file mode 100644 index 00000000..14dab2de --- /dev/null +++ b/test/fixtures/modules/issue-1589/source.css @@ -0,0 +1,3 @@ +.default { + background: red +} diff --git a/test/fixtures/modules/issue-1589/source.js b/test/fixtures/modules/issue-1589/source.js new file mode 100644 index 00000000..9b8393fb --- /dev/null +++ b/test/fixtures/modules/issue-1589/source.js @@ -0,0 +1,5 @@ +import * as css from './source.css'; + +__export__ = css.default; + +export default css; diff --git a/test/modules-option.test.js b/test/modules-option.test.js index c17f55dd..c2d190c9 100644 --- a/test/modules-option.test.js +++ b/test/modules-option.test.js @@ -2657,4 +2657,39 @@ describe('"modules" option', () => { expect(getWarnings(stats)).toMatchSnapshot("warnings"); expect(getErrors(stats)).toMatchSnapshot("errors"); }); + + it("should work with `default` class and without named export", async () => { + const compiler = getCompiler("./modules/issue-1589/source.js", { + modules: { + exportLocalsConvention: "as-is", + namedExport: false, + }, + }); + const stats = await compile(compiler); + + expect( + getModuleSource("./modules/issue-1589/source.css", stats), + ).toMatchSnapshot("module"); + expect(getExecutedCode("main.bundle.js", compiler, stats)).toMatchSnapshot( + "result", + ); + expect(getWarnings(stats)).toMatchSnapshot("warnings"); + expect(getErrors(stats)).toMatchSnapshot("errors"); + }); + + it("should work with `default` class and with named export", async () => { + const compiler = getCompiler("./modules/issue-1589/source.js", { + modules: true, + }); + const stats = await compile(compiler); + + expect( + getModuleSource("./modules/issue-1589/source.css", stats), + ).toMatchSnapshot("module"); + expect(getExecutedCode("main.bundle.js", compiler, stats)).toMatchSnapshot( + "result", + ); + expect(getWarnings(stats)).toMatchSnapshot("warnings"); + expect(getErrors(stats)).toMatchSnapshot("errors"); + }); });