From fabc58a301c8ad5687106003e58f9bfe1df2fb4f Mon Sep 17 00:00:00 2001 From: cap-Bernardito Date: Wed, 15 Jul 2020 19:19:12 +0300 Subject: [PATCH 1/2] feat: esModule export named --- README.md | 52 ++++++- src/options.json | 4 + src/plugins/postcss-icss-parser.js | 1 + src/utils.js | 48 +++++-- .../esModule-option.test.js.snap | 136 ++++++++++++++++++ test/esModule-option.test.js | 83 +++++++++++ test/fixtures/simple.css | 2 +- test/validate-options.test.js | 16 ++- 8 files changed, 330 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 0ccc5957..9242edb4 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,7 @@ module.exports = { | **[`importLoaders`](#importloaders)** | `{Number}` | `0` | Enables/Disables or setups number of loaders applied before CSS loader | | **[`onlyLocals`](#onlylocals)** | `{Boolean}` | `false` | Export only locals | | **[`esModule`](#esmodule)** | `{Boolean}` | `false` | Use ES modules syntax | +| **[`namedExport`](#namedExport)** | `{Boolean}` | `false` | Use ES modules named export | ### `url` @@ -531,7 +532,6 @@ module.exports = { mode: 'local', exportGlobals: true, localIdentName: '[path][name]__[local]--[hash:base64:5]', - localsConvention: 'camelCase', context: path.resolve(__dirname, 'src'), hashPrefix: 'my-custom-hash', }, @@ -1036,6 +1036,56 @@ module.exports = { }; ``` +### `namedExport` + +Type: `Boolean` +Default: `false` + +Enable/disable ES modules named export for css classes. +Names of exported classes are converted to camelCase. + +**styles.css** + +```css +.foo-baz { + color: red; +} +.bar { + color: blue; +} +``` + +**index.js** + +```js +import { fooBaz, bar } from './styles.css'; + +console.log(fooBaz, bar); +``` + +You can enable a ES module named export using: + +**webpack.config.js** + +```js +module.exports = { + module: { + rules: [ + { + test: /\.css$/i, + loader: 'css-loader', + options: { + esModule: true, + modules: { + namedExport: true, + }, + }, + }, + ], + }, +}; +``` + ## Examples ### Assets diff --git a/src/options.json b/src/options.json index 78f7f38c..7189313e 100644 --- a/src/options.json +++ b/src/options.json @@ -100,6 +100,10 @@ "instanceof": "Function" } ] + }, + "namedExport": { + "description": "Use the named export ES modules.", + "type": "boolean" } } } diff --git a/src/plugins/postcss-icss-parser.js b/src/plugins/postcss-icss-parser.js index f2de17ba..bc800625 100644 --- a/src/plugins/postcss-icss-parser.js +++ b/src/plugins/postcss-icss-parser.js @@ -83,6 +83,7 @@ export default postcss.plugin( value: { // 'CSS_LOADER_ICSS_IMPORT' order: 0, + icss: true, importName, url: options.urlHandler(resolvedUrl), index: currentIndex, diff --git a/src/utils.js b/src/utils.js index 12e24f95..95dd3942 100644 --- a/src/utils.js +++ b/src/utils.js @@ -264,7 +264,13 @@ function getPreRequester({ loaders, loaderIndex }) { }; } -function getImportCode(loaderContext, exportType, imports, esModule) { +function getImportCode( + loaderContext, + exportType, + imports, + esModule, + namedExport +) { let code = ''; if (exportType === 'full') { @@ -279,10 +285,12 @@ function getImportCode(loaderContext, exportType, imports, esModule) { } for (const item of imports) { - const { importName, url } = item; + const { importName, url, icss } = item; code += esModule - ? `import ${importName} from ${url};\n` + ? icss && namedExport + ? `import ${importName}, * as ${importName}_NAMED___ from ${url};\n` + : `import ${importName} from ${url};\n` : `var ${importName} = require(${url});\n`; } @@ -296,7 +304,8 @@ function getModuleCode( apiImports, urlReplacements, icssReplacements, - esModule + esModule, + namedExport ) { if (exportType !== 'full') { return 'var ___CSS_LOADER_EXPORT___ = {};\n'; @@ -345,9 +354,12 @@ function getModuleCode( for (const replacement of icssReplacements) { const { replacementName, importName, localName } = replacement; - code = code.replace( - new RegExp(replacementName, 'g'), - () => `" + ${importName}.locals[${JSON.stringify(localName)}] + "` + code = code.replace(new RegExp(replacementName, 'g'), () => + namedExport + ? `" + ${importName}_NAMED___[${JSON.stringify( + camelCase(localName) + )}] + "` + : `" + ${importName}.locals[${JSON.stringify(localName)}] + "` ); } @@ -365,10 +377,12 @@ function getExportCode( exportType, icssReplacements, esModule, - modulesOptions + modulesOptions, + namedExport ) { let code = ''; let localsCode = ''; + let namedCode = ''; const addExportToLocalsCode = (name, value) => { if (localsCode) { @@ -376,6 +390,10 @@ function getExportCode( } localsCode += `\t${JSON.stringify(name)}: ${JSON.stringify(value)}`; + + if (namedExport) { + namedCode += `export const ${name} = ${JSON.stringify(value)};\n`; + } }; for (const { name, value } of exports) { @@ -422,10 +440,22 @@ function getExportCode( new RegExp(replacementName, 'g'), () => `" + ${importName}.locals[${JSON.stringify(localName)}] + "` ); + + if (namedExport) { + namedCode = namedCode.replace( + new RegExp(replacementName, 'g'), + () => + `" + ${importName}_NAMED___[${JSON.stringify( + camelCase(localName) + )}] + "` + ); + } } if (localsCode) { - code += `___CSS_LOADER_EXPORT___.locals = {\n${localsCode}\n};\n`; + code += namedCode + ? `${namedCode}\n` + : `___CSS_LOADER_EXPORT___.locals = {\n${localsCode}\n};\n`; } code += `${ diff --git a/test/__snapshots__/esModule-option.test.js.snap b/test/__snapshots__/esModule-option.test.js.snap index 3f0b0b7b..005398c1 100644 --- a/test/__snapshots__/esModule-option.test.js.snap +++ b/test/__snapshots__/esModule-option.test.js.snap @@ -1,5 +1,68 @@ // 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 exportNamed 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 "exportNamed" option: errors 1`] = `Array []`; + +exports[`"esModule" option should work js template with "exportNamed" 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 "exportNamed" option: result 1`] = ` +Object { + "css": Array [ + Array [ + "./es-module/named/template/index.css", + ".header-baz { + color: red; +} + +.body { + color: coral; +} + +.footer { + color: blue; +} +", + "", + ], + ], + "html": " +
+
+
+", +} +`; + +exports[`"esModule" option should work js template with "exportNamed" option: warnings 1`] = `Array []`; + exports[`"esModule" option should work when not specified: errors 1`] = `Array []`; exports[`"esModule" option should work when not specified: module 1`] = ` @@ -46,6 +109,79 @@ Array [ exports[`"esModule" option should work when not specified: warnings 1`] = `Array []`; +exports[`"esModule" option should work with "exportNamed" option with nested import: errors 1`] = `Array []`; + +exports[`"esModule" option should work with "exportNamed" option with nested import: module 1`] = ` +"// Imports +import ___CSS_LOADER_API_IMPORT___ from \\"../../../../../src/runtime/api.js\\"; +import ___CSS_LOADER_ICSS_IMPORT_0___, * as ___CSS_LOADER_ICSS_IMPORT_0____NAMED___ from \\"-!../../../../../src/index.js??[ident]!../../../modules/composes/values.css\\"; +var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(false); +___CSS_LOADER_EXPORT___.i(___CSS_LOADER_ICSS_IMPORT_0___, \\"\\", true); +// Module +___CSS_LOADER_EXPORT___.push([module.id, \\".qwCT06AE1ZDvQtiz0EQJ8 {\\\\n color: \\" + ___CSS_LOADER_ICSS_IMPORT_0____NAMED___[\\"vDef\\"] + \\";\\\\n}\\\\n\\", \\"\\"]); +// Exports +export const vDef = \\"\\" + ___CSS_LOADER_ICSS_IMPORT_0____NAMED___[\\"vDef\\"] + \\"\\"; +export const ghi = \\"qwCT06AE1ZDvQtiz0EQJ8\\"; + +export default ___CSS_LOADER_EXPORT___; +" +`; + +exports[`"esModule" option should work with "exportNamed" option with nested import: result 1`] = ` +Array [ + Array [ + "../../src/index.js?[ident]!./modules/composes/values.css", + " +", + "", + ], + Array [ + "./es-module/named/nested/index.css", + ".qwCT06AE1ZDvQtiz0EQJ8 { + color: red; +} +", + "", + ], +] +`; + +exports[`"esModule" option should work with "exportNamed" option with nested import: warnings 1`] = `Array []`; + +exports[`"esModule" option should work with "exportNamed" option: errors 1`] = `Array []`; + +exports[`"esModule" option should work with "exportNamed" 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, \\"._1Cb_30DnkF22nebwtzVBFY {\\\\n color: red;\\\\n}\\\\n\\\\n.bar {\\\\n color: red;\\\\n}\\\\n\\", \\"\\"]); +// Exports +export const barBaz = \\"_1Cb_30DnkF22nebwtzVBFY\\"; + +export default ___CSS_LOADER_EXPORT___; +" +`; + +exports[`"esModule" option should work with "exportNamed" option: result 1`] = ` +Array [ + Array [ + "./es-module/named/base/index.css", + "._1Cb_30DnkF22nebwtzVBFY { + color: red; +} + +.bar { + color: red; +} +", + "", + ], +] +`; + +exports[`"esModule" option should work with "exportNamed" option: warnings 1`] = `Array []`; + exports[`"esModule" option should work with a value equal to "false": errors 1`] = `Array []`; exports[`"esModule" option should work with a value equal to "false": module 1`] = ` diff --git a/test/esModule-option.test.js b/test/esModule-option.test.js index 3bfdce61..a7bca5cc 100644 --- a/test/esModule-option.test.js +++ b/test/esModule-option.test.js @@ -100,4 +100,87 @@ describe('"esModule" option', () => { expect(getWarnings(stats)).toMatchSnapshot('warnings'); expect(getErrors(stats)).toMatchSnapshot('errors'); }); + + it('should work with "exportNamed" option', async () => { + const compiler = getCompiler('./es-module/named/base/index.js', { + esModule: true, + modules: { + namedExport: true, + }, + }); + const stats = await compile(compiler); + + expect( + getModuleSource('./es-module/named/base/index.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 "exportNamed" option with nested import', async () => { + const compiler = getCompiler('./es-module/named/nested/index.js', { + esModule: true, + modules: { + namedExport: true, + }, + }); + const stats = await compile(compiler); + + expect( + getModuleSource('./es-module/named/nested/index.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 js template with "exportNamed" option', async () => { + const compiler = getCompiler('./es-module/named/template/index.js', { + esModule: true, + modules: { + localIdentName: '[local]', + namedExport: true, + }, + }); + const stats = await compile(compiler); + + expect( + getModuleSource('./es-module/named/template/index.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 emit error when class has unsupported name', async () => { + const compiler = getCompiler('./es-module/named/broken/index.js', { + esModule: true, + modules: { + namedExport: true, + }, + }); + const stats = await compile(compiler); + + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats, true)).toMatchSnapshot('errors'); + }); + + it('should emit error when exportNamed true && esModule false', async () => { + const compiler = getCompiler('./es-module/named/base/index.js', { + esModule: false, + modules: { + namedExport: true, + }, + }); + const stats = await compile(compiler); + + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); }); diff --git a/test/fixtures/simple.css b/test/fixtures/simple.css index 19fce739..b58cc07a 100644 --- a/test/fixtures/simple.css +++ b/test/fixtures/simple.css @@ -1,3 +1,3 @@ -.class { +.some-class { color: red; } diff --git a/test/validate-options.test.js b/test/validate-options.test.js index df7217a6..b53b24ac 100644 --- a/test/validate-options.test.js +++ b/test/validate-options.test.js @@ -37,6 +37,8 @@ describe('validate options', () => { { localsConvention: 'camelCaseOnly' }, { localsConvention: 'dashes' }, { localsConvention: 'dashesOnly' }, + { namedExport: true }, + { namedExport: false }, ], failure: [ 'true', @@ -55,6 +57,7 @@ describe('validate options', () => { { exportGlobals: 'invalid' }, { auto: 'invalid' }, { localsConvention: 'unknown' }, + { namedExport: 'invalid' }, ], }, sourceMap: { @@ -94,7 +97,18 @@ describe('validate options', () => { it(`should ${ type === 'success' ? 'successfully validate' : 'throw an error on' } the "${key}" option with "${stringifyValue(value)}" value`, async () => { - const compiler = getCompiler('simple.js', { [key]: value }); + const options = { [key]: value }; + + if ( + key === 'modules' && + typeof value === 'object' && + value.namedExport === true + ) { + options.esModule = true; + } + + const compiler = getCompiler('simple.js', options); + let stats; try { From 5d192ea90d1ed3bb68951f7e338692b79b82afb7 Mon Sep 17 00:00:00 2001 From: cap-Bernardito Date: Thu, 16 Jul 2020 13:51:58 +0300 Subject: [PATCH 2/2] feat: namedExport --- README.md | 107 +++++++++--------- src/index.js | 25 +++- src/utils.js | 20 ++-- .../esModule-option.test.js.snap | 38 +++---- .../validate-options.test.js.snap | 36 +++--- test/esModule-option.test.js | 8 +- test/fixtures/es-module/named/base/index.css | 7 ++ test/fixtures/es-module/named/base/index.js | 5 + .../fixtures/es-module/named/broken/index.css | 3 + test/fixtures/es-module/named/broken/index.js | 5 + .../fixtures/es-module/named/nested/index.css | 5 + test/fixtures/es-module/named/nested/index.js | 5 + .../es-module/named/template/index.css | 11 ++ .../es-module/named/template/index.js | 8 ++ .../es-module/named/template/template.js | 11 ++ 15 files changed, 190 insertions(+), 104 deletions(-) create mode 100644 test/fixtures/es-module/named/base/index.css create mode 100644 test/fixtures/es-module/named/base/index.js create mode 100644 test/fixtures/es-module/named/broken/index.css create mode 100644 test/fixtures/es-module/named/broken/index.js create mode 100644 test/fixtures/es-module/named/nested/index.css create mode 100644 test/fixtures/es-module/named/nested/index.js create mode 100644 test/fixtures/es-module/named/template/index.css create mode 100644 test/fixtures/es-module/named/template/index.js create mode 100644 test/fixtures/es-module/named/template/template.js diff --git a/README.md b/README.md index 9242edb4..6566cc04 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,6 @@ module.exports = { | **[`importLoaders`](#importloaders)** | `{Number}` | `0` | Enables/Disables or setups number of loaders applied before CSS loader | | **[`onlyLocals`](#onlylocals)** | `{Boolean}` | `false` | Export only locals | | **[`esModule`](#esmodule)** | `{Boolean}` | `false` | Use ES modules syntax | -| **[`namedExport`](#namedExport)** | `{Boolean}` | `false` | Use ES modules named export | ### `url` @@ -532,8 +531,10 @@ module.exports = { mode: 'local', exportGlobals: true, localIdentName: '[path][name]__[local]--[hash:base64:5]', + localsConvention: 'camelCase', context: path.resolve(__dirname, 'src'), hashPrefix: 'my-custom-hash', + namedExport: true, }, }, }, @@ -756,7 +757,7 @@ module.exports = { }; ``` -### `localsConvention` +##### `localsConvention` Type: `String` Default: `'asIs'` @@ -915,6 +916,58 @@ module.exports = { }; ``` +##### `namedExport` + +Type: `Boolean` +Default: `false` + +Enable/disable ES modules named export for css classes. +Names of exported classes are converted to camelCase. + +> i It is not allowed to use JavaScript reserved words in css class names + +**styles.css** + +```css +.foo-baz { + color: red; +} +.bar { + color: blue; +} +``` + +**index.js** + +```js +import { fooBaz, bar } from './styles.css'; + +console.log(fooBaz, bar); +``` + +You can enable a ES module named export using: + +**webpack.config.js** + +```js +module.exports = { + module: { + rules: [ + { + test: /\.css$/i, + loader: 'css-loader', + options: { + esModule: true, + modules: { + namedExport: true, + }, + }, + }, + ], + }, +}; +``` + ### `sourceMap` Type: `Boolean` @@ -1036,56 +1089,6 @@ module.exports = { }; ``` -### `namedExport` - -Type: `Boolean` -Default: `false` - -Enable/disable ES modules named export for css classes. -Names of exported classes are converted to camelCase. - -**styles.css** - -```css -.foo-baz { - color: red; -} -.bar { - color: blue; -} -``` - -**index.js** - -```js -import { fooBaz, bar } from './styles.css'; - -console.log(fooBaz, bar); -``` - -You can enable a ES module named export using: - -**webpack.config.js** - -```js -module.exports = { - module: { - rules: [ - { - test: /\.css$/i, - loader: 'css-loader', - options: { - esModule: true, - modules: { - namedExport: true, - }, - }, - }, - ], - }, -}; -``` - ## Examples ### Assets diff --git a/src/index.js b/src/index.js index 4d4b7f63..494789f3 100644 --- a/src/index.js +++ b/src/index.js @@ -41,11 +41,22 @@ export default function loader(content, map, meta) { const urlHandler = (url) => stringifyRequest(this, preRequester(options.importLoaders) + url); + const esModule = + typeof options.esModule !== 'undefined' ? options.esModule : false; + let modulesOptions; if (shouldUseModulesPlugins(options.modules, this.resourcePath)) { modulesOptions = getModulesOptions(options, this); + if (modulesOptions.namedExport === true && esModule === false) { + this.emitError( + new Error( + '`Options.module.namedExport` cannot be used without `options.esModule`' + ) + ); + } + plugins.push(...getModulesPlugins(modulesOptions, this)); const icssResolver = this.getResolve({ @@ -177,10 +188,13 @@ export default function loader(content, map, meta) { ); }); - const esModule = - typeof options.esModule !== 'undefined' ? options.esModule : false; - - const importCode = getImportCode(this, exportType, imports, esModule); + const importCode = getImportCode( + this, + exportType, + imports, + esModule, + modulesOptions + ); const moduleCode = getModuleCode( result, exportType, @@ -188,7 +202,8 @@ export default function loader(content, map, meta) { apiImports, urlReplacements, icssReplacements, - esModule + esModule, + modulesOptions ); const exportCode = getExportCode( exports, diff --git a/src/utils.js b/src/utils.js index 95dd3942..78ee2591 100644 --- a/src/utils.js +++ b/src/utils.js @@ -146,6 +146,7 @@ function getModulesOptions(options, loaderContext) { getLocalIdent, hashPrefix: '', exportGlobals: false, + namedExport: false, }; if ( @@ -269,7 +270,7 @@ function getImportCode( exportType, imports, esModule, - namedExport + modulesOptions ) { let code = ''; @@ -288,7 +289,7 @@ function getImportCode( const { importName, url, icss } = item; code += esModule - ? icss && namedExport + ? icss && modulesOptions.namedExport ? `import ${importName}, * as ${importName}_NAMED___ from ${url};\n` : `import ${importName} from ${url};\n` : `var ${importName} = require(${url});\n`; @@ -305,7 +306,7 @@ function getModuleCode( urlReplacements, icssReplacements, esModule, - namedExport + modulesOptions ) { if (exportType !== 'full') { return 'var ___CSS_LOADER_EXPORT___ = {};\n'; @@ -355,7 +356,7 @@ function getModuleCode( const { replacementName, importName, localName } = replacement; code = code.replace(new RegExp(replacementName, 'g'), () => - namedExport + modulesOptions.namedExport ? `" + ${importName}_NAMED___[${JSON.stringify( camelCase(localName) )}] + "` @@ -377,8 +378,7 @@ function getExportCode( exportType, icssReplacements, esModule, - modulesOptions, - namedExport + modulesOptions ) { let code = ''; let localsCode = ''; @@ -391,8 +391,10 @@ function getExportCode( localsCode += `\t${JSON.stringify(name)}: ${JSON.stringify(value)}`; - if (namedExport) { - namedCode += `export const ${name} = ${JSON.stringify(value)};\n`; + if (modulesOptions.namedExport) { + namedCode += `export const ${camelCase(name)} = ${JSON.stringify( + value + )};\n`; } }; @@ -441,7 +443,7 @@ function getExportCode( () => `" + ${importName}.locals[${JSON.stringify(localName)}] + "` ); - if (namedExport) { + if (modulesOptions.namedExport) { namedCode = namedCode.replace( new RegExp(replacementName, 'g'), () => diff --git a/test/__snapshots__/esModule-option.test.js.snap b/test/__snapshots__/esModule-option.test.js.snap index 005398c1..82263331 100644 --- a/test/__snapshots__/esModule-option.test.js.snap +++ b/test/__snapshots__/esModule-option.test.js.snap @@ -9,16 +9,16 @@ 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 exportNamed true && esModule false: errors 1`] = ` +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 "exportNamed" option: errors 1`] = `Array []`; +exports[`"esModule" option should work js template with "namedExport" option: errors 1`] = `Array []`; -exports[`"esModule" option should work js template with "exportNamed" option: module 1`] = ` +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); @@ -33,7 +33,7 @@ export default ___CSS_LOADER_EXPORT___; " `; -exports[`"esModule" option should work js template with "exportNamed" option: result 1`] = ` +exports[`"esModule" option should work js template with "namedExport" option: result 1`] = ` Object { "css": Array [ Array [ @@ -61,7 +61,7 @@ Object { } `; -exports[`"esModule" option should work js template with "exportNamed" option: warnings 1`] = `Array []`; +exports[`"esModule" option should work js template with "namedExport" option: warnings 1`] = `Array []`; exports[`"esModule" option should work when not specified: errors 1`] = `Array []`; @@ -109,25 +109,25 @@ Array [ exports[`"esModule" option should work when not specified: warnings 1`] = `Array []`; -exports[`"esModule" option should work with "exportNamed" option with nested import: errors 1`] = `Array []`; +exports[`"esModule" option should work with "namedExport" option with nested import: errors 1`] = `Array []`; -exports[`"esModule" option should work with "exportNamed" option with nested import: module 1`] = ` +exports[`"esModule" option should work with "namedExport" option with nested import: module 1`] = ` "// Imports import ___CSS_LOADER_API_IMPORT___ from \\"../../../../../src/runtime/api.js\\"; import ___CSS_LOADER_ICSS_IMPORT_0___, * as ___CSS_LOADER_ICSS_IMPORT_0____NAMED___ from \\"-!../../../../../src/index.js??[ident]!../../../modules/composes/values.css\\"; var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(false); ___CSS_LOADER_EXPORT___.i(___CSS_LOADER_ICSS_IMPORT_0___, \\"\\", true); // Module -___CSS_LOADER_EXPORT___.push([module.id, \\".qwCT06AE1ZDvQtiz0EQJ8 {\\\\n color: \\" + ___CSS_LOADER_ICSS_IMPORT_0____NAMED___[\\"vDef\\"] + \\";\\\\n}\\\\n\\", \\"\\"]); +___CSS_LOADER_EXPORT___.push([module.id, \\".lOpALu8t_iv2-GccTMbIq {\\\\n color: \\" + ___CSS_LOADER_ICSS_IMPORT_0____NAMED___[\\"vDef\\"] + \\";\\\\n}\\\\n\\", \\"\\"]); // Exports export const vDef = \\"\\" + ___CSS_LOADER_ICSS_IMPORT_0____NAMED___[\\"vDef\\"] + \\"\\"; -export const ghi = \\"qwCT06AE1ZDvQtiz0EQJ8\\"; +export const ghi = \\"lOpALu8t_iv2-GccTMbIq\\"; export default ___CSS_LOADER_EXPORT___; " `; -exports[`"esModule" option should work with "exportNamed" option with nested import: result 1`] = ` +exports[`"esModule" option should work with "namedExport" option with nested import: result 1`] = ` Array [ Array [ "../../src/index.js?[ident]!./modules/composes/values.css", @@ -137,7 +137,7 @@ Array [ ], Array [ "./es-module/named/nested/index.css", - ".qwCT06AE1ZDvQtiz0EQJ8 { + ".lOpALu8t_iv2-GccTMbIq { color: red; } ", @@ -146,28 +146,28 @@ Array [ ] `; -exports[`"esModule" option should work with "exportNamed" option with nested import: warnings 1`] = `Array []`; +exports[`"esModule" option should work with "namedExport" option with nested import: warnings 1`] = `Array []`; -exports[`"esModule" option should work with "exportNamed" option: errors 1`] = `Array []`; +exports[`"esModule" option should work with "namedExport" option: errors 1`] = `Array []`; -exports[`"esModule" option should work with "exportNamed" option: module 1`] = ` +exports[`"esModule" option should work 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, \\"._1Cb_30DnkF22nebwtzVBFY {\\\\n color: red;\\\\n}\\\\n\\\\n.bar {\\\\n color: red;\\\\n}\\\\n\\", \\"\\"]); +___CSS_LOADER_EXPORT___.push([module.id, \\".jSf5EjnYI1bvqKHBrOPz6 {\\\\n color: red;\\\\n}\\\\n\\\\n.bar {\\\\n color: red;\\\\n}\\\\n\\", \\"\\"]); // Exports -export const barBaz = \\"_1Cb_30DnkF22nebwtzVBFY\\"; +export const barBaz = \\"jSf5EjnYI1bvqKHBrOPz6\\"; export default ___CSS_LOADER_EXPORT___; " `; -exports[`"esModule" option should work with "exportNamed" option: result 1`] = ` +exports[`"esModule" option should work with "namedExport" option: result 1`] = ` Array [ Array [ "./es-module/named/base/index.css", - "._1Cb_30DnkF22nebwtzVBFY { + ".jSf5EjnYI1bvqKHBrOPz6 { color: red; } @@ -180,7 +180,7 @@ Array [ ] `; -exports[`"esModule" option should work with "exportNamed" option: warnings 1`] = `Array []`; +exports[`"esModule" option should work with "namedExport" option: warnings 1`] = `Array []`; exports[`"esModule" option should work with a value equal to "false": errors 1`] = `Array []`; diff --git a/test/__snapshots__/validate-options.test.js.snap b/test/__snapshots__/validate-options.test.js.snap index cb4bbd1e..c0d1bbc8 100644 --- a/test/__snapshots__/validate-options.test.js.snap +++ b/test/__snapshots__/validate-options.test.js.snap @@ -39,7 +39,7 @@ exports[`validate options should throw an error on the "importLoaders" option wi exports[`validate options should throw an error on the "modules" option with "{"auto":"invalid"}" value 1`] = ` "Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema. - options.modules should be one of these: - boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent? } + boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent?, namedExport? } -> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules). Details: * options.modules.auto should be one of these: @@ -63,7 +63,7 @@ exports[`validate options should throw an error on the "modules" option with "{" exports[`validate options should throw an error on the "modules" option with "{"getLocalIdent":[]}" value 1`] = ` "Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema. - options.modules should be one of these: - boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent? } + boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent?, namedExport? } -> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules). Details: * options.modules.getLocalIdent should be one of these: @@ -86,7 +86,7 @@ exports[`validate options should throw an error on the "modules" option with "{" exports[`validate options should throw an error on the "modules" option with "{"localIdentRegExp":true}" value 1`] = ` "Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema. - options.modules should be one of these: - boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent? } + boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent?, namedExport? } -> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules). Details: * options.modules.localIdentRegExp should be one of these: @@ -106,7 +106,7 @@ exports[`validate options should throw an error on the "modules" option with "{" exports[`validate options should throw an error on the "modules" option with "{"mode":"globals"}" value 1`] = ` "Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema. - options.modules should be one of these: - boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent? } + boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent?, namedExport? } -> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules). Details: * options.modules.mode should be one of these: @@ -120,7 +120,7 @@ exports[`validate options should throw an error on the "modules" option with "{" exports[`validate options should throw an error on the "modules" option with "{"mode":"locals"}" value 1`] = ` "Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema. - options.modules should be one of these: - boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent? } + boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent?, namedExport? } -> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules). Details: * options.modules.mode should be one of these: @@ -134,7 +134,7 @@ exports[`validate options should throw an error on the "modules" option with "{" exports[`validate options should throw an error on the "modules" option with "{"mode":"pures"}" value 1`] = ` "Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema. - options.modules should be one of these: - boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent? } + boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent?, namedExport? } -> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules). Details: * options.modules.mode should be one of these: @@ -148,7 +148,7 @@ exports[`validate options should throw an error on the "modules" option with "{" exports[`validate options should throw an error on the "modules" option with "{"mode":true}" value 1`] = ` "Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema. - options.modules should be one of these: - boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent? } + boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent?, namedExport? } -> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules). Details: * options.modules.mode should be one of these: @@ -159,56 +159,62 @@ exports[`validate options should throw an error on the "modules" option with "{" * options.modules.mode should be an instance of function." `; +exports[`validate options should throw an error on the "modules" option with "{"namedExport":"invalid"}" value 1`] = ` +"Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema. + - options.modules.namedExport should be a boolean. + -> Use the named export ES modules." +`; + exports[`validate options should throw an error on the "modules" option with "globals" value 1`] = ` "Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema. - options.modules should be one of these: - boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent? } + boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent?, namedExport? } -> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules). Details: * options.modules should be a boolean. * options.modules should be one of these: \\"local\\" | \\"global\\" | \\"pure\\" * options.modules should be an object: - object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent? }" + object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent?, namedExport? }" `; exports[`validate options should throw an error on the "modules" option with "locals" value 1`] = ` "Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema. - options.modules should be one of these: - boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent? } + boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent?, namedExport? } -> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules). Details: * options.modules should be a boolean. * options.modules should be one of these: \\"local\\" | \\"global\\" | \\"pure\\" * options.modules should be an object: - object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent? }" + object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent?, namedExport? }" `; exports[`validate options should throw an error on the "modules" option with "pures" value 1`] = ` "Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema. - options.modules should be one of these: - boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent? } + boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent?, namedExport? } -> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules). Details: * options.modules should be a boolean. * options.modules should be one of these: \\"local\\" | \\"global\\" | \\"pure\\" * options.modules should be an object: - object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent? }" + object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent?, namedExport? }" `; exports[`validate options should throw an error on the "modules" option with "true" value 1`] = ` "Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema. - options.modules should be one of these: - boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent? } + boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent?, namedExport? } -> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules). Details: * options.modules should be a boolean. * options.modules should be one of these: \\"local\\" | \\"global\\" | \\"pure\\" * options.modules should be an object: - object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent? }" + object { auto?, mode?, exportGlobals?, localIdentName?, localIdentRegExp?, localsConvention?, context?, hashPrefix?, getLocalIdent?, namedExport? }" `; exports[`validate options should throw an error on the "onlyLocals" option with "true" value 1`] = ` diff --git a/test/esModule-option.test.js b/test/esModule-option.test.js index a7bca5cc..6e51ac5a 100644 --- a/test/esModule-option.test.js +++ b/test/esModule-option.test.js @@ -101,7 +101,7 @@ describe('"esModule" option', () => { expect(getErrors(stats)).toMatchSnapshot('errors'); }); - it('should work with "exportNamed" option', async () => { + it('should work with "namedExport" option', async () => { const compiler = getCompiler('./es-module/named/base/index.js', { esModule: true, modules: { @@ -120,7 +120,7 @@ describe('"esModule" option', () => { expect(getErrors(stats)).toMatchSnapshot('errors'); }); - it('should work with "exportNamed" option with nested import', async () => { + it('should work with "namedExport" option with nested import', async () => { const compiler = getCompiler('./es-module/named/nested/index.js', { esModule: true, modules: { @@ -139,7 +139,7 @@ describe('"esModule" option', () => { expect(getErrors(stats)).toMatchSnapshot('errors'); }); - it('should work js template with "exportNamed" option', async () => { + it('should work js template with "namedExport" option', async () => { const compiler = getCompiler('./es-module/named/template/index.js', { esModule: true, modules: { @@ -172,7 +172,7 @@ describe('"esModule" option', () => { expect(getErrors(stats, true)).toMatchSnapshot('errors'); }); - it('should emit error when exportNamed true && esModule false', async () => { + it('should emit error when namedExport true && esModule false', async () => { const compiler = getCompiler('./es-module/named/base/index.js', { esModule: false, modules: { diff --git a/test/fixtures/es-module/named/base/index.css b/test/fixtures/es-module/named/base/index.css new file mode 100644 index 00000000..628f3ed6 --- /dev/null +++ b/test/fixtures/es-module/named/base/index.css @@ -0,0 +1,7 @@ +:local(.bar-baz) { + color: red; +} + +:global(.bar) { + color: red; +} diff --git a/test/fixtures/es-module/named/base/index.js b/test/fixtures/es-module/named/base/index.js new file mode 100644 index 00000000..e1a19303 --- /dev/null +++ b/test/fixtures/es-module/named/base/index.js @@ -0,0 +1,5 @@ +import css from './index.css'; + +__export__ = css; + +export default css; diff --git a/test/fixtures/es-module/named/broken/index.css b/test/fixtures/es-module/named/broken/index.css new file mode 100644 index 00000000..a219fc2f --- /dev/null +++ b/test/fixtures/es-module/named/broken/index.css @@ -0,0 +1,3 @@ +:local(.class) { + color: red; +} diff --git a/test/fixtures/es-module/named/broken/index.js b/test/fixtures/es-module/named/broken/index.js new file mode 100644 index 00000000..e1a19303 --- /dev/null +++ b/test/fixtures/es-module/named/broken/index.js @@ -0,0 +1,5 @@ +import css from './index.css'; + +__export__ = css; + +export default css; diff --git a/test/fixtures/es-module/named/nested/index.css b/test/fixtures/es-module/named/nested/index.css new file mode 100644 index 00000000..ff9669f1 --- /dev/null +++ b/test/fixtures/es-module/named/nested/index.css @@ -0,0 +1,5 @@ +@value v-def from '../../../modules/composes/values.css'; + +.ghi { + color: v-def; +} diff --git a/test/fixtures/es-module/named/nested/index.js b/test/fixtures/es-module/named/nested/index.js new file mode 100644 index 00000000..e1a19303 --- /dev/null +++ b/test/fixtures/es-module/named/nested/index.js @@ -0,0 +1,5 @@ +import css from './index.css'; + +__export__ = css; + +export default css; diff --git a/test/fixtures/es-module/named/template/index.css b/test/fixtures/es-module/named/template/index.css new file mode 100644 index 00000000..b3ccc301 --- /dev/null +++ b/test/fixtures/es-module/named/template/index.css @@ -0,0 +1,11 @@ +:local(.header-baz) { + color: red; +} + +:local(.body) { + color: coral; +} + +:local(.footer) { + color: blue; +} diff --git a/test/fixtures/es-module/named/template/index.js b/test/fixtures/es-module/named/template/index.js new file mode 100644 index 00000000..8dc4c0e0 --- /dev/null +++ b/test/fixtures/es-module/named/template/index.js @@ -0,0 +1,8 @@ +import css from './index.css'; +import html from './template.js'; + +const result = {css, html}; + +__export__ = result; + +export default result; diff --git a/test/fixtures/es-module/named/template/template.js b/test/fixtures/es-module/named/template/template.js new file mode 100644 index 00000000..6bdb73a5 --- /dev/null +++ b/test/fixtures/es-module/named/template/template.js @@ -0,0 +1,11 @@ +import { headerBaz, body, footer } from './index.css'; + +let html = '\n'; + +html += `
\n`; +html += `
\n`; +html += `
\n`; + +__export__ = html; + +export default html;