From deac8b5f35c7b0563c6cbb9f6245007153cb5490 Mon Sep 17 00:00:00 2001 From: YiSiWang <597222964@qq.com> Date: Mon, 29 Aug 2016 15:30:48 +0800 Subject: [PATCH 01/16] add simple support for CSS modules --- lib/htmlCssModuleRewriter.js | 36 ++++++++++++++++++++++++ lib/loader.js | 54 +++++++++++++++++++++++++----------- lib/parser.js | 5 +++- test/test.js | 2 +- 4 files changed, 79 insertions(+), 18 deletions(-) create mode 100644 lib/htmlCssModuleRewriter.js diff --git a/lib/htmlCssModuleRewriter.js b/lib/htmlCssModuleRewriter.js new file mode 100644 index 000000000..6d0a41f7a --- /dev/null +++ b/lib/htmlCssModuleRewriter.js @@ -0,0 +1,36 @@ +var parse5 = require('parse5') + +module.exports = function (html, modules) { + var tree = parse5.parseFragment(html) + walk(tree, function (node) { + if (!node.attrs) return + node.attrs.forEach(function (attr) { + if (attr.name !== 'class') return + var arr = attr.value.split('.') + var module = arr[0] + var className = arr[1] + if (!className) return + var map = modules[module] + if (!map) return console.error('xxx') + if (!map[className]) return console.error('yyy') + attr.value = map[className] + }) + }) + return parse5.serialize(tree) +} + +function walk (tree, fn) { + if (tree.childNodes) { + tree.childNodes.forEach(function (node) { + var isTemplate = node.tagName === 'template' + if (!isTemplate) { + fn(node) + } + if (isTemplate && node.content) { + walk(node.content, fn) + } else { + walk(node, fn) + } + }) + } +} diff --git a/lib/loader.js b/lib/loader.js index f6e479a53..8cd35a354 100644 --- a/lib/loader.js +++ b/lib/loader.js @@ -22,7 +22,7 @@ var templateLoader = require.resolve('./template-loader') module.exports = function (content) { var defaultLoaders = { html: 'vue-html-loader', - css: 'vue-style-loader!css-loader', + css: 'vue-style-loader!css-loader?-sourceMap', js: 'babel-loader?presets[]=es2015&plugins[]=transform-runtime&comments=false' } @@ -46,25 +46,25 @@ module.exports = function (content) { options.cssSourceMap !== false && process.env.NODE_ENV !== 'production' ) { - defaultLoaders.css = 'vue-style-loader!css-loader?sourceMap' + defaultLoaders.css = 'vue-style-loader!css-loader?+sourceMap' } // check if there are custom loaders specified via // webpack config, otherwise use defaults var loaders = assign({}, defaultLoaders, options.loaders) - function getRequire (type, part, index, scoped) { + function getRequire (type, part, index, scoped, module) { return 'require(' + - getRequireString(type, part, index, scoped) + + getRequireString(type, part, index, scoped, module) + ')\n' } - function getRequireString (type, part, index, scoped) { + function getRequireString (type, part, index, scoped, module) { return loaderUtils.stringifyRequest(loaderContext, // disable all configuration loaders '!!' + // get loader string for pre-processors - getLoaderString(type, part, scoped) + + getLoaderString(type, part, scoped, module) + // select the corresponding part from the vue file getSelectorString(type, index || 0) + // the url to the actual vuefile @@ -72,26 +72,29 @@ module.exports = function (content) { ) } - function getRequireForImport (type, impt, scoped) { + function getRequireForImport (type, impt, scoped, module) { return 'require(' + - getRequireForImportString(type, impt, scoped) + + getRequireForImportString(type, impt, scoped, module) + ')\n' } - function getRequireForImportString (type, impt, scoped) { + function getRequireForImportString (type, impt, scoped, module) { return loaderUtils.stringifyRequest(loaderContext, '!!' + - getLoaderString(type, impt, scoped) + + getLoaderString(type, impt, scoped, module) + impt.src ) } - function getLoaderString (type, part, scoped) { + function getLoaderString (type, part, scoped, module) { var lang = part.lang || defaultLang[type] var loader = loaders[lang] - var rewriter = getRewriter(type, scoped) + var rewriter = getRewriter(type, scoped, module) var injectString = (type === 'script' && query.inject) ? 'inject!' : '' if (loader !== undefined) { + if (module && (type === 'style')) { + loader += '&modules' + } // inject rewriter before css/html loader for // extractTextPlugin use cases if (rewriterInjectRE.test(loader)) { @@ -115,7 +118,7 @@ module.exports = function (content) { } } - function getRewriter (type, scoped) { + function getRewriter (type, scoped, module) { var meta = '?id=' + moduleId switch (type) { case 'template': @@ -143,7 +146,8 @@ module.exports = function (content) { var parts = parse(content, fileName, this.sourceMap) var hasLocalStyles = false - var output = 'var __vue_script__, __vue_template__\n' + var output = 'var __vue_script__, __vue_template__\n' + + 'var __vue_styles__ = {}\n' // check if there are any template syntax errors var templateWarnings = parts.template.length && parts.template[0].warnings @@ -151,16 +155,31 @@ module.exports = function (content) { templateWarnings.forEach(this.emitError) } + var cssModules = {} + function setCssModule (style, require, context) { + if (!style.module) return require + if (!(/^[a-zA-Z][a-zA-Z0-9]*$/.test(style.module))) { + context.emitError('Invallid CSS module name "' + style.module + '"!') + return require + } + if (style.module in cssModules) { + context.emitError('CSS module name "' + style.module + '" is not unique!') + return require + } + cssModules[style.module] = true + return '__vue_styles__["' + style.module + '"] = ' + require + '\n' + } + // add requires for src imports parts.styleImports.forEach(function (impt) { if (impt.scoped) hasLocalStyles = true - output += getRequireForImport('style', impt, impt.scoped) + output += setCssModule(impt, getRequireForImport('style', impt, impt.scoped, impt.module), loaderContext) || '' }) // add requires for styles parts.style.forEach(function (style, i) { if (style.scoped) hasLocalStyles = true - output += getRequire('style', style, i, style.scoped) + output += setCssModule(style, getRequire('style', style, i, style.scoped, style.module), loaderContext) || '' }) // add require for script @@ -189,6 +208,7 @@ module.exports = function (content) { // add require for template var template + var rewriter = 'require("' + require.resolve('./htmlCssModuleRewriter') + '")' if (parts.template.length) { template = parts.template[0] output += '__vue_template__ = ' + ( @@ -196,6 +216,8 @@ module.exports = function (content) { ? getRequireForImport('template', template, hasLocalStyles) : getRequire('template', template, 0, hasLocalStyles) ) + output += 'console.warn(__vue_template__)\n' + output += '__vue_template__ = ' + rewriter + '(__vue_template__, __vue_styles__)\n' } if (!query.inject) { diff --git a/lib/parser.js b/lib/parser.js index a353635d1..1d338771f 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -40,6 +40,7 @@ module.exports = function (content, filename, needMap) { var lang = getAttribute(node, 'lang') var src = getAttribute(node, 'src') var scoped = getAttribute(node, 'scoped') != null + var module = getAttribute(node, 'module') || '' var warnings = null var map = null @@ -64,7 +65,8 @@ module.exports = function (content, filename, needMap) { output.styleImports.push({ src: src, lang: lang, - scoped: scoped + scoped: scoped, + module: module }) } else if (type === 'template') { output.template.push({ @@ -151,6 +153,7 @@ module.exports = function (content, filename, needMap) { output[type].push({ lang: lang, scoped: scoped, + module: module, content: result, map: map && map.toJSON(), warnings: warnings diff --git a/test/test.js b/test/test.js index 8644b3ef1..53ad1d434 100644 --- a/test/test.js +++ b/test/test.js @@ -34,7 +34,7 @@ describe('vue-loader', function () { function getFile (file, cb) { fs.readFile(path.resolve(outputDir, file), 'utf-8', function (err, data) { - expect(err).to.be.not.exist + expect(err).to.not.exist cb(data) }) } From c9516532cea2596fe89e8a182584ca7848c58941 Mon Sep 17 00:00:00 2001 From: YiSiWang <597222964@qq.com> Date: Mon, 29 Aug 2016 15:55:48 +0800 Subject: [PATCH 02/16] add test case --- README.md | 40 +++++++++++++++++++++++++++++++++++ test/fixtures/css-modules.vue | 13 ++++++++++++ test/test.js | 16 ++++++++++++++ 3 files changed, 69 insertions(+) create mode 100644 test/fixtures/css-modules.vue diff --git a/README.md b/README.md index a4178a27d..92a4067ef 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,43 @@ +# Add CSS modules support + +Covert + +```html + + + + + +``` + +to: + +```html + + + + + +``` + +# 下面是原来的 README + # vue-loader [![Build Status](https://circleci.com/gh/vuejs/vue-loader/tree/master.svg?style=shield)](https://circleci.com/gh/vuejs/vue-loader/tree/master) [![npm package](https://img.shields.io/npm/v/vue-loader.svg?maxAge=2592000)](https://www.npmjs.com/package/vue-loader) > Vue.js component loader for [Webpack](http://webpack.github.io). diff --git a/test/fixtures/css-modules.vue b/test/fixtures/css-modules.vue new file mode 100644 index 000000000..b796c5772 --- /dev/null +++ b/test/fixtures/css-modules.vue @@ -0,0 +1,13 @@ + + + + + diff --git a/test/test.js b/test/test.js index 53ad1d434..2a735b67f 100644 --- a/test/test.js +++ b/test/test.js @@ -276,4 +276,20 @@ describe('vue-loader', function () { done() }) }) + + it.only('css-modules', function (done) { + test({ + entry: './test/fixtures/css-modules.vue' + }, function (window) { + var module = window.vueModule + var match = module.template.match(/\s*

<\/h2>/) + expect(match).to.have.length(2) + var className = match[1] + expect(className).to.not.equal('red') + expect(className).to.match(/^_/) + var style = window.document.querySelector('style').textContent + expect(style).to.contain('.' + className + ' {\n color: red;\n}') + done() + }) + }) }) From 14712894e3dd196b345cc5fb9d707def78c67048 Mon Sep 17 00:00:00 2001 From: YiSiWang <597222964@qq.com> Date: Mon, 29 Aug 2016 15:58:54 +0800 Subject: [PATCH 03/16] add README --- README.md | 44 +++++++++++++++++++++++++++++++---- test/fixtures/css-modules.vue | 6 +++++ test/test.js | 1 - 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 92a4067ef..e40285e78 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ -# Add CSS modules support +# Add CSS modules support -Covert +## Basic + +Convert ```html ``` -# 下面是原来的 README +## Scoped animation name + +```html + +``` + +becomes: + +```html + +``` + +# TODO +1. convert static class name in binding class. +2. provide style entry in `script` + +Note: properties in `animation` shorthand should be in correct order. + +# Original README below: # vue-loader [![Build Status](https://circleci.com/gh/vuejs/vue-loader/tree/master.svg?style=shield)](https://circleci.com/gh/vuejs/vue-loader/tree/master) [![npm package](https://img.shields.io/npm/v/vue-loader.svg?maxAge=2592000)](https://www.npmjs.com/package/vue-loader) diff --git a/test/fixtures/css-modules.vue b/test/fixtures/css-modules.vue index b796c5772..d02b0eba3 100644 --- a/test/fixtures/css-modules.vue +++ b/test/fixtures/css-modules.vue @@ -2,6 +2,12 @@ .red { color: red; } +@keyframes fade { + from { opacity: 1; } to { opacity: 0; } +} +.animate { + animation: fade 1s; +} diff --git a/test/test.js b/test/test.js index fbd9e965c..aa4997e6c 100644 --- a/test/test.js +++ b/test/test.js @@ -277,12 +277,11 @@ describe('vue-loader', function () { }) }) - it.only('css-modules', function (done) { + it('css-modules', function (done) { test({ entry: './test/fixtures/css-modules.vue' }, function (window) { var module = window.vueModule - console.log(module.template) // get local class name var match = module.template.match(/\s*

