Skip to content

Commit 72cde14

Browse files
committed
feature #511 Add CSS modules support in Vue.js for Sass/Less/Stylus (Lyrkan)
This PR was merged into the master branch. Discussion ---------- Add CSS modules support in Vue.js for Sass/Less/Stylus As noted in #508 (comment), that previous PR actually only added support for standard CSS modules, but didn't work for other languages. This new PR makes it so it also works with the Scss, Less and Stylus syntaxes (using the same principle). Ping @Chesskingt :) Commits ------- 3224e6c Add CSS modules support in Vue.js for Sass/Less/Stylus
2 parents 853e0f9 + 3224e6c commit 72cde14

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)