@Dale-Lin
2022-08-28T12:06:22.000000Z
字数 4569
阅读 770
webpack5
原生 Webpack 对所有资源文件做的处理都是在一个线程内串行执行,CPU 利用率极低。因此社区出现了一些以多进程方式运行 webpack 或 webpack 的某部分构建任务的方案,例如:
thread-loader 放在其他 loader 之前,就能用一个线程池来管理其他 loader 的运行;terser-webpack-plugin 可以用多进程方式压缩、混淆代码。把 thread-loader 放在 loader 数组第一位,剩余其他 loader 将会在一个 worker 池里运行。
yarn add thread-loader -D
module.exports = {module: {rules: [{test: /\.js$/,include: path.resolve('src'),use: [{loader: 'thread-loader',options: {workers: 2, // 默认 require("os").cpus() - 1(最大数量,主线程占用一个)workerParallelJobs: 50, // worker 并行执行的任务数量,默认 20workerNodeArgs: ['--max-old-space-size=1024'], // 额外的 node 参数// Allow to respawn a dead worker pool// respawning slows down the entire compilation// and should be set to false for developmentpoolRespawn: false,// timeout for killing the worker processes when idle// defaults to 500 (ms)// can be set to Infinity for watching builds to keep workers alivepoolTimeout: 2000,// number of jobs the poll distributes to the workers// defaults to 200// decrease of less efficient but more fair distributionpoolParallelJobs: 50,// name of the pool// can be used to create different pools with elsewise identical optionsname: 'my-pool',}}// your expensive loader (e.g babel-loader)]}]}}
预热线程池可以避免启动工作线程时的高延迟:
const threadLoader = require('thread-loader')threadLoader.warmup({// pool options, like passed to loader options// must match loader options to boot the correct pool},[// modules to load'babel-loader','babel-preset-es2015','sass-loader'])
worker 池中的 loader 有一些限制条件:
- Loaders 不能输出文件(不能调用
emitAsset等接口;worker 不能共享?),会导致style-loader之类的加载器无法工作,解决方案是把这类 loader 放在 thread-loader 之前,例如[;
'style-loader', 'thread-loader', 'css-loader'
]- Loaders 不能使用自定义 loader 的 API;
- Loaders 不能访问 webpack options 配置,不能获取
compilation、compile等实例对象;
HappyPack 通过并行 transform 文件来让 webpack 的初次构建提速。
yarn add happypack -D
HappyPack 既提供了 plugin 也提供了 loader,必须同时使用才能开启。
将 use 的 loader 数组替换成 'happypack/loader' ;再导出包含 HappyPack 插件包裹的真正 loaders 数组:
多个 happypack 实例时最好用 shared thread pool
// @file: webpack.config.jsconst path = require('path')const HappyPack = require('happypack')const happyThreadPool = HappyPack.ThreadPool({ size: 4 })exports.module = {rules: [{test: /.js$/,use: 'happypack/loader?id=js',include: [path.resolve(__dirname, 'src/')],},{test: /\.less$/,use: 'happypack/loader?id=styles'}]}exports.plugins = [new HappyPack({id: 'js',threadPool: happyThreadPool,loaders: [{loader: 'babel-loader',options: {presets: ['es2015']}}]}),new HappyPack({id: 'styles',threadPool: happyThreadPool,loaders: ['style-loader','css-loader','less-loader']})]
HappyPack 执行流程:
happypack/loader 接收到 webpack 的 transform 请求,从 webpack 配置中读到 HappyPack 插件实例;HappyPack 实例的 compile 方法,从新建或传入的的 HappyThreadPool 取出空闲的 HappyThread 实例;HappyThread 内部调用 child_process.fork 创建子进程,执行 HappyWorkerChannel;HappyWorkerChannel 创建 HappyWorker,开始执行 Loader 转译逻辑;Thread-loader、HappyPack 这类工具提供的并行能力仅限于文件加载过程,对后续 AST 解析、依赖收集、打包、优化代码过程均没有提升;Parallel-Webpack 提供了以多个独立进程运行 Webpack 实例的方案:
yarn add parallel-webpack -D
在 webpack.config.js 中导出多个 Webpack 配置对象:
// webpack.config.jsconst path = require('path')module.exports = [{entry: './pageA.js',output: {path: path.resolve(__dirname, './dist'),filename: 'pageA.bundle.js'}},{entry: 'pageB.js',output: {path: './dist',filename: 'pageB.js'}}]
parallel-webpack 会把上述两个构建并行执行。
worker-farm 创建复数个工作进程;node-ipc 向主进程发送结束信号;在需要执行对应多份配置的多份构建时好用,若只是单个构建配置则无需使用。
例如 MPA 等多 entry 场景,或需要同时编译出 esm、umd、amd 等多种产物的 lib 场景。
webpack5 使用 Terser 做代码压缩和混淆,自带多进程并行压缩能力。
TerserWebpackPlugin 默认开启并行压缩,也可以通过 parallel 参数(默认值为 require('os').cpus() - 1)设置并发进程数量。
yarn add -D terser-webpack-plugin
const TerserPlugin = require("terser-webpack-plugin");module.exports = {optimization: {minimize: true,minimizer: [new TerserPlugin({// options})]}}
注意:使用时
devtool的值只有source-map、inline-source-map、hidden-source-map、nosources-source-map这几个会生效因为:
eval语句会把模块代码用字符串的形式执行,而 minimizer 不处理字符串;cheap配置没有列信息,minimizer 只生成单行,所以只有一个映射;
test:默认 /\.m?js(\?.*)?$/i)
type test = string | RegExp | Array<string | RegExp>;
include:默认 undefined
type include = string | RegExp | Array<string | RegExp>;
exclude:默认 undefined
type include = string | RegExp | Array<string | RegExp>;
parallel:默认 true,os.cpus().length - 1
type parallel = boolean | number;
minify:默认 TerserPlugin.terserMinify允许覆盖默认的压缩函数,在开发/测试新的压缩函数的时候有用。
terserOptionsextractComments:默认 true是否把注释抽到一个分开的文件里,
理论上,并行确实能够提升系统运行效率,但 Node 单线程架构下,所谓的并行计算都只能依托与派生子进程执行,而创建进程这个动作本身就有不小的消耗 —— 大约 600ms,对于小型项目,构建成本可能可能很低,引入多进程技术反而导致整体成本增加,因此建议大家按实际需求斟酌使用上述多进程方案。