diff --git a/README.md b/README.md index 7dcaae67..7548eae7 100644 --- a/README.md +++ b/README.md @@ -844,9 +844,10 @@ Type: `Boolean` Default: `false` Enables/disables ES modules named export for locals. -Names of locals are converted to camelCase. -> i It is not allowed to use JavaScript reserved words in css class names +> ⚠ Names of locals are converted to camelcase, i.e. the `exportLocalsConvention` option has `camelCaseOnly` value by default. + +> ⚠ It is not allowed to use JavaScript reserved words in css class names. **styles.css** @@ -920,12 +921,14 @@ module.exports = { ##### `exportlocalsConvention` Type: `String` -Default: `'asIs'` +Default: based on the `modules.namedExport` option value, if `true` - `camelCaseOnly`, otherwise `asIs` Style of exported class names. By default, the exported JSON keys mirror the class names (i.e `asIs` value). +> ⚠ Only `camelCaseOnly` value allowed if you set the `namedExport` value to `true`. + | Name | Type | Description | | :-------------------: | :--------: | :----------------------------------------------------------------------------------------------- | | **`'asIs'`** | `{String}` | Class names will be exported as is. | diff --git a/src/index.js b/src/index.js index d7e75f88..0ad95e72 100644 --- a/src/index.js +++ b/src/index.js @@ -36,7 +36,17 @@ export default async function loader(content, map, meta) { }); const plugins = []; - const options = normalizeOptions(rawOptions, this); + const callback = this.async(); + + let options; + + try { + options = normalizeOptions(rawOptions, this); + } catch (error) { + callback(error); + + return; + } if (shouldUseModulesPlugins(options)) { const icssResolver = this.getResolve({ @@ -117,8 +127,6 @@ export default async function loader(content, map, meta) { } } - const callback = this.async(); - let result; try { diff --git a/src/utils.js b/src/utils.js index 6441cc7f..6b5eb5fc 100644 --- a/src/utils.js +++ b/src/utils.js @@ -163,6 +163,13 @@ function getModulesOptions(rawOptions, loaderContext) { return false; } } + + if ( + rawOptions.modules.namedExport === true && + typeof rawOptions.modules.exportLocalsConvention === 'undefined' + ) { + modulesOptions.exportLocalsConvention = 'camelCaseOnly'; + } } modulesOptions = { ...modulesOptions, ...(rawOptions.modules || {}) }; @@ -172,12 +179,18 @@ function getModulesOptions(rawOptions, loaderContext) { modulesOptions.mode = modulesOptions.mode(loaderContext.resourcePath); } - if (modulesOptions.namedExport === true && rawOptions.esModule === false) { - loaderContext.emitError( - new Error( - '`Options.module.namedExport` cannot be used without `options.esModule`' - ) - ); + if (modulesOptions.namedExport === true) { + if (rawOptions.esModule === false) { + throw new Error( + 'The "modules.namedExport" option requires the "esModules" option to be enabled' + ); + } + + if (modulesOptions.exportLocalsConvention !== 'camelCaseOnly') { + throw new Error( + 'The "modules.namedExport" option requires the "modules.exportLocalsConvention" option to be "camelCaseOnly"' + ); + } } return modulesOptions; @@ -411,19 +424,18 @@ function dashesCamelCase(str) { function getExportCode(exports, icssReplacements, options) { let code = ''; let localsCode = ''; - let namedCode = ''; const addExportToLocalsCode = (name, value) => { - if (localsCode) { - localsCode += `,\n`; - } - - localsCode += `\t${JSON.stringify(name)}: ${JSON.stringify(value)}`; - if (options.modules.namedExport) { - namedCode += `export const ${camelCase(name)} = ${JSON.stringify( + localsCode += `export const ${camelCase(name)} = ${JSON.stringify( value )};\n`; + } else { + if (localsCode) { + localsCode += `,\n`; + } + + localsCode += `\t${JSON.stringify(name)}: ${JSON.stringify(value)}`; } }; @@ -467,25 +479,18 @@ function getExportCode(exports, icssReplacements, options) { for (const replacement of icssReplacements) { const { replacementName, importName, localName } = replacement; - localsCode = localsCode.replace( - new RegExp(replacementName, 'g'), - () => `" + ${importName}.locals[${JSON.stringify(localName)}] + "` - ); - - if (options.modules.namedExport) { - namedCode = namedCode.replace( - new RegExp(replacementName, 'g'), - () => - `" + ${importName}_NAMED___[${JSON.stringify( + localsCode = localsCode.replace(new RegExp(replacementName, 'g'), () => + options.modules.namedExport + ? `" + ${importName}_NAMED___[${JSON.stringify( camelCase(localName) )}] + "` - ); - } + : `" + ${importName}.locals[${JSON.stringify(localName)}] + "` + ); } if (localsCode) { - code += namedCode - ? `${namedCode}\n` + code += options.modules.namedExport + ? `${localsCode}` : `___CSS_LOADER_EXPORT___.locals = {\n${localsCode}\n};\n`; } diff --git a/test/__snapshots__/esModule-option.test.js.snap b/test/__snapshots__/esModule-option.test.js.snap index d749a592..f6efb7f9 100644 --- a/test/__snapshots__/esModule-option.test.js.snap +++ b/test/__snapshots__/esModule-option.test.js.snap @@ -1,68 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`"esModule" option should emit error when class has unsupported name: errors 1`] = ` -Array [ - "ModuleParseError: Module parse failed: Unexpected keyword 'class' (7:13) -File was processed with these loaders:", -] -`; - -exports[`"esModule" option should emit error when class has unsupported name: warnings 1`] = `Array []`; - -exports[`"esModule" option should emit error when namedExport true && esModule false: errors 1`] = ` -Array [ - "ModuleError: Module Error (from \`replaced original path\`): -\`Options.module.namedExport\` cannot be used without \`options.esModule\`", -] -`; - -exports[`"esModule" option should work js template with "namedExport" option: errors 1`] = `Array []`; - -exports[`"esModule" option should work js template with "namedExport" option: module 1`] = ` -"// Imports -import ___CSS_LOADER_API_IMPORT___ from \\"../../../../../src/runtime/api.js\\"; -var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(false); -// 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 const headerBaz = \\"header-baz\\"; -export const body = \\"body\\"; -export const footer = \\"footer\\"; - -export default ___CSS_LOADER_EXPORT___; -" -`; - -exports[`"esModule" option should work js template with "namedExport" option: result 1`] = ` -Object { - "css": Array [ - Array [ - "./es-module/named/template/index.css", - ".header-baz { - color: red; -} - -.body { - color: coral; -} - -.footer { - color: blue; -} -", - "", - ], - ], - "html": " -