首发于代码小屋

你是否需要webpack dll

本文中所有的测试都基于 2017款15寸MacBook Pro低配版本

关于dll的介绍已经有很多文章了,webpack性能优化都无可避免的会提到dll。在webpack 4已经进入生涯晚期,webpack 5已经发了几十个alpha版本的时间节点,我们是否还需要dll这个优化手段。

dll的作用

dll的作用是将项目中一些不常改变的依赖单独打包,webpack通过生成的manifest文件去引用对应的依赖。

优点

  • 一旦生成dll文件,只要依赖不变,dll文件就不会改变
  • dll中的依赖包只需要在文件生成的时候进行一次编译打包,以后的构建过程就可以跳过这些依赖,加快构建过程

缺点

  • 需要增加一份生成dll文件的webpack配置,并且要修改原来的配置项去引入dll文件
  • 需要多余的一次webpack构建,打破原先的项目构建流程
  • 在dll中的依赖有版本改变的时候,需要重新生成dll文件

webpack 4与dll

webpack 4相比于3增加了很多新的特性,并且根据mode增加了很多的默认配置。

其中和dll关系比较大的改变是在webpack 4中用terser来对文件进行压缩混淆,替换了原来的uglifyjs,并且默认开启了多进程和缓存。

@vue/cli在3.0的版本中不再支持dll,可能也和webpack 4在打包的时候默认开启缓存有关,因为dll说到底其实也是一种文件的缓存机制。

那我们是否不再需要dll呢?

真实的开发场景

一般的项目开发过程中,都避不开两个场景。一个是本地开发,需要运行webpack-dev-server启动一个本地服务;一个是线上发布,需要运行build命令,对项目进行打包然后进行代码发布。

项目build

对于项目打包,特别是升级到webpack 4以后,dll就显得有点不合时宜。一方面是因为terser的缓存机制已经很优秀了,另一方面是因为webpack 4中的sideEffects机制。

在webpack 4 中增加了sideEffects机制,一个包如果在package.json中声明了sideEffects为false,那么webpack会认为这个包中的各个模块是无副作用的,对于没有用到的模块就不会去加载,做到了文件级别的按需加载。对于那些已经使用了sideEffects来实现按需加载的包,我们在最终项目打包的时候可以做到按需打包,不会对这个包做全量引入。类似的还有lodash和antd这些通过babel插件来实现按需引入的包,最终打包的时候也不会全量引入整个包。

但是如果使用dll来处理这些第三方依赖的包,就会把这个包全量引入进来,最终的代码里会有很多多余的代码,最终代码的大小也会增加很多。

本地开发

webpack对于依赖的收集,会从配置的入口文件开始,去做递归的解析,最后生成一个依赖图。

在每次运行dev-server的时候,webpack都会重新去收集所有的依赖。terser只会在打包压缩的时候才会运行,所以在dev的时候是没有办法进行缓存的。babel-loader也可以进行缓存设置,但是一般的项目为了加快构建速度,都会默认不去处理node_modules里面的依赖,所以第三方的依赖包无法被编译缓存。

在这种情况下,dll的引入就会带来巨大的性能提升。使用dll对第三方依赖进行缓存,只要依赖版本不变更,那么webpac依赖解析就可以跳过所有node_modules里面的依赖文件,大大减少dev-server的启动时间。

测试

对于时间的测试,如果只是使用脚手架create一个没有什么依赖的空项目,那不会有任何意义。

为了还原真实场景的数据,这里将用一个PC的项目进行测试。主要的依赖有react全家桶、@babel/polyfill、antd和loadsh。但是项目的业务代码并不是很多,所以在某些时间上面可能会有一定的偏差。

绝大多数使用了缓存进行优化的项目,在项目第一次运行的时候都不会有效果,因为这时候的缓存文件还没有生成。

在仅使用babel-loader缓存的情况下,dev-server的启动时间如下

| 类型 | 启动时间 |
| -------- | -------- |
| 首次启动 | 22s |
| 第二次 | 17.7s |
| 第三次 | 18.1s |
| 第四次 | 17.8s |

可以看出在首次启动的时候,因为babel-loader还没有缓存,所以这时候启动时间最长。当缓存生成以后,启动时间就会有一个减少。

当启用dll以后,启动时间如下

| 类型 | 启动时间 |
| -------- | -------- |
| 首次启动 | 27.6s |
| 第二次 | 5.1s |
| 第三次 | 4.8s |
| 第四次 | 4.9s |

在首次启动的时候,花费的时间比没有dll的要多一些,其中生成dll文件的时间为19s。但是当dll文件生成以后,再次启动的时间会有巨大幅度的减少,因为webpack对于dll中的依赖不用再做任何的解析和处理。

hard-source-webpack-plugin

hard-source-webpack-plugin也提供了缓存系统,可以作为dll的代替方案。

相比于dll,它配置更简单,只需要增加一个plugin配置即可。但是在项目启动时间上,要比dll的方案慢一秒左右。

| 类型 | 启动时间 |
| -------- | -------- |
| 首次启动 | 21.8s |
| 第二次 | 6.1s |
| 第三次 | 6.3s |
| 第四次 | 6.0s |

但是在项目打包的时候,hard-source-webpack-plugin带来的提升是巨大的。缓存生成以后,build的时间从15s减少到5s。

不过hard-source-webpack-plugin停止维护快一年的时间了,这可能也是一个需要考虑的问题;另一个需要考虑的问题是它的缓存文件非常大,会占用非常多的硬盘空间。

dll 加 hard-source-webpack-plugin

在同时开启dll和hard-source-webpack-plugin的时候,当缓存同时生效的情况下,项目启动只需要两秒左右的时间,相比于开启单个功能,时间分别减少了一半和三分之二。

| 类型 | 启动时间 |
| -------- | -------- |
| 首次启动 | 27.3s |
| 第二次 | 2.1s |
| 第三次 | 2.1s |
| 第四次 | 2.2s |

webpack 5带来的可能性

webpack 5的alpha版本最近增加的很快,作者已经加快了开发速度。webpack 5中实验性的内置了一个文件缓存系统,可以完全替代hard-source-webpack-plugin、babel-loader以及一系列的缓存方案。

但是现在还有很多webpack的第三方插件没有对webpack 5做兼容,比如html-webpack-plugin。希望webpack 5正式发布的时候能够得到解决。

最终方案

在webpack 5到来之前,使用hard-source-webpack-plugin,配合babel-loader,能获得相当可观的缓存优化。

本地开发阶段如果加入dll,可以使得项目启动时间进一步减少,缩短等待项目启动的发呆时间。

编辑于 2019-09-29 10:14