<\/h2>/) @@ -311,6 +310,9 @@ describe('vue-loader', function () { ).replace(/style\.red/g, className) expect(module.template).to.contain(expected) + // get local class name in script + expect(module.computed.$styles().style.red).to.equal(className) + done() }) }) From 3944b328bbfa77d636cc4c6d9344f642887900b7 Mon Sep 17 00:00:00 2001 From: YiSiWang <597222964@qq.com> Date: Tue, 30 Aug 2016 13:49:16 +0800 Subject: [PATCH 06/16] update README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 191cfab94..f8779d81a 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,8 @@ # TODO - [x] convert static class name in binding class. -- [ ] provide style entry in `script` +- [x] provide style entry in `script` +- [ ] write document # Original README below: From bfe2ceb15dabc36a8e707f37dfee1dd037a3ff7d Mon Sep 17 00:00:00 2001 From: YiSiWang <597222964@qq.com> Date: Tue, 30 Aug 2016 15:56:54 +0800 Subject: [PATCH 07/16] update README --- README.md | 95 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 62 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index f8779d81a..b46f84d33 100644 --- a/README.md +++ b/README.md @@ -1,63 +1,92 @@ # Add CSS modules support -## Overview +## Basic + +Simply use [CSS modules](https://github.com/css-modules/css-modules) with ` + +``` - /* global class name */ - :global(.red) { color: red; } - /* is converted to */ - .red { color: red; } +becomes: - /* animation name */ - @keyframes fade { from { opacity: 1; } to { opacity: 0; } } - .animate { animation: fade 1s; } - /* is converted to */ - @keyframes n5Q3vnbE7aL9uu6uOUOLo { from { opacity: 1; } to { opacity: 0; } } - ._2hSs7mCBtiABMJoqSnwHAD { animation: n5Q3vnbE7aL9uu6uOUOLo 1s; } - /* Note: properties in `animation` shorthand should be in correct order. */ +```html + + +``` + +Class names are unique. So your style won't affect any other component. + +Tips: + +1. With `module`, animation names are also converted. Feel free to write short class & animation names! +2. With `module` + `scoped`, you can limit your style to your component more strictly. +3. With `module` only, you can style `` in your component. + +## Binding classes +*Static* class names in binding expression are converted, too. + +```html +``` + +becomes: + +```html + +``` + +Note: Here, "static class name" means class name in single quotes. + +Complex ones (eg. `'a' + 'b'`) are not supported (and not recommend in Vue). + +## Getting local class name in script + +In some cases, you will like to control your class name in script. +You can do it like this: - +```html + ``` -# TODO -- [x] convert static class name in binding class. -- [x] provide style entry in `script` -- [ ] write document - # Original README below: # vue-loader [![Build Status](https://circleci.com/gh/vuejs/vue-loader/tree/master.svg?style=shield)](https://circleci.com/gh/vuejs/vue-loader/tree/master) [![npm package](https://img.shields.io/npm/v/vue-loader.svg?maxAge=2592000)](https://www.npmjs.com/package/vue-loader) From 3b3d0aa6b10854cfd3dc7f5587bd89e87cbfd7b4 Mon Sep 17 00:00:00 2001 From: YiSiWang <597222964@qq.com> Date: Tue, 30 Aug 2016 16:57:51 +0800 Subject: [PATCH 08/16] improve test case --- test/fixtures/css-modules.vue | 3 ++- test/test.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/test/fixtures/css-modules.vue b/test/fixtures/css-modules.vue index 52098ed6b..30bd9c288 100644 --- a/test/fixtures/css-modules.vue +++ b/test/fixtures/css-modules.vue @@ -14,7 +14,8 @@

-
+
+
diff --git a/test/test.js b/test/test.js index aab1c3c07..a085e1f43 100644 --- a/test/test.js +++ b/test/test.js @@ -284,10 +284,7 @@ describe('vue-loader', function () { var module = window.vueModule // get local class name - var match = module.template.match(/\s*

<\/h2>/) - expect(match).to.have.length(2) - var className = match[1] - expect(className).to.not.equal('style.red') + var className = module.computed.style().red expect(className).to.match(/^_/) // class name in style @@ -295,25 +292,12 @@ describe('vue-loader', function () { expect(style).to.contain('.' + className + ' {\n color: red;\n}') // animation name - match = style.match(/@-webkit-keyframes\s+(\S+)\s+{/) + var match = style.match(/@-webkit-keyframes\s+(\S+)\s+{/) expect(match).to.have.length(2) var animationName = match[1] expect(animationName).to.not.equal('fade') expect(style).to.contain('animation: ' + animationName + ' 1s;') - // static class name replacement - var expected = ( - '

\n' + - '

\n' + - '

\n' + - '
\n' + - '
' - ).replace(/style\.red/g, className) - expect(module.template).to.contain(expected) - - // get local class name in script - expect(module.computed.$styles().style.red).to.equal(className) - done() }) }) From dfdf835800f6398d0fc56ae2f7c229799037dae1 Mon Sep 17 00:00:00 2001 From: YiSiWang <597222964@qq.com> Date: Wed, 31 Aug 2016 11:13:37 +0800 Subject: [PATCH 10/16] remove class replacement --- lib/htmlCssModuleRewriter.js | 59 ------------------------------------ lib/loader.js | 16 ++++------ 2 files changed, 6 insertions(+), 69 deletions(-) delete mode 100644 lib/htmlCssModuleRewriter.js diff --git a/lib/htmlCssModuleRewriter.js b/lib/htmlCssModuleRewriter.js deleted file mode 100644 index da129f005..000000000 --- a/lib/htmlCssModuleRewriter.js +++ /dev/null @@ -1,59 +0,0 @@ -var parse5 = require('parse5') - -function walk (tree, fn) { - if (tree.childNodes) { - tree.childNodes.forEach(function (node) { - var isTemplate = node.tagName === 'template' - if (!isTemplate) { - fn(node) - } - if (isTemplate && node.content) { - walk(node.content, fn) - } else { - walk(node, fn) - } - }) - } -} - -module.exports = function (html, modules) { - function convert (className) { - var arr = className.trim().split('.') - if (arr.length !== 2) return className - var module = arr[0] - className = arr[1] - var map = modules[module] - if (!map) { - throw new Error('CSS Module name "' + module + '" is not defined!') - } - if (!map[className]) { - throw new Error('Class name "' + className + '" is not defined in module "' + module + '"!') - } - return map[className] - } - - var tree = parse5.parseFragment(html) - walk(tree, function (node) { - if (!node.attrs) return - node.attrs.forEach(function (attr) { - var expression = '' - if (attr.name === 'class') { - var match = attr.value.match(/^\s*({{.+}})\s*$/) - if (!match) { - attr.value = convert(attr.value) - return - } - expression = match[1] - } else { - if (attr.name === 'v-bind:class' || attr.name === ':class') { - expression = attr.value - } - } - if (!expression) return - attr.value = expression.replace(/'(.+?)'/g, function (match, className) { - return "'" + convert(className) + "'" - }) - }) - }) - return parse5.serialize(tree) -} diff --git a/lib/loader.js b/lib/loader.js index e4a560799..0c5d49325 100644 --- a/lib/loader.js +++ b/lib/loader.js @@ -208,7 +208,6 @@ module.exports = function (content) { // add require for template var template - var rewriter = 'require("' + require.resolve('./htmlCssModuleRewriter') + '")' if (parts.template.length) { template = parts.template[0] output += '__vue_template__ = ' + ( @@ -216,8 +215,6 @@ module.exports = function (content) { ? getRequireForImport('template', template, hasLocalStyles) : getRequire('template', template, 0, hasLocalStyles) ) - output += 'console.warn(__vue_template__)\n' - output += '__vue_template__ = ' + rewriter + '(__vue_template__, __vue_styles__)\n' } if (!query.inject) { @@ -231,13 +228,12 @@ module.exports = function (content) { 'if (__vue_template__) {\n' + '__vue_options__.template = __vue_template__\n' + '}\n' + - 'if (__vue_options__.loader) {\n' + - 'var $styles = __vue_options__.loader.styles\n' + - 'if ($styles) {\n' + - '(__vue_options__.computed || (__vue_options__.computed = {}))[$styles] = ' + - 'function () { return __vue_styles__ }\n' + - '}\n' + - '}\n' + // inject style modules as computed properties + 'if (!__vue_options__.computed) __vue_options__.computed = {}\n' + + 'Object.keys(__vue_styles__).forEach(function (key) {\n' + + 'var module = __vue_styles__[key]\n' + + '__vue_options__.computed[key] = function () { return module }\n' + + '})\n' // hot reload if ( !this.minimize && From b4512fa28cc40efe22995ff3e8ee65e84bde1e40 Mon Sep 17 00:00:00 2001 From: YiSiWang <597222964@qq.com> Date: Wed, 31 Aug 2016 12:14:55 +0800 Subject: [PATCH 11/16] update README --- README.md | 84 +++++++++---------------------------------------------- 1 file changed, 13 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index b46f84d33..a05755b8c 100644 --- a/README.md +++ b/README.md @@ -1,89 +1,31 @@ # Add CSS modules support -## Basic +Simply use CSS Modules with ` - -``` - -becomes: +Example: ```html - -``` - -Class names are unique. So your style won't affect any other component. - -Tips: - -1. With `module`, animation names are also converted. Feel free to write short class & animation names! -2. With `module` + `scoped`, you can limit your style to your component more strictly. -3. With `module` only, you can style `` in your component. - -## Binding classes - -*Static* class names in binding expression are converted, too. -```html - -``` - -becomes: - -```html - -``` - -Note: Here, "static class name" means class name in single quotes. - -Complex ones (eg. `'a' + 'b'`) are not supported (and not recommend in Vue). - -## Getting local class name in script - -In some cases, you will like to control your class name in script. -You can do it like this: - -```html + ``` From 90145a4eeda504c5c05f941748e79a5e93c59435 Mon Sep 17 00:00:00 2001 From: YiSiWang <597222964@qq.com> Date: Wed, 31 Aug 2016 12:23:52 +0800 Subject: [PATCH 12/16] remove unused codes --- lib/loader.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/loader.js b/lib/loader.js index 0c5d49325..87ff62459 100644 --- a/lib/loader.js +++ b/lib/loader.js @@ -156,14 +156,10 @@ module.exports = function (content) { } var cssModules = {} - function setCssModule (style, require, context) { + function setCssModule (style, require) { if (!style.module) return require - if (!(/^[a-zA-Z][a-zA-Z0-9]*$/.test(style.module))) { - context.emitError('Invallid CSS module name "' + style.module + '"!') - return require - } if (style.module in cssModules) { - context.emitError('CSS module name "' + style.module + '" is not unique!') + loaderContext.emitError('CSS module name "' + style.module + '" is not unique!') return require } cssModules[style.module] = true @@ -173,13 +169,13 @@ module.exports = function (content) { // add requires for src imports parts.styleImports.forEach(function (impt) { if (impt.scoped) hasLocalStyles = true - output += setCssModule(impt, getRequireForImport('style', impt, impt.scoped, impt.module), loaderContext) || '' + output += setCssModule(impt, getRequireForImport('style', impt, impt.scoped, impt.module)) || '' }) // add requires for styles parts.style.forEach(function (style, i) { if (style.scoped) hasLocalStyles = true - output += setCssModule(style, getRequire('style', style, i, style.scoped, style.module), loaderContext) || '' + output += setCssModule(style, getRequire('style', style, i, style.scoped, style.module)) || '' }) // add require for script From da61470fd1d014a2a383b4240b3e16a07c50ca34 Mon Sep 17 00:00:00 2001 From: YiSiWang <597222964@qq.com> Date: Fri, 2 Sep 2016 15:04:06 +0800 Subject: [PATCH 13/16] add support for preprocessor --- lib/loader.js | 59 ++++++++++++++++++++++++----------- test/fixtures/css-modules.vue | 5 +++ test/test.js | 9 +++++- 3 files changed, 54 insertions(+), 19 deletions(-) diff --git a/lib/loader.js b/lib/loader.js index 87ff62459..d2a97b0c8 100644 --- a/lib/loader.js +++ b/lib/loader.js @@ -22,7 +22,7 @@ var templateLoader = require.resolve('./template-loader') module.exports = function (content) { var defaultLoaders = { html: 'vue-html-loader', - css: 'vue-style-loader!css-loader?-sourceMap', + css: 'vue-style-loader!css-loader', js: 'babel-loader?presets[]=es2015&plugins[]=transform-runtime&comments=false' } @@ -46,25 +46,25 @@ module.exports = function (content) { options.cssSourceMap !== false && process.env.NODE_ENV !== 'production' ) { - defaultLoaders.css = 'vue-style-loader!css-loader?+sourceMap' + defaultLoaders.css = 'vue-style-loader!css-loader?sourceMap' } // check if there are custom loaders specified via // webpack config, otherwise use defaults var loaders = assign({}, defaultLoaders, options.loaders) - function getRequire (type, part, index, scoped, module) { + function getRequire (type, part, index, scoped) { return 'require(' + - getRequireString(type, part, index, scoped, module) + + getRequireString(type, part, index, scoped) + ')\n' } - function getRequireString (type, part, index, scoped, module) { + function getRequireString (type, part, index, scoped) { return loaderUtils.stringifyRequest(loaderContext, // disable all configuration loaders '!!' + // get loader string for pre-processors - getLoaderString(type, part, scoped, module) + + getLoaderString(type, part, index, scoped) + // select the corresponding part from the vue file getSelectorString(type, index || 0) + // the url to the actual vuefile @@ -72,28 +72,48 @@ module.exports = function (content) { ) } - function getRequireForImport (type, impt, scoped, module) { + function getRequireForImport (type, impt, scoped) { return 'require(' + - getRequireForImportString(type, impt, scoped, module) + + getRequireForImportString(type, impt, scoped) + ')\n' } - function getRequireForImportString (type, impt, scoped, module) { + function getRequireForImportString (type, impt, scoped) { return loaderUtils.stringifyRequest(loaderContext, '!!' + - getLoaderString(type, impt, scoped, module) + + getLoaderString(type, impt, -1, scoped) + impt.src ) } - function getLoaderString (type, part, scoped, module) { + function addCssModulesToLoader (loader, part, index) { + if (!part.module) return loader + return loader.replace(/((?:^|!)css(?:-loader)?)(\?[^!]*)?/, function (m, $1, $2) { + // $1: !css-loader + // $2: ?a=b + var option = loaderUtils.parseQuery($2) + option.modules = true + option.importLoaders = true + option.localIdentName = '[hash:base64]' + if (index !== -1) { + // Note: + // Class name is generated according to its filename. + // Different + + diff --git a/test/test.js b/test/test.js index a085e1f43..2f638e665 100644 --- a/test/test.js +++ b/test/test.js @@ -288,7 +288,9 @@ describe('vue-loader', function () { expect(className).to.match(/^_/) // class name in style - var style = window.document.querySelector('style').textContent + var style = [].slice.call(window.document.querySelectorAll('style')).map(function (style) { + return style.textContent + }).join('\n') expect(style).to.contain('.' + className + ' {\n color: red;\n}') // animation name @@ -298,6 +300,11 @@ describe('vue-loader', function () { expect(animationName).to.not.equal('fade') expect(style).to.contain('animation: ' + animationName + ' 1s;') + // module + pre-processor + scoped + var anotherClassName = module.computed.combined().red + expect(anotherClassName).to.match(/^_/).and.not.equal(className) + expect(style).to.contain('.' + anotherClassName + '[') // scoped + done() }) }) From 773b0c647eea88d9e67c83b55baaf7596cc56680 Mon Sep 17 00:00:00 2001 From: YiSiWang <597222964@qq.com> Date: Fri, 2 Sep 2016 15:46:22 +0800 Subject: [PATCH 14/16] improve test case --- test/test.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/test.js b/test/test.js index 2f638e665..8e3cbb096 100644 --- a/test/test.js +++ b/test/test.js @@ -294,7 +294,7 @@ describe('vue-loader', function () { expect(style).to.contain('.' + className + ' {\n color: red;\n}') // animation name - var match = style.match(/@-webkit-keyframes\s+(\S+)\s+{/) + var match = style.match(/@keyframes\s+(\S+)\s+{/) expect(match).to.have.length(2) var animationName = match[1] expect(animationName).to.not.equal('fade') @@ -303,7 +303,8 @@ describe('vue-loader', function () { // module + pre-processor + scoped var anotherClassName = module.computed.combined().red expect(anotherClassName).to.match(/^_/).and.not.equal(className) - expect(style).to.contain('.' + anotherClassName + '[') // scoped + var id = '_v-' + hash(require.resolve('./fixtures/css-modules.vue')) + expect(style).to.contain('.' + anotherClassName + '[' + id + ']') done() }) From f52d96a785ca7794f71f7cb5ac2eeb10271f138a Mon Sep 17 00:00:00 2001 From: YiSiWang <597222964@qq.com> Date: Thu, 8 Sep 2016 19:02:05 +0800 Subject: [PATCH 15/16] add default module support --- lib/loader.js | 2 ++ lib/parser.js | 2 +- test/fixtures/css-modules.vue | 2 +- test/test.js | 4 ++-- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/loader.js b/lib/loader.js index d2a97b0c8..b41a09a00 100644 --- a/lib/loader.js +++ b/lib/loader.js @@ -190,6 +190,7 @@ module.exports = function (content) { // add requires for src imports parts.styleImports.forEach(function (impt) { if (impt.scoped) hasLocalStyles = true + if (impt.module === '') impt.module = '$style' var requireString = getRequireForImport('style', impt, impt.scoped, impt.module) output += setCssModule(impt, requireString) }) @@ -197,6 +198,7 @@ module.exports = function (content) { // add requires for styles parts.style.forEach(function (style, i) { if (style.scoped) hasLocalStyles = true + if (style.module === '') style.module = '$style' var requireString = getRequire('style', style, i, style.scoped, style.module) output += setCssModule(style, requireString) }) diff --git a/lib/parser.js b/lib/parser.js index 1d338771f..03d4514b6 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -40,7 +40,7 @@ module.exports = function (content, filename, needMap) { var lang = getAttribute(node, 'lang') var src = getAttribute(node, 'src') var scoped = getAttribute(node, 'scoped') != null - var module = getAttribute(node, 'module') || '' + var module = getAttribute(node, 'module') var warnings = null var map = null diff --git a/test/fixtures/css-modules.vue b/test/fixtures/css-modules.vue index da2f9b4bc..8646cb868 100644 --- a/test/fixtures/css-modules.vue +++ b/test/fixtures/css-modules.vue @@ -10,7 +10,7 @@ } - diff --git a/test/test.js b/test/test.js index 8e3cbb096..2abbc0580 100644 --- a/test/test.js +++ b/test/test.js @@ -300,8 +300,8 @@ describe('vue-loader', function () { expect(animationName).to.not.equal('fade') expect(style).to.contain('animation: ' + animationName + ' 1s;') - // module + pre-processor + scoped - var anotherClassName = module.computed.combined().red + // default module + pre-processor + scoped + var anotherClassName = module.computed.$style().red expect(anotherClassName).to.match(/^_/).and.not.equal(className) var id = '_v-' + hash(require.resolve('./fixtures/css-modules.vue')) expect(style).to.contain('.' + anotherClassName + '[' + id + ']') From b9fb1c8f5da1a72289592d91cf2d507098775281 Mon Sep 17 00:00:00 2001 From: YiSiWang <597222964@qq.com> Date: Wed, 14 Sep 2016 11:54:39 +0800 Subject: [PATCH 16/16] update docs --- README.md | 33 ------------------- docs/en/SUMMARY.md | 1 + docs/en/features/css-modules.md | 56 +++++++++++++++++++++++++++++++++ docs/en/features/scoped-css.md | 2 +- 4 files changed, 58 insertions(+), 34 deletions(-) create mode 100644 docs/en/features/css-modules.md diff --git a/README.md b/README.md index a05755b8c..a4178a27d 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,3 @@ -# Add CSS modules support - -Simply use CSS Modules with ` - - - - -``` - -# Original README below: - # vue-loader [![Build Status](https://circleci.com/gh/vuejs/vue-loader/tree/master.svg?style=shield)](https://circleci.com/gh/vuejs/vue-loader/tree/master) [![npm package](https://img.shields.io/npm/v/vue-loader.svg?maxAge=2592000)](https://www.npmjs.com/package/vue-loader) > Vue.js component loader for [Webpack](http://webpack.github.io). diff --git a/docs/en/SUMMARY.md b/docs/en/SUMMARY.md index 1c4d10f64..c8ed4b7a7 100644 --- a/docs/en/SUMMARY.md +++ b/docs/en/SUMMARY.md @@ -4,6 +4,7 @@ - Features - [ES2015 and Babel](features/es2015.md) - [Scoped CSS](features/scoped-css.md) + - [CSS Modules](features/css-modules.md) - [PostCSS and Autoprefixer](features/postcss.md) - [Hot Reload](features/hot-reload.md) - Configurations diff --git a/docs/en/features/css-modules.md b/docs/en/features/css-modules.md new file mode 100644 index 000000000..b69b74294 --- /dev/null +++ b/docs/en/features/css-modules.md @@ -0,0 +1,56 @@ +# CSS Modules + +[CSS Modules](https://github.com/css-modules/css-modules) aims to solve class & animation name conflicts. It replaces all the local names with unique hashes and provides a name-to-hash map. So you can write short and general names without worrying any conflict! + +With vue-loader, you can simply use CSS Modules with ` + + + + +``` + +If you need mutiple ` + + + +``` + +## Tips + +1. Animation names also get transformed. So, it's recommended to use animations with CSS modules. + +2. You can use `scoped` and `module` together to avoid problems in descendant selectors. + +3. Use `module` only (without `scoped`), you are able to style ``s and children components. But styling children components breaks the principle of components. You can put `` in a classed wrapper and style it under that class. + +4. You can expose the class name of component's root element for theming. diff --git a/docs/en/features/scoped-css.md b/docs/en/features/scoped-css.md index 4a1f19709..f2bb9d1e9 100644 --- a/docs/en/features/scoped-css.md +++ b/docs/en/features/scoped-css.md @@ -48,4 +48,4 @@ Into the following: 4. **Scoped styles do not eliminate the need for classes**. Due to the way browsers render various CSS selectors, `p { color: red }` will be many times slower when scoped (i.e. when combined with an attribute selector). If you use classes or ids instead, such as in `.example { color: red }`, then you virtually eliminate that performance hit. [Here's a playground](http://stevesouders.com/efws/css-selectors/csscreate.php) where you can test the differences yourself. -5. **Be careful with descendant selectors in recursive components!** For a CSS rule with the selector `.a .b`, if the element that matches `.a` contains a recursive child component, then all `.b` in that child component will be matched by the rule. +5. **Be careful with descendant selectors in recursive components!** For a CSS rule with the selector `.a .b`, if the element that matches `.a` contains a recursive child component, then all `.b` in that child component will be matched by the rule. To avoid class name conflicts, you can use [CSS Modules](features/css-modules.md).