diff --git a/fixtures/vuejs-css-modules/App.vue b/fixtures/vuejs-css-modules/App.vue new file mode 100644 index 00000000..d75d3bdc --- /dev/null +++ b/fixtures/vuejs-css-modules/App.vue @@ -0,0 +1,15 @@ + + + + + diff --git a/fixtures/vuejs-css-modules/main.js b/fixtures/vuejs-css-modules/main.js new file mode 100644 index 00000000..8845066e --- /dev/null +++ b/fixtures/vuejs-css-modules/main.js @@ -0,0 +1,8 @@ +import Vue from 'vue' +import App from './App' + +new Vue({ + el: '#app', + template: '', + components: { App } +}) diff --git a/lib/config-generator.js b/lib/config-generator.js index d6c5839c..e7993bcf 100644 --- a/lib/config-generator.js +++ b/lib/config-generator.js @@ -229,7 +229,21 @@ class ConfigGenerator { }, { test: /\.css$/, - use: cssExtractLoaderUtil.prependLoaders(this.webpackConfig, cssLoaderUtil.getLoaders(this.webpackConfig)) + oneOf: [ + { + resourceQuery: /module/, + use: cssExtractLoaderUtil.prependLoaders( + this.webpackConfig, + cssLoaderUtil.getLoaders(this.webpackConfig, true) + ) + }, + { + use: cssExtractLoaderUtil.prependLoaders( + this.webpackConfig, + cssLoaderUtil.getLoaders(this.webpackConfig) + ) + } + ] } ]; diff --git a/lib/loaders/css.js b/lib/loaders/css.js index 7685fbee..190a3b58 100644 --- a/lib/loaders/css.js +++ b/lib/loaders/css.js @@ -15,9 +15,10 @@ const applyOptionsCallback = require('../utils/apply-options-callback'); module.exports = { /** * @param {WebpackConfig} webpackConfig + * @param {boolean} useCssModules * @return {Array} of loaders to use for CSS files */ - getLoaders(webpackConfig) { + getLoaders(webpackConfig, useCssModules = false) { const usePostCssLoader = webpackConfig.usePostCssLoader; const options = { @@ -27,7 +28,9 @@ module.exports = { // be applied to those imports? This defaults to 0. When postcss-loader // is used, we set it to 1, so that postcss-loader is applied // to @import resources. - importLoaders: usePostCssLoader ? 1 : 0 + importLoaders: usePostCssLoader ? 1 : 0, + modules: useCssModules, + localIdentName: '[local]_[hash:base64:5]', }; const cssLoaders = [ diff --git a/test/functional.js b/test/functional.js index 3b717b63..14e3d460 100644 --- a/test/functional.js +++ b/test/functional.js @@ -1354,6 +1354,61 @@ module.exports = { }); }); + it('Vue.js supports CSS modules', (done) => { + const appDir = testSetup.createTestAppDir(); + const config = testSetup.createWebpackConfig(appDir, 'www/build', 'dev'); + config.enableSingleRuntimeChunk(); + config.setPublicPath('/build'); + config.addEntry('main', './vuejs-css-modules/main'); + config.enableVueLoader(); + config.enableSassLoader(); + config.enableLessLoader(); + config.configureCssLoader(options => { + // Remove hashes from local ident names + // since they are not always the same. + options.localIdentName = '[local]_foo'; + }); + + testSetup.runWebpack(config, (webpackAssert) => { + expect(config.outputPath).to.be.a.directory().with.deep.files([ + 'main.js', + 'main.css', + 'manifest.json', + 'entrypoints.json', + 'runtime.js', + ]); + + // Standard CSS + webpackAssert.assertOutputFileContains( + 'main.css', + '.red {' + ); + + // CSS modules + webpackAssert.assertOutputFileContains( + 'main.css', + '.italic_foo {' + ); + + testSetup.requestTestPage( + path.join(config.getContext(), 'www'), + [ + 'build/runtime.js', + 'build/main.js' + ], + (browser) => { + // Standard CSS + browser.assert.hasClass('#app', 'red'); + + // CSS modules + browser.assert.hasClass('#app', 'italic_foo'); + + done(); + } + ); + }); + }); + it('Vue.js error when using non-activated loaders', (done) => { const config = createWebpackConfig('www/build', 'dev'); config.setPublicPath('/build'); diff --git a/test/loaders/css.js b/test/loaders/css.js index d7ebbc68..ddf72fb7 100644 --- a/test/loaders/css.js +++ b/test/loaders/css.js @@ -31,6 +31,7 @@ describe('loaders/css', () => { expect(actualLoaders).to.have.lengthOf(1); expect(actualLoaders[0].options.sourceMap).to.be.true; expect(actualLoaders[0].options.minimize).to.be.false; + expect(actualLoaders[0].options.modules).to.be.false; }); it('getLoaders() for production', () => { @@ -42,6 +43,7 @@ describe('loaders/css', () => { expect(actualLoaders).to.have.lengthOf(1); expect(actualLoaders[0].options.sourceMap).to.be.false; expect(actualLoaders[0].options.minimize).to.be.true; + expect(actualLoaders[0].options.modules).to.be.false; }); it('getLoaders() with options callback', () => { @@ -56,6 +58,22 @@ describe('loaders/css', () => { expect(actualLoaders).to.have.lengthOf(1); expect(actualLoaders[0].options.minimize).to.be.true; expect(actualLoaders[0].options.url).to.be.false; + expect(actualLoaders[0].options.modules).to.be.false; + }); + + it('getLoaders() with CSS modules enabled', () => { + const config = createConfig(); + + config.configureCssLoader(function(options) { + options.minimize = true; + options.url = false; + }); + + const actualLoaders = cssLoader.getLoaders(config, true); + expect(actualLoaders).to.have.lengthOf(1); + expect(actualLoaders[0].options.minimize).to.be.true; + expect(actualLoaders[0].options.url).to.be.false; + expect(actualLoaders[0].options.modules).to.be.true; }); describe('getLoaders() with PostCSS', () => {