标准
这里定义的前端构建工具需要符合以下条件:
- 能够构建生产用的前端资源。
- 提供带 HMR 的 Dev Server。
- 支持常见的前端技术,比如 Sass, LESS, CSS modules, code splitting。
- 支持插件扩展。
- 不限于某种 UI 框架,不捆绑不必要的运行时内容,比如 Redux。(排除 UMI 之类的深度定制框架)
生产可用
这里定义的生产可用条件:
- 流行度高。(npm 周下载量 >100k)
- 维护活跃。(六个月内至少发布了一个版本。)
- 没有严重的性能或兼容性问题。
Vite 4
Vite 基于 esbuild (用 Go 语言编写) 和 Rollup。它采用了 Unbundled Dev Server 模式,通过浏览器原生 ESM 特性,省去了 Bundle 步骤,以达到比 Webpack Dev Server 更快的 HMR 和启动速度。Vite 4 提供了一个新的基于 SWC 的 React 插件 @vitejs/plugin-react-swc ,用于替换旧的基于 Babel 的插件。这让 React Fast Refresh 的速度快了 20 倍。目前,Vite 仍然是速度最快的前端构建解决方案之一。
Vite 易于使用。即使不进行任何配置,也能开始使用。如果需要定制,它的文档也非常详细好用。Vite 兼容 Rollup 插件和配置接口,甚至可以借用 Rollup 的文档。用户更容易找到需要的插件,遇到问题也更容易找到解决方案。编写 Vite 插件也相对容易。所以 Vite 的社群和生态系统成长很快。Vite 的 JS API 设计简单清晰,功能丰富。基于 Vite 开发你自己的构建工具很容易。
Parcel 2
Parcel 2 基于一系列用 Rust 重写的高性能模块,比如 SWC 和 Parcel CSS,实现了优于 Webpack 的性能。默认配置下,Parcel 2 的性能介于 Vite 4 和 Webpack 5 之间。但是可以通过启用一些实验性功能提升性能,比如 swc 压缩。
Parcel 2 的另外一个特点是开箱即用。它的默认配置已经涵盖了 TypeScript,CSS modules,JSX,React Fast Refresh,图片压缩,SVG 优化等特性支持。另外,当检测到 Sass 或 LESS 时,Parcel 2 会自动帮你安装需要的依赖包。对于常规的前端应用,你完全不需要配置文件。
Parcel 有自己独特的架构和接口设计,这让它几乎不可能直接借用 Webpack 和 Rollup 的插件。因此它的生态系统中第三方插件很少。另外,由于 Parcel 发布大版本的频率很低,内置插件为了避免 Breaking Change 而很难升级,比如 MDX 2.0 至今没有被支持。这可能是 Parcel 2 最大的劣势。
Webpack 5
Webpack 依然是最流行的选择,有最完善的特性,以及最大的生态系统。
Webpack 很慢,但是你可以用一些 loader 和插件替换默认组件来提速:
观察列表
一些还处于早期阶段的有潜力项目。
Rspack
用 Rust 写的 Webpack 替代品,由字节跳动创建。虽然还处于早期阶段,但是已经表现出比 Vite 更快的构建速度。同时兼容 Webpack 插件,可扩展性很强。
未入选
Turbopack
为 Next.js 框架设计的高性能构建工具,原生支持 SSR。未入选原因是因为目前只能用于 Next.js 框架。
UMI
蚂蚁集团的构建工具。未入选原因是因为过度定制化,只支持 React,内置了过多 Redux 之类的运行时。
ICE
阿里巴巴的构建工具。未入选原因是因为过度定制化,只支持 React,内置了过多 Redux 之类的运行时。
Create React App
React 官方的构建工具。未入选的原因是因为过度定制化,只支持 React,不支持配置和插件。
性能测试
dev server cold start | dev server warm start | production build | |
---|---|---|---|
rspack 0.1.1 | 2.2s | 2.0s | 1.4s |
vite 4.2.1 | 4.3s | 1.3s | 6.5s |
parcel 2.8.3 | 13.0s | 1.3s | 15.7s |
webpack 5.76.2 | 14.2s | 14.2s | 15.8s |
详情参见 https://github.com/guoyunhe/front-end-builder-benchmark
技术趋势
一些可能会让前端构建工具更快更好的技术。
原生 Node.js 模块与 WASM 模块
用 Rust,Go 等语言编写的 Node.js 模块,其性能与产物格式有很大的关系。不考虑多线程的情况,从高到低分别是 Native > WASM > JS。JS 和 WASM 本身都是单线程的,除非使用 Cluster 模式(目前还没有在前端构建工具上比较成功的)。支持多线程的 Native 模块,性能会更强,esbuild 就是一个例子。
但是,原生模块必须为不同的平台预编译对应的二进制文件,不如 WASM 或 JS 的“一次编译,到处运行”方便。
在实践中,原生模块通常用于核心高负载模块,比如 rspack,swc,esbuild。而插件还是广泛采用 JS。WASM 仍然比较少见,一个例子是 swc 的插件开发。
从 Unbundled 回到 Bundled?
用 JS 写的 Webpack Dev Server 采用 Bundled 模式启动。即先由 Webpack 将多个源码文件打包成一个,再传输给浏览器。在浏览器的网络记录中,你只会看到几个网络请求。因为 Webpack 打包的效率低,启动速度很慢,热加载也很慢。
后来出来了 Snowpack (已废弃),可能是第一个 Unbundled 模式的 Dev Server。采用 Unbundled 模式启动 Dev Server,通过原生 ESM 加载,省去了 Bundle 的过程,提高了启动速度。当 Dev Server 启动时,不会进行编译,而是在接到浏览器请求时,动态编译每个文件,每次传输一个源文件对应的代码。过程大致是:
请求 → 编译 → 响应 → 请求 → 编译 → 响应 → … → 请求 → 编译 → 响应
这导致 Snowpack 的启动速度极其缓慢,对比 Webpack Dev Server 并没有显著优势。
后来 Vite 继承并改进了 Snowpack 的思路。它将 node_modules 中大量的依赖用 esbuild 先 Bundle 成单个文件,而项目源码依旧采用 Unbundled 模式。得益于 esbuild 的强大性能,不管是依赖的 Bundle,还是单个源码文件的转译,都非常快。因此 Vite 对比 Webpack Dev Server 取得了巨大的性能优势。
然而,随着项目体积增大,需要加载的 ESM 模块越来越多,Vite 的启动速度会变慢。假设项目的 src 中有 1000 个 JS/TS 文件,就会有 1000 个 HTTP 请求。现代的浏览器只允许每个服务器建立 6 个连接,也就是说最多只能并发 6 个 HTTP 请求,导致阻塞。
因此 Parcel 2,Turbopack 和 Rspack 选择了回到 Webpack Dev Server 的 Bundled 模式,通过用 Rust 重写关键部件,改进打包和编译性能来提高性能。
但是根据 Turbopack 的 Benchmark,只有在 src 中有 1000 个以上的 React 组件时,Turbopack 对比 Vite 才会有比较明显的优势。但实际应用中,1000 个组件(约100K到1M行代码)已经是相当大规模的应用了。而 Vite 的冷启动速度仍然可以接受:4.2s。另外,我觉得这个 Benchmark 用 Turobopack 的 Server Side Rendering 对比 Vite 的 Client Side Rendering,并不公平,因为 Vite 也是支持 Server Side Rendering 的。
总结一下,我觉得 Vite 的 Unbundled 策略和 Parcel 等的 Bundled 策略各有所长,在实际应用中并不能拉开明显的性能差距,未来很长一段时间仍然会共存,而不是此消彼长。
更快的 Sass 编译器
Sass 的老编译器 node-sass 已经被官方废弃,不再维护。新的官方编译器 Dart Sass 令人迷惑:
- 原生构建二进制程序没有 Node.js 接口,无法集成到现有的 Node.js 工具中。除非你用 Node.js 的 exec 函数执行。(不推荐在生产中这样做)
- JavaScript 构建要比 node-sass 慢 4 到 30 倍 (取决于项目大小)。如果你的构建工具调用 Sass 接口的方式不正确,速度甚至会更慢。详见 Issue #1557 和 #1534。
对于大量使用 Sass 的生产项目,这肯定是不可接受的。很多项目选择继续使用废弃的 node-sass 编译器。
现在已经有人在开发用 Rust 重写的 Sass 编译器:
更快的 TypeScript 检查器
TypeScript 很慢,因为它完全是 JavaScript 写的。幸运的是,我们通常不需要在构建前端项目的时候检查类型。Babel, esbuild 或 swc 会直接忽略所有类型。但是,如果你项目有很严格的 QA 要求,你不得不运行 tsc 命令检查类型,CI 工作流就会被拖慢,即使对于小型项目 (10k lines) 或中型项目 (100k lines) 也很明显。
于是乎,重写 TypeScript 检查器的呼声越来越高。我们已经能看到一些这方面的尝试:
- stc, 由 swc 的作者创建。已经可以试用。
- deno, Node.js 的替代项目,提出了原生支持 TypeScript 的计划。
发表回复