区块链 区块链技术 比特币公众号手机端

React Compiler 现已用 Rust 编写:我们实测了迁移前后的构建时间

liumuhui 4小时前 阅读数 1 #区块链

我们的生产构建耗时 4 分 52 秒。切换一个实验性标志后,耗时变为 3 分 8 秒。

没有代码变更。输出逐字节完全一致。唯一区别是 React Compiler 不再是通过 Babel 运行的 TypeScript,而是运行在 Turbopack 内部的 Rust。

React 团队于 2026 年 6 月 9 日在 PR #36173 中合并了 Rust 移植版本。Vercel 随后不久将其以标志位形式接入 Turbopack。流传的头条数字令人印象深刻,但也略有误导,这是构建工具宣传的常见问题。他们衡量的是编译器。你在乎的是你的构建。

所以我们测量了自己的构建。冷启动、增量、开发服务器和 CI。以下是实际变化的部分、没有变化的部分,以及关于这个编译器编写方式的一个比基准测试更值得关注的细节。

React Compiler 实际发生了什么变化?

React Compiler 从 TypeScript 重写为 Rust,架构保持不变:AST 到高级中间表示、控制流图、静态单赋值形式,然后是插入记忆化的优化 pass。公共 API 未变。据报道,作为即插即用的 Babel 插件性能提升约 3 倍,独立的转换逻辑接近 10 倍,原生链接到 Turbopack 时编译速度提升 20% 到 50%。

架构层面的意义大于语言本身。编译器的思考方式没有任何改变。相同的 pass、相同的中间表示,只是用 arena 分配和基于索引的数据结构重建,而非对象图。全部 1,725 个测试夹具通过,中间状态与 TypeScript 版本几乎逐字节一致。

真正的胜利来自集成路径,而非原始执行速度。

以前,在 Turbopack 中使用 React Compiler 意味着 Turbopack(Rust)将你的代码交给 Babel(JavaScript)再取回。这个边界在每个模块上都产生序列化开销。现在编译器是与打包器同进程的原生 Rust 代码,数据永远不会跨越语言边界。

这就是为什么插件基准测试显示 3 倍,而端到端 Turbopack 的数字超过 40%。它们衡量的是不同的东西。

如何开启并正确测量?

在 Next.js 配置中启用 reactCompiler: true 和实验性的 turbopackRustReactCompiler 标志。然后清除缓存进行测量,至少运行五次,并与基于 Babel 的 React Compiler 对比,而不是与完全关闭编译器对比。将 Rust 编译器构建与关闭编译器的构建对比会夸大你的结果,且毫无参考价值。

配置只有两行:

// next.config.ts
const nextConfig = {
  reactCompiler: true,          // 自 Next.js 16 起稳定
  experimental: {
    turbopackRustReactCompiler: true,  // 使用 Rust 移植版本
  },
};

export default nextConfig;

测量纪律比标志位更重要。构建耗时噪声很大,单次运行说明不了任何问题。

## 清除可能污染运行的所有缓存
rm -rf .next node_modules/.cache

## 五次运行,丢弃三次预热,两种配置
hyperfine --warmup 3 --runs 5 \
  --prepare 'rm -rf .next' \
  'RUST_COMPILER=0 npx next build' \
  'RUST_COMPILER=1 npx next build'

我们吃一堑长一智学到的两条规则:

→ 在没有任何其他任务的机器上测量,因为后台类型检查器对你的数字影响比编译器还大 → 单独测量 CI,因为 CI 机器核心更少、磁盘特性不同,而那里通常才是构建时间真正花钱的地方

顺便验证输出。这是几乎没人会跑的检查:

npx next build && mv .next .next-babel
## 切换标志位,重新构建
npx next build && diff -r .next-babel/static .next/static

我们的 diff 除了构建 ID 之外完全干净。这就是你想要的结果。一个速度快但输出不同的编译器不是更快的编译器,而是另一个编译器。

前后构建实际显示了什么?

在一个约 1,200 个组件、340 条路由的 Next.js 应用上,冷生产构建从 4 分 52 秒降至 3 分 08 秒,减少了 36%。编译器在该构建中的自身耗时从 71 秒降至 19 秒,减少了 73%。开发服务器冷启动和热重载都有改善。构建期间峰值内存下降了超过三分之一。

测量项 Babel React Compiler Rust React Compiler 变化
冷生产构建 4分52秒 3分08秒 -36%
仅编译器阶段 71秒 19秒 -73%
增量构建,1 个文件变更 14.2秒 9.6秒 -32%
CI 构建,冷缓存 6分41秒 4分22秒 -35%
开发服务器冷启动 11.4秒 7.9秒 -31%
HMR p95 380 毫秒 240 毫秒 -37%
构建期间峰值内存 4.1 GB 2.6 GB -37%

内存这一行比时间更让我们惊讶。Babel 在运行中持有大量对象图。Rust 的 arena 分配则不会。在我们的内存受限的 CI 运行器上,这一减少消除了我们几个月来反复重试的间歇性内存不足故障。

仔细看第二行。 编译器阶段快了 73%,但整个构建只快了 36%。构建中的其他部分没有任何加速,其余由阿姆达尔定律决定。

关于这些数字的提醒: 它们来自一个硬件配置上的一个应用。可迁移的部分是测量方法。

为什么你的收益可能比我的小?

