diff --git a/README.md b/README.md index 9179e787..8020201e 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 | | **[`localsConvention`](#localsconvention)** | `{String}` | `'asIs'` | Style of exported classnames | | **[`onlyLocals`](#onlylocals)** | `{Boolean}` | `false` | Export only locals | +| **[`esModule`](#esmodule)** | `{Boolean}` | `false` | Use ES modules syntax | ### `url` @@ -857,6 +858,34 @@ module.exports = { }; ``` +### `esModule` + +Type: `Boolean` +Default: `false` + +By default, `css-loader` generates JS modules that use the CommonJS modules syntax. +There are some cases in which using ES modules is beneficial, like in the case of [module concatenation](https://webpack.js.org/plugins/module-concatenation-plugin/) and [tree shaking](https://webpack.js.org/guides/tree-shaking/). + +You can enable a ES module syntax using: + +**webpack.config.js** + +```js +module.exports = { + module: { + rules: [ + { + test: /\.css$/i, + loader: 'css-loader', + options: { + esModule: true, + }, + }, + ], + }, +}; +``` + ## Examples ### Assets diff --git a/package-lock.json b/package-lock.json index 79338ad5..b509417c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9727,9 +9727,9 @@ "dev": true }, "postcss": { - "version": "7.0.24", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.24.tgz", - "integrity": "sha512-Xl0XvdNWg+CblAXzNvbSOUvgJXwSjmbAKORqyw9V2AlHrm1js2gFw9y3jibBAhpKZi8b5JzJCVh/FyzPsTtgTA==", + "version": "7.0.25", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.25.tgz", + "integrity": "sha512-NXXVvWq9icrm/TgQC0O6YVFi4StfJz46M1iNd/h6B26Nvh/HKI+q4YZtFN/EjcInZliEscO/WL10BXnc1E5nwg==", "requires": { "chalk": "^2.4.2", "source-map": "^0.6.1", @@ -11897,9 +11897,9 @@ "dev": true }, "terser": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.4.2.tgz", - "integrity": "sha512-Uufrsvhj9O1ikwgITGsZ5EZS6qPokUOkCegS7fYOdGTv+OA90vndUbU6PEjr5ePqHfNUbGyMO7xyIZv2MhsALQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.4.3.tgz", + "integrity": "sha512-0ikKraVtRDKGzHrzkCv5rUNDzqlhmhowOBqC0XqUHFpW+vJ45+20/IFBcebwKfiS2Z9fJin6Eo+F1zLZsxi8RA==", "dev": true, "requires": { "commander": "^2.20.0", diff --git a/src/index.js b/src/index.js index aeb94239..b4d7c311 100644 --- a/src/index.js +++ b/src/index.js @@ -107,12 +107,16 @@ export default function loader(content, map, meta) { } const { importLoaders, localsConvention } = options; + const esModule = + typeof options.esModule !== 'undefined' ? options.esModule : false; + const importCode = getImportCode( this, imports, exportType, sourceMap, - importLoaders + importLoaders, + esModule ); const moduleCode = getModuleCode( this, @@ -126,7 +130,8 @@ export default function loader(content, map, meta) { exports, exportType, replacers, - localsConvention + localsConvention, + esModule ); return callback(null, [importCode, moduleCode, exportCode].join('')); diff --git a/src/options.json b/src/options.json index efa13be8..e3125073 100644 --- a/src/options.json +++ b/src/options.json @@ -94,6 +94,10 @@ "onlyLocals": { "description": "Export only locals (https://github.com/webpack-contrib/css-loader#onlylocals).", "type": "boolean" + }, + "esModule": { + "description": "Use the ES modules syntax (https://github.com/webpack-contrib/css-loader#esmodule).", + "type": "boolean" } }, "type": "object" diff --git a/src/utils.js b/src/utils.js index 4fc8efe3..c5b00d10 100644 --- a/src/utils.js +++ b/src/utils.js @@ -205,7 +205,8 @@ function getImportCode( imports, exportType, sourceMap, - importLoaders + importLoaders, + esModule ) { const importItems = []; const codeItems = []; @@ -216,12 +217,21 @@ function getImportCode( if (exportType === 'full') { importItems.push( - `var ___CSS_LOADER_API_IMPORT___ = require(${stringifyRequest( - loaderContext, - require.resolve('./runtime/api') - )});` + esModule + ? `import ___CSS_LOADER_API_IMPORT___ from ${stringifyRequest( + loaderContext, + require.resolve('./runtime/api') + )};` + : `var ___CSS_LOADER_API_IMPORT___ = require(${stringifyRequest( + loaderContext, + require.resolve('./runtime/api') + )});` + ); + codeItems.push( + esModule + ? `var exports = ___CSS_LOADER_API_IMPORT___(${sourceMap});` + : `exports = ___CSS_LOADER_API_IMPORT___(${sourceMap});` ); - codeItems.push(`exports = ___CSS_LOADER_API_IMPORT___(${sourceMap});`); } imports.forEach((item) => { @@ -251,10 +261,15 @@ function getImportCode( importName = `___CSS_LOADER_AT_RULE_IMPORT_${atRuleImportNames.size}___`; importItems.push( - `var ${importName} = require(${stringifyRequest( - loaderContext, - importPrefix + url - )});` + esModule + ? `import ${importName} from ${stringifyRequest( + loaderContext, + importPrefix + url + )};` + : `var ${importName} = require(${stringifyRequest( + loaderContext, + importPrefix + url + )});` ); atRuleImportNames.set(url, importName); @@ -267,10 +282,15 @@ function getImportCode( { if (urlImportNames.size === 0) { importItems.push( - `var ___CSS_LOADER_GET_URL_IMPORT___ = require(${stringifyRequest( - loaderContext, - require.resolve('./runtime/getUrl.js') - )});` + esModule + ? `import ___CSS_LOADER_GET_URL_IMPORT___ from ${stringifyRequest( + loaderContext, + require.resolve('./runtime/getUrl.js') + )};` + : `var ___CSS_LOADER_GET_URL_IMPORT___ = require(${stringifyRequest( + loaderContext, + require.resolve('./runtime/getUrl.js') + )});` ); } @@ -281,10 +301,15 @@ function getImportCode( if (!importName) { importName = `___CSS_LOADER_URL_IMPORT_${urlImportNames.size}___`; importItems.push( - `var ${importName} = require(${stringifyRequest( - loaderContext, - url - )});` + esModule + ? `import ${importName} from ${stringifyRequest( + loaderContext, + url + )};` + : `var ${importName} = require(${stringifyRequest( + loaderContext, + url + )});` ); urlImportNames.set(url, importName); @@ -311,10 +336,15 @@ function getImportCode( } importItems.push( - `var ${importName} = require(${stringifyRequest( - loaderContext, - importPrefix + url - )});` + esModule + ? `import ${importName} from ${stringifyRequest( + loaderContext, + importPrefix + url + )};` + : `var ${importName} = require(${stringifyRequest( + loaderContext, + importPrefix + url + )});` ); if (exportType === 'full') { @@ -380,7 +410,8 @@ function getExportCode( exports, exportType, replacers, - localsConvention + localsConvention, + esModule ) { const exportItems = []; let exportLocalsCode; @@ -448,13 +479,19 @@ function getExportCode( } if (exportType === 'locals') { - exportItems.push(`module.exports = {\n${exportLocalsCode}\n};`); + exportItems.push( + `${ + esModule ? 'export default' : 'module.exports =' + } {\n${exportLocalsCode}\n};` + ); } else { if (exportLocalsCode) { exportItems.push(`exports.locals = {\n${exportLocalsCode}\n};`); } - exportItems.push('module.exports = exports;'); + exportItems.push( + `${esModule ? 'export default' : 'module.exports ='} exports;` + ); } return `// Exports\n${exportItems.join('\n')}\n`; diff --git a/test/__snapshots__/esModule-option.test.js.snap b/test/__snapshots__/esModule-option.test.js.snap new file mode 100644 index 00000000..9aca855e --- /dev/null +++ b/test/__snapshots__/esModule-option.test.js.snap @@ -0,0 +1,283 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`"esModule" option should work when not specified: errors 1`] = `Array []`; + +exports[`"esModule" option should work when not specified: module 1`] = ` +"// Imports +var ___CSS_LOADER_API_IMPORT___ = require(\\"../../../src/runtime/api.js\\"); +var ___CSS_LOADER_AT_RULE_IMPORT_0___ = require(\\"-!../../../src/index.js??[ident]!./imported.css\\"); +var ___CSS_LOADER_GET_URL_IMPORT___ = require(\\"../../../src/runtime/getUrl.js\\"); +var ___CSS_LOADER_URL_IMPORT_0___ = require(\\"./img.png\\"); +exports = ___CSS_LOADER_API_IMPORT___(false); +exports.i(___CSS_LOADER_AT_RULE_IMPORT_0___); +var ___CSS_LOADER_URL_REPLACEMENT_0___ = ___CSS_LOADER_GET_URL_IMPORT___(___CSS_LOADER_URL_IMPORT_0___); +// Module +exports.push([module.id, \\"@charset \\\\\\"UTF-8\\\\\\";\\\\n\\\\n/* Comment */\\\\n\\\\n.class {\\\\n color: red;\\\\n background: url(\\" + ___CSS_LOADER_URL_REPLACEMENT_0___ + \\");\\\\n}\\\\n\\", \\"\\"]); +// Exports +module.exports = exports; +" +`; + +exports[`"esModule" option should work when not specified: result 1`] = ` +Array [ + Array [ + "../../src/index.js?[ident]!./es-module/imported.css", + ".foo { + color: red; +} +", + "", + ], + Array [ + "./es-module/source.css", + "@charset \\"UTF-8\\"; + +/* Comment */ + +.class { + color: red; + background: url(/webpack/public/path/img.png); +} +", + "", + ], +] +`; + +exports[`"esModule" option should work when not specified: 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`] = ` +"// Imports +var ___CSS_LOADER_API_IMPORT___ = require(\\"../../../src/runtime/api.js\\"); +var ___CSS_LOADER_AT_RULE_IMPORT_0___ = require(\\"-!../../../src/index.js??[ident]!./imported.css\\"); +var ___CSS_LOADER_GET_URL_IMPORT___ = require(\\"../../../src/runtime/getUrl.js\\"); +var ___CSS_LOADER_URL_IMPORT_0___ = require(\\"./img.png\\"); +exports = ___CSS_LOADER_API_IMPORT___(false); +exports.i(___CSS_LOADER_AT_RULE_IMPORT_0___); +var ___CSS_LOADER_URL_REPLACEMENT_0___ = ___CSS_LOADER_GET_URL_IMPORT___(___CSS_LOADER_URL_IMPORT_0___); +// Module +exports.push([module.id, \\"@charset \\\\\\"UTF-8\\\\\\";\\\\n\\\\n/* Comment */\\\\n\\\\n.class {\\\\n color: red;\\\\n background: url(\\" + ___CSS_LOADER_URL_REPLACEMENT_0___ + \\");\\\\n}\\\\n\\", \\"\\"]); +// Exports +module.exports = exports; +" +`; + +exports[`"esModule" option should work with a value equal to "false": result 1`] = ` +Array [ + Array [ + "../../src/index.js?[ident]!./es-module/imported.css", + ".foo { + color: red; +} +", + "", + ], + Array [ + "./es-module/source.css", + "@charset \\"UTF-8\\"; + +/* Comment */ + +.class { + color: red; + background: url(/webpack/public/path/img.png); +} +", + "", + ], +] +`; + +exports[`"esModule" option should work with a value equal to "false": warnings 1`] = `Array []`; + +exports[`"esModule" option should work with a value equal to "true" and the "mode" value equal to "global": errors 1`] = `Array []`; + +exports[`"esModule" option should work with a value equal to "true" and the "mode" value equal to "global": module 1`] = ` +"// Imports +import ___CSS_LOADER_API_IMPORT___ from \\"../../../src/runtime/api.js\\"; +import ___CSS_LOADER_AT_RULE_IMPORT_0___ from \\"-!../../../src/index.js??[ident]!./imported.css\\"; +import ___CSS_LOADER_GET_URL_IMPORT___ from \\"../../../src/runtime/getUrl.js\\"; +import ___CSS_LOADER_URL_IMPORT_0___ from \\"./img.png\\"; +var exports = ___CSS_LOADER_API_IMPORT___(false); +exports.i(___CSS_LOADER_AT_RULE_IMPORT_0___); +var ___CSS_LOADER_URL_REPLACEMENT_0___ = ___CSS_LOADER_GET_URL_IMPORT___(___CSS_LOADER_URL_IMPORT_0___); +// Module +exports.push([module.id, \\"@charset \\\\\\"UTF-8\\\\\\";\\\\n\\\\n/* Comment */\\\\n\\\\n.class {\\\\n color: red;\\\\n background: url(\\" + ___CSS_LOADER_URL_REPLACEMENT_0___ + \\");\\\\n}\\\\n\\", \\"\\"]); +// Exports +export default exports; +" +`; + +exports[`"esModule" option should work with a value equal to "true" and the "mode" value equal to "global": result 1`] = ` +Array [ + Array [ + "../../src/index.js?[ident]!./es-module/imported.css", + ".foo { + color: red; +} +", + "", + ], + Array [ + "./es-module/source.css", + "@charset \\"UTF-8\\"; + +/* Comment */ + +.class { + color: red; + background: url(/webpack/public/path/img.png); +} +", + "", + ], +] +`; + +exports[`"esModule" option should work with a value equal to "true" and the "mode" value equal to "global": warnings 1`] = `Array []`; + +exports[`"esModule" option should work with a value equal to "true" and the "mode" value equal to "local": errors 1`] = `Array []`; + +exports[`"esModule" option should work with a value equal to "true" and the "mode" value equal to "local": module 1`] = ` +"// Imports +import ___CSS_LOADER_API_IMPORT___ from \\"../../../src/runtime/api.js\\"; +import ___CSS_LOADER_AT_RULE_IMPORT_0___ from \\"-!../../../src/index.js??[ident]!./imported.css\\"; +import ___CSS_LOADER_GET_URL_IMPORT___ from \\"../../../src/runtime/getUrl.js\\"; +import ___CSS_LOADER_URL_IMPORT_0___ from \\"./img.png\\"; +var exports = ___CSS_LOADER_API_IMPORT___(false); +exports.i(___CSS_LOADER_AT_RULE_IMPORT_0___); +var ___CSS_LOADER_URL_REPLACEMENT_0___ = ___CSS_LOADER_GET_URL_IMPORT___(___CSS_LOADER_URL_IMPORT_0___); +// Module +exports.push([module.id, \\"@charset \\\\\\"UTF-8\\\\\\";\\\\n\\\\n/* Comment */\\\\n\\\\n._3S58jeCkC6SOPhVLbU-Bwn {\\\\n color: red;\\\\n background: url(\\" + ___CSS_LOADER_URL_REPLACEMENT_0___ + \\");\\\\n}\\\\n\\", \\"\\"]); +// Exports +exports.locals = { + \\"class\\": \\"_3S58jeCkC6SOPhVLbU-Bwn\\" +}; +export default exports; +" +`; + +exports[`"esModule" option should work with a value equal to "true" and the "mode" value equal to "local": result 1`] = ` +Array [ + Array [ + "../../src/index.js?[ident]!./es-module/imported.css", + "._3xfjtZ03Dx7Cld7Debi-wl { + color: red; +} +", + "", + ], + Array [ + "./es-module/source.css", + "@charset \\"UTF-8\\"; + +/* Comment */ + +._3S58jeCkC6SOPhVLbU-Bwn { + color: red; + background: url(/webpack/public/path/img.png); +} +", + "", + ], +] +`; + +exports[`"esModule" option should work with a value equal to "true" and the "mode" value equal to "local": warnings 1`] = `Array []`; + +exports[`"esModule" option should work with a value equal to "true" and the "mode" value equal to "pure": errors 1`] = `Array []`; + +exports[`"esModule" option should work with a value equal to "true" and the "mode" value equal to "pure": module 1`] = ` +"// Imports +import ___CSS_LOADER_API_IMPORT___ from \\"../../../src/runtime/api.js\\"; +import ___CSS_LOADER_AT_RULE_IMPORT_0___ from \\"-!../../../src/index.js??[ident]!./imported.css\\"; +import ___CSS_LOADER_GET_URL_IMPORT___ from \\"../../../src/runtime/getUrl.js\\"; +import ___CSS_LOADER_URL_IMPORT_0___ from \\"./img.png\\"; +var exports = ___CSS_LOADER_API_IMPORT___(false); +exports.i(___CSS_LOADER_AT_RULE_IMPORT_0___); +var ___CSS_LOADER_URL_REPLACEMENT_0___ = ___CSS_LOADER_GET_URL_IMPORT___(___CSS_LOADER_URL_IMPORT_0___); +// Module +exports.push([module.id, \\"@charset \\\\\\"UTF-8\\\\\\";\\\\n\\\\n/* Comment */\\\\n\\\\n._3S58jeCkC6SOPhVLbU-Bwn {\\\\n color: red;\\\\n background: url(\\" + ___CSS_LOADER_URL_REPLACEMENT_0___ + \\");\\\\n}\\\\n\\", \\"\\"]); +// Exports +exports.locals = { + \\"class\\": \\"_3S58jeCkC6SOPhVLbU-Bwn\\" +}; +export default exports; +" +`; + +exports[`"esModule" option should work with a value equal to "true" and the "mode" value equal to "pure": result 1`] = ` +Array [ + Array [ + "../../src/index.js?[ident]!./es-module/imported.css", + "._3xfjtZ03Dx7Cld7Debi-wl { + color: red; +} +", + "", + ], + Array [ + "./es-module/source.css", + "@charset \\"UTF-8\\"; + +/* Comment */ + +._3S58jeCkC6SOPhVLbU-Bwn { + color: red; + background: url(/webpack/public/path/img.png); +} +", + "", + ], +] +`; + +exports[`"esModule" option should work with a value equal to "true" and the "mode" value equal to "pure": warnings 1`] = `Array []`; + +exports[`"esModule" option should work with a value equal to "true": errors 1`] = `Array []`; + +exports[`"esModule" option should work with a value equal to "true": module 1`] = ` +"// Imports +import ___CSS_LOADER_API_IMPORT___ from \\"../../../src/runtime/api.js\\"; +import ___CSS_LOADER_AT_RULE_IMPORT_0___ from \\"-!../../../src/index.js??[ident]!./imported.css\\"; +import ___CSS_LOADER_GET_URL_IMPORT___ from \\"../../../src/runtime/getUrl.js\\"; +import ___CSS_LOADER_URL_IMPORT_0___ from \\"./img.png\\"; +var exports = ___CSS_LOADER_API_IMPORT___(false); +exports.i(___CSS_LOADER_AT_RULE_IMPORT_0___); +var ___CSS_LOADER_URL_REPLACEMENT_0___ = ___CSS_LOADER_GET_URL_IMPORT___(___CSS_LOADER_URL_IMPORT_0___); +// Module +exports.push([module.id, \\"@charset \\\\\\"UTF-8\\\\\\";\\\\n\\\\n/* Comment */\\\\n\\\\n.class {\\\\n color: red;\\\\n background: url(\\" + ___CSS_LOADER_URL_REPLACEMENT_0___ + \\");\\\\n}\\\\n\\", \\"\\"]); +// Exports +export default exports; +" +`; + +exports[`"esModule" option should work with a value equal to "true": result 1`] = ` +Array [ + Array [ + "../../src/index.js?[ident]!./es-module/imported.css", + ".foo { + color: red; +} +", + "", + ], + Array [ + "./es-module/source.css", + "@charset \\"UTF-8\\"; + +/* Comment */ + +.class { + color: red; + background: url(/webpack/public/path/img.png); +} +", + "", + ], +] +`; + +exports[`"esModule" option should work with a value equal to "true": warnings 1`] = `Array []`; diff --git a/test/__snapshots__/loader.test.js.snap b/test/__snapshots__/loader.test.js.snap index dd9c88b7..681807fc 100644 --- a/test/__snapshots__/loader.test.js.snap +++ b/test/__snapshots__/loader.test.js.snap @@ -151,6 +151,10 @@ exports[`loader should work with ModuleConcatenationPlugin (url-loader): errors exports[`loader should work with ModuleConcatenationPlugin (url-loader): warnings 1`] = `Array []`; +exports[`loader should work with ModuleConcatenationPlugin: errors 1`] = `Array []`; + +exports[`loader should work with ModuleConcatenationPlugin: warnings 1`] = `Array []`; + exports[`loader should work with empty css: errors 1`] = `Array []`; exports[`loader should work with empty css: module 1`] = ` diff --git a/test/__snapshots__/onlyLocals-option.test.js.snap b/test/__snapshots__/onlyLocals-option.test.js.snap index 0ec7a29e..82845d42 100644 --- a/test/__snapshots__/onlyLocals-option.test.js.snap +++ b/test/__snapshots__/onlyLocals-option.test.js.snap @@ -1,5 +1,117 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`"onlyLocals" option should work with the "esModule" option: errors 1`] = `Array []`; + +exports[`"onlyLocals" option should work with the "esModule" option: module 1`] = ` +"// Imports +import ___CSS_LOADER_ICSS_IMPORT_0___ from \\"-!../../../../src/index.js??[ident]!./values.css\\"; +import ___CSS_LOADER_ICSS_IMPORT_1___ from \\"-!../../../../src/index.js??[ident]!./something.css\\"; +import ___CSS_LOADER_ICSS_IMPORT_2___ from \\"-!../../../../src/index.js??[ident]!./imported-simple.css\\"; +import ___CSS_LOADER_ICSS_IMPORT_3___ from \\"-!../../../../src/index.js??[ident]!./relative.css\\"; +import ___CSS_LOADER_ICSS_IMPORT_4___ from \\"-!../../../../src/index.js??[ident]!./top-relative.css\\"; +import ___CSS_LOADER_ICSS_IMPORT_5___ from \\"-!../../../../src/index.js??[ident]!../issue-861/node_modules/package/style.css\\"; +import ___CSS_LOADER_ICSS_IMPORT_6___ from \\"-!../../../../src/index.js??[ident]!aliasesComposes/alias.css\\"; +// Exports +export default { + \\"v-def\\": \\"\\" + ___CSS_LOADER_ICSS_IMPORT_0___[\\"v-def\\"] + \\"\\", + \\"v-other\\": \\"\\" + ___CSS_LOADER_ICSS_IMPORT_0___[\\"v-other\\"] + \\"\\", + \\"s-white\\": \\"\\" + ___CSS_LOADER_ICSS_IMPORT_0___[\\"s-white\\"] + \\"\\", + \\"m-small\\": \\"\\" + ___CSS_LOADER_ICSS_IMPORT_0___[\\"m-small\\"] + \\"\\", + \\"v-something\\": \\"\\" + ___CSS_LOADER_ICSS_IMPORT_1___[\\"v-something\\"] + \\"\\", + \\"v-foo\\": \\"blue\\", + \\"v-bar\\": \\"block\\", + \\"v-primary\\": \\"#BF4040\\", + \\"s-black\\": \\"black-selector\\", + \\"m-large\\": \\"(min-width: 960px)\\", + \\"v-ident\\": \\"validIdent\\", + \\"v-pre-defined-ident\\": \\"left\\", + \\"v-string\\": \\"'content'\\", + \\"v-string-1\\": \\"''\\", + \\"v-url\\": \\"url(https://www.exammple.com/images/my-background.png)\\", + \\"v-url-1\\": \\"url('https://www.exammple.com/images/my-background.png')\\", + \\"v-url-2\\": \\"url(\\\\\\"https://www.exammple.com/images/my-background.png\\\\\\")\\", + \\"v-integer\\": \\"100\\", + \\"v-integer-1\\": \\"-100\\", + \\"v-integer-2\\": \\"+100\\", + \\"v-number\\": \\".60\\", + \\"v-number-1\\": \\"-456.8\\", + \\"v-number-2\\": \\"-3.4e-2\\", + \\"v-dimension\\": \\"12px\\", + \\"v-percentage\\": \\"100%\\", + \\"v-hex\\": \\"#fff\\", + \\"v-function\\": \\"rgb(0,0,0)\\", + \\"v-unicode-range\\": \\"U+0025-00FF\\", + \\"ghi\\": \\"_ghi\\", + \\"class\\": \\"_class\\", + \\"other\\": \\"_other\\", + \\"other-other\\": \\"_other-other\\", + \\"green\\": \\"_green\\", + \\"foo\\": \\"_foo\\", + \\"simple\\": \\"_simple \\" + ___CSS_LOADER_ICSS_IMPORT_2___[\\"imported-simple\\"] + \\"\\", + \\"relative\\": \\"_relative \\" + ___CSS_LOADER_ICSS_IMPORT_3___[\\"imported-relative\\"] + \\"\\", + \\"top-relative\\": \\"_top-relative \\" + ___CSS_LOADER_ICSS_IMPORT_4___[\\"imported-relative\\"] + \\"\\", + \\"module\\": \\"_module \\" + ___CSS_LOADER_ICSS_IMPORT_5___[\\"imported-module\\"] + \\"\\", + \\"alias\\": \\"_alias \\" + ___CSS_LOADER_ICSS_IMPORT_6___[\\"imported-alias\\"] + \\"\\", + \\"primary-selector\\": \\"_primary-selector\\", + \\"black-selector\\": \\"_black-selector\\", + \\"header\\": \\"_header\\", + \\"foobarbaz\\": \\"_foobarbaz\\", + \\"url\\": \\"_url\\" +}; +" +`; + +exports[`"onlyLocals" option should work with the "esModule" option: result 1`] = ` +Object { + "alias": "_alias _imported-alias", + "black-selector": "_black-selector", + "class": "_class", + "foo": "_foo", + "foobarbaz": "_foobarbaz", + "ghi": "_ghi", + "green": "_green", + "header": "_header", + "m-large": "(min-width: 960px)", + "m-small": "(min-width: 320px)", + "module": "_module _imported-module", + "other": "_other", + "other-other": "_other-other", + "primary-selector": "_primary-selector", + "relative": "_relative _imported-relative", + "s-black": "black-selector", + "s-white": "white", + "simple": "_simple _imported-simple", + "top-relative": "_top-relative undefined", + "url": "_url", + "v-bar": "block", + "v-def": "red", + "v-dimension": "12px", + "v-foo": "blue", + "v-function": "rgb(0,0,0)", + "v-hex": "#fff", + "v-ident": "validIdent", + "v-integer": "100", + "v-integer-1": "-100", + "v-integer-2": "+100", + "v-number": ".60", + "v-number-1": "-456.8", + "v-number-2": "-3.4e-2", + "v-other": "green", + "v-percentage": "100%", + "v-pre-defined-ident": "left", + "v-primary": "#BF4040", + "v-something": "2112moon", + "v-string": "'content'", + "v-string-1": "''", + "v-unicode-range": "U+0025-00FF", + "v-url": "url(https://www.exammple.com/images/my-background.png)", + "v-url-1": "url('https://www.exammple.com/images/my-background.png')", + "v-url-2": "url(\\"https://www.exammple.com/images/my-background.png\\")", +} +`; + +exports[`"onlyLocals" option should work with the "esModule" option: warnings 1`] = `Array []`; + exports[`"onlyLocals" option should work: errors 1`] = `Array []`; exports[`"onlyLocals" option should work: module 1`] = ` diff --git a/test/__snapshots__/validate-options.test.js.snap b/test/__snapshots__/validate-options.test.js.snap index 212f97ef..3b259f02 100644 --- a/test/__snapshots__/validate-options.test.js.snap +++ b/test/__snapshots__/validate-options.test.js.snap @@ -1,5 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`validate options should throw an error on the "esModule" option with "true" value 1`] = ` +"Invalid options object. CSS Loader has been initialised using an options object that does not match the API schema. + - options.esModule should be a boolean. + -> Use the ES modules syntax (https://github.com/webpack-contrib/css-loader#esmodule)." +`; + exports[`validate options should throw an error on the "import" option with "true" value 1`] = ` "Invalid options object. CSS Loader has been initialised using an options object that does not match the API schema. - options.import should be one of these: @@ -159,49 +165,49 @@ exports[`validate options should throw an error on the "sourceMap" option with " exports[`validate options should throw an error on the "unknown" option with "/test/" value 1`] = ` "Invalid options object. CSS Loader has been initialised using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { url?, import?, modules?, sourceMap?, importLoaders?, localsConvention?, onlyLocals? }" + object { url?, import?, modules?, sourceMap?, importLoaders?, localsConvention?, onlyLocals?, esModule? }" `; exports[`validate options should throw an error on the "unknown" option with "[]" value 1`] = ` "Invalid options object. CSS Loader has been initialised using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { url?, import?, modules?, sourceMap?, importLoaders?, localsConvention?, onlyLocals? }" + object { url?, import?, modules?, sourceMap?, importLoaders?, localsConvention?, onlyLocals?, esModule? }" `; exports[`validate options should throw an error on the "unknown" option with "{"foo":"bar"}" value 1`] = ` "Invalid options object. CSS Loader has been initialised using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { url?, import?, modules?, sourceMap?, importLoaders?, localsConvention?, onlyLocals? }" + object { url?, import?, modules?, sourceMap?, importLoaders?, localsConvention?, onlyLocals?, esModule? }" `; exports[`validate options should throw an error on the "unknown" option with "{}" value 1`] = ` "Invalid options object. CSS Loader has been initialised using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { url?, import?, modules?, sourceMap?, importLoaders?, localsConvention?, onlyLocals? }" + object { url?, import?, modules?, sourceMap?, importLoaders?, localsConvention?, onlyLocals?, esModule? }" `; exports[`validate options should throw an error on the "unknown" option with "1" value 1`] = ` "Invalid options object. CSS Loader has been initialised using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { url?, import?, modules?, sourceMap?, importLoaders?, localsConvention?, onlyLocals? }" + object { url?, import?, modules?, sourceMap?, importLoaders?, localsConvention?, onlyLocals?, esModule? }" `; exports[`validate options should throw an error on the "unknown" option with "false" value 1`] = ` "Invalid options object. CSS Loader has been initialised using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { url?, import?, modules?, sourceMap?, importLoaders?, localsConvention?, onlyLocals? }" + object { url?, import?, modules?, sourceMap?, importLoaders?, localsConvention?, onlyLocals?, esModule? }" `; exports[`validate options should throw an error on the "unknown" option with "test" value 1`] = ` "Invalid options object. CSS Loader has been initialised using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { url?, import?, modules?, sourceMap?, importLoaders?, localsConvention?, onlyLocals? }" + object { url?, import?, modules?, sourceMap?, importLoaders?, localsConvention?, onlyLocals?, esModule? }" `; exports[`validate options should throw an error on the "unknown" option with "true" value 1`] = ` "Invalid options object. CSS Loader has been initialised using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object { url?, import?, modules?, sourceMap?, importLoaders?, localsConvention?, onlyLocals? }" + object { url?, import?, modules?, sourceMap?, importLoaders?, localsConvention?, onlyLocals?, esModule? }" `; exports[`validate options should throw an error on the "url" option with "true" value 1`] = ` diff --git a/test/esModule-option.test.js b/test/esModule-option.test.js new file mode 100644 index 00000000..3bfdce61 --- /dev/null +++ b/test/esModule-option.test.js @@ -0,0 +1,103 @@ +import { + compile, + getCompiler, + getErrors, + getExecutedCode, + getModuleSource, + getWarnings, +} from './helpers/index'; + +describe('"esModule" option', () => { + it('should work when not specified', async () => { + const compiler = getCompiler('./es-module/source.js'); + const stats = await compile(compiler); + + expect(getModuleSource('./es-module/source.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 a value equal to "true"', async () => { + const compiler = getCompiler('./es-module/source.js', { esModule: true }); + const stats = await compile(compiler); + + expect(getModuleSource('./es-module/source.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 a value equal to "true" and the "mode" value equal to "local"', async () => { + const compiler = getCompiler('./es-module/source.js', { + esModule: true, + modules: 'local', + }); + const stats = await compile(compiler); + + expect(getModuleSource('./es-module/source.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 a value equal to "true" and the "mode" value equal to "global"', async () => { + const compiler = getCompiler('./es-module/source.js', { + esModule: true, + modules: 'global', + }); + const stats = await compile(compiler); + + expect(getModuleSource('./es-module/source.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 a value equal to "true" and the "mode" value equal to "pure"', async () => { + const compiler = getCompiler('./es-module/source.js', { + esModule: true, + modules: 'pure', + }); + const stats = await compile(compiler); + + expect(getModuleSource('./es-module/source.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 a value equal to "false"', async () => { + const compiler = getCompiler('./es-module/source.js', { esModule: false }); + const stats = await compile(compiler); + + expect(getModuleSource('./es-module/source.css', stats)).toMatchSnapshot( + 'module' + ); + expect(getExecutedCode('main.bundle.js', compiler, stats)).toMatchSnapshot( + 'result' + ); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); +}); diff --git a/test/fixtures/es-module/img.png b/test/fixtures/es-module/img.png new file mode 100644 index 00000000..b74b839e Binary files /dev/null and b/test/fixtures/es-module/img.png differ diff --git a/test/fixtures/es-module/imported.css b/test/fixtures/es-module/imported.css new file mode 100644 index 00000000..a15c877a --- /dev/null +++ b/test/fixtures/es-module/imported.css @@ -0,0 +1,3 @@ +.foo { + color: red; +} diff --git a/test/fixtures/es-module/source.css b/test/fixtures/es-module/source.css new file mode 100644 index 00000000..e0750e64 --- /dev/null +++ b/test/fixtures/es-module/source.css @@ -0,0 +1,10 @@ +@charset "UTF-8"; + +@import './imported.css'; + +/* Comment */ + +.class { + color: red; + background: url("./img.png"); +} diff --git a/test/fixtures/es-module/source.js b/test/fixtures/es-module/source.js new file mode 100644 index 00000000..1996779e --- /dev/null +++ b/test/fixtures/es-module/source.js @@ -0,0 +1,5 @@ +import css from './source.css'; + +__export__ = css; + +export default css; diff --git a/test/loader.test.js b/test/loader.test.js index 40af0f9b..da973d79 100644 --- a/test/loader.test.js +++ b/test/loader.test.js @@ -154,6 +154,44 @@ describe('loader', () => { expect(getErrors(stats)).toMatchSnapshot('errors'); }); + it('should work with ModuleConcatenationPlugin', async () => { + const compiler = getCompiler( + './basic.js', + {}, + { + mode: 'production', + module: { + rules: [ + { + test: /\.css$/i, + use: [ + { + loader: path.resolve(__dirname, '../src'), + options: { esModule: true }, + }, + ], + }, + { + test: /\.(png|jpg|gif|svg|eot|ttf|woff|woff2)$/, + loader: 'file-loader', + options: { name: '[name].[ext]', esModule: true }, + }, + ], + }, + } + ); + const stats = await compile(compiler); + + if (stats.compilation.modules.size) { + expect(stats.compilation.modules.size).toBe(10); + } else { + expect(stats.compilation.modules.length).toBe(6); + } + + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + it('should work with ModuleConcatenationPlugin (file-loader)', async () => { const compiler = getCompiler( './basic.js', @@ -169,7 +207,7 @@ describe('loader', () => { { test: /\.(png|jpg|gif|svg|eot|ttf|woff|woff2)$/, loader: 'file-loader', - options: { name: '[name].[ext]', esModules: true }, + options: { name: '[name].[ext]', esModule: true }, }, ], }, @@ -202,7 +240,7 @@ describe('loader', () => { { test: /\.(png|jpg|gif|svg|eot|ttf|woff|woff2)$/, loader: 'url-loader', - options: { name: '[name].[ext]', limit: true, esModules: true }, + options: { name: '[name].[ext]', limit: true, esModule: true }, }, ], }, diff --git a/test/onlyLocals-option.test.js b/test/onlyLocals-option.test.js index fd0f0b0d..04c819c2 100644 --- a/test/onlyLocals-option.test.js +++ b/test/onlyLocals-option.test.js @@ -24,4 +24,22 @@ describe('"onlyLocals" option', () => { expect(getWarnings(stats)).toMatchSnapshot('warnings'); expect(getErrors(stats)).toMatchSnapshot('errors'); }); + + it('should work with the "esModule" option', async () => { + const compiler = getCompiler('./modules/composes/composes.js', { + modules: { mode: 'local', localIdentName: '_[local]' }, + onlyLocals: true, + esModule: true, + }); + const stats = await compile(compiler); + + expect( + getModuleSource('./modules/composes/composes.css', stats) + ).toMatchSnapshot('module'); + expect(getExecutedCode('main.bundle.js', compiler, stats)).toMatchSnapshot( + 'result' + ); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); }); diff --git a/test/validate-options.test.js b/test/validate-options.test.js index 26b49cb6..814ee799 100644 --- a/test/validate-options.test.js +++ b/test/validate-options.test.js @@ -59,6 +59,10 @@ describe('validate options', () => { success: [true, false], failure: ['true'], }, + esModule: { + success: [true, false], + failure: ['true'], + }, unknown: { success: [], failure: [1, true, false, 'test', /test/, [], {}, { foo: 'bar' }],