diff --git a/docs/config/README.md b/docs/config/README.md index f0a7d08372..d05f06d447 100644 --- a/docs/config/README.md +++ b/docs/config/README.md @@ -247,10 +247,20 @@ See [the plugin's README](https://github.com/vuejs/vue-cli/blob/dev/packages/%40 ### css.modules +Deprecated since v4, please use [`css.requireModuleExtension`](#css-requireModuleExtension) instead. + +In v3 this means the opposite of `css.requireModuleExtension`. + +### css.requireModuleExtension + - Type: `boolean` -- Default: `false` +- Default: `true` + + By default, only files that ends in `*.module.[ext]` are treated as CSS modules. Setting this to `false` will allow you to drop `.module` in the filenames and treat all `*.(css|scss|sass|less|styl(us)?)` files as CSS modules. - By default, only files that ends in `*.module.[ext]` are treated as CSS modules. Setting this to `true` will allow you to drop `.module` in the filenames and treat all `*.(css|scss|sass|less|styl(us)?)` files as CSS modules. + ::: tip + If you have customized CSS Modules configurations in `css.loaderOptions.css`, then the `css.requireModuleExtension` field must be explictly configured to `true` or `false`, otherwise we can't be sure whether you want to apply these options to all CSS files or not. + ::: See also: [Working with CSS > CSS Modules](../guide/css.md#css-modules) diff --git a/docs/guide/css.md b/docs/guide/css.md index 83cf75e1d7..75bfb199dd 100644 --- a/docs/guide/css.md +++ b/docs/guide/css.md @@ -89,13 +89,13 @@ import styles from './foo.module.css' import sassStyles from './foo.module.scss' ``` -If you want to drop the `.module` in the filenames, set `css.modules` to `true` in `vue.config.js`: +If you want to drop the `.module` in the filenames, set `css.requireModuleExtension` to `false` in `vue.config.js`: ``` js // vue.config.js module.exports = { css: { - modules: true + requireModuleExtension: false } } ``` diff --git a/docs/zh/config/README.md b/docs/zh/config/README.md index 539df1a814..94d131fa26 100644 --- a/docs/zh/config/README.md +++ b/docs/zh/config/README.md @@ -235,10 +235,19 @@ module.exports = { ### css.modules +从 v4 起已弃用,请使用[`css.requireModuleExtension`](#css-requireModuleExtension)。 +在 v3 中,这个选项含义与 `css.requireModuleExtension` 相反。 + +### css.requireModuleExtension + - Type: `boolean` -- Default: `false` +- Default: `true` - 默认情况下,只有 `*.module.[ext]` 结尾的文件才会被视作 CSS Modules 模块。设置为 `true` 后你就可以去掉文件名中的 `.module` 并将所有的 `*.(css|scss|sass|less|styl(us)?)` 文件视为 CSS Modules 模块。 + 默认情况下,只有 `*.module.[ext]` 结尾的文件才会被视作 CSS Modules 模块。设置为 `false` 后你就可以去掉文件名中的 `.module` 并将所有的 `*.(css|scss|sass|less|styl(us)?)` 文件视为 CSS Modules 模块。 + + ::: tip 提示 + 如果你在 `css.loaderOptions.css` 里配置了自定义的 CSS Module 选项,则 `css.requireModuleExtension` 必须被显式地指定为 `true` 或者 `false`,否则我们无法确定你是否希望将这些自定义配置应用到所有 CSS 文件中。 + ::: 更多细节可查阅:[配合 CSS > CSS Modules](../guide/css.md#css-modules) diff --git a/docs/zh/guide/css.md b/docs/zh/guide/css.md index aa8d562677..2758e6c0bc 100644 --- a/docs/zh/guide/css.md +++ b/docs/zh/guide/css.md @@ -81,13 +81,13 @@ import styles from './foo.module.css' import sassStyles from './foo.module.scss' ``` -如果你想去掉文件名中的 `.module`,可以设置 `vue.config.js` 中的 `css.modules` 为 `true`: +如果你想去掉文件名中的 `.module`,可以设置 `vue.config.js` 中的 `css.requireModuleExtension` 为 `false`: ``` js // vue.config.js module.exports = { css: { - modules: true + requireModuleExtension: false } } ``` diff --git a/packages/@vue/cli-service/__tests__/css.spec.js b/packages/@vue/cli-service/__tests__/css.spec.js index e087586511..7d6d6a6e36 100644 --- a/packages/@vue/cli-service/__tests__/css.spec.js +++ b/packages/@vue/cli-service/__tests__/css.spec.js @@ -1,5 +1,10 @@ +const { logs } = require('@vue/cli-shared-utils') const Service = require('../lib/Service') +beforeEach(() => { + logs.warn = [] +}) + const LANGS = ['css', 'sass', 'scss', 'less', 'styl', 'stylus'] const extractLoaderPath = require('mini-css-extract-plugin').loader @@ -82,7 +87,7 @@ test('CSS Modules rules', () => { const config = genConfig({ vue: { css: { - modules: true + requireModuleExtension: false } } }) @@ -103,6 +108,116 @@ test('CSS Modules rules', () => { }) }) +test('Customized CSS Modules rules', () => { + const userOptions = { + vue: { + css: { + loaderOptions: { + css: { + modules: { + localIdentName: '[folder]-[name]-[local][emoji]' + } + } + } + } + } + } + + expect(() => { + genConfig(userOptions) + }).toThrow('`css.requireModuleExtension` is required when custom css modules options provided') + + userOptions.vue.css.requireModuleExtension = true + const config = genConfig(userOptions) + + LANGS.forEach(lang => { + const expected = { + importLoaders: 1, // no postcss-loader + sourceMap: false, + modules: { + localIdentName: `[folder]-[name]-[local][emoji]` + } + } + // vue-modules rules + expect(findOptions(config, lang, 'css', 0)).toEqual(expected) + // normal-modules rules + expect(findOptions(config, lang, 'css', 2)).toEqual(expected) + // normal rules + expect(findOptions(config, lang, 'css', 3)).not.toEqual(expected) + }) +}) + +test('deprecate `css.modules` option', () => { + const config = genConfig({ + vue: { + css: { + modules: true, + loaderOptions: { + css: { + modules: { + localIdentName: '[folder]-[name]-[local][emoji]' + } + } + } + } + } + }) + expect(logs.warn.some(([msg]) => msg.match('please use "css.requireModuleExtension" instead'))).toBe(true) + + LANGS.forEach(lang => { + const expected = { + importLoaders: 1, // no postcss-loader + sourceMap: false, + modules: { + localIdentName: `[folder]-[name]-[local][emoji]` + } + } + // vue-modules rules + expect(findOptions(config, lang, 'css', 0)).toEqual(expected) + // normal-modules rules + expect(findOptions(config, lang, 'css', 2)).toEqual(expected) + // normal rules + expect(findOptions(config, lang, 'css', 3)).toEqual(expected) + }) +}) + +test('favor `css.requireModuleExtension` over `css.modules`', () => { + const config = genConfig({ + vue: { + css: { + requireModuleExtension: false, + modules: false, + + loaderOptions: { + css: { + modules: { + localIdentName: '[folder]-[name]-[local][emoji]' + } + } + } + } + } + }) + + expect(logs.warn.some(([msg]) => msg.match('"css.modules" will be ignored in favor of "css.requireModuleExtension"'))).toBe(true) + + LANGS.forEach(lang => { + const expected = { + importLoaders: 1, // no postcss-loader + sourceMap: false, + modules: { + localIdentName: `[folder]-[name]-[local][emoji]` + } + } + // vue-modules rules + expect(findOptions(config, lang, 'css', 0)).toEqual(expected) + // normal-modules rules + expect(findOptions(config, lang, 'css', 2)).toEqual(expected) + // normal rules + expect(findOptions(config, lang, 'css', 3)).toEqual(expected) + }) +}) + test('css.extract', () => { const config = genConfig({ vue: { diff --git a/packages/@vue/cli-service/lib/Service.js b/packages/@vue/cli-service/lib/Service.js index 84c9af1a13..e42d12ef0f 100644 --- a/packages/@vue/cli-service/lib/Service.js +++ b/packages/@vue/cli-service/lib/Service.js @@ -346,6 +346,22 @@ module.exports = class Service { resolvedFrom = 'inline options' } + + if (resolved.css && typeof resolved.css.modules !== 'undefined') { + if (typeof resolved.css.requireModuleExtension !== 'undefined') { + warn( + `You have set both "css.modules" and "css.requireModuleExtension" in ${chalk.bold('vue.config.js')}, ` + + `"css.modules" will be ignored in favor of "css.requireModuleExtension".` + ) + } else { + warn( + `"css.modules" option in ${chalk.bold('vue.config.js')} ` + + `is deprecated now, please use "css.requireModuleExtension" instead.` + ) + resolved.css.requireModuleExtension = !resolved.css.modules + } + } + // normalize some options ensureSlash(resolved, 'publicPath') if (typeof resolved.publicPath === 'string') { diff --git a/packages/@vue/cli-service/lib/config/css.js b/packages/@vue/cli-service/lib/config/css.js index df55ce82f1..2133924be4 100644 --- a/packages/@vue/cli-service/lib/config/css.js +++ b/packages/@vue/cli-service/lib/config/css.js @@ -27,6 +27,14 @@ module.exports = (api, rootOptions) => { loaderOptions = {} } = rootOptions.css || {} + let { requireModuleExtension } = rootOptions.css || {} + if (typeof requireModuleExtension === 'undefined') { + if (loaderOptions.css && loaderOptions.css.modules) { + throw new Error('`css.requireModuleExtension` is required when custom css modules options provided') + } + requireModuleExtension = true + } + const shouldExtract = extract !== false && !shadowMode const filename = getAssetPath( rootOptions, @@ -91,8 +99,7 @@ module.exports = (api, rootOptions) => { // rules for normal CSS imports const normalRule = baseRule.oneOf('normal') - const treatAllAsModules = !!(rootOptions.css && rootOptions.css.modules) - applyLoaders(normalRule, treatAllAsModules) + applyLoaders(normalRule, !requireModuleExtension) function applyLoaders (rule, isCssModule) { if (shouldExtract) { @@ -127,6 +134,8 @@ module.exports = (api, rootOptions) => { localIdentName: '[name]_[local]_[hash:base64:5]', ...cssLoaderOptions.modules } + } else { + delete cssLoaderOptions.modules } rule diff --git a/packages/@vue/cli-service/lib/options.js b/packages/@vue/cli-service/lib/options.js index 4e12520d5f..fad7a32341 100644 --- a/packages/@vue/cli-service/lib/options.js +++ b/packages/@vue/cli-service/lib/options.js @@ -33,7 +33,9 @@ const schema = createSchema(joi => joi.object({ // css css: joi.object({ + // TODO: deprecate this after joi 16 release modules: joi.boolean(), + requireModuleExtension: joi.boolean(), extract: joi.alternatives().try(joi.boolean(), joi.object()), sourceMap: joi.boolean(), loaderOptions: joi.object({