Repository: vuejs/vue-loader (ts)

Issue:
8.6.1 : 'TypeError: loader.charAt is not a function' in webpack 2.2.1
I use webpack2.2.1 in vue1.0.28 , when i use extract-text-webpack-plugin in webpack configuration like this 
```js
  ...
  var ExtractTextPlugin = require('extract-text-webpack-plugin')

  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          loaders: {
            stylus: ExtractTextPlugin.extract({
              use: [
                'css-loader?{discardComments:{removeAll:true}}',
                'stylus-loader'
              ],
              fallback: 'vue-style-loader'
            })
          },
          postcss: [
            require('autoprefixer')({
              browsers: ['> 0.1% in CN']
            })
          ]
        }
      }
    ]
  },
  ...
  plugins: [
    ...
    new ExtractTextPlugin({
      filename: '[name].[contenthash:8].css',
      disable: false,
      allChunks: true
    })
    ...
  ]
  ...

```
and it show me  this message:
**"Module build failed: TypeError: loader.charAt is not a function"**
when i remove the  extract-text-webpack-plugin like this
```js
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          loaders: {
            stylus: 'vue-style-loader!css-loader!stylus-loader'
          },
          postcss: [
            require('autoprefixer')({
              browsers: ['> 0.1% in CN']
            })
          ]
        }
      }
    ]
  }
```
then it works.
by the way, webpack2.2.1 must use the new version of extract-text-webpack-plugin, so the latest version is
 "extract-text-webpack-plugin": "^2.0.0-rc.3",
so, i do not know it is my wrong in somewhere or it is a bug.
please help, thanks a lot!

---
update at 19:37 in china
I switched the version of "extract-text-webpack-plugin" to "^2.0.0-beta.5" and change my configuration in this code
```js
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          loaders: {
            stylus: ExtractTextPlugin.extract({
              loader: 'css-loader?{discardComments:{removeAll:true}}!stylus-loader',
              fallback: 'vue-style-loader'
            })
          },
          postcss: [
            require('autoprefixer')({
              browsers: ['> 0.1% in CN']
            })
          ]
        }
      }
```
finally, "extract-text-webpack-plugin" works

Pull request description:
support webpack2 and extract-text-webpack-plugin 2
fix #624 

Patch:
diff --git a/lib/loader.js b/lib/loader.js
index 5c174c9..d7053a9 100644
--- a/lib/loader.js
+++ b/lib/loader.js
@@ -106,12 +106,25 @@ module.exports = function (content) {
     })
   }
 
+  // stringify an Array of loader objects
+  function stringifyLoaders (loaders) {
+    return loaders.map(function (obj) {
+      return obj && typeof obj === 'object' && typeof obj.loader === 'string'
+        ? obj.loader + (obj.options ? '?' + JSON.stringify(obj.options) : '')
+        : obj
+    }).join('!')
+  }
+
   function getLoaderString (type, part, index, scoped) {
     var lang = part.lang || defaultLang[type]
     var loader = loaders[lang]
     var rewriter = getRewriter(type, scoped)
     var injectString = (type === 'script' && query.inject) ? 'inject!' : ''
     if (loader !== undefined) {
+      if (Array.isArray(loader)) {
+        loader = stringifyLoaders(loader)
+      }
+
       if (type === 'style') {
         loader = addCssModulesToLoader(loader, part, index)
       }


Test patch:
diff --git a/test/test.js b/test/test.js
index 2abbc05..4424567 100644
--- a/test/test.js
+++ b/test/test.js
@@ -221,6 +221,27 @@ describe('vue-loader', function () {
     })
   })
 
+  it('extract CSS by extract-text-webpack-plugin 2', function (done) {
+    webpack(Object.assign({}, globalConfig, {
+      entry: './test/fixtures/extract-css.vue',
+      vue: {
+        loaders: {
+          css: [{ loader: ExtractTextPlugin.loader({ remove: true }) }, { loader: 'css-loader' }],
+          stylus: [{ loader: ExtractTextPlugin.loader({ remove: true }) }, { loader: 'css-loader?sourceMap' }, { loader: 'stylus-loader' }]
+        }
+      },
+      plugins: [
+        new ExtractTextPlugin('test.output2.css')
+      ]
+    }), function (err, stats) {
+      expect(stats.compilation.errors).to.be.empty
+      getFile('test.output2.css', function (data) {
+        expect(data).to.contain('h1 {\n  color: #f00;\n}\n\n\n\n\n\n\nh2 {\n  color: green;\n}')
+        done()
+      })
+    })
+  })
+
   it('dependency injection', function (done) {
     test({
       entry: './test/fixtures/inject.js'