这是我参与 8 月更文挑战的第 9 天,活动详情查看: 8月更文挑战
随着版本的迭代,业务的发展,代码会越来越多,这时候 webpack
的构建速度有可能就会变慢。
构建时间的长短直接关联着我们的工作效率,所以很有必要熟悉下如何提升 webpack
构建速度。
速度分析
在提升 webpack
构建速度之前,先使用 speed-measure-webpack-plugin
进行速度分析。
示例如下:
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
const webpackConfig = smp.wrap({
plugins: [new MyPlugin(), new MyOtherPlugin()],
});
可以看到每个 loader
和插件执行耗行
使用高版本的 webpack 与 Node.js
这也是官网建议的方案,目前 webpack
已经更新到 5 了,但由于更新到 webpack5
,一系列的 plugin
与 loader
都要做相应的更新,所以如果是新项目建议用 webpack5
,老项目就使用 webpack4
,至于 4
以下就不建议了。
使用多进程/ 多实例构建
使用 HappyPack 解析资源
原理:每次 webapck
解析一个模块,HappyPack
会将它及它的依赖分配给 worker
线程中
const HappyPack = require('happypack');
exports.module = {
rules: [
{
test: /.js$/,
// 1) replace your original list of loaders with "happypack/loader":
// loaders: [ 'babel-loader?presets[]=es2015' ],
use: 'happypack/loader',
include: [ /* ... */ ],
exclude: [ /* ... */ ]
}
]
};
exports.plugins = [
// 2) create the plugin:
new HappyPack({
// 3) re-add the loaders you replaced above in #1:
loaders: [ 'babel-loader?presets[]=es2015' ]
})
];
这个库作者已经不维护了,webpack4 后的推荐使用 thread-loader
使用 thread-loader 解析资源
原理:每次 webpack
解析一个模块,thread-loader
会将它及它的依赖分配给 worker
线程中
module.exports = {
module: {
rules: [
{
test: /\.js$/,
include: path.resolve('src'),
use: [
'thread-loader',
// your expensive loader (e.g babel-loader)
],
},
],
},
};
多进程/多实例:并行压缩
方法一:使用 parallel-uglify-plugin 插件
import ParallelUglifyPlugin from 'webpack-parallel-uglify-plugin';
module.exports = {
plugins: [
new ParallelUglifyPlugin({
uglifyJS: {
output: {
beautify: false,
comments: false
},
comperss: {
// 是否在UglifyJS删除没有用到的代码时输出警告信息,默认为输出,可以设置为false关闭这些作用
warnings: false,
drop_console: true,
collapse_vars: true,
reduce_vars: true
}
},
}),
],
};
方法二:uglifyjs-webpack-plugin 开启 paralle
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
plugins: [
new UglifyJsPlugin({
uglifyOptions: {
warnings: false,
parse: {},
compress: {},
mangle: true,
output: null,
toplevel: false,
nameCache: null,
ie8: false,
keep_fnames: false
},
parallel: true
})
]
}
方法三:terser-webpack-plugin 开启 parallel 参数(推荐使用)
module.exports = {
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true,
}),
],
},
};
让 webpack 少做点事
webpack
少做点事,自然构建速度就提升了
减少 resolve 的解析
通过精简 resolve
配置,让 webpack
在查询模块路径时尽可能快速地定位到需要的模块,不做额外的查询工作,那么 webpack
的构建速度也会快一些。
比如:
resolve: {
modules: [
path.resolve(__dirname, 'node_modules'), // 使用绝对路径指定 node_modules,不做过多查询
],
// 删除不必要的后缀自动补全,少了文件后缀的自动匹配,即减少了文件路径查询的工作
// 其他文件可以在编码时指定后缀,如 import('./index.scss')
extensions: [".js"],
// 避免新增默认文件,编码时使用详细的文件路径,代码会更容易解读,也有益于提高构建速度
mainFiles: ['index'],
}
在编码时,如果是使用我们自己本地的代码模块,尽可能编写完整的路径,避免使用目录名,如:import './lib/slider/index.js'
,这样的代码既清晰易懂,webpack
也不用去多次查询来确定使用哪个文件,一步到位。
把 loader
应用的文件范围缩小
在使用 loader
的时候,尽可能把 loader
应用的文件范围缩小,只在最少数必须的代码模块中去使用必要的 loader
,例如 node_modules
目录下的其他依赖类库文件,基本就是直接编译好可用的代码,无须再经过 loader
处理了:
rules: [
{
test: /\.jsx?/,
include: [
path.resolve(__dirname, 'src'),
// 限定只在 src 目录下的 js/jsx 文件需要经 babel-loader 处理
// 通常我们需要 loader 处理的文件都是存放在 src 目录
],
use: 'babel-loader',
},
// ...
],
如上边这个例子,如果没有配置 include
,所有的外部依赖模块都经过 Babel
处理的话,构建速度也是会收很大影响的。
使用 DLLPlugin
- 需求:将
react
react-dom
redux
等基础包和业务基础包打成一个文件 - 方法: 使用
DLLPlugin
进行分包,DllReferencePlugin
对manifest.json
引用
- 需要新建个构建配置文件,比如是 webpack.dll.config.js
module.exports = {
context: process.cwd(),
entry: {
library: [
'react',
'react-dom',
'redux',
'react-redux'
],
// 如果有多个,直接在增加一个
}
output: {
// 这里打包后的文字是 library.dll.js
filename: '[name].dll.js',
path: path.resolve(__dirname, 'build/libarary'),
// 暴露的库的名字
library: '[name]'
},
plugins: [
// 指定包存放的位置
new webpack.DllPlugin({
name: '[name]',
// 描述动态链接库 mainfest 文件输出时的文件名称
// path: 'manifest.json'
path: path.resolve(__dirname, 'build/libarary/[name].json')
})
]
}
- 在
pageage.json
scripts
中增加命令
"scripts": {
"dll": "webpack --config webpack.dll.js"
}
- 执行
npm run dll
分包 - 在
webpack.config.js
引入
module.exports = {
plugins: [
new webpack.DllReferencePlugin({
// 刚打包后的json文件地址
// manifest: require('xxx.json'),
manifest: require('./build/library/libary.json'),
// 指定需要用到的 manifest 文件,
// webpack 会根据这个 manifest 文件的信息,分析出哪些模块无需打包,直接从另外的文件暴露出来的内容中获取
})
// 如果引入多个,使用多次此插件
]
}
作用和 optimization.splitChunks
很相似,但是有个区别,DLLPlugin
构建出来的内容无需每次都重新构建,后续应用代码部分变更时,你不用再执行配置为 webpack.dll.config.js
这一部分的构建,沿用原本的构建结果即可,所以相比 optimization.splitChunks
,使用 DLLPlugin
时,构建速度是会有显著提高的。
但是很显然,DLLPlugin
的配置要麻烦得多,并且需要关心你公共部分代码的变化,当你的公共部分代码的内容变更时,要重新去执行 webpack.dll.config.js
这一部分的构建,不然沿用的依旧是旧的构建结果,使用上并不如 optimization.splitChunks
来得方便。这是一种取舍,根据项目的实际情况采用合适的做法。
利用缓存提升构建速度
通过开启缓存来提升二次构建速度,开启缓存后 node_modules
下会有一个 .cache
目录
babel-loader 开启缓存
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true
}
}
]
terser-webpack-plugin 开启缓存
module.exports = {
optimization: {
minimizer: [
new Terserplugin({
parallel: true,
cache: true
})
]
}
}
使用 hard-source-webpack-plugin 开启缓存
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
plugins: [
new HardSourceWebpackPlugin()
]