浅析如何优化Webpack构建速度:使用可量化工具、配置include、配置缓存、配置多进程多实例thread-loader/parallel、硬件加速、noParse、IgnorePlugin、抽取公共代码、DllPlugin分包、开启webpack内置优化、babel配置优化、可视化分析针对性优化
一、量化
有时,我们以为的优化是负优化,这时如果有一个量化的指标可以看出前后对比,那将会是再好不过的一件事。
speed-measure-webpack-plugin
插件可以测量各个插件和loader
所花费的时间,使用之后,构建时,会得到类似下面这样的信息:
对比前后的信息,来确定优化的效果。speed-measure-webpack-plugin 的使用很简单,可以直接用其来包裹 Webpack
的配置
//webpack.config.js
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
const config = {
//...webpack配置
}
module.exports = smp.wrap(config);
二、exclude/include 确定loader规则范围
我们可以通过 exclude
、include
配置来确保转译尽可能少的文件。顾名思义,exclude
指定要排除的文件,include
指定要包含的文件。exclude
的优先级高于 include
,在 include
和 exclude
中使用绝对路径数组,尽量避免 exclude
,更倾向于使用 include
。
//webpack.config.js
const path = require('path');
module.exports = {
//...
module: {
rules: [
{
test: /\.js[x]?$/,
use: ['babel-loader'],
include: [path.resolve(__dirname, 'src')]
}
]
},
}
三、cache-loader
在一些性能开销较大的 loader
之前添加 cache-loader
,将结果缓存中磁盘中。默认保存在 node_modueles/.cache/cache-loader
目录下。
首先安装依赖:npm install cache-loader -D
cache-loader
的配置很简单,放在其他 loader
之前即可。修改Webpack
的配置如下:
module.exports = {
module: {
//babel-loader耗时比较长,给它配置了cache-loader
rules: [
{
test: /\.jsx?$/,
use: ['cache-loader','babel-loader']
}
]
}
}
如果你跟我一样,只打算给 babel-loader
配置 cache
的话,也可以不使用 cache-loader
,给 babel-loader
增加选项 cacheDirectory
。
cacheDirectory
:默认值为 false
。当有设置时,指定的目录将用来缓存 loader
的执行结果。之后的 Webpack
构建,将会尝试读取缓存,来避免在每次执行时,可能产生的、高性能消耗的 Babel
重新编译过程。设置空值或者 true
的话,使用默认缓存目录:node_modules/.cache/babel-loader
。开启 babel-loader
的缓存和配置 cache-loader
,我比对了下,构建时间很接近。四、多进程多实例构建:thread-loader
五、开启 JS 多进程压缩:配置 parallel true
当前 Webpack
默认使用的是 TerserWebpackPlugin
,默认就开启了多进程和缓存,构建时,你的项目中可以看到 terser
的缓存文件 node_modules/.cache/terser-webpack-plugin
。
HardSourceWebpackPlugin
为模块提供中间缓存,缓存默认的存放路径是: node_modules/.cache/hard-source
。
配置 hard-source-webpack-plugin
,首次构建时间没有太大变化,但是第二次开始,构建时间大约可以节约 80%。
如果一些第三方模块没有AMD/CommonJS规范版本,可以使用 noParse
来标识这个模块,这样 Webpack
会引入这些模块,但是不进行转化和解析,从而提升 Webpack
的构建性能 ,例如:jquery
、lodash
。
noParse 属性的值是一个正则表达式或者是一个 function
。
//webpack.config.js
module.exports = {
//...
module: {
noParse: /jquery|lodash/
}
}
八、IgnorePlugin
webpack
的内置插件,作用是忽略第三方包指定目录。例如 moment
(2.24.0版本) 会将所有本地化内容和核心功能一起打包,我们就可以使用 IgnorePlugin
在打包时忽略本地化内容。
//webpack.config.js
module.exports = {
//...
plugins: [
//忽略 moment 下的 ./locale 目录
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
]
}
在使用时,如果我们需要指定语言,那么需要我们手动的去引入语言包,例如,引入中文语言包
import moment from 'moment';
import 'moment/locale/zh-cn';// 手动引入
index.js
中只引入 moment
,打包出来的 bundle.js
大小为 263KB
,如果配置了 IgnorePlugin
,单独引入 moment/locale/zh-cn
,构建出来的包大小为 55KB
。
有些时候,如果所有的JS文件都打成一个JS文件,会导致最终生成的JS文件很大,这个时候,我们就要考虑拆分 bundles
。
DllPlugin
和 DLLReferencePlugin
可以实现拆分 bundles
,并且可以大大提升构建速度,DllPlugin
和 DLLReferencePlugin
都是 webpack
的内置模块。
我们使用 DllPlugin
将不会频繁更新的库进行编译,当这些依赖的版本没有变化时,就不需要重新编译。
十、抽离公共代码
十一、借助 webpack-bundle-analyzer 进一步优化
十二、webpack自身的优化
1、tree-shaking:如果使用ES6的import
语法,那么在生产环境下,会自动移除没有使用到的代码
2、scope hosting 作用域提升:变量提升,可以减少一些变量声明。在生产环境下,默认开启
十三、babel 配置的优化
在不配置 @babel/plugin-transform-runtime
时,babel
会使用很小的辅助函数来实现类似 _createClass
等公共方法。默认情况下,它将被注入(inject
)到需要它的每个文件中。但是这样的结果就是导致构建出来的JS体积变大。
我们也并不需要在每个 js
中注入辅助函数,因此我们可以使用 @babel/plugin-transform-runtime
,@babel/plugin-transform-runtime
是一个可以重复使用 Babel
注入的帮助程序,以节省代码大小的插件。
因此我们可以在 .babelrc
中增加 @babel/plugin-transform-runtime
的配置。
{
"presets": [],
"plugins": [
[
"@babel/plugin-transform-runtime"
]
]
}