版本: webpack@^5.9.0
CSS编译优化
先说css的配置,因为rules
中有一个典型的错误配置习惯,在某些视频教程中经常出现。
举个栗子:
{
test: [/\.scss$/, /\.css$/, /\.less$/],
exclude: /node_modules/,
use: [
'style-loader',
'css-loader',
'sass-loader',
],
},
首先简单了解下 loader 的功能
style-loader
生成style标签,并将css代码注入css-loader
因为当前普遍做法是将css当成模块导入到js里面的,css-loader
就是为了识别处理css模块sass-loader
编译sass代码为css
问题:在上述webpack配置代码中,会将后缀名为.css、.scss、.less的文件经历三重loader的编译。这就会导致css、less也会走 sass-loader
的编译,这会浪费许多时间,特别是当你在项目中 import '/antd/antd.css'
引入了全量的第三方无需编译css代码时。
正确做法应该进行匹配规则拆分:
const config = {
...
rules:[
{
test: [/\.css$/i],
include: path.resolve(__dirname, '../node_modules'),
use: [
'style-loader',
'css-loader',
],
},
{
test: [/\.s[ac]ss$/i],
exclude: path.resolve(__dirname, '../node_modules'),
use: [
'style-loader',
'css-loader',
'sass-loader',
],
},
]
}
如果要独立打包css文件,我们还可以区分一下开发与生产的配置:
const isDev = appConfig.env.isDev
const isPro = appConfig.env.isPro
const config = {
...
rules:[
{
test: [/\.css$/i],
include: path.resolve(__dirname, '../node_modules'),
use: [
isPro && MiniCssExtractPlugin.loader,
isDev && 'style-loader',
'css-loader',
].filter(Boolean),
},
{
test: [/\.s[ac]ss$/i],
exclude: path.resolve(__dirname, '../node_modules'),
use: [
isPro && MiniCssExtractPlugin.loader,
isDev && 'style-loader',
'css-loader',
'sass-loader',
].filter(Boolean),
},
]
}
JS编译优化
js的配置参考上述css配置问题,也可能需要类似的调整。这里就不再重复说明。
JS babel编译优化
主要是开启babel的 cacheDirectory 缓存,开启后第一次编译速度没有变化,第二次编译就会读取上次的编译缓存,提升编译效率。
这里有个提醒,如果你也使用了
react-refresh/babel
,要将babel.config.js中的react-refresh/babel
配置转移到webpack中进行配置,否则生产编译后浏览器会报错,配置如下:
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')
const isDev = appConfig.env.isDev
const isPro = appConfig.env.isPro
const config = {
plugins:[
...
isDev && new ReactRefreshWebpackPlugin()
].filter(Boolean),
module:{
rules:[
{
test: /\.(jsx|js)$/,
exclude: [path.join(__dirname, '../node_modules')],
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
plugins:[
isDev && require.resolve('react-refresh/babel'),
].filter(Boolean),
},
},
],
},
]
}
}
开启webpack缓存
除了babel提供了 cacheDirectory 缓存配置,webpack也有自身的缓存机制,配置 cache 即可启用
const webpackConfig = {
cache: true
}
ESLint优化闭坑
eslint可以通过添加 exclude 指定不需要检测的文件。同时eslint启动时会进行一次全量的代码语法检测,如果是较为复杂的项目,文件代码特别多,启动耗时就非常旧。
在我做webpack优化时,发现耗时最长的就是
eslint-webpack-plugin
了,我真是觉得这很坑。
看取舍吧,想要提升启动速度,只需添加 lintDirtyModulesOnly
配置。该配置只针对有修改的文件代码进行语法检查
const isDev = appConfig.env.isDev
const isPro = appConfig.env.isPro
const config = {
plugins:[
...
isDev && new ESLintPlugin({
exclude: ['node_modules', 'build', 'configs', 'server'],
lintDirtyModulesOnly: true,
})
].filter(Boolean),
}
生产optimization splitChunks配置
这个不应该放着讲,顺便提一下吧
webpack的optimization配置,能将代码进行归类拆分,根据代码分类编译出多个js文件。 先上配置:
webpackConfig.optimization = {
...
splitChunks: {
chunks: 'initial',
cacheGroups: {
common: {
test: (module) => {
const result = /[\\/]node_modules[\\/]/.test(module.context)
return result
},
name: 'common',
priority: 10,
},
reactBase: {
test: (module) => {
// const suffix = path.basename(module.context)
const curPath = path.parse(module.context)
const cwd = process.cwd()
const suffix = curPath.dir.replace(cwd, '')
const result = /react|redux|prop-types/.test(suffix)
return result
},
name: 'reactBase',
priority: 30,
},
},
},
}
入上述配置,根据test进行规则分类匹配划分为公共代码common和react相关的reactBase代码,其余代码和以前一样编译到app.bundle.js中。
这么做的好处: 一般来说,react等包是不进行升级而common公共部分的代码也不是每次更新生产都会有变化,这些模块编译为独立的js代码后。浏览器访问后会进行缓存,生产更新后只需获取最新的 app.bundle.js 资源,而reactBase.js、common.js则直接获取缓存即可。
编译耗时分析工具 speed-measure-webpack-plugin
speed-measure-webpack-plugin
可以对webpack的启动耗时进行分析,有了以下耗时分析数据,就可以根据loader、plugin的耗时有针对性的进行配置优化尝试
- 可以看到该项目首次编译启动耗时 1min 32s
- 第二次启动获取了缓存,速度明显提升
webpack-bundle-analyzer
如果针对生产编译,还可以使用 webpack-bundle-analyzer
进行编译包分析,针对引用包进行体积优化。
进行splitChunks后觉得文件体积还是太大,可以开启gzip压缩功能。只需服务端开启gzip的访问资源。
使用 compression-webpack-plugin
webpack插件即可编译出压缩代码,下图可以看到 reactBase.js和reactBase.gz文件大小相差将近 10倍