Repository: vuejs/vue-loader
Pull request #616 (2017-02-02 23:30:38)

fix typo


Patch:
diff --git a/docs/en/configurations/advanced.md b/docs/en/configurations/advanced.md
index 9edee1f..5c48d64 100644
--- a/docs/en/configurations/advanced.md
+++ b/docs/en/configurations/advanced.md
@@ -1,57 +1,80 @@
 # Advanced Loader Configuration
 
-Sometimes you may want to apply a custom loader string to a language instead of letting `vue-loader` infer it. Or you may simply want to overwrite the built-in loader configuration for the default languages. To do that, add a `vue` block in your Webpack config file, and specify the `loaders` option.
+Sometimes the you may want to:
 
-### Webpack 1.x
+1. Apply a custom loader string to a language instead of letting `vue-loader` infer it;
+
+2. Overwrite the built-in loader configuration for the default languages;
+
+3. Pre-process or post-process a specific language block with custom loaders.
+
+To do that, specify the `loaders` option for `vue-loader`:
+
+> Note that `preLoaders` and `postLoaders` are only supported in >=10.3.0
+
+### Webpack 2.x
 
 ``` js
-// webpack.config.js
 module.exports = {
   // other options...
   module: {
-    loaders: [
+    // module.rules is the same as module.loaders in 1.x
+    rules: [
       {
         test: /\.vue$/,
-        loader: 'vue'
+        loader: 'vue-loader',
+        options: {
+          // `loaders` will overwrite the default loaders.
+          // The following config will cause all <script> tags without "lang"
+          // attribute to be loaded with coffee-loader
+          loaders: {
+            js: 'coffee-loader'
+          },
+
+          // `preLoaders` are attached before the default loaders.
+          // You can use this to pre-process language blocks - a common use
+          // case would be build-time i18n.
+          preLoaders: {
+            js: '/path/to/custom/loader'
+          },
+
+          // `postLoaders` are attached after the default loaders.
+          //
+          // - For `html`, the result returned by the default loader
+          //   will be compiled JavaScript render function code.
+          //
+          // - For `css`, the result will be returned by vue-style-loader
+          //   which isn't particularly useful in most cases. Using a postcss
+          //   plugin will be a better option.
+          postLoaders: {
+            html: 'babel-loader'
+          }
+        }
       }
     ]
-  },
-  // vue-loader configurations
-  vue: {
-    // ... other vue options
-    loaders: {
-      // load all <script> without "lang" attribute with coffee-loader
-      js: 'coffee-loader',
-      // allows you to write markdown inside <template> tags...
-      // (note this only works for 10.2.0+)
-      html: 'marked'
-    }
   }
 }
 ```
 
-### Webpack 2.x
+### Webpack 1.x
 
 ``` js
+// webpack.config.js
 module.exports = {
   // other options...
   module: {
-    // module.rules is the same as module.loaders in 1.x
-    rules: [
+    loaders: [
       {
         test: /\.vue$/,
-        loader: 'vue-loader',
-        options: {
-          loaders: {
-            // load all <script> without "lang" attribute with coffee-loader
-            js: 'coffee-loader',
-            // allows you to write markdown inside <template> tags...
-            // (note this only works for 10.2.0+)
-            html: 'marked'
-          }
-        }
+        loader: 'vue'
       }
     ]
+  },
+  // vue-loader configurations
+  vue: {
+    loaders: {
+      // same configuration rules as above
+    }
   }
 }
 ```
diff --git a/docs/en/configurations/asset-url.md b/docs/en/configurations/asset-url.md
index 938e258..eeceef7 100644
--- a/docs/en/configurations/asset-url.md
+++ b/docs/en/configurations/asset-url.md
@@ -18,6 +18,6 @@ Because `.png` is not a JavaScript file, you will need to configure Webpack to u
 
 The benefits of all this are:
 
