diff --git a/CHANGELOG.md b/CHANGELOG.md index 43ef2e59..9a9f3c0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,21 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [6.10.0](https://github.com/webpack-contrib/css-loader/compare/v6.9.1...v6.10.0) (2024-01-30) + + +### Features + +* add `@rspack/core` as an optional peer dependency ([#1568](https://github.com/webpack-contrib/css-loader/issues/1568)) ([3924679](https://github.com/webpack-contrib/css-loader/commit/3924679cb9a8f31996c742290a71a3446c9782b9)) +* pass the `resourceQuery` and `resourceFragment` to the `auto` and `mode` callback ([#1569](https://github.com/webpack-contrib/css-loader/issues/1569)) ([d641c4d](https://github.com/webpack-contrib/css-loader/commit/d641c4d48264518dfeb77d7e1e8ef03bbb09b645)) +* support named exports with any characters ([6f43929](https://github.com/webpack-contrib/css-loader/commit/6f439299838eab7e6fb18f6e9f47b9dee2208463)) + ### [6.9.1](https://github.com/webpack-contrib/css-loader/compare/v6.9.0...v6.9.1) (2024-01-18) ### 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) @@ -170,7 +179,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 @@ -189,7 +198,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) diff --git a/README.md b/README.md index 71331ea7..57c86b33 100644 --- a/README.md +++ b/README.md @@ -605,12 +605,19 @@ module.exports = { Type: ```ts -type auto = boolean | regExp | ((resourcePath: string) => boolean); +type auto = + | boolean + | regExp + | (( + resourcePath: string, + resourceQuery: string, + resourceFragment: string + ) => boolean); ``` Default: `undefined` -Allows auto enable CSS modules/ICSS based on filename when `modules` option is object. +Allows auto enable CSS modules/ICSS based on the filename, query or fragment when `modules` option is object. Possible values: @@ -673,7 +680,7 @@ module.exports = { ###### `function` -Enable CSS modules for files based on the filename satisfying your filter function check. +Enable CSS modules for files based on the filename, query or fragment satisfying your filter function check. **webpack.config.js** @@ -686,7 +693,9 @@ module.exports = { loader: "css-loader", options: { modules: { - auto: (resourcePath) => resourcePath.endsWith(".custom-module.css"), + auto: (resourcePath, resourceQuery, resourceFragment) => { + return resourcePath.endsWith(".custom-module.css"); + }, }, }, }, @@ -705,7 +714,11 @@ type mode = | "global" | "pure" | "icss" - | ((resourcePath: string) => "local" | "global" | "pure" | "icss"); + | (( + resourcePath: string, + resourceQuery: string, + resourceFragment: string + ) => "local" | "global" | "pure" | "icss"); ``` Default: `'local'` @@ -745,7 +758,7 @@ module.exports = { ###### `function` -Allows set different values for the `mode` option based on a filename +Allows set different values for the `mode` option based on the filename, query or fragment. Possible return values - `local`, `global`, `pure` and `icss`. @@ -761,7 +774,7 @@ module.exports = { options: { modules: { // Callback must return "local", "global", or "pure" values - mode: (resourcePath) => { + mode: (resourcePath, resourceQuery, resourceFragment) => { if (/pure.css$/i.test(resourcePath)) { return "pure"; } @@ -1119,11 +1132,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** @@ -1139,9 +1156,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: @@ -1224,10 +1243,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. | @@ -1739,7 +1754,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"); } @@ -1755,7 +1770,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"); } diff --git a/package-lock.json b/package-lock.json index 350f18e1..dff4370e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "css-loader", - "version": "6.9.1", + "version": "6.10.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "css-loader", - "version": "6.9.1", + "version": "6.10.0", "license": "MIT", "dependencies": { "icss-utils": "^5.1.0", diff --git a/package.json b/package.json index 804bdf24..a7f78ef4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "css-loader", - "version": "6.9.1", + "version": "6.10.0", "description": "css loader module for webpack", "license": "MIT", "repository": "webpack-contrib/css-loader", @@ -43,8 +43,17 @@ "dist" ], "peerDependencies": { + "@rspack/core": "0.x || 1.x", "webpack": "^5.0.0" }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + }, "dependencies": { "icss-utils": "^5.1.0", "postcss": "^8.4.33", diff --git a/src/utils.js b/src/utils.js index 80d6673c..9d68a598 100644 --- a/src/utils.js +++ b/src/utils.js @@ -594,6 +594,7 @@ function getModulesOptions(rawOptions, exportType, loaderContext) { : "asIs", exportOnlyLocals: false, ...rawModulesOptions, + useExportsAs: rawModulesOptions.exportLocalsConvention === "asIs", }; let exportLocalsConventionType; @@ -645,7 +646,12 @@ function getModulesOptions(rawOptions, exportType, loaderContext) { return false; } } else if (typeof modulesOptions.auto === "function") { - const isModule = modulesOptions.auto(resourcePath); + const { resourceQuery, resourceFragment } = loaderContext; + const isModule = modulesOptions.auto( + resourcePath, + resourceQuery, + resourceFragment + ); if (!isModule) { return false; @@ -653,7 +659,11 @@ function getModulesOptions(rawOptions, exportType, loaderContext) { } if (typeof modulesOptions.mode === "function") { - modulesOptions.mode = modulesOptions.mode(loaderContext.resourcePath); + modulesOptions.mode = modulesOptions.mode( + loaderContext.resourcePath, + loaderContext.resourceQuery, + loaderContext.resourceFragment + ); } if (needNamedExport) { @@ -679,6 +689,7 @@ function getModulesOptions(rawOptions, exportType, loaderContext) { if ( typeof exportLocalsConventionType === "string" && + exportLocalsConventionType !== "asIs" && exportLocalsConventionType !== "camelCaseOnly" && exportLocalsConventionType !== "dashesOnly" ) { @@ -1158,6 +1169,7 @@ function getExportCode( if (icssPluginUsed) { let localsCode = ""; + let identifierId = 0; const addExportToLocalsCode = (names, value) => { const normalizedNames = Array.isArray(names) @@ -1165,22 +1177,24 @@ function getExportCode( : 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}`; } } }; diff --git a/test/__snapshots__/modules-option.test.js.snap b/test/__snapshots__/modules-option.test.js.snap index 5ec97206..15d944d7 100644 --- a/test/__snapshots__/modules-option.test.js.snap +++ b/test/__snapshots__/modules-option.test.js.snap @@ -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`] = ` diff --git a/test/fixtures/modules/namedExport/exportsAs/exportsAs.css b/test/fixtures/modules/namedExport/exportsAs/exportsAs.css new file mode 100644 index 00000000..c89328e4 --- /dev/null +++ b/test/fixtures/modules/namedExport/exportsAs/exportsAs.css @@ -0,0 +1,7 @@ +:local(.class) { + color: red; +} + +:local(.class-name) { + color: red; +} diff --git a/test/fixtures/modules/namedExport/exportsAs/index.js b/test/fixtures/modules/namedExport/exportsAs/index.js new file mode 100644 index 00000000..557ae21c --- /dev/null +++ b/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']; diff --git a/test/modules-option.test.js b/test/modules-option.test.js index 4f2f1bf9..ed4b9329 100644 --- a/test/modules-option.test.js +++ b/test/modules-option.test.js @@ -834,7 +834,11 @@ describe('"modules" option', () => { it("issue #1063", async () => { const compiler = getCompiler("./modules/issue-1063/issue-1063.js", { modules: { - mode: (resourcePath) => { + mode: (resourcePath, resourceQuery, resourceFragment) => { + expect(resourcePath).toBeDefined(); + expect(resourceQuery).toBeDefined(); + expect(resourceFragment).toBeDefined(); + if (/pure.css$/i.test(resourcePath)) { return "pure"; } @@ -1269,7 +1273,13 @@ describe('"modules" option', () => { it('should work with a modules.auto Function that returns "true"', async () => { const compiler = getCompiler("./modules/mode/modules.js", { modules: { - auto: (relativePath) => relativePath.endsWith("module.css"), + auto: (resourcePath, resourceQuery, resourceFragment) => { + expect(resourcePath).toBeDefined(); + expect(resourceQuery).toBeDefined(); + expect(resourceFragment).toBeDefined(); + + return resourcePath.endsWith("module.css"); + }, }, }); const stats = await compile(compiler); @@ -1837,6 +1847,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: {