前言
本文主要介绍一些优化 Webpack 配置的方法,目的是让打包的速度更快,输出的资源更小。本文将会包含以下内容:
多线程打包与 happyPack
缩小打包的作用域
动态链接库思想与 DLLPlugin
死代码检测与 tree shaking
HappyPack
HappyPack 是一个通过多线程来提升 Webpack 打包速度的工具。
工作原理
在打包过程当中最耗时的工作就是使用 loader 将各种资源进行转译处理,最常见的包括使用 babel-loader 转译 ES6+ 语法,和 ts-loader 转译 TypeScript。转译的工作流程如下:
1、从配置中获取打包入口
2、匹配 loader 规则,并对入口模块进行转译
3、对转译后的模块进行依赖查找(如 a.js 中加载了 b.js 和 c.js)
4、对新找到的模块重复进行第 2、3 步骤,直到没有新的依赖模块。
由于运行在 Node.js 上的 Webpack 是单线程模型的,所以 Webpack 需要一步步地获取更深层级的资源,然后逐个进行串行转译,所以在模块多、依赖复杂的情况下,Webpack 构建速度将会变得很慢。HappyPack 恰好能解决此问题。
核心特性
可以开启多个线程,并行地对不同模块进行转译,这样就可以充分利用本地的计算资源来提升打包速度。
适合场景
HappyPack 适用于那些转译任务比较重的工程,例如 babel-loader、ts-loader,对于其他的如 sass-loader、less-loader 本身消耗时间并不太多的工程效果一般。
单个 loader 的优化
在实际使用过程中,要用 HappyPack 提供的 loader 来替换原有 loader,并将原有的那个 loader 通过 HappyPack 插件传进去。
使用 HappyPack 之前
1 | module.exports = { |
使用 HappyPack
1 |
|
由上面的结果可以大致发现,用 HappyPack 确实能够提升打包速度。
多个 loader 的优化
在使用 HappyLoader 优化多个 loader 时,需要为每一个 loader 配置一个 id,否则 HappyPack 无法知道 rules 与 plugins 如何一一对应。
1 | module.exports = { |
完整示例
缩小打包作用域
从宏观角度来讲,提升性能的方法有两种:
- 增加资源
就是使用更多 CPU 和内存,用更多的计算能力来缩短执行任务的时间。例如上文提到的 HappyPack
- 缩小范围
针对任务本身,比如去掉冗余的流程,尽量不要作重复性的工作等。
exclude 和 include
exclude 和 include 是用来排除或包含指定目录下的模块,可接收正则表达式或者字符串(文件绝对路径),以及由他们组成的数组。
1 | module.exports = { |
exclude
所有被正则表达式所匹配到的模块都排除在该规则之外
include
该规则只针对被正则表达式匹配到的模块生效
Note
exclude 和 include 同时生效时,exclude 的优先级更高
noParse
有些库我们是希望 Webpack 完全不要去进行解析的,即不希望应用任何 loader 规则,库的内部也不会有对其他模块的依赖,那么这时可以使用 noParse 进行忽略。
1 | module.exports = { |
1 | module.exports = { |
IgnorePlugin
exclude 和 include 是确定 loader 的规则范围,noparse 是不去解析但仍会打包到 bundle 中。而 IgnorePlugin 则可以完全排除一些模块,被排除的模块即便被引用了也不会被打包进资源文件中。
1 | plugins: [ |
一些由库产生的额外资源我们用不到但又无法去掉,因为引用的语句处于库文件内部。因此可以使用 IgnorePlugin 来排除一些库相关文件。例如,Moment.js 是一个日期时间处理相关的库,为了做本地化它会加载很多语言包,对于我们来说,一般不会用到其他地区的语言包,但他们会占用很多体积,这时就可以像上面这样用 IgnorePlugin 来去掉。
Cache
有些 loader 会有一个 cache 配置项,用来在编译代码后同时保存一份缓存,在执行下一次编译前会先检查源文件是否有变化,如果没有就直接采用缓存。
动态链接库与 DLLPlugin
动态链接库
早期 windows 系统由于受限于当时计算机内存空间较小的问题而出现的一种内存优化方法
当一段相同的子程序被多个程序调用时,为了减少内存消耗,可以将这段子程序存储为一个可执行文件,当多个程序调用时只在内存中生成和使用一个实例
DLLPlugin
DLLPlugin 借鉴动态链接库的思路,对于第三方库或者一些不常变化的模块,可以将它们预先编译和打包,然后在项目实际构建过程中直接取用即可。
DLLPlugin 和 Code Spliting 的区别
两者类似都可以用来提取公共模块,但本质上有区别
Code Spliting 设置一些特定的规则并在打包过程中根据这些规则提取模块
DLLPlugin 则是将 vendor 完全拆出来,有自己的一整套 Webpack 配置并独立打包,在实际工程构建时就不用再对它进行任何处理,直接取用即可。因此,理论上来讲,DLLPlugin 打包速度更快,但相应配置增加,资源管理复杂度增加。
vendor 配置
首先需要为动态链接库单独创建一个 webpack 配置文件,比如文件名为 webpack.vendor.config.js
1 | const path = require("path"); |
vendor 打包
在 package.json 中新增配置,利用 dll 命令进行打包
1 | "scripts": { |
链接到业务代码
使用 DLLReferencePlugin 插件将 vendor 链接到项目中。它起到一个索引和链接的作用。
1 | // webpack.config.js |
潜在的问题
当我们对 vendor 进行操作时,本来 vendor 中不应该受到影响的模块却改变了他们的 id。导致 vendor 变化需要用户重新下载左右模块资源
解决办法
在打包 vendor 时添加上 HashedModuleIdsPlugin
1 | const path = require("path"); |
完整示例
tree shaking
tree shaking 可以在打包过程中帮助我们检查工程中没有被引用过的模块,这部分代码将永远无法被执行到,因此也被称为“死代码”。Webpack 会对这部分代码进行标记,并在资源压缩时将它们从最终的 bundle 中去掉。
Note
tree shaking 本身只是为死代码添加标记,真正去除死代码是通过压缩工具来进行的。例如上一篇文章中我们提到过的 terser-webpack-plugin。在 webpack4 之后的版本中,将 mode 标记为 production 也可以达到相同的效果。
完整示例
总结
本文介绍了加快打包速度,减小资源体积的一些方法。对于一些对性能要求很高的项目来说这些方法可以起到一定的效果。
最后需要说明的一点是,每一种优化策略都有其使用场景,并不是任何一个点放在一切项目中都有效。当我们发现性能的问题时,还是要根据现有的情况分析出瓶颈在哪里,然后对症下药。
参考文献
1、《Webpack 实战入门、进阶与调优)》- 居玉皓
3、Webpack
5、Webpack extract-text-webpack-plugin
友情提示:请尊重作者劳动成果,如需转载本博客文章请注明出处!谢谢合作!
【作者:吴林 https://super-lin0.github.io/】