webpack学习笔记-3(打包优化)
打包优化
让打包的速度更快,输出的资源更小。从宏观角度看,提升性能的方法无非两种:
- 增加资源:使用更多CPU和内存,用更多的计算能力来缩短执行任务的时间
- 缩小范围:针对任务本身,去掉冗余的流程,尽量不做重复性的工作等
HapplyPack
HapplyPack是一个通过多线程来提升webpack打包速度的工具。
打包中有一项非常耗时的工作,就是使用loader将各种资源进行转译成处理。最常见的是babel转译es6、ts-loader转译typescript。转译的过程大概是这样的:
- 从配置中获取打包入口
- 匹配loader规则,对入口模块进行转译
- 对转译后的模块进行依赖查找(比如a.js中加载了b.js和c.js)
- 对新找到的模块重复进行步骤2和步骤3,直到没有新的依赖模块。
这个过程中,2、3步骤其实是一是个递归过程,webpack需要一步步获取更深层的资源,然后逐个转译。但webpack是单线程的,假如一个模块依赖好几个模块,且这些依赖模块中彼此没有任何依赖关系,却必须串行。这就是为什么编译过程会耗时久的原因。
HappyPack的核心特性就是开启多个线程,并行地对不同模块进行转译,这样可以充分利用本地的计算资源来提升打包速度。
npm i happlypack
module.exports = {
module: {
rules: [
{
test: /\.js$/,
// use: 'babel-loader',
// 只有一个loader需要happypack时
// loader: 'happypack/loader'
// 多个时,需要加上id标识
use: 'happypack/loader?id=js'
},
{
test: /\.ts$/,
// use: 'ts-loader',
use: 'happypack/loader?id=ts'
}
]
},
plugins: [
new HapplyPack({
// 当有多个loader需要使用happypack时,要添加id作为标识,如果只有一个,则不需要id
id: "js",
loaders: [
{
loader: 'babel-loader',
options: {}
}
]
}),
new HapplyPack({
id: "ts",
loaders: [
{
loader: 'ts-loader',
options: {}
}
]
})
]
}
缩小作用域范围
exclude和include:缩小loader解析范围
使用loader时,为其设置indclude和exclude来缩小作用域范围。当exclude和include规则有重叠部分时,exclude的优先级更高。
modules: {
rules: [{
test: /\.js$/,
include: /src\/scripts/,
loader: 'babel-loader'
}]
}
noParse: 不去解析但仍会打包
忽略某些文件,这些模块仍会被打包进资源文件,但webpak不会对其进行任何解析
modules: {
noParse: /lodash/
}
IgnorePlugin 排队模块中一些用不到模块
比如moment.js,为了本地化它会加载很多语言包,但对我们一般用不到其他地区语言包,但它们会占很多体积
plugins: [
new webpack.IgnorePlugin({
resourceRegExp: /^\.\/locale$/, // 匹配资源文件
contextRegExp: /moment$/ // 匹配检索目录
})
]
cache
有时候配置loader时,会有一个配置项cache,用来在编译代码后同时保存一份缓存。在执行下一次编译前,会先检查源码文件是否有变化,如果没有直接使用缓存。
动态链接库与DllPlugin
动态链接库是早期windows系统由于受限当时计算机内存空间较小问题而出现的一种同存优化方法:当一段相同子程序被多个程序调用时,为减少内存消耗,可以将这段子程序存储为一个可执行文件,当被多个程序调用时,只用在内存中生成和使用同一个实例。
DllPlugin借鉴了动态库的这种思路,对于第三方模块或一些不常变化的模块,可以将它们预先编译和打包,然后在项目实例构建过程中直接取用即可。
DllPlugin生成的还是js文件,在打包vendor时,还会附加一份vendor的模块清单,这清单将会在工程业务模块打包时起来链接和索引的作用。
DllPlugin和code splitting有点类似,都可以用来撮公共模块,但本质上有一些区别:
- code splitting:设置一些特定的规则并在打包过程中根据这些规则提取模块
- DllPlugin: 将vendor完全拆出来,有自己的一整套webpack配置并独立打包,在实际工程构建时不用再对它进行任何处理,直接取用即可。
理论上说,dllplugin会比code splitting打包速度上更胜一筹,但也相应增加了配置以及资源管理的复杂度。
tree shaking
es6 module依赖关系的构建是在代码编译时而非运行时,基于这项特性,webpack特供了tree shaking功能,它可以在打包过程中帮我们检测工程中没有被引用过的模块,也就是”死代码“。webpack会对这部分进行标记,并在资源压缩时将它们从最终的bundle中去掉。
tree shaking必须有一些前置条件:
- 必须是支持es6 module的模块,才可以使用tree shaking。如果某些npm包是使用commonjs形式导出的,那么使用tree shaking会发现bundle体积并没有减小
- 使用webpack进行依赖关系构建
如果使用了babel-loader,那么一定要禁用它的模块依赖,否则webpack接收到的都是转化过的commonjs形式的模块,无法进行tree-shaking。所以要禁用babel-loader模块依赖解析的配置:
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
presets: [
// 这里一定要加上 modules: false
[@babel/preset-env, { modules: false }]
]
}
}
]
}
]
}
}
- 标记死代码的工作,就是tree shaking来完成的,而删除死代码则是由前面压缩插件terser-webpack-plugin来完成的。在webpack4之后,只要将mode设置为production,就可以达到压缩的效果