[关闭]
@Dale-Lin 2022-08-28T20:06:22.000000Z 字数 4569 阅读 383

Webpack多线程并行构建

webpack5


原生 Webpack 对所有资源文件做的处理都是在一个线程内串行执行,CPU 利用率极低。因此社区出现了一些以多进程方式运行 webpack 或 webpack 的某部分构建任务的方案,例如:

thread-loader

thread-loader 放在 loader 数组第一位,剩余其他 loader 将会在一个 worker 池里运行。

  1. yarn add thread-loader -D

用法

  1. module.exports = {
  2. module: {
  3. rules: [{
  4. test: /\.js$/,
  5. include: path.resolve('src'),
  6. use: [{
  7. loader: 'thread-loader',
  8. options: {
  9. workers: 2, // 默认 require("os").cpus() - 1(最大数量,主线程占用一个)
  10. workerParallelJobs: 50, // worker 并行执行的任务数量,默认 20
  11. workerNodeArgs: ['--max-old-space-size=1024'], // 额外的 node 参数
  12. // Allow to respawn a dead worker pool
  13. // respawning slows down the entire compilation
  14. // and should be set to false for development
  15. poolRespawn: false,
  16. // timeout for killing the worker processes when idle
  17. // defaults to 500 (ms)
  18. // can be set to Infinity for watching builds to keep workers alive
  19. poolTimeout: 2000,
  20. // number of jobs the poll distributes to the workers
  21. // defaults to 200
  22. // decrease of less efficient but more fair distribution
  23. poolParallelJobs: 50,
  24. // name of the pool
  25. // can be used to create different pools with elsewise identical options
  26. name: 'my-pool',
  27. }
  28. }
  29. // your expensive loader (e.g babel-loader)
  30. ]
  31. }]
  32. }
  33. }

预热

预热线程池可以避免启动工作线程时的高延迟:

  1. const threadLoader = require('thread-loader')
  2. threadLoader.warmup(
  3. {
  4. // pool options, like passed to loader options
  5. // must match loader options to boot the correct pool
  6. },
  7. [
  8. // modules to load
  9. 'babel-loader',
  10. 'babel-preset-es2015',
  11. 'sass-loader'
  12. ]
  13. )

缺点

worker 池中的 loader 有一些限制条件:

  • Loaders 不能输出文件(不能调用 emitAsset 等接口;worker 不能共享?),会导致 style-loader 之类的加载器无法工作,解决方案是把这类 loader 放在 thread-loader 之前,例如 [
    'style-loader', 'thread-loader', 'css-loader'
    ]
  • Loaders 不能使用自定义 loader 的 API;
  • Loaders 不能访问 webpack options 配置,不能获取 compilationcompile 等实例对象;

HappyPack

HappyPack 通过并行 transform 文件来让 webpack 的初次构建提速。

  1. yarn add happypack -D

用法

HappyPack 既提供了 plugin 也提供了 loader,必须同时使用才能开启。

将 use 的 loader 数组替换成 'happypack/loader' ;再导出包含 HappyPack 插件包裹的真正 loaders 数组:

多个 happypack 实例时最好用 shared thread pool

  1. // @file: webpack.config.js
  2. const path = require('path')
  3. const HappyPack = require('happypack')
  4. const happyThreadPool = HappyPack.ThreadPool({ size: 4 })
  5. exports.module = {
  6. rules: [
  7. {
  8. test: /.js$/,
  9. use: 'happypack/loader?id=js',
  10. include: [path.resolve(__dirname, 'src/')],
  11. },
  12. {
  13. test: /\.less$/,
  14. use: 'happypack/loader?id=styles'
  15. }
  16. ]
  17. }
  18. exports.plugins = [
  19. new HappyPack({
  20. id: 'js',
  21. threadPool: happyThreadPool,
  22. loaders: [
  23. {
  24. loader: 'babel-loader',
  25. options: {
  26. presets: ['es2015']
  27. }
  28. }
  29. ]
  30. }),
  31. new HappyPack({
  32. id: 'styles',
  33. threadPool: happyThreadPool,
  34. loaders: [
  35. 'style-loader',
  36. 'css-loader',
  37. 'less-loader'
  38. ]
  39. })
  40. ]

HappyPack 执行流程:

缺点

Parallel-Webpack

Thread-loader、HappyPack 这类工具提供的并行能力仅限于文件加载过程,对后续 AST 解析、依赖收集、打包、优化代码过程均没有提升;Parallel-Webpack 提供了以多个独立进程运行 Webpack 实例的方案:

用法

  1. yarn add parallel-webpack -D

在 webpack.config.js 中导出多个 Webpack 配置对象:

  1. // webpack.config.js
  2. const path = require('path')
  3. module.exports = [
  4. {
  5. entry: './pageA.js',
  6. output: {
  7. path: path.resolve(__dirname, './dist'),
  8. filename: 'pageA.bundle.js'
  9. }
  10. },
  11. {
  12. entry: 'pageB.js',
  13. output: {
  14. path: './dist',
  15. filename: 'pageB.js'
  16. }
  17. }
  18. ]

parallel-webpack 会把上述两个构建并行执行。

原理

在需要执行对应多份配置的多份构建时好用,若只是单个构建配置则无需使用。

例如 MPA 等多 entry 场景,或需要同时编译出 esm、umd、amd 等多种产物的 lib 场景。

并行压缩

webpack5 使用 Terser 做代码压缩和混淆,自带多进程并行压缩能力。

TerserWebpackPlugin 默认开启并行压缩,也可以通过 parallel 参数(默认值为 require('os').cpus() - 1)设置并发进程数量。

用法

  1. yarn add -D terser-webpack-plugin
  1. const TerserPlugin = require("terser-webpack-plugin");
  2. module.exports = {
  3. optimization: {
  4. minimize: true,
  5. minimizer: [new TerserPlugin({
  6. // options
  7. })]
  8. }
  9. }

注意:使用时 devtool 的值只有 source-mapinline-source-maphidden-source-mapnosources-source-map 这几个会生效

因为:

  • eval 语句会把模块代码用字符串的形式执行,而 minimizer 不处理字符串;
  • cheap 配置没有列信息,minimizer 只生成单行,所以只有一个映射;

options

  1. type test = string | RegExp | Array<string | RegExp>;
  1. type include = string | RegExp | Array<string | RegExp>;
  1. type include = string | RegExp | Array<string | RegExp>;
  1. type parallel = boolean | number;

允许覆盖默认的压缩函数,在开发/测试新的压缩函数的时候有用。

Terser 配置项

是否把注释抽到一个分开的文件里,

结语

理论上,并行确实能够提升系统运行效率,但 Node 单线程架构下,所谓的并行计算都只能依托与派生子进程执行,而创建进程这个动作本身就有不小的消耗 —— 大约 600ms,对于小型项目,构建成本可能可能很低,引入多进程技术反而导致整体成本增加,因此建议大家按实际需求斟酌使用上述多进程方案。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注