diff --git a/README.md b/README.md index 19ceaa14..c0aeba3b 100644 --- a/README.md +++ b/README.md @@ -535,6 +535,7 @@ module.exports = { namedExport: true, exportLocalsConvention: 'camelCase', exportOnlyLocals: false, + type: 'full', }, }, }, @@ -1001,14 +1002,16 @@ module.exports = { }; ``` -### `icss` +##### `type` -Type: Boolean Default: `true` if `modules` are enabled, false otherwise +Type: `'full' | 'icss'` +Default: `'full'` -Enables/disables handling of the low level "Interoperable CSS" format for declaring -import and export dependencies between CSS and other languages. ICSS enables -CSS Module support, and is enabled automatically when `modules` are enabled. It -can also be enabled independently to allow other loaders to handle processing CSS modules. +Controls the level of compilation applied to the input styles. `'full'` handles class and id scoping and +`@value` values. `'icss'` will only compile the low level "Interoperable CSS" format for declaring +import and export dependencies between CSS and other languages. ICSS underpins +CSS Module support, and provides a low level syntax for other tools to +implement CSS-module variations of their own. **webpack.config.js** diff --git a/src/index.js b/src/index.js index 8aeca76b..a6dcf57b 100644 --- a/src/index.js +++ b/src/index.js @@ -52,6 +52,8 @@ export default async function loader(content, map, meta) { const exports = []; const needUseModulesPlugins = shouldUseModulesPlugins(options); + const needICSSPlugin = + needUseModulesPlugins || options.icss || options.modules.type === 'icss'; if (needUseModulesPlugins) { plugins.push(...getModulesPlugins(options, this)); @@ -112,7 +114,7 @@ export default async function loader(content, map, meta) { const icssPluginImports = []; const icssPluginApi = []; - if (needUseModulesPlugins || options.icss) { + if (needICSSPlugin) { const icssResolver = this.getResolve({ conditionNames: ['style'], extensions: [], diff --git a/src/options.json b/src/options.json index a7703f3f..17c5b54e 100644 --- a/src/options.json +++ b/src/options.json @@ -36,6 +36,10 @@ "type": "object", "additionalProperties": false, "properties": { + "type": { + "description": "Controls the extent to which css-lodaer will process module code ((https://github.com/webpack-contrib/css-loader#icss)", + "enum": ["icss", "full"] + }, "auto": { "description": "Allows auto enable CSS modules based on filename (https://github.com/webpack-contrib/css-loader#auto).", "anyOf": [ diff --git a/src/utils.js b/src/utils.js index eadecc60..4eee6c81 100644 --- a/src/utils.js +++ b/src/utils.js @@ -127,6 +127,7 @@ function getModulesOptions(rawOptions, loaderContext) { let modulesOptions = { auto: true, mode: 'local', + type: rawOptions.icss ? 'icss' : 'full', exportGlobals: false, localIdentName: '[hash:base64]', localIdentContext: loaderContext.rootContext, @@ -147,6 +148,12 @@ function getModulesOptions(rawOptions, loaderContext) { typeof rawOptions.modules === 'string' ? rawOptions.modules : 'local'; } else { if (rawOptions.modules) { + if ('icss' in rawOptions && rawOptions.modules.type) { + throw new Error( + 'The "modules.type" option cannot be set with "options.icss", remove the `icss` option and just use `type`' + ); + } + if (typeof rawOptions.modules.auto === 'boolean') { const isModules = rawOptions.modules.auto && moduleRegExp.test(resourcePath); @@ -202,11 +209,19 @@ function getModulesOptions(rawOptions, loaderContext) { function normalizeOptions(rawOptions, loaderContext) { const modulesOptions = getModulesOptions(rawOptions, loaderContext); + + if ('icss' in rawOptions) { + loaderContext.emitWarning( + new Error( + 'The `icss` option is deprecated, use modules.type: "icss" instead' + ) + ); + } + return { url: typeof rawOptions.url === 'undefined' ? true : rawOptions.url, import: typeof rawOptions.import === 'undefined' ? true : rawOptions.import, modules: modulesOptions, - icss: modulesOptions ? true : rawOptions.icss, sourceMap: typeof rawOptions.sourceMap === 'boolean' ? rawOptions.sourceMap @@ -242,6 +257,9 @@ function shouldUseURLPlugin(options) { } function shouldUseModulesPlugins(options) { + if (options.modules && options.modules.type === 'icss') { + return false; + } return Boolean(options.modules); } diff --git a/test/__snapshots__/icss.test.js.snap b/test/__snapshots__/icss.test.js.snap index d7dd69c6..72205992 100644 --- a/test/__snapshots__/icss.test.js.snap +++ b/test/__snapshots__/icss.test.js.snap @@ -117,7 +117,7 @@ exports[`ICSS show work with the case "export": module 1`] = ` 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, \\"\\\\n\\", \\"\\"]); +___CSS_LOADER_EXPORT___.push([module.id, \\".other {\\\\n a: a;\\\\n}\\\\n\\", \\"\\"]); // Exports ___CSS_LOADER_EXPORT___.locals = { \\"_test\\": \\"_test\\" @@ -130,7 +130,9 @@ exports[`ICSS show work with the case "export": result 1`] = ` Array [ Array [ "./icss/tests-cases/export/source.css", - " + ".other { + a: a; +} ", "", ], @@ -169,6 +171,35 @@ Array [ exports[`ICSS show work with the case "export-reserved-keywords": warnings 1`] = `Array []`; +exports[`ICSS show work with the case "exports-only": errors 1`] = `Array []`; + +exports[`ICSS show work with the case "exports-only": 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, \\"\\\\n\\", \\"\\"]); +// Exports +___CSS_LOADER_EXPORT___.locals = { + \\"_test\\": \\"_test\\" +}; +export default ___CSS_LOADER_EXPORT___; +" +`; + +exports[`ICSS show work with the case "exports-only": result 1`] = ` +Array [ + Array [ + "./icss/tests-cases/exports-only/source.css", + " +", + "", + ], +] +`; + +exports[`ICSS show work with the case "exports-only": warnings 1`] = `Array []`; + exports[`ICSS show work with the case "import": errors 1`] = `Array []`; exports[`ICSS show work with the case "import": module 1`] = ` @@ -311,3 +342,25 @@ Array [ `; exports[`ICSS show work with the case "multiple-keys-values-in-export": warnings 1`] = `Array []`; + +exports[`ICSS work with exports only: errors 1`] = `Array []`; + +exports[`ICSS work with exports only: module 1`] = ` +"var ___CSS_LOADER_EXPORT___ = {}; +// Exports +___CSS_LOADER_EXPORT___.locals = { + \\"_test\\": \\"_test\\" +}; +export default ___CSS_LOADER_EXPORT___; +" +`; + +exports[`ICSS work with exports only: result 1`] = ` +Object { + "locals": Object { + "_test": "_test", + }, +} +`; + +exports[`ICSS work with exports only: warnings 1`] = `Array []`; diff --git a/test/__snapshots__/validate-options.test.js.snap b/test/__snapshots__/validate-options.test.js.snap index fb725763..b9344d4b 100644 --- a/test/__snapshots__/validate-options.test.js.snap +++ b/test/__snapshots__/validate-options.test.js.snap @@ -51,7 +51,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?, localIdentName?, localIdentContext?, localIdentHashPrefix?, localIdentRegExp?, getLocalIdent?, namedExport?, exportGlobals?, exportLocalsConvention?, exportOnlyLocals? } + boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { type?, auto?, mode?, localIdentName?, localIdentContext?, localIdentHashPrefix?, localIdentRegExp?, getLocalIdent?, namedExport?, exportGlobals?, exportLocalsConvention?, exportOnlyLocals? } -> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules). Details: * options.modules.auto should be one of these: @@ -109,7 +109,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?, localIdentName?, localIdentContext?, localIdentHashPrefix?, localIdentRegExp?, getLocalIdent?, namedExport?, exportGlobals?, exportLocalsConvention?, exportOnlyLocals? } + boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { type?, auto?, mode?, localIdentName?, localIdentContext?, localIdentHashPrefix?, localIdentRegExp?, getLocalIdent?, namedExport?, exportGlobals?, exportLocalsConvention?, exportOnlyLocals? } -> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules). Details: * options.modules.localIdentRegExp should be one of these: @@ -123,7 +123,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?, localIdentName?, localIdentContext?, localIdentHashPrefix?, localIdentRegExp?, getLocalIdent?, namedExport?, exportGlobals?, exportLocalsConvention?, exportOnlyLocals? } + boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { type?, auto?, mode?, localIdentName?, localIdentContext?, localIdentHashPrefix?, localIdentRegExp?, getLocalIdent?, namedExport?, exportGlobals?, exportLocalsConvention?, exportOnlyLocals? } -> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules). Details: * options.modules.mode should be one of these: @@ -138,7 +138,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?, localIdentName?, localIdentContext?, localIdentHashPrefix?, localIdentRegExp?, getLocalIdent?, namedExport?, exportGlobals?, exportLocalsConvention?, exportOnlyLocals? } + boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { type?, auto?, mode?, localIdentName?, localIdentContext?, localIdentHashPrefix?, localIdentRegExp?, getLocalIdent?, namedExport?, exportGlobals?, exportLocalsConvention?, exportOnlyLocals? } -> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules). Details: * options.modules.mode should be one of these: @@ -153,7 +153,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?, localIdentName?, localIdentContext?, localIdentHashPrefix?, localIdentRegExp?, getLocalIdent?, namedExport?, exportGlobals?, exportLocalsConvention?, exportOnlyLocals? } + boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { type?, auto?, mode?, localIdentName?, localIdentContext?, localIdentHashPrefix?, localIdentRegExp?, getLocalIdent?, namedExport?, exportGlobals?, exportLocalsConvention?, exportOnlyLocals? } -> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules). Details: * options.modules.mode should be one of these: @@ -168,7 +168,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?, localIdentName?, localIdentContext?, localIdentHashPrefix?, localIdentRegExp?, getLocalIdent?, namedExport?, exportGlobals?, exportLocalsConvention?, exportOnlyLocals? } + boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { type?, auto?, mode?, localIdentName?, localIdentContext?, localIdentHashPrefix?, localIdentRegExp?, getLocalIdent?, namedExport?, exportGlobals?, exportLocalsConvention?, exportOnlyLocals? } -> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules). Details: * options.modules.mode should be one of these: @@ -189,53 +189,53 @@ exports[`validate options should throw an error on the "modules" option with "{" 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?, localIdentName?, localIdentContext?, localIdentHashPrefix?, localIdentRegExp?, getLocalIdent?, namedExport?, exportGlobals?, exportLocalsConvention?, exportOnlyLocals? } + boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { type?, auto?, mode?, localIdentName?, localIdentContext?, localIdentHashPrefix?, localIdentRegExp?, getLocalIdent?, namedExport?, exportGlobals?, exportLocalsConvention?, exportOnlyLocals? } -> 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?, localIdentName?, localIdentContext?, localIdentHashPrefix?, localIdentRegExp?, getLocalIdent?, namedExport?, exportGlobals?, exportLocalsConvention?, exportOnlyLocals? }" + object { type?, auto?, mode?, localIdentName?, localIdentContext?, localIdentHashPrefix?, localIdentRegExp?, getLocalIdent?, namedExport?, exportGlobals?, exportLocalsConvention?, exportOnlyLocals? }" `; 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?, localIdentName?, localIdentContext?, localIdentHashPrefix?, localIdentRegExp?, getLocalIdent?, namedExport?, exportGlobals?, exportLocalsConvention?, exportOnlyLocals? } + boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { type?, auto?, mode?, localIdentName?, localIdentContext?, localIdentHashPrefix?, localIdentRegExp?, getLocalIdent?, namedExport?, exportGlobals?, exportLocalsConvention?, exportOnlyLocals? } -> 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?, localIdentName?, localIdentContext?, localIdentHashPrefix?, localIdentRegExp?, getLocalIdent?, namedExport?, exportGlobals?, exportLocalsConvention?, exportOnlyLocals? }" + object { type?, auto?, mode?, localIdentName?, localIdentContext?, localIdentHashPrefix?, localIdentRegExp?, getLocalIdent?, namedExport?, exportGlobals?, exportLocalsConvention?, exportOnlyLocals? }" `; 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?, localIdentName?, localIdentContext?, localIdentHashPrefix?, localIdentRegExp?, getLocalIdent?, namedExport?, exportGlobals?, exportLocalsConvention?, exportOnlyLocals? } + boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { type?, auto?, mode?, localIdentName?, localIdentContext?, localIdentHashPrefix?, localIdentRegExp?, getLocalIdent?, namedExport?, exportGlobals?, exportLocalsConvention?, exportOnlyLocals? } -> 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?, localIdentName?, localIdentContext?, localIdentHashPrefix?, localIdentRegExp?, getLocalIdent?, namedExport?, exportGlobals?, exportLocalsConvention?, exportOnlyLocals? }" + object { type?, auto?, mode?, localIdentName?, localIdentContext?, localIdentHashPrefix?, localIdentRegExp?, getLocalIdent?, namedExport?, exportGlobals?, exportLocalsConvention?, exportOnlyLocals? }" `; 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?, localIdentName?, localIdentContext?, localIdentHashPrefix?, localIdentRegExp?, getLocalIdent?, namedExport?, exportGlobals?, exportLocalsConvention?, exportOnlyLocals? } + boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { type?, auto?, mode?, localIdentName?, localIdentContext?, localIdentHashPrefix?, localIdentRegExp?, getLocalIdent?, namedExport?, exportGlobals?, exportLocalsConvention?, exportOnlyLocals? } -> 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?, localIdentName?, localIdentContext?, localIdentHashPrefix?, localIdentRegExp?, getLocalIdent?, namedExport?, exportGlobals?, exportLocalsConvention?, exportOnlyLocals? }" + object { type?, auto?, mode?, localIdentName?, localIdentContext?, localIdentHashPrefix?, localIdentRegExp?, getLocalIdent?, namedExport?, exportGlobals?, exportLocalsConvention?, exportOnlyLocals? }" `; exports[`validate options should throw an error on the "sourceMap" option with "true" value 1`] = ` diff --git a/test/fixtures/icss/tests-cases/export/source.css b/test/fixtures/icss/tests-cases/export/source.css index 10b9ec77..9371dbc7 100644 --- a/test/fixtures/icss/tests-cases/export/source.css +++ b/test/fixtures/icss/tests-cases/export/source.css @@ -1,3 +1,7 @@ +.other { + a: a; +} + :export { _test: _test } diff --git a/test/fixtures/icss/tests-cases/exports-only/source.css b/test/fixtures/icss/tests-cases/exports-only/source.css new file mode 100644 index 00000000..10b9ec77 --- /dev/null +++ b/test/fixtures/icss/tests-cases/exports-only/source.css @@ -0,0 +1,3 @@ +:export { + _test: _test +} diff --git a/test/fixtures/icss/tests-cases/exports-only/source.js b/test/fixtures/icss/tests-cases/exports-only/source.js new file mode 100644 index 00000000..1996779e --- /dev/null +++ b/test/fixtures/icss/tests-cases/exports-only/source.js @@ -0,0 +1,5 @@ +import css from './source.css'; + +__export__ = css; + +export default css; diff --git a/test/icss.test.js b/test/icss.test.js index ff1ba97f..2bed6d97 100644 --- a/test/icss.test.js +++ b/test/icss.test.js @@ -17,8 +17,7 @@ describe('ICSS', () => { testCases.forEach((name) => { it(`show work with the case "${name}"`, async () => { const compiler = getCompiler(`./icss/tests-cases/${name}/source.js`, { - modules: false, - icss: true, + modules: { type: 'icss' }, }); const stats = await compile(compiler); @@ -32,4 +31,51 @@ describe('ICSS', () => { expect(getErrors(stats)).toMatchSnapshot('errors'); }); }); + + it(`show warn about icss deprecation`, async () => { + const stats = await compile( + getCompiler(`./icss/tests-cases/import/source.js`, { + modules: false, + icss: true, + }) + ); + const warnings = getWarnings(stats); + expect(warnings).toHaveLength(1); + expect(warnings[0]).toContain( + 'The `icss` option is deprecated, use modules.type: "icss" instead' + ); + }); + + it(`show throw when both options are specified`, async () => { + const stats = await compile( + getCompiler(`./icss/tests-cases/import/source.js`, { + modules: { type: 'full' }, + icss: true, + }) + ); + const errors = getErrors(stats); + expect(errors).toHaveLength(1); + expect(errors[0]).toContain( + 'The "modules.type" option cannot be set with "options.icss", remove the `icss` option and just use `type`' + ); + }); + + it(`work with exports only`, async () => { + const compiler = getCompiler(`./icss/tests-cases/exports-only/source.js`, { + modules: { + type: 'icss', + exportOnlyLocals: true, + }, + }); + const stats = await compile(compiler); + + expect( + getModuleSource(`./icss/tests-cases/exports-only/source.css`, stats) + ).toMatchSnapshot('module'); + expect(getExecutedCode('main.bundle.js', compiler, stats)).toMatchSnapshot( + 'result' + ); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); });