专注React,学不会你打我!
「代码编译打包」是如今前端工程化中绕不开的一环,这项功能依赖于「打包工具」。
最常见、受众最广的打包工具当属webpack。
同时,在webpack势力范围之外,存在一些在某些方面很突出的打包工具满足一部分细分领域的需求。
当我们要开发一个新项目,该使用哪种打包工具?怎么衡量打包工具的优劣呢?
本文会从几个纬度来评价一款打包工具优劣。
首先让我们简单了解下市面上常见打包工具的特点:
基于loader与plugin机制,接入灵活。同时由于有先发优势,有完备的社区储备。
缺点是灵活的配置造成上手成本高,遇到问题不易调试。
如果说webpack偏向应用打包,那rollup更偏向于库的打包。
其对ESM更好的支持使更好的tree-shaking能力有了原生的底层支持。
对标webpack的繁杂配置,parcel的的目标是「零配置完成打包」。
向开发者屏蔽配置固然利于上手,但是当默认配置无法满足需要时这种优势就会被打破。
特点是使用CJS标准打包,使一份代码同时在node环境与浏览器环境(打包后)执行。
其中,在浏览器环境中,node的一些核心库(如events、stream、path...)会被打包成浏览器支持的版本。
缺点:缺少ESM标准的约束,在tree-shaking上存在天生劣势。
基于浏览器原生支持的ESM标准,vite在dev环境可以提供极快的预览效果。
同时基于go语言编写的esbuild,使vite的打包速度与以上几个工具有了数量级的差异。

Google工程师Surma[1]和其他人一起打造了一个评价打包工具优劣的开源项目tooling.report[2]。
该项目按以下5个纬度衡量打包工具优劣:
「代码分割」可以在开发者无感知(或者很少感知)的情况下,将代码拆分到不同到包,在运行时按需加载。
这种方式可以显著减少运行时需要下载和执行的JS代码。
「代码分割」包含很多因素:
Dynamic import(动态import)比如webpack会将动态import语法编译为运行时以jsonp形式加载并执行代码。
以CJS作为打包标准的browserify不支持ESM,显然更不会支持动态import。
浏览器中除了JS线程,还有worker线程(如service worker、web worker)。
当使用了worker,打包工具是否会为不同上下文打包不同的文件?
JS线程与worker之间,worker与worker之间之间是否能复用公用代码?
chunk是否能复用引用不同入口是否能将公共的引用抽离出来只实例化一次?这里又分为「单入口应用」与「多入口应用」。
最理想的用户体验:第一次访问页面时请求静态资源数据,并缓存下来。再次请求时使用缓存数据。
这样能极大加快页面展示时间,减少服务器负荷。
但是缓存有失效/更新问题,如果静态资源已经更新,但是缓存未失效,这是很严重的问题。
当前业界主要解决方式是:静态资源本身不会失效,通过在资源url上增加hash来区分不同版本的资源。
这就为打包工具带来挑战:
一个「非JS资源」使用hash url,当其发生变化,引用他的「JS文件」需要改变引用的hash url,这可能造成该「JS文件」的hash url改变,从而造成递归的连锁反应。
如何将这种连锁反应控制在最合理的限度?
webpack将hash分为hash、content hash、chunk hash,就是为了以不同粒度的hash控制连锁反应的范围。
随着ESM规范普及,越来越多的工具开始支持导出为ESM规范。
但是由于历史原因,很多以库都是以CJS规范导出。
打包工具是否同时支持CJS和ESM?如何处理依赖文件(node_modules)中CJS与ESM混用的情况?
典型的web应用不仅仅包含JS代码,还包含HTML、CSS、图片、字体等。
如何在打包工具中处理好这些资源之间的依赖关系?
JS有不同宿主环境,浏览器、Node、worker等。
Node v12之前,Node环境只支持CJS规范。
大部分现代浏览器支持ESM规范。
Web Workers只有在chrome中支持ESM规范。
针对不同宿主环境,需要能打包出不同规范的产物。
针对不同类型资源,打包工具是否支持代码转换。
我们知道babel可以将JS代码转换为AST,在此基础上完成诸如:
ES6转ES5在这一步,打包工具是否能做的更优秀?
比如基于「数据流分析」的Dead Code Elimination(移除未使用代码)
是否能支持更多格式?
比如压缩图片、SVG...
基于以上5个纬度,4款打包工具的得分如下:

可以看到,虽然我们时常吐槽webpack配置让人抓狂,但是webpack各方面确实很优秀。
颇有种带头大哥“每手都要抓,每手都要硬”的感觉。
关于每个得分标准的详细解析参考tooling.report官网[3]
[1]
Surma: https://github.com/surma
[2]
tooling.report: https://github.com/GoogleChromeLabs/tooling.report
[3]
tooling.report官网: https://bundlers.tooling.report/