Skip to content

Commit 3224e6c

Browse files
committed
Add CSS modules support in Vue.js for Sass/Less/Stylus
1 parent f35841a commit 3224e6c

File tree

9 files changed

+159
-59
lines changed

9 files changed

+159
-59
lines changed

fixtures/vuejs-css-modules/App.vue

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<div id="app" class="red" :class="$style.italic"></div>
2+
<div id="app" class="red large justified lowercase" :class="[$css.italic, $scss.bold, $less.underline, $stylus.rtl]"></div>
33
</template>
44

55
<style>
@@ -8,8 +8,42 @@
88
}
99
</style>
1010

11-
<style module>
11+
<style lang="scss">
12+
.large {
13+
font-size: 50px;
14+
}
15+
</style>
16+
17+
<style lang="less">
18+
.justified {
19+
text-align: justify;
20+
}
21+
</style>
22+
23+
<style lang="styl">
24+
.lowercase
25+
text-transform: lowercase
26+
</style>
27+
28+
<style module="$css">
1229
.italic {
1330
font-style: italic;
1431
}
1532
</style>
33+
34+
<style lang="scss" module="$scss">
35+
.bold {
36+
font-weight: bold;
37+
}
38+
</style>
39+
40+
<style lang="less" module="$less">
41+
.underline {
42+
text-decoration: underline;
43+
}
44+
</style>
45+
46+
<style lang="styl" module="$stylus">
47+
.rtl
48+
direction: rtl;
49+
</style>