-1. `file-loader` allows you to designate where to copy and place the asset file, and how to name it using version hashes for better caching. Moreoever, this also means **you can just place images next to your `*.vue` files and use relative paths based on the folder structure instead of worrying about deployment URLs**. With proper config, Webpack will auto-rewrite the file paths into correct URLs in the bundled output.
+1. `file-loader` allows you to designate where to copy and place the asset file, and how to name it using version hashes for better caching. Moreover, this also means **you can just place images next to your `*.vue` files and use relative paths based on the folder structure instead of worrying about deployment URLs**. With proper config, Webpack will auto-rewrite the file paths into correct URLs in the bundled output.
 
 2. `url-loader` allows you to conditionally inline a file as base-64 data URL if they are smaller than a given threshold. This can reduce the amount of HTTP requests for trivial files. If the file is larger than the threshold, it automatically falls back to `file-loader`.
diff --git a/docs/en/configurations/custom-blocks.md b/docs/en/configurations/custom-blocks.md
index b3420b8..2066b59 100644
--- a/docs/en/configurations/custom-blocks.md
+++ b/docs/en/configurations/custom-blocks.md
@@ -4,6 +4,8 @@
 
 You can define custom language blocks inside `*.vue` files. The content of a custom block will be processed by the loaders specified in the `loaders` object of `vue-loader` options and then required by the component module. The configuration is similar to what is described in [Advanced Loader Configuration](../configurations/advanced.md), except the matching uses the tag name instead of the `lang` attribute.
 
+If a matching loader is found for a custom block, it will be processed; otherwise the custom block will simply be ignored.
+
 ## Example
 
 Here's an example of extracting all `<docs>` custom blocks into a single docs file:
@@ -63,5 +65,3 @@ module.exports = {
   }
 }
 ```
-
-Note that custom blocks would require specific configuration to work properly, so you probably want to avoid distributing reusable components with custom blocks in source form.
diff --git a/docs/en/configurations/extract-css.md b/docs/en/configurations/extract-css.md
index a648418..750f65a 100644
--- a/docs/en/configurations/extract-css.md
+++ b/docs/en/configurations/extract-css.md
@@ -2,10 +2,10 @@
 
 Example config to extract all the processed CSS in all Vue components into a single CSS file:
 
-### Webpack 1.x
+### Webpack 2.x
 
 ``` bash
-npm install extract-text-webpack-plugin --save-dev
+npm install extract-text-webpack-plugin@2.x --save-dev
 ```
 
 ``` js
@@ -15,30 +15,31 @@ var ExtractTextPlugin = require("extract-text-webpack-plugin")
 module.exports = {
   // other options...
   module: {
-    loaders: [
+    rules: [
       {
         test: /\.vue$/,
-        loader: 'vue'
-      },
+        loader: 'vue-loader',
+        options: {
+          loaders: {
+            css: ExtractTextPlugin.extract({
+              loader: 'css-loader',
+              fallbackLoader: 'vue-style-loader' // <- this is a dep of vue-loader, so no need to explicitly install if using npm3
+            })
+          }
+        }
+      }
     ]
   },
-  vue: {
-    loaders: {
-      css: ExtractTextPlugin.extract("css"),
-      // you can also include <style lang="less"> or other langauges
-      less: ExtractTextPlugin.extract("css!less")
-    }
-  },
   plugins: [
     new ExtractTextPlugin("style.css")
   ]
 }
 ```
 
-### Webpack 2.x
+### Webpack 1.x
 
 ``` bash
-npm install extract-text-webpack-plugin@2.x --save-dev
+npm install extract-text-webpack-plugin --save-dev
 ```
 
 ``` js
