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). diff --git a/lib/loader.js b/lib/loader.js index f6e479a53..b41a09a00 100644 --- a/lib/loader.js +++ b/lib/loader.js @@ -64,7 +64,7 @@ module.exports = function (content) { // disable all configuration loaders '!!' + // get loader string for pre-processors - getLoaderString(type, part, scoped) + + getLoaderString(type, part, index, scoped) + // select the corresponding part from the vue file getSelectorString(type, index || 0) + // the url to the actual vuefile @@ -81,17 +81,40 @@ module.exports = function (content) { function getRequireForImportString (type, impt, scoped) { return loaderUtils.stringifyRequest(loaderContext, '!!' + - getLoaderString(type, impt, scoped) + + getLoaderString(type, impt, -1, scoped) + impt.src ) } - function getLoaderString (type, part, scoped) { + 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 8644b3ef1..2abbc0580 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) }) } @@ -276,4 +276,37 @@ describe('vue-loader', function () { done() }) }) + + it('css-modules', function (done) { + test({ + entry: './test/fixtures/css-modules.vue' + }, function (window) { + var module = window.vueModule + + // get local class name + var className = module.computed.style().red + expect(className).to.match(/^_/) + + // class name in style + 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 + var match = style.match(/@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;') + + // 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 + ']') + + done() + }) + }) })