diff --git a/src/index.js b/src/index.js index 7b20b92ca..4945d634d 100644 --- a/src/index.js +++ b/src/index.js @@ -216,7 +216,17 @@ export default async function loader(content, map, meta) { } const importCode = getImportCode(imports, options); - const moduleCode = getModuleCode(result, api, replacements, options, this); + + let moduleCode; + + try { + moduleCode = getModuleCode(result, api, replacements, options, this); + } catch (error) { + callback(error); + + return; + } + const exportCode = getExportCode( exports, replacements, diff --git a/src/options.json b/src/options.json index ddbffb7a4..2fb5ce09d 100644 --- a/src/options.json +++ b/src/options.json @@ -135,7 +135,14 @@ "namedExport": { "description": "Enables/disables ES modules named export for locals.", "link": "https://github.com/webpack-contrib/css-loader#namedexport", - "type": "boolean" + "anyOf": [ + { + "type": "boolean" + }, + { + "instanceof": "Function" + } + ] }, "exportGlobals": { "description": "Allows to export names from global class or id, so you can use that as local name.", diff --git a/src/utils.js b/src/utils.js index 30b796afd..bddcfd96f 100644 --- a/src/utils.js +++ b/src/utils.js @@ -484,7 +484,11 @@ function getFilter(filter, resourcePath) { }; } -function getValidLocalName(localName, exportLocalsConvention) { +function getValidLocalName(localName, exportLocalsConvention, namedExportFn) { + if (namedExportFn) { + return namedExportFn(localName); + } + if (exportLocalsConvention === "dashesOnly") { return dashesCamelCase(localName); } @@ -546,6 +550,10 @@ function getModulesOptions(rawOptions, loaderContext) { ...rawModulesOptions, }; + if (typeof modulesOptions.namedExport === "function") { + modulesOptions.namedExportFn = modulesOptions.namedExport; + } + if (typeof modulesOptions.auto === "boolean") { const isModules = modulesOptions.auto && IS_MODULES.test(resourcePath); @@ -914,7 +922,8 @@ function getModuleCode(result, api, replacements, options, loaderContext) { ? `" + ${importName}_NAMED___[${JSON.stringify( getValidLocalName( localName, - options.modules.exportLocalsConvention + options.modules.exportLocalsConvention, + options.modules.namedExportFn ) )}] + "` : `" + ${importName}.locals[${JSON.stringify(localName)}] + "` @@ -970,6 +979,13 @@ function getExportCode(exports, replacements, needToUseIcssPlugin, options) { }; for (const { name, value } of exports) { + if (options.modules.namedExportFn) { + addExportToLocalsCode(options.modules.namedExportFn(name), value); + + // eslint-disable-next-line no-continue + continue; + } + switch (options.modules.exportLocalsConvention) { case "camelCase": { addExportToLocalsCode(name, value); @@ -1015,7 +1031,11 @@ function getExportCode(exports, replacements, needToUseIcssPlugin, options) { localsCode = localsCode.replace(new RegExp(replacementName, "g"), () => { if (options.modules.namedExport) { return `" + ${importName}_NAMED___[${JSON.stringify( - getValidLocalName(localName, options.modules.exportLocalsConvention) + getValidLocalName( + localName, + options.modules.exportLocalsConvention, + options.modules.namedExportFn + ) )}] + "`; } else if (options.modules.exportOnlyLocals) { return `" + ${importName}[${JSON.stringify(localName)}] + "`; diff --git a/test/__snapshots__/modules-option.test.js.snap b/test/__snapshots__/modules-option.test.js.snap index 76a91c778..450220520 100644 --- a/test/__snapshots__/modules-option.test.js.snap +++ b/test/__snapshots__/modules-option.test.js.snap @@ -1749,6 +1749,15 @@ Error: The \\"modules.namedExport\\" option requires the \\"esModules\\" option exports[`"modules" option should throw an error when the "namedExport" option is "true", but the "esModule" is "false": warnings 1`] = `Array []`; +exports[`"modules" option should throw error when the "namedExport" function throw error: errors 1`] = ` +Array [ + "ModuleBuildError: Module build failed (from \`replaced original path\`): +Error: namedExportFn error", +] +`; + +exports[`"modules" option should throw error when the "namedExport" function throw error: warnings 1`] = `Array []`; + exports[`"modules" option should throw error with composes when the "namedExport" is enabled and "exportLocalsConvention" options has invalid value: errors 1`] = ` Array [ "ModuleBuildError: Module build failed (from \`replaced original path\`): @@ -4606,6 +4615,52 @@ h1 #pWzFEVR2SnlD5kUmOw_N { exports[`"modules" option should work and support "pure" mode: warnings 1`] = `Array []`; +exports[`"modules" option should work js template with "namedExport" option when "namedExport" option is function: errors 1`] = `Array []`; + +exports[`"modules" option should work js template with "namedExport" option when "namedExport" option is function: 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, \\".header-baz {\\\\n color: red;\\\\n}\\\\n\\\\n.body {\\\\n color: coral;\\\\n}\\\\n\\\\n.footer {\\\\n color: blue;\\\\n}\\\\n\\", \\"\\"]); +// Exports +export var header_baz_TEST = \\"header-baz\\"; +export var body_TEST = \\"body\\"; +export var footer_TEST = \\"footer\\"; +export default ___CSS_LOADER_EXPORT___; +" +`; + +exports[`"modules" option should work js template with "namedExport" option when "namedExport" option is function: result 1`] = ` +Object { + "css": Array [ + Array [ + "./modules/namedExport/template-2/index.css", + ".header-baz { + color: red; +} + +.body { + color: coral; +} + +.footer { + color: blue; +} +", + "", + ], + ], + "html": " +