你的改进幅度受限于编译器在你的构建中所占的比例。如果 React Compiler 占你构建时间的 25%,那么在那里削减 73% 最多只能带来 18% 的整体收益。仍在使用 Webpack 和 Babel 的应用几乎看不到任何提升,因为原生集成才是关键,而它们并没有使用它。

记住我以加快登录

三个因素决定你的结果:

组件密度。 编译器按组件工作。一个拥有 1,200 个组件的应用比只有 90 个组件的应用收益大得多。

打包器。 大幅收益需要原生链接到 Rust 打包器。在使用 Babel 插件的 Webpack 上,你只能获得约 3 倍的插件数字,而且作用在更小的部分上。

还有什么很慢。 如果 TypeScript 类型检查或图片优化主导了你的构建,那才是你的瓶颈,而这次改动不会改变任何东西。

我们在一个较小的内部仪表盘上运行了同样的测试,60 条路由、约 140 个组件。冷构建从 48 秒降至 42 秒。12% 的提升,真实存在,但不值得大书特书。

在向团队做出任何承诺之前,先测量。

这个编译器大部分由 AI 编写。这重要吗?

领导这次移植的 Joseph Savona 在 pull request 中直言不讳地表示,架构由人类指导,但大部分代码由 AI 编写。他确定了架构、测试和验证策略以及增量迁移方案,然后进行了大量审查。大约 123,000 行代码以这种方式完成。这是前端工具链中公开记录的最大规模 AI 辅助迁移,但几乎没有人讨论它。

这是故事中两年后仍然重要的部分,远在构建数字过时之后。

看看是什么让它成功的。不是因为模型擅长 Rust,而是因为这个问题具有让验证变得廉价的性质:

→ 存在参考实现,因此每个输出都可以与已知正确行为进行对比 → 1,725 个测试夹具为每个移植的 pass 提供机械化的通过或失败信号 → 架构由一位已经深刻理解它的人预先确定 → 移植是逐个 pass 进行的,因此失败被隔离在小的单元内

这才是大规模 AI 辅助迁移的真正模板:一个附带 oracle 的重写。如果你在移植一个旧版本仍在运行、可以与新版本进行 diff 的服务,同样的模式适用。如果你在编写没有参考可对照的新东西,那就不适用。

怀疑论也值得被倾听。公开讨论中的审查者提出了一个显而易见的问题:一个主要由模型编写的代码库,能否由没有编写它的人类来维护?一位评论者指出,用 Rust 编写并不等于写得好的 Rust,因为模型可能会用 RefCell 来满足借用检查器,从而悄悄地把编译期错误变成运行时错误。

两个担忧都合理。但都还没有答案,因为代码被维护的时间还不够长,无法得知。

pull request 中还有一条坦诚的说明:早期的性能数字本身也是由 AI 得出的,Savona 表示他没有花太多时间验证基准测试的设置。这是一个令人耳目一新的坦诚披露,也是另一个应该测量自己的构建而不是复述头条数字的理由。

启用之前应该检查什么?

把它当作构建工具链中的实验性标志来对待,这是一个冒险的敏感位置。验证输出等价性,锁定版本,在 CI 中运行一段时间之前不要让该标志进入生产发布,并记住构建工具链今年一直是供应链攻击的活跃目标。

→ 在信任之前,对两种配置的构建输出进行 diff,如上所示

→ 在 lockfile 中锁定精确版本并启用 provenance 检查,因为 JavaScript 生态系统在 2026 年经历了多起包被攻破的事件

→ 将标志位放在环境变量后面,这样你可以在不修改代码的情况下回滚一次失败的 CI 构建

→ 运行完整的端到端测试套件,而不仅仅是单元测试,因为记忆化 bug 表现为过期的 UI,而不是异常

→ 注意违反 Rules of React 的情况,因为违反规则的组件在任何版本的编译器下都可能被跳过或表现异常

→ 不要在周五启用实验性标志,这不是技术上的理由,但它比任何技术理由都拯救了更多的周末

编译器在构建时运行,而不是在用户的浏览器中。缺陷会导致发布错误的输出,而不会打开运行时攻击面。这降低了风险,但并没有消除验证你所发布内容的需要。

结论

Rust 移植版本是一个真正的改进,但你应该预期的数字比头条中的要小。

对我们来说,冷构建减少了 36%,CI 减少了 35%,内存减少还修复了一个不稳定的流水线。对于较小的应用是 12%。对于仍在使用 Webpack 的团队,几乎为零。编译器变得快了很多。你的构建提速幅度与编译器在构建中所占的比例成正比。

在一个分支中开启它。每种方式运行五次构建。对输出进行 diff。这只需要一个下午,就能给你一个真实的数字,而不是别人的。

更有趣的故事隐藏在 pull request 的描述中。一个 123,000 行的编译器移植,架构由人类设定,大部分代码由模型编写,并对照参考实现和 1,725 个测试夹具进行验证。它之所以成功,是因为验证是机械化的,而不是因为模型很聪明。

每个拥有旧服务和测试套件的团队都应该把那个 pull request 当作迁移手册来读。但很少有人这样做。

如果你在自己的应用上运行了这些数字,请连同路由数、组件数和打包器一起发布。20% 到 50% 这个区间需要比现在更多的真实数据点。

  • 原文链接: medium.com/@ancilartech/...
  • 鸿途知科网 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
版权声明

本文仅代表作者观点,不代表区块链技术网立场。
本文系作者授权本站发表,未经许可,不得转载。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门