Skip to content

Support CSS modules in Vue.js projects by default #508

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions fixtures/vuejs-css-modules/App.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<template>
<div id="app" class="red" :class="$style.italic"></div>
</template>

<style>
.red {
color: red;
}
</style>

<style module>
.italic {
font-style: italic;
}
</style>
8 changes: 8 additions & 0 deletions fixtures/vuejs-css-modules/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Vue from 'vue'
import App from './App'

new Vue({
el: '#app',
template: '<App/>',
components: { App }
})
16 changes: 15 additions & 1 deletion lib/config-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,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)
)
}
]
}
];

Expand Down
7 changes: 5 additions & 2 deletions lib/loaders/css.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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 = [
Expand Down
55 changes: 55 additions & 0 deletions test/functional.js
Original file line number Diff line number Diff line change
Expand Up @@ -1350,6 +1350,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');
Expand Down
18 changes: 18 additions & 0 deletions test/loaders/css.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand All @@ -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', () => {
Expand All @@ -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', () => {
Expand Down