# Turbopack真的比Vite快10倍吗？

一周前，Vercel宣布推出了Turbopack，这是一个基于Rust的Webpack继任者。

在公告中，其中一个标题是Turbopack比Vite快"10倍"。这句话在Vercel的各种营销材料中反复出现，包括推文、博客文章和发送给Vercel用户的营销邮件。Turbopack文档中还包含了性能基准图表，最初显示Next 13与Turbopack可以在0.01秒内执行React热模块替换（HMR），而对于Vite来说需要0.09秒。还有冷启动性能的基准测试结果，但由于没有一个冷启动基准测试显示出10倍优势，我们只能假设"10倍更快"的声明是基于HMR性能。

Vercel在营销材料或文档中没有提供用于生成这些数字的基准测试链接。因此我很好奇，并决定使用刚发布的Next 13和Vite 3.2进行自己验证这些声明。代码和方法论都可以在[此处](https://github.com/yyx990803/vite-vs-next-turbo-hmr)公开获取。

我的方法论要点是通过测量以下两个时间戳之间的差异来比较HMR性能：

1. 源文件被修改时记录下来的时间戳，通过监视文件变化的单独Node.js进程记录；
2. 更新后React组件重新渲染时记录下来的时间戳，通过在组件的渲染输出中直接调用Date.now()进行记录。请注意，此调用发生在组件的虚拟DOM渲染阶段，因此不受React协调或实际DOM更新的影响。

基准测试还分别针对两种不同情况进行了测量：

1. "根"情况，其中组件导入了1,000个不同的子组件，并将它们一起渲染。
2. "叶"情况，其中该组件由根导入，但没有自己的导入或子组件。



## 细微差别

在我们开始讨论具体数字之前，有几个值得一提的细微差别：

Next 是否使用 React 服务器组件（RSC）。
Vite 是否使用 SWC 而不是 Babel 进行 React 转换。



### React 服务器组件

Next 13 引入了一个重大的架构变化，现在默认情况下组件都是服务器组件，除非用户通过"use client"指令明确选择客户端模式。不仅如此，默认情况下 Next 文档还建议用户尽可能保持在服务器模式下以提高最终用户性能。

我的初始基准测试中，在根组件和叶子组件都处于服务器模式时，结果显示 Next 13 在两种情况下实际上都比较慢，并且对于叶子组件来说差距很大。

[**第一轮快照（Next 使用 RSC，Vite 使用 Babel）**](https://github.com/yyx990803/vite-vs-next-turbo-hmr/tree/df326fb22c8cf46118cf801b0250245dfbe29f9d#numbers)

当我将这些数据发布到 Twitter 上时，很快就有人指出我应该对 Next 组件进行无 RSC 的基准测试以使其公平。因此，我给 Next 根组件添加了"use client"指令以选择客户端模式。事实上，在客户端模式下，Next 的 HMR 显著改善，并且比 Vite 快2倍：

[**第二轮快照（Next 不使用 RSC, Vite 使用 Babel）**](https://github.com/yyx990803/vite-vs-next-turbo-hmr/tree/97c58aee02c465099103593f8441af7fc7b8fae1#numbers)



### SWC vs. Babel转换

我们的目标是让基准测试专注于HMR性能差异。为了确保我们实际上在比较苹果和苹果，我们还应该消除另一个变量：Vite的默认React预设使用Babel来转换React HMR和JSX。

React HMR和JSX转换不是与构建工具耦合的功能。它可以通过Babel（基于JavaScript）或SWC（基于Rust）来完成。Esbuild也可以转换JSX，但不支持HMR。SWC比Babel快得多（单线程20倍，多核心70倍）。Vite当前默认使用Babel的原因是安装大小和实用性之间的权衡考虑。SWC的安装大小相当大（node_modules中有58MB，而Vite本身只有19MB），许多用户仍然依赖Babel进行其他转换，因此对他们来说使用Babel是不可避免的。然而，在将来可能会发生改变。

更重要的是，在同一基准测试中Webpack实现也在使用SWC。

Vite核心不依赖于Babel。将SWC替代Babel处理React转换并不需要对Vite本身进行任何更改--这只涉及将默认的React插件替换为`vite-plugin-swc-react-refresh`即可。切换后，我们看到了根本案例下Vite显著改进，并迎头赶上了Next：

|                    | Vite (root) | Vite (leaf) | Next (root) | Next (leaf) |
| ------------------ | ----------- | ----------- | ----------- | ----------- |
| 5-run average (ms) | 338.2       | 141.8       | 334.6       | 84.4        |

有趣的是，这里的增长曲线显示，在根案例中，Next/turbo相比叶子案例慢了4倍，而Vite只慢了2.4倍。这意味着在更大的组件中，Vite HMR能够更好地扩展。

此外，切换到SWC还应该改善Vite在Vercel基准测试中的冷启动指标。

### 不同硬件上的性能

由于这是一个涉及Node.js和本机Rust部分的综合基准测试，因此在不同硬件上会存在非平凡差异。我发布的数字是在我的M1 MacBook Pro上收集到的。其他用户已经在不同硬件上运行了相同的基准测试，并报告了不同结果。在某些情况下，[Vite在根案例中](https://twitter.com/SaurabhCharde/status/1586352676668723201)更快，在[其他情况下](https://twitter.com/Saied_Za/status/1586067681794719744)，则无论是根案例还是叶子案例都明显更快。



## Vercel的澄清

在我发布我的基准测试之后，Vercel发表了一篇博文，对他们的基准测试方法进行了澄清，并公开提供了可供公众验证的基准测试。虽然这可能应该在第一天就完成，但这无疑是朝着正确方向迈出的一步。

阅读完帖子和基准代码后，以下是一些关键要点：

1. Vite实现仍然使用默认的[基于Babel的React插件](https://github.com/vercel/turbo/blob/main/crates/next-dev/benches/bundlers/vite/vite.config.js#L2)，而同一个基准测试中Turbopack和[Webpack实现](https://github.com/vercel/turbo/blob/main/crates/next-dev/benches/bundlers/webpack/mod.rs#L31)都使用SWC。单单这个因素就使得该基准测试成为一个根本不公平的比较。

2. **原始数据中1k组件案例存在四舍五入问题 - Turbopack 的15ms被四舍五入为0.01s，而 Vite 的87ms则被进位为0.09s。当原始数据接近6倍时，宣传成10倍优势。** 
3. Vercel 的基准测试将“浏览器 eval 时间”作为更新模块结束时间戳, 而不是 React 组件重新渲染时间。前者是理论上的时间戳, 后者反映用户感知到端到端 HMR 更新速度 。我还发现了 Vite 在收集 eval vs. update 时间戳方面存在缺陷（请参见最后更新）。

4. 文章中包含了一个图表，显示当总模块数超过30k时，Turbopack 可以比 Vite 快10倍。然而，实际上这两个工具在所有模块数量上都能不断扩展到10k。只有在超过20k的模块数量时，Vite 的 HMR 曲线开始上升。考虑到 Vite 应用程序预打包其依赖项，在现实中几乎不可能存在一个拥有20k+源代码模块的项目。使用30k的数字来证明10倍优势感觉像是选择性挑选。


总之，“比 Vite 快10倍”的说法只有在以下条件全部成立时才有效：

- Vite没有使用相同的 SWC 转换
- 应用程序包含超过 30k 模块
- 基准测试仅测量热更新模块被评估的时间，并未测量更改实际应用的时间。



## 什么是“公平”的比较？

如果我们要比较的是“开箱即用默认设置”，那么我们应该将Next启用RSC与之进行比较，因为这是默认设置，并且Next正在积极鼓励用户使用它。由于Vercel的基准测试没有使用RSC，并且通过测量“模块评估时间”来排除React的HMR运行时引起的差异，可以合理地假设基准测试旨在对Vite和Turbopack固有的HMR机制进行苹果对苹果式的比较。

然而，在这个前提下，不幸的是，Vite在基准测试中仍然使用Babel，这使得情况不相等，并且依然无法证明其10倍速度优势。直到使用SWC转换更新了数据后才能被认为准确。

此外，我相信大多数人会同意以下观点：

- **对于绝大多数用户来说，“30k个模块”是一个非常不太可能出现的场景**。随着Vite采用SWC转换后，需要达到10倍速度优势所需的模块数量很可能变得更加不切实际。虽然从理论上讲可能存在这种情况，但将其作为推动Vercel一直以来所倡导营销手段合理性的依据似乎有失诚意。
- **用户更关心的是端到端的HMR性能**，即从保存到看到变化反映出来所需的时间，而不仅仅是理论上的“模块评估”时间。当看到“10倍更快更新”的时候，普通用户会以前者为准而非后者。Vercel在其营销中方便地遗漏了这个细节。实际上，在Next中，默认情况下服务器组件（默认设置）的端到端HMR速度比Vite慢。

作为Vite的作者，我很高兴看到像Vercel这样资金雄厚的公司对改进前端工具进行重大投资。我们甚至可能在适用时利用Turbopack来完善Vite。我相信开源软件领域健康竞争最终将使所有开发人员受益。

然而，我也认为开源软件竞争应该基于公开沟通、公平比较和相互尊重。看到使用精选、未经同行评审且近乎误导性数字进行激烈营销行为让人失望和担忧，这种行为通常只存在于商业竞争之中。作为建立在其开源成功基础之上的公司，我相信Vercel可以做得更好。

## 更新

我成功地克隆并运行了Vercel的基准测试，并将基准测试更新为在Vite中使用SWC。该基准测试还包括一个名为hmr_to_commit的指标，除了在基准测试自己的README中没有提到之外，其他地方都没有提及。在运行完基准测试后，我注意到对于Vite来说，hmr_to_eval和hmr_to_commit套件显示的数字完全相同（约100毫秒），而对于Turbopack来说，这个数字从15毫秒增加到54毫秒。由于评估到提交成本是真实存在的，唯一合理的解释是该基准测试有缺陷：hmr_to_eval套件未能正确捕获Vite模块评估时间（可能是由于原生ESM机制上的差异），而报告了hmr_to_commit数字。否则，我们无法解释为什么只有Turbopack才会出现额外开销以提交更改。

考虑到：

- hmr_to_eval套件有缺陷
- hmr_to_commit更好地反映用户感知的端到端性能

我认为使用hmr_to_commit作为测量指标更合理。这将使速度优势降至不超过2倍（约100毫秒与约54毫秒），这与我的基准测试结果相吻合。

