webpack5升级指南(附打包性能优化大全)
目录
- webpack5新特性
- 升级到webpack5问题汇总
- 着手优化项
- 开始优化
- 使用新增cache属性,缓存进行优化
- resolve部分优化
- module 部分优化
- optimization部分
- plugin部分
- 多线程打包
- 其他优化项
- 最终结果
1. webpack5新特性
- 增加持久化存储能力,提升构建性能(核心)
- 提升算法能力来改进长期缓存(降低产物资源的缓存失效率)
- 提升 Tree Shaking 能力降低产物大小和代码生成逻辑
- 提升 web 平台的兼容性能力
- 清除了内部结构中,在 Webpack4 没有重大更新而引入一些新特性时所遗留下来的一些奇怪的 state
- 通过引入一些重大的变更为未来的一些特性做准备,使得能够长期的稳定在 Webpack5 版本上
2. 升级到webpack5
问题1:命令行env环境对象传参格式改变
原因:在webpack4中使用--env.xx xxx对webpack.config文件进行环境变量传参,在webpack5中会报如下错误
解决方案:修改--env.xx xxx 改为 --env xx=xxx可解决
问题2: 使用webpack-dev-server 报错:Cannot find module ‘webpack-cli/bin/config-yargs
解决方案:将webpack-dev-server 改写成 webpack serve 即可,或者修改webpack-cli
版本改为3.x也可解决
问题3: 使用babel-loader?cacheDirectory报错
解决方案:
- 使用Webpack5新增的cache
- 使用Happypack打包cache
问题4:无法使用hard-source-webpack-plugin
插件
使用插件会报 TypeError: Cannot read property 'tap' of undefined
解决方案:webpack5废弃了hard-source-webpack-plugin
使用,可以使用webpack新增的cache属性替代
问题5:Conflicting values for 'process.env.NODE_ENV'
报错
使用new webpack.DefinePlugin 定义NODE_ENV报错
问题6: Can't resolve '../../core-js/object/define-property
报错
解决方案:需增module中增加一条rules解决。
module: {
rules: [{
test: /\.m?js/, // fix:issue: https://github.com/webpack/webpack/issues/11467
resolve: {
fullySpecified: false,
},
...
]},
},
问题7:merge is not a function
报错
解决方案:需要将const { merge } = require('webpack-merge')
改为const merge = require('webpack-merge')
3. 着手优化项
- webpack作为前端主流打包工具,配置项错综复杂,刚接手优化就像以下场景,不知道从何下手:
可以从以下几个思路进行优化:
思路1:
先确定哪些可以进行优化模块,webpack主要配置(entry、output、resolve、module、performance、externals、module、plugins,其他)进行优化
思路2:
使用包体积检测工具webpack-bundle-analyzer
分析包大小,着手优化
思路3:
使用打包速度及各个模块检测插件speed-measure-webpack-plugin
,分析着手优化
4. 开始优化
使用新增cache
属性,缓存进行优化(推荐)
-
在webpack5之前,一般会使用
cache-loader
将编译结构写入磁盘缓存,或者使用babel-loader?cacheDirectory=true
,设置babel编译的结果写进磁盘缓存。 -
webpack5新增的
cache
属性,会默认开启磁盘缓存,默认将编译结果缓存在node_modules/.cache/webpack
目录下。 -
当设置
cache.type: "filesystem"
时,webpack 会在内部以分层方式启用文件系统缓存和内存缓存。 从缓存读取时,会先查看内存缓存,如果内存缓存未找到,则降级到文件系统缓存。 写入缓存将同时写入内存缓存和文件系统缓存。 -
文件系统缓存不会直接将对磁盘写入的请求进行序列化。它将等到编译过程完成且编译器处于空闲状态才会执行。 如此处理的原因是序列化和磁盘写入会占用资源,并且我们不想额外延迟编译过程。
resolve部分优化:
externals
(推荐):对第三方包进行公共包CDN引用,降低包大小
// 在index.html添加react及jquery的cdn地址,配置外部cdn引用
module.exports = {
...
externals: {
react: 'React',
jquery: 'jQuery'
}
...
};
resolve.alias
:使用别名缩短引用模块路径,降低文件解析成本。(webpack使用enhanced-resolve解析文件路径,如果是相对路径,会先进行文件路径拼接,再进行模块引用,使用alias将不会进行文件路径拼接,并且引用后直接进行缓存:webpack.docschina.org/concepts/mo…)
module.exports = {
resolve: {
alias: {
'@': path.resolve('src'), // @ 代表 src 路径
},
}
}
resolve.mainFields
:减少第三方模块搜索步骤。webpack.target
默认为browserslist
和web
,当target
为web
或者webworker
时,值是["browser", "module", "main"]
,这就会导致不必要的检索。由于大多数第三方模块都采用main
字段去描述入口文件的位置, 可以直接设置为:
module.exports = {
resolve: {
// 只采用 main 字段作为入口文件描述字段,以减少搜索步骤
mainFields: ['main'],
},
};
- 合理配置
resolve.extensions
检索文件类型- 列表值尽量少
- 频率高的文件类型的后缀写在前面
- 源码中的导入语句尽可能的写上文件后缀,如
require(./data)
要写成require(./data.json)
module.exports = {
...
resolve:{
extensions: ['.js', '.jsx'],
}
}
module 部分优化
include
和exclude
:排除不需要处理loader文件(exclude优先include)cache-loader
(推荐):对loader解析过的文件进行缓存,默认保存在node_modueles/.cache/cache-loader
目录下(与cache
结合使用减少10%左右速度)
rules: [
{
test: /.ext$/,
use: ['cache-loader', ...loaders],
include: path.resolve('src'),
},
],
- 设置
noPrase
(不推荐):与external
类似,但无法共存。主要作用模块跳过编译环节,区别在于是否需要提取到cdn进行异步加载
module.exports = {
module:{ noParse:[/jquery|chartjs/, /react.min.js$/] }
}
optimization部分
- 去除
uglifyjs-webpack-plugin
改用terser-webpack-plugin
做代码压缩(uglifyjs-webpack-plugin
社区已失去维护,都是2年前的代码,虽然也已支持多进程,但实测效果明显不如terser-webpack-plugin
)
module.exports = {
...
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true,
}),
],
},
...
};
optimize-css-assets-webpack-plugin
(推荐):对进行css压缩
module.exports = {
...
optimization: {
minimize: true,
minimizer: [
new OptimizeCSSAssetsPlugin({
assetNameRegExp: /\.css$/,
safe: true,
cache: true,
parallel: true,
discardComments: {
removeAll: true,
},
}),
],
},
...
};
splitChunks
代码分割 (推荐): 主要作用是提取公共代码,防止代码被重复打包,拆分过大的js文件,合并零散的js文件)
module.exports = {
...
optimization: {
...
splitChunks: {
chunks: "all", //指定打包同步加载还是异步加载
minSize: 30000, //构建出来的chunk大于30000才会被分割
minRemainingSize: 0,
maxSize: 0, //会尝试根据这个大小进行代码分割
minChunks: 1, //制定用了几次才进行代码分割
maxAsyncRequests: 6,
maxInitialRequests: 4,
automaticNameDelimiter: "~", //文件生成的连接符
cacheGroups: {
defaultVendors: { test: /[\\/]node_modules[\\/]/, //符合组的要求就给构建venders
priority: -10, //优先级用来判断打包到哪个里面去
filename: "vendors", //指定chunks名称 },
default: {
minChunks: 2, //被引用两次就提取出来
priority: -20,
reuseExistingChunk: true, //检查之前是否被引用过有的话就不被打包了
},
},
}
...
}
runtimeChunk
:创建一个额外的文件或chunk,减少 entry chunk 体积,提高性能。
module.exports = {
```
optimization: {
runtimeChunk: true
}
}
Plugin部分
- eslint-webpack-plugin:eslint-loader替代方案,可以配置自动fix,和多核编译
const ESLintPlugin = require('eslint-webpack-plugin');
module.exports = {
// ...
plugins: [new ESLintPlugin()],
// ...
};
mini-css-extract-plugin
(推荐) :抽离css文件,可用于上传cdn- DllPlugin (不推荐):主要作用还是缓存, webpack5推荐使用
cache
(把网页依赖的基础模块抽离出来打包到dll文件中,当需要导入的模块存在于某个dll中时,这个模块不再被打包,而是去dll中获取) - Hardsourcewebpackplugin (webpack5报错,推荐改用
cache
) :issue: github.com/mzgoddard/h…
多线程打包
-
使用Happypack:打包构建时,Happypack会创建一个线程池,将构建任务模块进行拆分及分配线程,这些线程会各自去处理其中的模块以及它的依赖。处理完成之后会有一个通信的过程,会将处理好的资源传输给HappyPack的一个主进程,完成整个的一个构建过程。
- 使用方法:
-
id: 在配置文件中设置的与 loader 关联的 id 首先会设置到实例上,为了后续 loader 与 plugin 能进行一对一匹配
-
name: 标识插件类型为
HappyPack
,方便快速在 loader 中定位对应 plugin,同时也可以避免其他插件中存在 id 属性引起错误的风险
-
const happyThreadPool = HappyPack.ThreadPool({ size: 3 });
module: {
rules: [{
test: /\.js[x]?$/,
use: [ 'happypack/loader?id=jsx']
}]
}
plugins: [
new HappyPack({
id: 'jsx',
threadPool: happyThreadPool,
loaders: [
{
loader: 'babel-loader',
},
verbose: true,
],
...
}),
- 使用thread-loader
- 注意:thread-loader不可以和 mini-css-extract-plugin 结合使用
- 原理:与HappyPack类似,每次webpack解析一个模块,thread-loader会将它及它的依赖分配给worker进程中
- 使用:
rules: [
{
test: /\.js[x]?$/, //对所有js后缀的文件进行编译
include: path.resolve('src'), //表示在src目录下的.js文件都要进行一下使用的loader
use: [ 'babel-loader', {
loader: 'thread-loader',
options: { workers: 3, },
}, // 'happypack/loader', ]
}
]
其他优化项
- 使用
mode
属性 - JS-Tree-Shaking: package.json设置side-effect:false,删除死代码(效果不理想,开发过程eslint本身会提示,无需设置)
purgecss-webpack-plugin
: 进行CSS文件进行Tree-Shaking (推荐,css文件可能会有大幅降低)- Module Federation:webpack5新增的微前端解决方案,公用npm,及发布消费组件代码(示例:webpack.docschina.org/concepts/mo…
- esbuild-loader: 相比babel-loader速度更快
5. 最终结果
- 优化前打包速度:
- 优化前打包体积:
- 优化后打包速度:
- 优化后打包体积:
转载自:https://juejin.cn/post/6997227418113032200