@Dale-Lin
2022-08-28T20:06:22.000000Z
字数 4569
阅读 383
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 并行执行的任务数量,默认 20
workerNodeArgs: ['--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 development
poolRespawn: false,
// timeout for killing the worker processes when idle
// defaults to 500 (ms)
// can be set to Infinity for watching builds to keep workers alive
poolTimeout: 2000,
// number of jobs the poll distributes to the workers
// defaults to 200
// decrease of less efficient but more fair distribution
poolParallelJobs: 50,
// name of the pool
// can be used to create different pools with elsewise identical options
name: '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.js
const 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.js
const 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
允许覆盖默认的压缩函数,在开发/测试新的压缩函数的时候有用。
terserOptions
extractComments
:默认 true是否把注释抽到一个分开的文件里,
理论上,并行确实能够提升系统运行效率,但 Node 单线程架构下,所谓的并行计算都只能依托与派生子进程执行,而创建进程这个动作本身就有不小的消耗 —— 大约 600ms,对于小型项目,构建成本可能可能很低,引入多进程技术反而导致整体成本增加,因此建议大家按实际需求斟酌使用上述多进程方案。