有多个同事反应公司打包脚本本地开发时非常慢, 总是卡在18%, 我debug了一下确实找到了个之前没被发现的问题.
而如何知道webpack编译过程卡在了哪里就成了问题.
等本地开发环境可用, 已经花了75秒.
从webpack进度条看, 只能知道卡了, 而不知道哪个地方卡了, 所以也无从优化.
使用measure-webpack-plugin
使用measure-webpack-plugin插件很容易, 可以不传任何参数, 调用方式也在插件首页介绍了.
无论在本地开发或是打包, 插件都会产生/measureResult.html文件, 访问他就可以看到编译速度统计.
使用了插件后打开localhost//measureResult.html后看到:
本地开发时antd.less可能因为webpack-chain的社区版本(支持webpack5)有bug, 导致修改参数的行为让less-loader加载了2次, loader运行时间28秒.
于是我修改了webpack配置的语法, 并重新运行dev脚本.
可以看到现在antd.less的loader已经减少了一个, 耗时也减少了:
antd.less的耗时从28秒比到了1秒多. build总耗时从36秒减少到了10秒.
我的项目使用插件进行分析后, 还发现了一些别的问题:
- cjs三方包的引用导致了依赖的antd/icons不能被tree-shaking而全部引入了.
- dev时候引入了polyfill, 是没有必要的.
当然这些对你的项目都不通用, 你可以自己运行插件来看看自己项目的情况. 要注意在大多数情况下, 打包和本地开发是不同的.
插件原理
从插件源码中看, tap了以下hook, 并维护了变量来记录时间:
-
make.
-
seal.
-
beginIdle. 到这里, webpack的编译流程已经结束.
在都执行完后, 才会执行processAssetshook, 在这里输出内容.
另外, make流程中, 每个module也有自己的生命周期, 做了记录可以分析build里每个步骤占用的时间.
-
buildModule: 开始module的build.
-
beforeLoaders: 执行loader前, 这步离上一步的执行时间很短.
-
beforeParse: 执行完loader, 这步离上一步的时间差就是module运行loader的时间.
-
beforeSnapshot: 执行完parse, parse是从ast里找出依赖的过程, 速度和module的复杂程度有关.
-
succeedModule: module的build完成.
开发中的难点
一开始是参考了webpack的progress插件, 比较简单, 具体的可以看我之前的文章.
而发现这些生命周期并不能满足我的需求. 于是自己探索了一些生命周期, 记录在这个地址.
在测试过程中, 发现了很多生命周期之间的速度是很快的, 在大多模块都几乎是0, 所以就精简留了几个重要的.
而进入make环节, 就比较特殊, 很多操作是进入队列并行的.
尝试设置了compiler.options.parallelism = 1来控制队列长度. 但还存在问题:
-
进入队列的生命周期还是无效的, 因为队列长度1也只能控制不并行, 入队时间还是会挤在一起.
-
在分析依赖以后, 队列会被加长, 因为有队列嵌套, 所以必须加长才能继续执行.
顺便提一下, parallelism参数是可以通过webpack.config.js传入生效的, 只是文档里没写.
另外我发现, 在每个module的build流程之间, 会消耗很多时间, 经过2天努力也没找到发生在哪里, 那暂时就认为面对复杂的系统, 从ast里找出依赖会比较花时间.