amireh / happypack

Happiness in the form of faster webpack build times.
MIT License
4.24k stars 124 forks source link

Can HappyPack support MiniCssExtractPlugin? #223

Closed gaterking closed 6 years ago

gaterking commented 6 years ago

When I use happypack with MiniCssExtractPlugin, it throw error as bellow. If I remove happypack, it work well. webpack 4.5.0 happypack 5.0.0-beta.3 mini-css-extract-plugin 0.4.0

ERROR in ./demo/index.scss
Module build failed: TypeError: Cannot read property 'outputOptions' of undefined
    at Object.pitch (/Users/***/Documents/Netease Workspace/CodeRepos/project/mobile/node_modules/mini-css-extract-plugin/dist/loader.js:72:98)
    at applySyncOrAsync (/Users/***/Documents/Netease Workspace/CodeRepos/project/mobile/node_modules/happypack/lib/applyLoaders.js:350:21)
    at applyPitchLoader (/Users/***/Documents/Netease Workspace/CodeRepos/project/mobile/node_modules/happypack/lib/applyLoaders.js:205:7)
    at applyPitchLoaders (/Users/***/Documents/Netease Workspace/CodeRepos/project/mobile/node_modules/happypack/lib/applyLoaders.js:226:4)
    at applyLoaders (/Users/***/Documents/Netease Workspace/CodeRepos/project/mobile/node_modules/happypack/lib/applyLoaders.js:120:3)
    at HappyWorker.compile (/Users/***/Documents/Netease Workspace/CodeRepos/project/mobile/node_modules/happypack/lib/HappyWorker.js:27:3)
    at COMPILE (/Users/***/Documents/Netease Workspace/CodeRepos/project/mobile/node_modules/happypack/lib/HappyWorkerChannel.js:46:10)
    at process.accept (/Users/***/Documents/Netease Workspace/CodeRepos/project/mobile/node_modules/happypack/lib/HappyWorkerChannel.js:75:7)
    at emitTwo (events.js:126:13)
    at process.emit (events.js:214:7)
 @ ./demo/index.js 2:0-22

rules

{
            test: /\.scss$/,
            loader: 'happypack/loader?id=scss',
        },
        {
            test: /\.css$/,
            loader: 'happypack/loader?id=css'
        },

plugins:

new HappyPack({
            "id": "scss",
            "threadPool": happyThreadPool,
            "loaders": [
                MiniCssExtractPlugin.loader,
                {
                    "loader": "css-loader",
                    "options": {
                        "minimize": true,
                        "sourceMap": false
                    }
                },
                {
                    "loader": "postcss-loader",
                    "options": {
                        plugins: (loader) => [
                                require('autoprefixer')()
                        ],
                        "sourceMap": false
                    }
                },
                {
                    "loader": "sass-loader",
                    "options": {
                        "sourceMap": false
                    }
                }
            ]
        }),
        new HappyPack({
            "id": "css",
            "threadPool": happyThreadPool,
            "loaders": [
                MiniCssExtractPlugin.loader,                {
                    "loader": "css-loader",
                    "options": {
                        "minimize": true,
                        "sourceMap": false
                    }
                },
                {
                    "loader": "postcss-loader",
                    "options": {
                        plugins: (loader) => [
                            require('autoprefixer')()
                        ],
                        "sourceMap": false
                    }
                }
            ]
})
henopied commented 6 years ago

Mini-css-extract-plugin is using _compilation from loaderContext. A HappyFakeCompilation will need to be made and added to HappyFakeLoaderContext. It looks like it is only using _compilation to get the webpack config or more specifically the publicPath and it uses the method createChildCompiler.

Edit: I'm not sure if the above would work (or be a reasonable solution.) If we make a config that is more similar to the extract-text-webpack-plugin example then we get an option that works :smiley:. It would seem that we don't want to run mini-css-extract-plugin with multi-threading. Instead we run it on the main thread like so

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HappyPack = require('happypack');

const happyThreadPool = HappyPack.ThreadPool({ size: 5 });

module.exports = {
  mode: 'production',
  devtool: false,
  context: path.resolve(__dirname),

  entry: {
    css: './test.css',
    scss: './test.scss'
  },

  output: {
    path: path.resolve('./dist'),
    filename: '[name].js'
  },

  module: {
    rules: [
      {
        test: /\.scss$/,
        loaders: [
          MiniCssExtractPlugin.loader,
          'happypack/loader?id=scss',
        ]
      },
      {
        test: /\.css$/,
        loaders: [
          MiniCssExtractPlugin.loader,
          'happypack/loader?id=css',
        ]
      },
    ]
  },

  plugins: [
    new HappyPack({
      id: 'scss',
      threadPool: happyThreadPool,
      loaders: [
        {
          loader: 'css-loader',
          options: {
            minimize: true,
            sourceMap: false
          }
        },
        {
          loader: 'postcss-loader',
          options: {
            indent: 'postcss',
            plugins: (loader) => [
              require('autoprefixer')()
            ],
            sourceMap: false
          }
        },
        {
          loader: 'sass-loader',
          options: {
            sourceMap: false
          }
        }
      ]
    }),
    new HappyPack({
      id: 'css',
      threadPool: happyThreadPool,
      loaders: [
        {
          loader: 'css-loader',
          options: {
            minimize: true,
            sourceMap: false
          }
        },
        {
          loader: 'postcss-loader',
          options: {
            plugins: (loader) => [
              require('autoprefixer')()
            ],
            sourceMap: false
          }
        }
      ]
    }),
    new MiniCssExtractPlugin({
      filename: "[name].css",
      chunkFilename: "[id].css"
    })
  ],
}
gaterking commented 6 years ago

