Skip to content

Commit e388186

Browse files
author
YiSiWang
committed
add css module support with parser hack
1 parent 21c9511 commit e388186

File tree

2 files changed

+61
-7
lines changed

2 files changed

+61
-7
lines changed

lib/loader.js

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ module.exports = function (content) {
7777
// disable all configuration loaders
7878
'!!' +
7979
// get loader string for pre-processors
80-
getLoaderString(type, part, scoped) +
80+
getLoaderString(type, part, index, scoped) +
8181
// select the corresponding part from the vue file
8282
getSelectorString(type, index || 0) +
8383
// the url to the actual vuefile
@@ -94,17 +94,41 @@ module.exports = function (content) {
9494
function getRequireForImportString (type, impt, scoped) {
9595
return loaderUtils.stringifyRequest(loaderContext,
9696
'!!' +
97-
getLoaderString(type, impt, scoped) +
97+
getLoaderString(type, impt, -1, scoped) +
9898
impt.src
9999
)
100100
}
101101

102-
function getLoaderString (type, part, scoped) {
102+
function addCssModulesToLoader (loader, part, index) {
103+
if (!part.module) return loader
104+
return loader.replace(/((?:^|!)css(?:-loader)?)(\?[^!]*)?/, function (m, $1, $2) {
105+
// $1: !css-loader
106+
// $2: ?a=b
107+
var option = loaderUtils.parseQuery($2)
108+
option.modules = true
109+
option.importLoaders = true
110+
option.localIdentName = '[hash:base64]'
111+
if (index !== -1) {
112+
// Note:
113+
// Class name is generated according to its filename.
114+
// Different <style> tags in the same .vue file may generate same names.
115+
// Append `_[index]` to class name to avoid this.
116+
option.localIdentName += '_' + index
117+
}
118+
return $1 + '?' + JSON.stringify(option)
119+
})
120+
}
121+
122+
function getLoaderString (type, part, index, scoped) {
103123
var lang = part.lang || defaultLang[type]
104124
var loader = loaders[lang]
105125
var rewriter = type === 'styles' ? styleRewriter + (scoped ? '&scoped=true!' : '!') : ''
106126
var injectString = (type === 'script' && query.inject) ? 'inject!' : ''
107127
if (loader !== undefined) {
128+
// add css modules
129+
if (type === 'styles') {
130+
loader = addCssModulesToLoader(loader, part, index)
131+
}
108132
// inject rewriter before css/html loader for
109133
// extractTextPlugin use cases
110134
if (rewriterInjectRE.test(loader)) {
@@ -121,7 +145,8 @@ module.exports = function (content) {
121145
case 'template':
122146
return defaultLoaders.html + '!' + templateLoaderPath + '?raw&engine=' + lang + '!'
123147
case 'styles':
124-
return defaultLoaders.css + '!' + rewriter + lang + '!'
148+
loader = addCssModulesToLoader(defaultLoaders.css, part, index)
149+
return loader + '!' + rewriter + lang + '!'
125150
case 'script':
126151
return injectString + lang + '!'
127152
}
@@ -146,13 +171,31 @@ module.exports = function (content) {
146171
var hasScoped = parts.styles.some(function (s) { return s.scoped })
147172
var output = 'var __vue_exports__, __vue_options__\n'
148173

174+
// css modules
175+
output += 'var __vue_styles__ = {}\n'
176+
var cssModules = {}
177+
149178
// add requires for styles
150179
if (!isServer && parts.styles.length) {
151180
output += '\n/* styles */\n'
152181
parts.styles.forEach(function (style, i) {
153-
output += style.src
182+
/* !HACK! */
183+
style.module = i === 0 ? 'style' : '$style'
184+
var requireString = style.src
154185
? getRequireForImport('styles', style, style.scoped)
155186
: getRequire('styles', style, i, style.scoped)
187+
// setCssModule
188+
if (style.module) {
189+
if (style.module in cssModules) {
190+
loaderContext.emitError('CSS module name "' + style.module + '" is not unique!')
191+
output += requireString
192+
} else {
193+
cssModules[style.module] = true
194+
output += '__vue_styles__["' + style.module + '"] = ' + requireString + '\n'
195+
}
196+
} else {
197+
output += requireString
198+
}
156199
})
157200
}
158201

@@ -205,6 +248,16 @@ module.exports = function (content) {
205248
exports += '__vue_options__._scopeId = "' + moduleId + '"\n'
206249
}
207250

251+
if (Object.keys(cssModules).length) {
252+
// inject style modules as computed properties
253+
exports +=
254+
'if (!__vue_options__.computed) __vue_options__.computed = {}\n' +
255+
'Object.keys(__vue_styles__).forEach(function (key) {\n' +
256+
'var module = __vue_styles__[key]\n' +
257+
'__vue_options__.computed[key] = function () { return module }\n' +
258+
'})\n'
259+
}
260+
208261
if (!query.inject) {
209262
output += exports
210263
// hot reload

test/test.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ function bundle (options, cb) {
4040
})
4141
}
4242
expect(stats.compilation.errors).to.be.empty
43+
require('fs').writeFileSync('./test.build.js', mfs.readFileSync('/test.build.js').toString())
4344
cb(mfs.readFileSync('/test.build.js').toString())
4445
})
4546
}
@@ -378,7 +379,7 @@ describe('vue-loader', function () {
378379
})
379380
})
380381

381-
it('css-modules', function (done) {
382+
it.only('css-modules', function (done) {
382383
test({
383384
entry: './test/fixtures/css-modules.vue'
384385
}, function (window) {
@@ -404,7 +405,7 @@ describe('vue-loader', function () {
404405
// default module + pre-processor + scoped
405406
var anotherClassName = module.computed.$style().red
406407
expect(anotherClassName).to.match(/^_/).and.not.equal(className)
407-
var id = '_v-' + hash(require.resolve('./fixtures/css-modules.vue'))
408+
var id = 'data-v-' + genId(require.resolve('./fixtures/css-modules.vue'))
408409
expect(style).to.contain('.' + anotherClassName + '[' + id + ']')
409410

410411
done()

0 commit comments

Comments
 (0)