lib/config-generator.js

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -306,21 +306,45 @@ class ConfigGenerator {
306306
if (this.webpackConfig.useSassLoader) {
307307
rules.push({
308308
test: /\.s[ac]ss$/,
309-
use: cssExtractLoaderUtil.prependLoaders(this.webpackConfig, sassLoaderUtil.getLoaders(this.webpackConfig))
309+
oneOf: [
310+
{
311+
resourceQuery: /module/,
312+
use: cssExtractLoaderUtil.prependLoaders(this.webpackConfig, sassLoaderUtil.getLoaders(this.webpackConfig, true))
313+
},
314+
{
315+
use: cssExtractLoaderUtil.prependLoaders(this.webpackConfig, sassLoaderUtil.getLoaders(this.webpackConfig))
316+
}
317+
]
310318
});
311319
}
312320

313321
if (this.webpackConfig.useLessLoader) {
314322
rules.push({
315323
test: /\.less/,
316-
use: cssExtractLoaderUtil.prependLoaders(this.webpackConfig, lessLoaderUtil.getLoaders(this.webpackConfig))
324+
oneOf: [
325+
{
326+
resourceQuery: /module/,
327+
use: cssExtractLoaderUtil.prependLoaders(this.webpackConfig, lessLoaderUtil.getLoaders(this.webpackConfig, true))
328+
},
329+
{
330+
use: cssExtractLoaderUtil.prependLoaders(this.webpackConfig, lessLoaderUtil.getLoaders(this.webpackConfig))
331+
}
332+
]
317333
});
318334
}
319335

320336
if (this.webpackConfig.useStylusLoader) {
321337
rules.push({
322338
test: /\.styl/,
323-
use: cssExtractLoaderUtil.prependLoaders(this.webpackConfig, stylusLoaderUtil.getLoaders(this.webpackConfig))
339+
oneOf: [
340+
{
341+
resourceQuery: /module/,
342+
use: cssExtractLoaderUtil.prependLoaders(this.webpackConfig, stylusLoaderUtil.getLoaders(this.webpackConfig, true))
343+
},
344+
{
345+
use: cssExtractLoaderUtil.prependLoaders(this.webpackConfig, stylusLoaderUtil.getLoaders(this.webpackConfig))
346+
}
347+
]
324348
});
325349
}
326350

lib/loaders/less.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,19 @@ const applyOptionsCallback = require('../utils/apply-options-callback');
1515

1616
/**
1717
* @param {WebpackConfig} webpackConfig
18-
* @param {bool} ignorePostCssLoader If true, postcss-loader will never be added
18+
* @param {bool} useCssModules
1919
* @return {Array} of loaders to use for Less files
2020
*/
2121
module.exports = {
22-
getLoaders(webpackConfig, ignorePostCssLoader = false) {
22+
getLoaders(webpackConfig, useCssModules = false) {
2323
loaderFeatures.ensurePackagesExistAndAreCorrectVersion('less');
2424

2525
const config = {
2626
sourceMap: webpackConfig.useSourceMaps
2727
};
2828

2929
return [
30-
...cssLoader.getLoaders(webpackConfig, ignorePostCssLoader),
30+
...cssLoader.getLoaders(webpackConfig, useCssModules),
3131
{
3232
loader: 'less-loader',
3333
options: applyOptionsCallback(webpackConfig.lessLoaderOptionsCallback, config)

lib/loaders/sass.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ const applyOptionsCallback = require('../utils/apply-options-callback');
1515

1616
/**
1717
* @param {WebpackConfig} webpackConfig
18-
* @param {Object} sassOption Options to pass to the loader
18+
* @param {bool} useCssModules
1919
* @return {Array} of loaders to use for Sass files
2020
*/
2121
module.exports = {
22-
getLoaders(webpackConfig, sassOptions = {}) {
22+
getLoaders(webpackConfig, useCssModules = false) {
2323
loaderFeatures.ensurePackagesExistAndAreCorrectVersion('sass');
2424

25-
const sassLoaders = [...cssLoader.getLoaders(webpackConfig)];
25+
const sassLoaders = [...cssLoader.getLoaders(webpackConfig, useCssModules)];
2626
if (true === webpackConfig.sassOptions.resolveUrlLoader) {
2727
// responsible for resolving Sass url() paths
2828
// without this, all url() paths must be relative to the
@@ -35,7 +35,7 @@ module.exports = {
3535
});
3636
}
3737

38-
const config = Object.assign({}, sassOptions, {
38+
const config = Object.assign({}, {
3939
// needed by the resolve-url-loader
4040
sourceMap: (true === webpackConfig.sassOptions.resolveUrlLoader) || webpackConfig.useSourceMaps
4141
});

lib/loaders/stylus.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,19 @@ const applyOptionsCallback = require('../utils/apply-options-callback');
1515

1616
/**
1717
* @param {WebpackConfig} webpackConfig
18-
* @param {bool} ignorePostCssLoader If true, postcss-loader will never be added
18+
* @param {bool} useCssModules
1919
* @return {Array} of loaders to use for Stylus files
2020
*/
2121
module.exports = {
22-
getLoaders(webpackConfig, ignorePostCssLoader = false) {
22+
getLoaders(webpackConfig, useCssModules = false) {
2323
loaderFeatures.ensurePackagesExistAndAreCorrectVersion('stylus');
2424

2525
const config = {
2626
sourceMap: webpackConfig.useSourceMaps
2727
};
2828

2929
return [
30-
...cssLoader.getLoaders(webpackConfig, ignorePostCssLoader),
30+
...cssLoader.getLoaders(webpackConfig, useCssModules),
3131
{
3232
loader: 'stylus-loader',
3333
options: applyOptionsCallback(webpackConfig.stylusLoaderOptionsCallback, config)

test/functional.js

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1354,7 +1354,7 @@ module.exports = {
13541354
});
13551355
});
13561356

1357-
it('Vue.js supports CSS modules', (done) => {
1357+
it('Vue.js supports CSS/Sass/Less/Stylus modules', (done) => {
13581358
const appDir = testSetup.createTestAppDir();
13591359
const config = testSetup.createWebpackConfig(appDir, 'www/build', 'dev');
13601360
config.enableSingleRuntimeChunk();
@@ -1363,6 +1363,7 @@ module.exports = {
13631363
config.enableVueLoader();
13641364
config.enableSassLoader();
13651365
config.enableLessLoader();
1366+
config.enableStylusLoader();
13661367
config.configureCssLoader(options => {
13671368
// Remove hashes from local ident names
13681369
// since they are not always the same.
@@ -1378,17 +1379,22 @@ module.exports = {
13781379
'runtime.js',
13791380
]);
13801381

1381-
// Standard CSS
1382-
webpackAssert.assertOutputFileContains(
1383-
'main.css',
1384-
'.red {'
1385-
);
1382+
const expectClassDeclaration = (className) => {
1383+
webpackAssert.assertOutputFileContains(
1384+
'main.css',
1385+
`.${className} {`
1386+
);
1387+
};
13861388

1387-
// CSS modules
1388-
webpackAssert.assertOutputFileContains(
1389-
'main.css',
1390-
'.italic_foo {'
1391-
);
1389+
expectClassDeclaration('red'); // Standard CSS
1390+
expectClassDeclaration('large'); // Standard SCSS
1391+
expectClassDeclaration('justified'); // Standard Less
1392+
expectClassDeclaration('lowercase'); // Standard Stylus
1393+
1394+
expectClassDeclaration('italic_foo'); // CSS Module
1395+
expectClassDeclaration('bold_foo'); // SCSS Module
1396+
expectClassDeclaration('underline_foo'); // Less Module
1397+
expectClassDeclaration('rtl_foo'); // Stylus Module
13921398

13931399
testSetup.requestTestPage(
13941400
path.join(config.getContext(), 'www'),
@@ -1397,11 +1403,15 @@ module.exports = {
13971403
'build/main.js'
13981404
],
13991405
(browser) => {
1400-
// Standard CSS
1401-
browser.assert.hasClass('#app', 'red');
1402-
1403-
// CSS modules
1404-
browser.assert.hasClass('#app', 'italic_foo');
1406+
browser.assert.hasClass('#app', 'red'); // Standard CSS
1407+
browser.assert.hasClass('#app', 'large'); // Standard SCSS
1408+
browser.assert.hasClass('#app', 'justified'); // Standard Less
1409+
browser.assert.hasClass('#app', 'lowercase'); // Standard Stylus
1410+
1411+
browser.assert.hasClass('#app', 'italic_foo'); // CSS module
1412+
browser.assert.hasClass('#app', 'bold_foo'); // SCSS module
1413+
browser.assert.hasClass('#app', 'underline_foo'); // Less module
1414+
browser.assert.hasClass('#app', 'rtl_foo'); // Stylus module
14051415

14061416
done();
14071417
}

test/loaders/less.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,13 @@ describe('loaders/less', () => {
3030
config.enableSourceMaps(true);
3131

3232
// make the cssLoader return nothing
33-
sinon.stub(cssLoader, 'getLoaders')
33+
const cssLoaderStub = sinon.stub(cssLoader, 'getLoaders')
3434
.callsFake(() => []);
3535

3636
const actualLoaders = lessLoader.getLoaders(config);
3737
expect(actualLoaders).to.have.lengthOf(1);
3838
expect(actualLoaders[0].options.sourceMap).to.be.true;
39+
expect(cssLoaderStub.getCall(0).args[1]).to.be.false;
3940

4041
cssLoader.getLoaders.restore();
4142
});
@@ -81,4 +82,20 @@ describe('loaders/less', () => {
8182
expect(actualLoaders[0].options).to.deep.equals({ foo: true });
8283
cssLoader.getLoaders.restore();
8384
});
85+
86+
it('getLoaders() with CSS modules enabled', () => {
87+
const config = createConfig();
88+
config.enableSourceMaps(true);
89+
90+
// make the cssLoader return nothing
91+
const cssLoaderStub = sinon.stub(cssLoader, 'getLoaders')
92+
.callsFake(() => []);
93+
94+
const actualLoaders = lessLoader.getLoaders(config, true);
95+
expect(actualLoaders).to.have.lengthOf(1);
96+
expect(actualLoaders[0].options.sourceMap).to.be.true;
97+
expect(cssLoaderStub.getCall(0).args[1]).to.be.true;
98+
99+
cssLoader.getLoaders.restore();
100+
});
84101
});

test/loaders/sass.js

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ describe('loaders/sass', () => {
3030
config.enableSourceMaps(true);
3131

3232
// make the cssLoader return nothing
33-
sinon.stub(cssLoader, 'getLoaders')
33+
const cssLoaderStub = sinon.stub(cssLoader, 'getLoaders')
3434
.callsFake(() => []);
3535

3636
const actualLoaders = sassLoader.getLoaders(config);
@@ -40,6 +40,7 @@ describe('loaders/sass', () => {
4040

4141
expect(actualLoaders[1].loader).to.equal('sass-loader');
4242
expect(actualLoaders[1].options.sourceMap).to.be.true;
43+
expect(cssLoaderStub.getCall(0).args[1]).to.be.false;
4344

4445
cssLoader.getLoaders.restore();
4546
});
@@ -83,24 +84,6 @@ describe('loaders/sass', () => {
8384
cssLoader.getLoaders.restore();
8485
});
8586

86-
it('getLoaders() with extra options', () => {
87-
const config = createConfig();
88-
89-
// make the cssLoader return nothing
90-
sinon.stub(cssLoader, 'getLoaders')
91-
.callsFake(() => []);
92-
93-
const actualLoaders = sassLoader.getLoaders(config, {
94-
custom_option: 'foo'
95-
});
96-
// the normal option
97-
expect(actualLoaders[1].options.sourceMap).to.be.true;
98-
// custom option
99-
expect(actualLoaders[1].options.custom_option).to.equal('foo');
100-
101-
cssLoader.getLoaders.restore();
102-
});
103-
10487
it('getLoaders() with options callback', () => {
10588
const config = createConfig();
10689

@@ -113,16 +96,11 @@ describe('loaders/sass', () => {
11396
sassOptions.other_option = true;
11497
});
11598

116-
const actualLoaders = sassLoader.getLoaders(config, {
117-
custom_optiona: 'foo',
118-
custom_optionb: 'bar'
119-
});
99+
const actualLoaders = sassLoader.getLoaders(config);
120100

121101
expect(actualLoaders[1].options).to.deep.equals({
122102
sourceMap: true,
123-
// callback wins over passed in options
124103
custom_optiona: 'baz',
125-
custom_optionb: 'bar',
126104
other_option: true
127105
});
128106
cssLoader.getLoaders.restore();
@@ -143,9 +121,29 @@ describe('loaders/sass', () => {
143121
});
144122

145123

146-
const actualLoaders = sassLoader.getLoaders(config, {});
124+
const actualLoaders = sassLoader.getLoaders(config);
147125
expect(actualLoaders[1].options).to.deep.equals({ foo: true });
148126

149127
cssLoader.getLoaders.restore();
150128
});
129+
130+
it('getLoaders() with CSS modules enabled', () => {
131+
const config = createConfig();
132+
config.enableSourceMaps(true);
133+
134+
// make the cssLoader return nothing
135+
const cssLoaderStub = sinon.stub(cssLoader, 'getLoaders')
136+
.callsFake(() => []);
137+
138+
const actualLoaders = sassLoader.getLoaders(config, true);
139+
expect(actualLoaders).to.have.lengthOf(2);
140+
expect(actualLoaders[0].loader).to.equal('resolve-url-loader');
141+
expect(actualLoaders[0].options.sourceMap).to.be.true;
142+
143+
expect(actualLoaders[1].loader).to.equal('sass-loader');
144+
expect(actualLoaders[1].options.sourceMap).to.be.true;
145+
expect(cssLoaderStub.getCall(0).args[1]).to.be.true;
146+
147+
cssLoader.getLoaders.restore();
148+
});
151149
});

0 commit comments

Comments
 (0)