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()
+ })
+ })
})