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', () => {