Webpack打包速度优化实践

2021-08-21  本文已影响0人  jjjkkkhhhggg

随着项目的增大,webpack的打包速度已成前端工程师的“不可承受之重”。最近对团队内某项目的打包速度进行了一些优化,本文没有具体的配置教程,只提供一些优化思路,供启发和参考。

更换更快的打包工具

1. bundler:代表webpack、parcel

parcel和webpack主要区别

更新webpack版本

webpack5内置了持久性缓存和跟好的缓存策略,Tree Shaking性能提升,摇掉更多无用代码。
尝试改善与网络平台的兼容性。

2. noBundler:代表snowpack、vite

主流的浏览器版本都支持直接使用 JavaScript Module。
HTTP/2可以合并请求来优化模块并发请求性能。
vite针对复杂的第三方库,会自动识别并提前打包缓存起来,避免过多http请求(类似于dll)

从webpack迁移到vite
web和node共用变量

由于webpack到vite的迁移成本比较高,vite build时的速度和webpack也差不多,使用hardsource等插件后webpack dev的速度也是可以接受的,决定还是在webpack体系下做优化

升级webpack

截止目前,cra正式版还停留在webpack4版本,好在alpha版升级到了webpack5,可以使用react-app-rewired start --scripts-version react-scripts来指定react-scripts版本。实测升级到alpha版后速度更慢了,在优化了配置后速度也没有明显提升。
猜测原因是webpack5的缓存策略主要是dev的时候有用,本质上和hardsource没有太大区别,但首次生成缓存的速度比hardsource稍快,生产环境中一般会禁用或重新构建缓存。

esbuild官网上也可见webpack5比webpack4的速度更慢了
替换babel
替换terser

terser负责压缩babel和webpack生成的产物,去掉无效代码,去掉日志输出代码,缩短变量名,生成source-map等,可以有效压缩体积

source-map

webpack提供了如下的source-map选项,
不同选项的构建速度和性能以及适用场景都有很大差别,在这里不详细叙述


source-map的速度以及适用场景比较
使用多进程打包

happypack和thread-loader


image.png
使用缓存

cache-loader、开启loader自带的cache选项、dll、hardsource、webpack5

pitch阶段根据当前正在处理的文件,读取.cache目录中对应的cache文件,对比mtime判断是否可以复用
loader阶段依赖pitch阶段的判断,如果pitch阶段判断当前文件的缓存失效了,loader阶段就要重新生成缓存。

hardsource和webpack5
hardsource和webpack5持久化缓存的方案类似,webpack5持久化缓存结果至硬盘上,第一次编译文件的时候,计算文件的hash。将编译结果与hash关联起来。第二次编译文件时,首先加载本地缓存结果,进入正常编译环节时,对编译的文件再次求hash,如果此hash在缓存库中已经存在了,那么将直接跳过编译环节,直接输出编译结果。
这两种方案都是dev的时候才会有用(记得官方有个issue说实验基于缓存build可能会有5%的概率出错),升级到cra5之后发现复用缓存的条件极其严格,每次重新build时都会重新构建缓存,hardsource首次构建的消耗时间比较大,webpack5由于的缓存是基于webpack4构建时的内存改造得来,首次构建带来的额外时间消耗并不大,二次构建hardsource和webpack5的速度相当。

external

external可以配合Systemjs使用,systemjs支持直接引入esm而不必去找umd,webpack4也内建支持把libraryTarget改成SystemJs的形式,直接使用esm格式包时不需要type=module,也可以进行动态加载改造,微前端框架single-spa也使用这种方式进行依赖动态加载

<script type="systemjs-importmap">
        {
               imports": {
                   "react": "//gw.alipayobjects.com/os/lib/react/16.13.1/umd/react.production.min.js",
                   "react-dom": "//gw.alipayobjects.com/os/lib/react-dom/16.13.1/umd/react-dom.production.min.js",
                   "moment": "//cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.0/moment.min.js",
                   "antd": "//unpkg.com/antd@4.6.6/dist/antd.min.js",
                   "handsontable": "//cdn.jsdelivr.net/npm/handsontable@8.4.0/dist/handsontable.full.min.js",
                   "braft-editor": "//cdn.jsdelivr.net/npm/braft-editor@2.3.9/dist/index.min.js",
                   "lodash": "//cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js",
                   "mobx": "//cdnjs.cloudflare.com/ajax/libs/mobx/5.15.7/mobx.min.js",
                   "webpackBundle":"./output.bundle.js"
               }
           }
       </script>
<script>
System.import('xxx').then((res) => {
   System.import('webpackBundle');
 })
</scirpt>
webpack5 Module Federation预编译node_modules

module federation 是 webpack5 提出的新特性,含义为模块联邦。主要是使用于微前端场景,可以在运行时动态引入子应用。
优化思路:利用 webpack5 的 module federation 特性,构建一个虚拟的 federation 应用,项目直接仅使用编译好的依赖,这样就可以直接减去热更新和启动时对依赖的重新编译。实际和dll的原理类似?但速度比dll要快很多

module federation
umi使用module federation优化的思路
业内脚手架umi已在这方面做出了实践,但没有提供通用的webpack插件,可以期待未来社区的产出
硬件更换

由于JS的多线程能力不佳,webpack在打包时更吃CPU单核性能,多核性能几乎(在多核服务器上测试还没有笔记本块)没有用。
截止目前,苹果还没有发布第二代自研桌面处理器,(APPLE M1x在制程不变的情况下无法大幅提高主频,大概率会通过堆核心、增加GPU、增加总线带宽、提高内存频率等方式做优化,可以预见单核性能不会有类似Intel -> M1的巨大提升)基于的硬件选购建议是,M1 16g配置的Mac电脑在未来几年内都会是非常适合前端开发者使用的(传统x86芯片的电脑在不改变封装逻辑的前提下预计相同价位提升至M1的单核性能水平需要很长时间,基于JS的打包生态迁移到rust和go也需要很久)。

使用nice命令提高本机webpack进程优先级后速度也略有提升(需要root权限)

总结

基于上述调研和项目的业务场景,最终决定应用以下优化

  1. 用esbuild替换terser作压缩
  2. 测试和预上线环境禁用压缩并启用cheap-source-map
  3. 应用dll
    以下是在本机的一些实验结果


    external和dll
    esbuild压缩测试

    优化后vs优化前生产环境构建提升了大约43%的时间,测试环境构建提升了大约58%的时间


    项目测试

一些困惑

  1. Module Federation为什么比dll快
  2. 能不能使用JS实现多线程打包

参考

上一篇 下一篇

猜你喜欢

热点阅读