It work well for .js and .css. But in my project, I use vue-loader v15-rc.2. I think it is the error created by vue-style-loader. @yyx990803 may see these.

error detail:

Module build failed: CssSyntaxError: ****\components\win-prize-board.vue:1:1: Unknown word

> 1 | // extracted by mini-css-extract-plugin
    | ^

    at Input.error (E:\****\****\mobile\node_modules\postcss\lib\input.es6:94:22)
    at Parser.unknownWord (E:\****\****\mobile\node_modules\postcss\lib\parser.es6:486:26)
    at Parser.other (E:\****\****\mobile\node_modules\postcss\lib\parser.es6:150:18)
    at Parser.parse (E:\****\****\mobile\node_modules\postcss\lib\parser.es6:58:22)
    at parse (E:\****\****\mobile\node_modules\postcss\lib\parse.es6:13:16)
    at new LazyResult (E:\****\****\mobile\node_modules\postcss\lib\lazy-result.es6:42:24)
    at Processor.process (E:\****\****\mobile\node_modules\postcss\lib\processor.es6:95:16)
    at compileStyle (E:\****\****\mobile\node_modules\@vue\component-compiler-utils\dist\compileStyle.js:29:35)
    at Object.module.exports (E:\****\****\mobile\node_modules\vue-loader\lib\loaders\stylePostLoader.js:9:33)

rule:

{
            test: /\.scss$/,
            loaders: [MiniCssExtractPlugin.loader, 
                // 'vue-style-loader',
                'happypack/loader?id=scss'],
        }

new HappyPack({
            id: 'scss',
            threadPool: happyThreadPool,
            loaders: [
                // 'vue-style-loader',
                {
                loader: 'css-loader',
                options: {
                    minimize: true,
                    sourceMap: false
                }
            }, {
                loader: 'sass-loader',
                options: {
                    minimize: true,
                    sourceMap: false
                }
            }]
        })
henopied commented 6 years ago

You will want to choose vue-style-loader or MiniCssExtractPlugin. The vue-style-loader should be put in the happypack loaders option. However, I don't believe happypack supports vue-loader@next.

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HappyPack = require('happypack');
const { VueLoaderPlugin } = require('vue-loader')

const happyThreadPool = HappyPack.ThreadPool({ size: 5 });

module.exports = {
  mode: 'production',
  devtool: false,
  context: path.resolve(__dirname),

  entry: {
    css: './testcss.js',
    scss: './testscss.js',
    vue: './test.vue'
  },

  output: {
    path: path.resolve('./dist'),
    filename: '[name].js'
  },

  module: {
    rules: [
      {
        test: /\.vue$/,
        loaders: 'happypack/loader?id=vue'
      },
      {
        test: /\.scss$/,
        loaders: [
          // MiniCssExtractPlugin.loader,
          'happypack/loader?id=scss'
        ]
      },
      {
        test: /\.css$/,
        loaders: [
          // MiniCssExtractPlugin.loader,
          'happypack/loader?id=css'
        ]
      },
    ]
  },

  plugins: [
    new HappyPack({
      id: 'scss',
      threadPool: happyThreadPool,
      loaders: [
        'vue-style-loader',
        {
          loader: 'css-loader',
          options: {
            minimize: true,
            sourceMap: false
          }
        },
        {
          loader: 'postcss-loader',
          options: {
            plugins: (loader) => [
              require('autoprefixer')()
            ],
            sourceMap: false
          }
        },
        {
          loader: 'sass-loader',
          options: {
            sourceMap: false
          }
        }
      ]
    }),
    new HappyPack({
      id: 'css',
      threadPool: happyThreadPool,
      loaders: [
        'vue-style-loader',
        {
          loader: 'css-loader',
          options: {
            minimize: true,
            sourceMap: false
          }
        },
        {
          loader: 'postcss-loader',
          options: {
            plugins: (loader) => [
              require('autoprefixer')()
            ],
            sourceMap: false
          }
        }
      ]
    }),
    new HappyPack({
      id: 'vue',
      threadPool: happyThreadPool,
      loaders: ['vue-loader']
    }),
    new VueLoaderPlugin(),
    new MiniCssExtractPlugin({
      filename: "[name].css",
      chunkFilename: "[id].css"
    })
  ],
}

When I run this config, the VueLoaderPlugin cannot find vue-loader in the module rules because it is defined in HappyPack loader options. If I put vue-loader in module rules I get different errors. You may have to use vue-loader@14.x.x and this adapter in the extractCSS vue-loader options.

class MiniCssAdapter extends MiniCssExtractPlugin {
  static extract(options) {
    const rawLoaders = options.use || options;
    const loaders = typeof rawLoaders === 'string' ? rawLoaders.split('!') : rawLoaders;
    return [this.loader].concat(loaders);
  }
}
...
extractCSS: MiniCssAdapter
amireh commented 6 years ago

Thanks @henopied for helping and @gaterking for reporting. Generally, any loader that relies on a plugin won't work since we can't serialize the plugins across to the background threads.

The only way to work around that (as in the case of postcss-loader) is to have the "plugins" instantiated in the background threads themselves, e.g. by a config file that gets required by the loader (in which case you only pass the path to that file as an option to that loader.) But in this case I don't know if that's viable since VueLoaderPlugin may be doing things that require access to webpack and thus has to be in the foreground thread.

If it's significant enough to support, we need to look at the implementation of that plugin and see what kind of APIs it's relying on.

gaterking commented 6 years ago

vuejs/vue-loader#1273 vue-loader 15 not suppport happypack