@@ -48,21 +49,20 @@ var ExtractTextPlugin = require("extract-text-webpack-plugin")
 module.exports = {
   // other options...
   module: {
-    rules: [
+    loaders: [
       {
         test: /\.vue$/,
-        loader: 'vue-loader',
-        options: {
-          loaders: {
-            css: ExtractTextPlugin.extract({
-              loader: 'css-loader',
-              fallbackLoader: 'vue-style-loader' // <- this is a dep of vue-loader, so no need to explicitly install if using npm3
-            })
-          }
-        }
-      }
+        loader: 'vue'
+      },
     ]
   },
+  vue: {
+    loaders: {
+      css: ExtractTextPlugin.extract("css"),
+      // you can also include <style lang="less"> or other langauges
+      less: ExtractTextPlugin.extract("css!less")
+    }
+  },
   plugins: [
     new ExtractTextPlugin("style.css")
   ]
diff --git a/docs/en/options.md b/docs/en/options.md
index 7f0d883..74d6610 100644
--- a/docs/en/options.md
+++ b/docs/en/options.md
@@ -2,18 +2,7 @@
 
 ## Usage Difference Between Webpack 1 & 2
 
-For Webpack 1.x: add a root `vue` block in your Webpack config:
-
-``` js
-module.exports = {
-  // ...
-  vue: {
-    // vue-loader options
-  }
-}
-```
-
-For Webpack 2 (^2.1.0-beta.25):
+For Webpack 2: pass the options directly to the loader rule.
 
 ``` js
 module.exports = {
@@ -32,11 +21,22 @@ module.exports = {
 }
 ```
 
+For Webpack 1.x: add a root `vue` block in your Webpack config.
+
+``` js
+module.exports = {
+  // ...
+  vue: {
+    // vue-loader options
+  }
+}
+```
+
 ### loaders
 
-- type: `Object`
+- type: `{ [lang: string]: string }`
 
-  An object specifying Webpack loaders to use for language blocks inside `*.vue` files. The key corresponds to the `lang` attribute for language blocks, if specified. The default `lang` for each type is:
+  An object specifying Webpack loaders to overwrite the default loaders used for language blocks inside `*.vue` files. The key corresponds to the `lang` attribute for language blocks, if specified. The default `lang` for each type is:
 
   - `<template>`: `html`
   - `<script>`: `js`
@@ -45,18 +45,43 @@ module.exports = {
   For example, to use `babel-loader` and `eslint-loader` to process all `<script>` blocks:
 
   ``` js
-  // ...
-  vue: {
-    loaders: {
-      js: 'babel!eslint'
-    }
+  // Webpack 2.x config
+  module: {
+    rules: [
+      {
+        test: /\.vue$/,
+        loader: 'vue-loader',
+        options: {
+          loaders: {
+            js: 'babel-loader!eslint-loader'
+          }
+        }
+      }
+    ]
   }
   ```
 
+### preLoaders
+
+- type: `{ [lang: string]: string }`
+- only supported in >=10.3.0
+
+  The config format is the same as `loaders`, but `preLoaders` are applied to corresponding language blocks before the default loaders. You can use this to pre-process language blocks - a common use case would be build-time i18n.
+
+### postLoaders
+
+- type: `{ [lang: string]: string }`
+- only supported in >=10.3.0
+
+  The config format is the same as `loaders`, but `postLoaders` are applied after the default loaders. You can use this to post-process language blocks. However note that this is a bit more complicated:
+
+  - For `html`, the result returned by the default loader will be compiled JavaScript render function code.
+
+  - For `css`, the result will be returned by `vue-style-loader` which isn't particularly useful in most cases. Using a postcss plugin will be a better option.
+
 ### postcss
 
 - type: `Array` or `Function` or `Object`
-- `Object` format only supported in ^8.5.0
 
   Specify custom PostCSS plugins to be applied to CSS inside `*.vue` files. If using a function, the function will called using the same loader context and should return an Array of plugins.
 
diff --git a/docs/en/workflow/linting.md b/docs/en/workflow/linting.md
index a88e55b..63bab5e 100644
--- a/docs/en/workflow/linting.md
+++ b/docs/en/workflow/linting.md
@@ -2,7 +2,7 @@
 
 You may have been wondering how do you lint your code inside `*.vue` files, since they are not JavaScript. We will assume you are using [ESLint](http://eslint.org/) (if you are not, you should!).
 
-You will also need the [eslint-html-plugin](https://github.com/BenoitZugmeyer/eslint-plugin-html) with supports extracting and linting the JavaScript inside `*.vue` files.
+You will also need the [eslint-html-plugin](https://github.com/BenoitZugmeyer/eslint-plugin-html) which supports extracting and linting the JavaScript inside `*.vue` files.
 
 Make sure to include the plugin in your ESLint config:
 
diff --git a/lib/gen-id.js b/lib/gen-id.js
index 8c9621d..1919293 100644
--- a/lib/gen-id.js
+++ b/lib/gen-id.js
@@ -1,8 +1,13 @@
 // utility for generating a uid for each component file
 // used in scoped CSS rewriting
+var path = require('path')
 var hash = require('hash-sum')
 var cache = Object.create(null)
+var sepRE = new RegExp(path.sep.replace('\\', '\\\\'), 'g')
 
-module.exports = function genId (file) {
+module.exports = function genId (file, context) {
+  var contextPath = context.split(path.sep)
+  var rootId = contextPath[contextPath.length - 1]
+  file = rootId + '/' + path.relative(context, file).replace(sepRE, '/')
   return cache[file] || (cache[file] = hash(file))
 }
diff --git a/lib/loader.js b/lib/loader.js
index 81a0cc0..dbb404a 100644
--- a/lib/loader.js
+++ b/lib/loader.js
@@ -60,7 +60,7 @@ module.exports = function (content) {
 
   var filePath = this.resourcePath
   var fileName = path.basename(filePath)
-  var moduleId = 'data-v-' + genId(filePath)
+  var moduleId = 'data-v-' + genId(filePath, this._compiler.context)
   var styleRewriter = styleRewriterPath + '?id=' + moduleId
 
   var cssLoaderOptions = ''
@@ -84,6 +84,8 @@ module.exports = function (content) {
   // check if there are custom loaders specified via
   // webpack config, otherwise use defaults
   var loaders = Object.assign({}, defaultLoaders, options.loaders)
+  var preLoaders = options.preLoaders || {}
+  var postLoaders = options.postLoaders || {}
 
   function getRequire (type, part, index, scoped) {
     return 'require(' +
@@ -161,6 +163,18 @@ module.exports = function (content) {
   }
 
   function getLoaderString (type, part, index, scoped) {
+    var loader = getRawLoaderString(type, part, index, scoped)
+    var lang = part.lang || defaultLang[type]
+    if (preLoaders[lang]) {
+      loader = loader + ensureBang(preLoaders[lang])
+    }
+    if (postLoaders[lang]) {
+      loader = ensureBang(postLoaders[lang]) + loader
+    }
+    return loader
+  }
+
+  function getRawLoaderString (type, part, index, scoped) {
     var lang = part.lang || defaultLang[type]
     var loader = loaders[lang]
     var rewriter = type === 'styles' ? styleRewriter + (scoped ? '&scoped=true!' : '!') : ''
@@ -174,7 +188,7 @@ module.exports = function (content) {
         loader = addCssModulesToLoader(loader, part, index)
       }
       // if user defines custom loaders for html, add template compiler to it
-      if (type === 'template' && loader !== defaultLoaders.html) {
+      if (type === 'template' && loader.indexOf(defaultLoaders.html) < 0) {
         loader = defaultLoaders.html + '!' + loader
       }
       // inject rewriter before css/html loader for
@@ -281,32 +295,6 @@ module.exports = function (content) {
     output += '\n'
   }
 
-    // add requires for customBlocks
-  if (parts.customBlocks && parts.customBlocks.length) {
-    var addedPrefix = false
-
-    parts.customBlocks.forEach(function (customBlock, i) {
-      if (!loaders[customBlock.type]) {
-        loaderContext.emitWarning('Loader for custom block type "' + customBlock.type + '" not found in webpack configuration')
-      } else {
-        // require customBlock
-        customBlock.src = customBlock.attrs.src
-        var requireString = customBlock.src
-          ? getRequireForImport(customBlock.type, customBlock)
-          : getRequire(customBlock.type, customBlock, i)
-
-        if (!addedPrefix) {
-          output += '\n/* customBlocks */\n'
-          addedPrefix = true
-        }
-
-        output += requireString + '\n'
-      }
-    })
-
-    output += '\n'
-  }
-
   // we require the component normalizer function, and call it like so:
   // normalizeComponent(
   //   scriptExports,
@@ -385,6 +373,30 @@ module.exports = function (content) {
     }
   }
 
+  // add requires for customBlocks
+  if (parts.customBlocks && parts.customBlocks.length) {
+    var addedPrefix = false
+
+    parts.customBlocks.forEach(function (customBlock, i) {
+      if (loaders[customBlock.type]) {
+        // require customBlock
+        customBlock.src = customBlock.attrs.src
+        var requireString = customBlock.src
+          ? getRequireForImport(customBlock.type, customBlock)
+          : getRequire(customBlock.type, customBlock, i)
+
+        if (!addedPrefix) {
+          output += '\n/* customBlocks */\n'
+          addedPrefix = true
+        }
+
+        output += requireString + '\n'
+      }
+    })
+
+    output += '\n'
+  }
+
   if (!query.inject) {
     // hot reload
     if (
diff --git a/package.json b/package.json
index 21055a4..f302e68 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "vue-loader",
-  "version": "10.2.3",
+  "version": "10.3.0",
   "description": "Vue single-file component loader for Webpack",
   "main": "index.js",
   "repository": {


Test patch:
diff --git a/test/mock-loaders/css.js b/test/mock-loaders/css.js
new file mode 100644
index 0000000..fe5d6d1
--- /dev/null
+++ b/test/mock-loaders/css.js
@@ -0,0 +1,3 @@
+module.exports = function (content) {
+  return content.replace(/#f00/, '#00f')
+}
diff --git a/test/mock-loaders/html.js b/test/mock-loaders/html.js
new file mode 100644
index 0000000..182d66e
--- /dev/null
+++ b/test/mock-loaders/html.js
@@ -0,0 +1,3 @@
+module.exports = function (content) {
+  return content.replace(/red/, 'green')
+}
diff --git a/test/mock-loader.js b/test/mock-loaders/js.js
similarity index 100%
rename from test/mock-loader.js
rename to test/mock-loaders/js.js
diff --git a/test/test.js b/test/test.js
index 69e7165..2379ce8 100644
--- a/test/test.js
+++ b/test/test.js
@@ -5,7 +5,7 @@ var jsdom = require('jsdom')
 var webpack = require('webpack')
 var MemoryFS = require('memory-fs')
 var expect = require('chai').expect
-var genId = require('../lib/gen-id')
+var hash = require('hash-sum')
 var SSR = require('vue-server-renderer')
 var compiler = require('../lib/template-compiler')
 var normalizeNewline = require('normalize-newline')
@@ -163,7 +163,7 @@ describe('vue-loader', function () {
     test({
       entry: './test/fixtures/scoped-css.vue'
     }, function (window, module) {
-      var id = 'data-v-' + genId(require.resolve('./fixtures/scoped-css.vue'))
+      var id = 'data-v-' + hash('vue-loader/test/fixtures/scoped-css.vue')
       expect(module._scopeId).to.equal(id)
 
       var vnode = mockRender(module, {
@@ -198,7 +198,7 @@ describe('vue-loader', function () {
       var styles = window.document.querySelectorAll('style')
       expect(styles[0].textContent).to.contain('h1 { color: red;\n}')
       // import with scoped
-      var id = 'data-v-' + genId(require.resolve('./fixtures/style-import.vue'))
+      var id = 'data-v-' + hash('vue-loader/test/fixtures/style-import.vue')
       expect(styles[1].textContent).to.contain('h1[' + id + '] { color: green;\n}')
       done()
     })
@@ -258,7 +258,7 @@ describe('vue-loader', function () {
     }, function (window) {
       var style = window.document.querySelector('style').textContent
       style = normalizeNewline(style)
-      var id = 'data-v-' + genId(require.resolve('./fixtures/media-query.vue'))
+      var id = 'data-v-' + hash('vue-loader/test/fixtures/media-query.vue')
       expect(style).to.contain('@media print {\n.foo[' + id + '] {\n    color: #000;\n}\n}')
       done()
     })
@@ -465,7 +465,7 @@ describe('vue-loader', function () {
         // default module + pre-processor + scoped
         var anotherClassName = module.computed.$style().red
         expect(anotherClassName).to.match(regexToMatch).and.not.equal(className)
-        var id = 'data-v-' + genId(require.resolve('./fixtures/css-modules.vue'))
+        var id = 'data-v-' + hash('vue-loader/test/fixtures/css-modules.vue')
         expect(style).to.contain('.' + anotherClassName + '[' + id + ']')
 
         cb()
@@ -524,7 +524,7 @@ describe('vue-loader', function () {
   })
 
   it('support chaining with other loaders', done => {
-    const mockLoaderPath = require.resolve('./mock-loader')
+    const mockLoaderPath = require.resolve('./mock-loaders/js')
     test({
       entry: './test/fixtures/basic.vue',
       module: {
@@ -617,50 +617,15 @@ describe('vue-loader', function () {
     })
   })
 
-  it('custom blocks can be removed from the output', function (done) {
+  it('custom blocks can be ignored', function (done) {
     bundle({
-      entry: './test/fixtures/custom-language.vue',
-      vue: {
-        loaders: {
-          'unit-test': 'null-loader'
-        }
-      }
+      entry: './test/fixtures/custom-language.vue'
     }, function (code, warnings) {
       expect(code).not.to.contain('describe(\'example\', function () {\n  it(\'basic\', function (done) {\n    done();\n  });\n})')
       done()
     })
   })
 
-  it('no warnings when loaders are specified for all custom blocks', function (done) {
-    bundle({
-      entry: './test/fixtures/custom-language.vue',
-      vue: {
-        loaders: {
-          'documentation': 'null-loader',
-          'unit-test': 'null-loader'
-        }
-      }
-    }, function (code, warnings) {
-      expect(warnings.length).to.equal(0)
-      done()
-    })
-  })
-
-  it('warning should be raised when a loader is not specified for a custom block', function (done) {
-    bundle({
-      entry: './test/fixtures/custom-language.vue',
-      vue: {
-        loaders: {
-          'documentation': 'null-loader'
-        }
-      }
-    }, function (code, warnings) {
-      expect(warnings.length).to.equal(1)
-      expect(warnings[0].message).to.equal('Loader for custom block type "unit-test" not found in webpack configuration')
-      done()
-    })
-  })
-
   it('passes attributes as options to the loader', function (done) {
     bundle({
       entry: './test/fixtures/custom-options.vue',
@@ -689,4 +654,33 @@ describe('vue-loader', function () {
       done()
     })
   })
+
+  it('pre/post loaders', done => {
+    test({
+      entry: './test/fixtures/basic.vue',
+      vue: {
+        preLoaders: {
+          js: require.resolve('./mock-loaders/js'),
+          css: require.resolve('./mock-loaders/css')
+        },
+        postLoaders: {
+          html: require.resolve('./mock-loaders/html')
+        }
+      }
+    }, (window, module) => {
+      var vnode = mockRender(module, {
+        msg: 'hi'
+      })
+      // <h2 class="green">{{msg}}</h2>
+      expect(vnode.tag).to.equal('h2')
+      expect(vnode.data.staticClass).to.equal('green')
+      expect(vnode.children[0]).to.equal('hi')
+
+      expect(module.data().msg).to.contain('Changed!')
+      var style = window.document.querySelector('style').textContent
+      style = normalizeNewline(style)
+      expect(style).to.contain('comp-a h2 {\n  color: #00f;\n}')
+      done()
+    })
+  })
 })