Webpack5升级踩坑实录 打包性能优化的那些事儿
这次打包慢到让人怀疑人生
上个月重构老项目,Webpack升级到5,结果构建速度慢得离谱。本地开发热更新要等十几秒,生产环境打包更是要两分钟起步。同事都快疯了,每天光等打包就得浪费好几个小时。
折腾了半天,发现主要问题是依赖包太多,还有各种loader处理大量文件。后来试了下几种优化方案,现在开发热更新基本1-2秒,生产打包也能控制在30秒内。
缓存配置搞起来
先说缓存,这个是最立竿见影的。Webpack5自带持久化缓存,配置起来也不复杂:
module.exports = {
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename],
},
},
// 其他配置...
}
这里我踩了个坑,一开始忘了加buildDependencies,每次修改webpack.config.js后缓存就没用了。加了之后配置文件变了才会重新生成缓存,不然就一直用旧的。
还有就是给模块加name,这样路径变化不影响缓存:
module.exports = {
optimization: {
moduleIds: 'deterministic',
chunkIds: 'deterministic',
}
}
这两个设置能确保模块ID稳定,避免因为路径变化导致缓存失效。
externals把大包扔出去
React、Vue这些框架用CDN引入,不要打进去。我们项目里React全家桶加上lodash这些工具库,打包出来几十MB,简直是噩梦:
module.exports = {
externals: {
'react': 'React',
'react-dom': 'ReactDOM',
'lodash': '_'
}
}
对应的HTML里加上CDN链接:
<script src="https://cdn.jsdelivr.net/npm/react@18/umd/react.production.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.production.min.js"></script>
这样处理后包体积一下小了一半多。不过要注意版本兼容性,线上环境CDN可能会抽风,最好自己搭个私有CDN。
babel-loader缓存别忘了
babel编译很耗时,开启缓存能提升不少:
module.exports = {
module: {
rules: [
{
test: /.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true,
cacheCompression: false,
}
}
}
]
}
}
cacheCompression设为false可以减少压缩时间,反正本地开发磁盘够用。这里踩过坑,之前设了true反而更慢。
splitChunks按需切割
代码分割这块也得仔细调,不能用默认配置:
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\/]node_modules[\/]/,
name: 'vendors',
chunks: 'all',
priority: 10
},
common: {
name: 'common',
minChunks: 2,
chunks: 'all',
priority: 5,
enforce: true
}
}
}
}
}
vendor专门提取第三方包,common提取公共代码。enforce强制分割,不然可能分不了。priority数字越大优先级越高,这样保证第三方包先被提取。
IgnorePlugin忽略不需要的包
moment.js这种包自带各种语言包,但我们只用中文,用IgnorePlugin排除掉:
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.IgnorePlugin({
resourceRegExp: /^./locale$/,
contextRegExp: /moment$/
})
]
}
这样处理后包体积能小几MB,虽然现在都推荐用dayjs替代moment,但老项目改不动只能这么凑合。
terser并行压缩
生产环境压缩代码时开启多进程:
module.exports = {
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true,
terserOptions: {
compress: {
drop_console: true,
}
}
})
]
}
}
parallel设为true会自动检测CPU核心数,充分利用多核优势。drop_console去掉console.log,在生产环境很有必要。
devtool模式要选对
开发环境的source map设置也有讲究,速度和调试体验需要平衡:
// 开发环境
module.exports = {
devtool: 'eval-cheap-module-source-map'
}
// 生产环境
module.exports = {
devtool: false // 或者 'source-map'
}
开发用eval-cheap-module-source-map构建快,出错能定位到源码。生产环境要么关掉要么用source-map单独生成文件。
alias加快模块查找
长路径引用改成短别名,也能提升一点速度:
module.exports = {
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
'@components': path.resolve(__dirname, 'src/components'),
'@utils': path.resolve(__dirname, 'src/utils')
},
extensions: ['.js', '.jsx', '.ts', '.tsx']
}
}
这样import ‘@components/Header’比相对路径清晰多了,查找也更快。
DllPlugin预编译不变模块
对于那些基本不变的第三方包,可以用DllPlugin提前编译好:
// webpack.dll.config.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
vendor: ['react', 'react-dom', 'lodash']
},
output: {
path: path.join(__dirname, 'dll'),
filename: '[name].dll.js',
library: '[name]_[hash]'
},
plugins: [
new webpack.DllPlugin({
path: path.join(__dirname, 'dll', '[name]-manifest.json'),
name: '[name]_[hash]'
})
]
}
然后在主配置里引用:
plugins: [
new webpack.DllReferencePlugin({
manifest: require('./dll/vendor-manifest.json')
})
]
不过现在Webpack5缓存已经很好用了,DllPlugin维护成本高,一般情况下不太需要。
最后的整合配置
把所有优化点放在一起就是:
const path = require('path');
const webpack = require('webpack');
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
mode: 'production',
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename],
},
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
},
extensions: ['.js', '.jsx']
},
externals: {
'react': 'React',
'react-dom': 'ReactDOM'
},
optimization: {
moduleIds: 'deterministic',
chunkIds: 'deterministic',
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\/]node_modules[\/]/,
name: 'vendors',
chunks: 'all',
priority: 10
}
}
},
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true
})
]
},
module: {
rules: [
{
test: /.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true
}
}
}
]
},
plugins: [
new webpack.IgnorePlugin({
resourceRegExp: /^./locale$/,
contextRegExp: /moment$/
})
]
}
这样配置下来,打包速度确实提升明显。不过还是要说,不同项目的优化策略会有差异,这套配置在我们项目里效果不错,仅供参考。
以上是我踩坑后的总结,如果你有更好的方案欢迎评论区交流。

暂无评论