diff --git a/src/utils.js b/src/utils.js index 4375a3b7..64ee8b1e 100644 --- a/src/utils.js +++ b/src/utils.js @@ -491,40 +491,39 @@ function getValidLocalName(localName, exportLocalsConvention) { return camelCase(localName); } -const moduleRegExp = /\.module(s)?\.\w+$/i; -const icssRegExp = /\.icss\.\w+$/i; +const IS_MODULES = /\.module(s)?\.\w+$/i; +const IS_ICSS = /\.icss\.\w+$/i; function getModulesOptions(rawOptions, loaderContext) { + if (typeof rawOptions.modules === "boolean" && rawOptions.modules === false) { + return false; + } + const resourcePath = // eslint-disable-next-line no-underscore-dangle (loaderContext._module && loaderContext._module.matchResource) || loaderContext.resourcePath; - let isIcss; + let auto; + let rawModulesOptions; if (typeof rawOptions.modules === "undefined") { - const isModules = moduleRegExp.test(resourcePath); - - if (!isModules) { - isIcss = icssRegExp.test(resourcePath); - } - - if (!isModules && !isIcss) { - return false; - } - } else if ( - typeof rawOptions.modules === "boolean" && - rawOptions.modules === false - ) { - return false; + rawModulesOptions = {}; + auto = true; + } else if (typeof rawOptions.modules === "boolean") { + rawModulesOptions = {}; + } else if (typeof rawOptions.modules === "string") { + rawModulesOptions = { mode: rawOptions.modules }; + } else { + rawModulesOptions = rawOptions.modules; + ({ auto } = rawModulesOptions); } // eslint-disable-next-line no-underscore-dangle const { outputOptions } = loaderContext._compilation; - - let modulesOptions = { - auto: true, - mode: isIcss ? "icss" : "local", + const modulesOptions = { + auto, + mode: "local", exportGlobals: false, localIdentName: "[hash:base64]", localIdentContext: loaderContext.rootContext, @@ -537,48 +536,43 @@ function getModulesOptions(rawOptions, loaderContext) { // eslint-disable-next-line no-undefined getLocalIdent: undefined, namedExport: false, - exportLocalsConvention: "asIs", + exportLocalsConvention: + rawModulesOptions.namedExport === true && + typeof rawModulesOptions.exportLocalsConvention === "undefined" + ? "camelCaseOnly" + : "asIs", exportOnlyLocals: false, + ...rawModulesOptions, }; - if ( - typeof rawOptions.modules === "boolean" || - typeof rawOptions.modules === "string" - ) { - modulesOptions.mode = - typeof rawOptions.modules === "string" ? rawOptions.modules : "local"; - } else { - if (rawOptions.modules) { - if (typeof rawOptions.modules.auto === "boolean") { - const isModules = - rawOptions.modules.auto && moduleRegExp.test(resourcePath); + if (typeof modulesOptions.auto === "boolean") { + const isModules = modulesOptions.auto && IS_MODULES.test(resourcePath); - if (!isModules) { - return false; - } - } else if (rawOptions.modules.auto instanceof RegExp) { - const isModules = rawOptions.modules.auto.test(resourcePath); + let isIcss; - if (!isModules) { - return false; - } - } else if (typeof rawOptions.modules.auto === "function") { - const isModule = rawOptions.modules.auto(resourcePath); + if (!isModules) { + isIcss = IS_ICSS.test(resourcePath); - if (!isModule) { - return false; - } + if (isIcss) { + modulesOptions.mode = "icss"; } + } - if ( - rawOptions.modules.namedExport === true && - typeof rawOptions.modules.exportLocalsConvention === "undefined" - ) { - modulesOptions.exportLocalsConvention = "camelCaseOnly"; - } + if (!isModules && !isIcss) { + return false; } + } else if (modulesOptions.auto instanceof RegExp) { + const isModules = modulesOptions.auto.test(resourcePath); - modulesOptions = { ...modulesOptions, ...(rawOptions.modules || {}) }; + if (!isModules) { + return false; + } + } else if (typeof modulesOptions.auto === "function") { + const isModule = modulesOptions.auto(resourcePath); + + if (!isModule) { + return false; + } } if (typeof modulesOptions.mode === "function") { diff --git a/test/__snapshots__/modules-option.test.js.snap b/test/__snapshots__/modules-option.test.js.snap index ee72f724..f47b2779 100644 --- a/test/__snapshots__/modules-option.test.js.snap +++ b/test/__snapshots__/modules-option.test.js.snap @@ -4414,6 +4414,126 @@ Object { exports[`"modules" option should work js template with "namedExport" option: warnings 1`] = `Array []`; +exports[`"modules" option should work when the "auto" is not specified, but specified other modules options: errors 1`] = `Array []`; + +exports[`"modules" option should work when the "auto" is not specified, but specified other modules options: modules-module 1`] = ` +"// Imports +import ___CSS_LOADER_API_IMPORT___ from \\"../../../../src/runtime/api.js\\"; +var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(function(i){return i[1]}); +// Module +___CSS_LOADER_EXPORT___.push([module.id, \\".modules-mode-style-modules__class {\\\\n color: red;\\\\n}\\\\n\\", \\"\\"]); +// Exports +___CSS_LOADER_EXPORT___.locals = { + \\"class\\": \\"modules-mode-style-modules__class\\" +}; +export default ___CSS_LOADER_EXPORT___; +" +`; + +exports[`"modules" option should work when the "auto" is not specified, but specified other modules options: not-modules-module 1`] = ` +"// Imports +import ___CSS_LOADER_API_IMPORT___ from \\"../../../../src/runtime/api.js\\"; +var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(function(i){return i[1]}); +// Module +___CSS_LOADER_EXPORT___.push([module.id, \\".modules-mode-no-modules__class {\\\\n color: red;\\\\n}\\\\n\\", \\"\\"]); +// Exports +___CSS_LOADER_EXPORT___.locals = { + \\"class\\": \\"modules-mode-no-modules__class\\" +}; +export default ___CSS_LOADER_EXPORT___; +" +`; + +exports[`"modules" option should work when the "auto" is not specified, but specified other modules options: result 1`] = ` +".modules-mode-style-modules__class { + color: red; +} +.modules-mode-no-modules__class { + color: red; +} +" +`; + +exports[`"modules" option should work when the "auto" is not specified, but specified other modules options: warnings 1`] = `Array []`; + +exports[`"modules" option should work when the "auto" is not specified: errors 1`] = `Array []`; + +exports[`"modules" option should work when the "auto" is not specified: modules-module 1`] = ` +"// Imports +import ___CSS_LOADER_API_IMPORT___ from \\"../../../../src/runtime/api.js\\"; +var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(function(i){return i[1]}); +// Module +___CSS_LOADER_EXPORT___.push([module.id, \\"._BNmWUIwputjT_WFqpoZ {\\\\n color: red;\\\\n}\\\\n\\", \\"\\"]); +// Exports +___CSS_LOADER_EXPORT___.locals = { + \\"class\\": \\"_BNmWUIwputjT_WFqpoZ\\" +}; +export default ___CSS_LOADER_EXPORT___; +" +`; + +exports[`"modules" option should work when the "auto" is not specified: not-modules-module 1`] = ` +"// Imports +import ___CSS_LOADER_API_IMPORT___ from \\"../../../../src/runtime/api.js\\"; +var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(function(i){return i[1]}); +// Module +___CSS_LOADER_EXPORT___.push([module.id, \\".class {\\\\n color: red;\\\\n}\\\\n\\", \\"\\"]); +// Exports +export default ___CSS_LOADER_EXPORT___; +" +`; + +exports[`"modules" option should work when the "auto" is not specified: result 1`] = ` +"._BNmWUIwputjT_WFqpoZ { + color: red; +} +.class { + color: red; +} +" +`; + +exports[`"modules" option should work when the "auto" is not specified: warnings 1`] = `Array []`; + +exports[`"modules" option should work when the "auto" option is "true" with other options: errors 1`] = `Array []`; + +exports[`"modules" option should work when the "auto" option is "true" with other options: modules-module 1`] = ` +"// Imports +import ___CSS_LOADER_API_IMPORT___ from \\"../../../../src/runtime/api.js\\"; +var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(function(i){return i[1]}); +// Module +___CSS_LOADER_EXPORT___.push([module.id, \\".modules-mode-style-modules__class {\\\\n color: red;\\\\n}\\\\n\\", \\"\\"]); +// Exports +___CSS_LOADER_EXPORT___.locals = { + \\"class\\": \\"modules-mode-style-modules__class\\" +}; +export default ___CSS_LOADER_EXPORT___; +" +`; + +exports[`"modules" option should work when the "auto" option is "true" with other options: not-modules-module 1`] = ` +"// Imports +import ___CSS_LOADER_API_IMPORT___ from \\"../../../../src/runtime/api.js\\"; +var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(function(i){return i[1]}); +// Module +___CSS_LOADER_EXPORT___.push([module.id, \\".class {\\\\n color: red;\\\\n}\\\\n\\", \\"\\"]); +// Exports +export default ___CSS_LOADER_EXPORT___; +" +`; + +exports[`"modules" option should work when the "auto" option is "true" with other options: result 1`] = ` +".modules-mode-style-modules__class { + color: red; +} +.class { + color: red; +} +" +`; + +exports[`"modules" option should work when the "auto" option is "true" with other options: warnings 1`] = `Array []`; + exports[`"modules" option should work when the "namedExport" is enabled and the "exportLocalsConvention" options has "dashesOnly" value: errors 1`] = `Array []`; exports[`"modules" option should work when the "namedExport" is enabled and the "exportLocalsConvention" options has "dashesOnly" value: module 1`] = ` @@ -13743,9 +13863,48 @@ Array [ exports[`"modules" option should work with the "auto" by default: warnings 1`] = `Array []`; -exports[`"modules" option should work with the "auto" when it is "false": errors 1`] = `Array []`; +exports[`"modules" option should work with the "auto" option in the "modules" option for icss: errors 1`] = `Array []`; + +exports[`"modules" option should work with the "auto" option in the "modules" option for icss: module 1`] = ` +"// Imports +import ___CSS_LOADER_API_IMPORT___ from \\"../../../../../src/runtime/api.js\\"; +import ___CSS_LOADER_ICSS_IMPORT_0___ from \\"-!../../../../../src/index.js??ruleSet[1].rules[0].use[0]!./vars.icss.css\\"; +var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(function(i){return i[1]}); +___CSS_LOADER_EXPORT___.i(___CSS_LOADER_ICSS_IMPORT_0___, \\"\\", true); +// Module +___CSS_LOADER_EXPORT___.push([module.id, \\".className {\\\\n color: \\" + ___CSS_LOADER_ICSS_IMPORT_0___.locals[\\"primary-color\\"] + \\";\\\\n}\\\\n\\", \\"\\"]); +// Exports +___CSS_LOADER_EXPORT___.locals = { + \\"primary-color\\": \\"\\" + ___CSS_LOADER_ICSS_IMPORT_0___.locals[\\"primary-color\\"] + \\"\\" +}; +export default ___CSS_LOADER_EXPORT___; +" +`; + +exports[`"modules" option should work with the "auto" option in the "modules" option for icss: result 1`] = ` +Array [ + Array [ + "../../src/index.js??ruleSet[1].rules[0].use[0]!./modules/mode/icss/vars.icss.css", + " +", + "", + ], + Array [ + "./modules/mode/icss/relative.icss.css", + ".className { + color: red; +} +", + "", + ], +] +`; + +exports[`"modules" option should work with the "auto" option in the "modules" option for icss: warnings 1`] = `Array []`; + +exports[`"modules" option should work with the "auto" option is "false": errors 1`] = `Array []`; -exports[`"modules" option should work with the "auto" when it is "false": module 1`] = ` +exports[`"modules" option should work with the "auto" option is "false": module 1`] = ` "// Imports import ___CSS_LOADER_API_IMPORT___ from \\"../../../../src/runtime/api.js\\"; var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(function(i){return i[1]}); @@ -13756,7 +13915,7 @@ export default ___CSS_LOADER_EXPORT___; " `; -exports[`"modules" option should work with the "auto" when it is "false": result 1`] = ` +exports[`"modules" option should work with the "auto" option is "false": result 1`] = ` Array [ Array [ "./modules/mode/relative.module.css", @@ -13769,11 +13928,11 @@ Array [ ] `; -exports[`"modules" option should work with the "auto" when it is "false": warnings 1`] = `Array []`; +exports[`"modules" option should work with the "auto" option is "false": warnings 1`] = `Array []`; -exports[`"modules" option should work with the "auto" when it is "true": errors 1`] = `Array []`; +exports[`"modules" option should work with the "auto" option is "true": errors 1`] = `Array []`; -exports[`"modules" option should work with the "auto" when it is "true": module 1`] = ` +exports[`"modules" option should work with the "auto" option is "true": module 1`] = ` "// Imports import ___CSS_LOADER_API_IMPORT___ from \\"../../../../src/runtime/api.js\\"; var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(function(i){return i[1]}); @@ -13787,7 +13946,7 @@ export default ___CSS_LOADER_EXPORT___; " `; -exports[`"modules" option should work with the "auto" when it is "true": result 1`] = ` +exports[`"modules" option should work with the "auto" option is "true": result 1`] = ` Array [ Array [ "./modules/mode/relative.module.css", @@ -13800,7 +13959,7 @@ Array [ ] `; -exports[`"modules" option should work with the "auto" when it is "true": warnings 1`] = `Array []`; +exports[`"modules" option should work with the "auto" option is "true": warnings 1`] = `Array []`; exports[`"modules" option should work with the "namedExport" option with nested import: errors 1`] = `Array []`; diff --git a/test/fixtures/modules/mode/no-modules.css b/test/fixtures/modules/mode/no-modules.css new file mode 100644 index 00000000..a95613b1 --- /dev/null +++ b/test/fixtures/modules/mode/no-modules.css @@ -0,0 +1,3 @@ +.class { + color: red; +} diff --git a/test/fixtures/modules/mode/not-specified.js b/test/fixtures/modules/mode/not-specified.js new file mode 100644 index 00000000..6aecc673 --- /dev/null +++ b/test/fixtures/modules/mode/not-specified.js @@ -0,0 +1,6 @@ +import modules from './style.modules.css'; +import noModules from './no-modules.css'; + +__export__ = modules + noModules; + +export default [modules, noModules]; diff --git a/test/fixtures/modules/mode/style.modules.css b/test/fixtures/modules/mode/style.modules.css new file mode 100644 index 00000000..a95613b1 --- /dev/null +++ b/test/fixtures/modules/mode/style.modules.css @@ -0,0 +1,3 @@ +.class { + color: red; +} diff --git a/test/modules-option.test.js b/test/modules-option.test.js index e737f17b..2f97dc5d 100644 --- a/test/modules-option.test.js +++ b/test/modules-option.test.js @@ -1,8 +1,6 @@ import path from "path"; import fs from "fs"; -import webpack from "webpack"; - import { compile, getCompiler, @@ -17,8 +15,6 @@ const testCases = fs.readdirSync(testCasesPath); jest.setTimeout(60000); -const isWebpack5 = webpack.version.startsWith(5); - describe('"modules" option', () => { [ true, @@ -918,6 +914,24 @@ describe('"modules" option', () => { expect(getErrors(stats)).toMatchSnapshot("errors"); }); + it('should work with the "auto" option in the "modules" option for icss', async () => { + const compiler = getCompiler("./modules/mode/icss/icss.js", { + modules: { + auto: true, + }, + }); + const stats = await compile(compiler); + + expect( + getModuleSource("./modules/mode/icss/relative.icss.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 "false" alises', async () => { const compiler = getCompiler( "./modules/icss-false-alias/icss.js", @@ -925,12 +939,7 @@ describe('"modules" option', () => { { resolve: { alias: { - "./unknown.css": isWebpack5 - ? false - : path.resolve( - __dirname, - "./fixtures/modules/icss-false-alias/unknown.css" - ), + "./unknown.css": false, }, }, } @@ -947,7 +956,45 @@ describe('"modules" option', () => { expect(getErrors(stats)).toMatchSnapshot("errors"); }); - it('should work with the "auto" when it is "false"', async () => { + it('should work when the "auto" is not specified', async () => { + const compiler = getCompiler("./modules/mode/not-specified.js"); + const stats = await compile(compiler); + + expect( + getModuleSource("./modules/mode/style.modules.css", stats) + ).toMatchSnapshot("modules-module"); + expect( + getModuleSource("./modules/mode/no-modules.css", stats) + ).toMatchSnapshot("not-modules-module"); + expect(getExecutedCode("main.bundle.js", compiler, stats)).toMatchSnapshot( + "result" + ); + expect(getWarnings(stats)).toMatchSnapshot("warnings"); + expect(getErrors(stats)).toMatchSnapshot("errors"); + }); + + it('should work when the "auto" is not specified, but specified other modules options', async () => { + const compiler = getCompiler("./modules/mode/not-specified.js", { + modules: { + localIdentName: "[path][name]__[local]", + }, + }); + const stats = await compile(compiler); + + expect( + getModuleSource("./modules/mode/style.modules.css", stats) + ).toMatchSnapshot("modules-module"); + expect( + getModuleSource("./modules/mode/no-modules.css", stats) + ).toMatchSnapshot("not-modules-module"); + expect(getExecutedCode("main.bundle.js", compiler, stats)).toMatchSnapshot( + "result" + ); + expect(getWarnings(stats)).toMatchSnapshot("warnings"); + expect(getErrors(stats)).toMatchSnapshot("errors"); + }); + + it('should work with the "auto" option is "false"', async () => { const compiler = getCompiler("./modules/mode/modules.js", { modules: { auto: false, @@ -965,7 +1012,7 @@ describe('"modules" option', () => { expect(getErrors(stats)).toMatchSnapshot("errors"); }); - it('should work with the "auto" when it is "true"', async () => { + it('should work with the "auto" option is "true"', async () => { const compiler = getCompiler("./modules/mode/modules.js", { modules: { auto: true, @@ -983,6 +1030,28 @@ describe('"modules" option', () => { expect(getErrors(stats)).toMatchSnapshot("errors"); }); + it('should work when the "auto" option is "true" with other options', async () => { + const compiler = getCompiler("./modules/mode/not-specified.js", { + modules: { + auto: true, + localIdentName: "[path][name]__[local]", + }, + }); + const stats = await compile(compiler); + + expect( + getModuleSource("./modules/mode/style.modules.css", stats) + ).toMatchSnapshot("modules-module"); + expect( + getModuleSource("./modules/mode/no-modules.css", stats) + ).toMatchSnapshot("not-modules-module"); + expect(getExecutedCode("main.bundle.js", compiler, stats)).toMatchSnapshot( + "result" + ); + expect(getWarnings(stats)).toMatchSnapshot("warnings"); + expect(getErrors(stats)).toMatchSnapshot("errors"); + }); + it('should work with a modules.auto RegExp that returns "true"', async () => { const compiler = getCompiler("./modules/mode/modules.js", { modules: {