- 引言 — 编译器明明已经 1.0 了,为什么还要再移植一次
- 到底合并了什么 — 先把事实说清楚
- "3 倍"和"10 倍"——作者本人贴上的警告标签
- "架构由人主导,代码大部分是 AI 写的"
- 真正该看的不是速度,而是验证方式
- 架构 — 同一套算法,不同的内存模型
- 合并之后的一个月 — 计划变了的地方,仍在漏的地方
- 现在能用了吗 — npm 会给出答案
- 编译器现在仍然咬人的地方 — 记忆化(memoization)的安全性
- Meta 的 12% 和 2.5 倍,量的到底是什么
- 结语 — 现在你该做的事
- 参考资料
引言 — 编译器明明已经 1.0 了,为什么还要再移植一次
React Compiler 看起来像是一个已经画上句号的故事。React Compiler v1.0 已经在 2025 年 10 月 7 日发布,那个要终结手动写 useMemo、useCallback 时代的承诺,也贴上了稳定版的标签。
然而 2026 年 6 月 9 日,PR #36173 — [compiler] Port React Compiler to Rust 合并进了 facebook/react 的 main 分支。整个编译器从 TypeScript 被重写成了 Rust:461 个文件变更,新增 123,289 行。作者是 React 团队的 Joseph Savona(@josephsavona)。
没过几天,"React Compiler 换成 Rust 后快了 10 倍" 这句话就开始在生态圈里流传。本文要做的,就是把这句话拉回一手资料重新核对一遍。先说结论:合并这件事是事实,那个数字则很难称得上是事实——因为作者本人就是这么写的。
到底合并了什么 — 先把事实说清楚
PR 正文的第一句话就把话说尽了。
This is an experimental, work-in-progress port of React Compiler to Rust. (这是 React Compiler 移植到 Rust 的一个实验性的、进行中的版本。)
后面几条的语气也是一样的。
- 还在进行中。 "we are sharing early, prior to testing internally at Meta, to get feedback from partners in parallel with continued development"——意思是这甚至是在 Meta 内部测试之前就公开出来,专门为了在继续开发的同时收集合作方的反馈。
- 没有构建产物。 "No builds available yet, you'll have to do some hacking if you want to try this." 想试用,得自己动手折腾。
- 没有已知的缺口,但可能藏着 bug。 "All fixtures pass, no known gaps but there may be lurking bugs."
时间线是 2026 年 3 月 30 日开 PR,6 月 9 日合并,花了两个半月。
还有一点值得指出——这不是第一次尝试。翻一下仓库历史会发现,2025 年 1 月 24 日有一条提交叫 [compiler][be] Remove unused experimental Rust port。Rust 移植曾经被尝试过一次,因为没被用上而被删掉了。这次是第二回合。
"3 倍"和"10 倍"——作者本人贴上的警告标签
现在来看那个数字本身。把 PR 正文里讲性能的那一段原样搬过来是这样的。
Early performance numbers are derived from AI and i haven't spent much time validating the benchmark setup, beyond the fact that the optimization opportunities it discovered made complete sense and the fixes were right. With that caveat, it does appear that the Rust version is quite fast already: 3x faster when operating as a Babel plugin. The serialization cost is quite high, but the actual transformation logic is ~10x faster, so it's net faster. Native integrations (oxc, swc) should be even faster.
翻译过来是——"早期的性能数字是 AI 给出的,我没怎么花时间去验证基准测试的设置,只是确认了它找出来的优化点确实说得通、改动也是对的。带着这个前提说的话,Rust 版看起来确实已经相当快了:作为 Babel 插件运行时快 3 倍。序列化成本相当高,但实际的转换逻辑本身快了大约 10 倍,所以净下来还是更快的。原生集成(oxc、swc)理应更快。"
来拆解一下这里到底发生了什么。
第一,数字的来源是 AI。 不是作者自己写基准测试、跑出来的,而是 AI 给出的数值,作者明确写道自己"没怎么花时间去验证基准测试的设置"。他说自己验证过的,不是性能数字本身,而是AI 找出来的优化点是合理的。这是完全不同的两件事。
第二,3 倍和 10 倍量的根本不是同一件事。 3 倍是作为 Babel 插件运行时的端到端数字,10 倍是刨去序列化成本之后、仅转换逻辑本身的数字。一旦把它简化成"快了 10 倍",序列化成本就悄悄消失了——可作者在同一句话里说这项成本"相当高(quite high)"。真正会体现在你构建产物里的,是 3 倍那个数字,不是 10 倍。
第三,oxc/swc 更快是预期,不是实测。 原文写的是"should be even faster"——带着情态动词。不应该把它翻译成断言式的"更快"。
第四,就连那个 3 倍,也已经过时了。 这里有点讽刺,后面会讲到。
总结一下,这些数字是在一个实验性的 WIP 分支上,由 AI 算出、作者本人声明没有验证过的数值。这不代表不能引用它们,而是说引用的时候,得把这些前提一起带上。这个生态圈很擅长把不带前提的数字变成民间传说。
"架构由人主导,代码大部分是 AI 写的"
PR 里另一句被引用得几乎和那个数字一样多的话是这个。
The architecture was heavily guided by humans (me, @josephsavona) but majority coded by AI.
只截取"大部分代码由 AI 写"这半句,很容易产生误解。紧接着的下一句把范围收窄了。
I was very hands-on in setting the architecture, the testing and verification strategy, incremental migration approach, etc. I also kept a close eye on the code and spent a decent amount of time going back and forth to get code quality to a decent level.
也就是说,人主导的是架构、测试与验证策略、渐进式迁移方案,代码质量也是靠"反复来回打磨"才拉上去的。AI 填进去的,是这个框架内的具体实现。
还要看到的一点是,这项工作本身的条件对 AI 有利。这不是从一张白纸开始设计编译器,而是一次已经有标准答案的移植。TypeScript 实现作为参照物存在,能做到按 pass 一一对应(用作者的话说,"It's very much a pass-by-pass port"),输出是否正确可以机械地比对。这是一个规范已经以可执行形式存在的问题。把这里得出的结果推广成"AI 能写编译器",是过度引申了。
真正该看的不是速度,而是验证方式
就我个人而言,这个 PR 里最值得学习的部分不是那个 10 倍,而是这里——把一个有标准答案的移植交给 AI 时,信任是怎么建立起来的。
第一步——输出比对。
all 1725 fixtures pass in snap when comparing the temporary rust version of the plugin with the main version. this compares generated code output as well as errors.
针对 1,725 个 fixture,比对 Rust 版与现有版本的生成代码与错误。到这一步为止,还是常见的黄金测试(golden test)。
第二步——按 pass 逐一比对内部状态。 这才是关键所在。
all fixtures also pass a full comparison of the per-pass compiler intermediate representation — the intermediate state (including log events and errors) are ~identical after every single pass (modulo some normalization of ids)
只对最终输出,"碰巧答案一样"也能蒙混过关。于是他们在每一个 pass 结束之后,就把整个中间表示(IR),连同日志事件和错误,一起比对一遍。作者为此写的脚本是 compiler/script/test-rust-port.sh,据 PR 描述,它可以指向任意的 JS 目录,计划用于 Meta 的内部测试。
这个教训可以总结成——让 AI 去做大规模移植时,真正要搭建的不是代码,而是比对装置。要让机器能够确认的不是"输出一样",而是"每一个中间步骤都一样",人才可能在不逐行读完 12 万行代码的情况下建立信任。作者说自己"hands-on"参与的清单里包含"testing and verification strategy",这不是巧合。
不过这里也有一处诚实的缺口。
The OXC and SWC example integrations seem to be working well, though i haven't manually verified this to the same extent as i have the Babel integration.
他表示自己没有像验证 Babel 集成那样,对这两者做同等程度的人工验证。
架构 — 同一套算法,不同的内存模型
内部结构出乎意料地保守。按 PR 描述本身的说法,Rust 版和 TypeScript 版是同一套架构。
AST -> HIR (High-level Intermediate Representation)
- CFG (control-flow graph)
- SSA (single-static assignment)
|
+-> 相同的一组 pass,相同的算法
|
v
输出 AST
打开仓库里的 compiler/crates 会发现,这套结构原样拆分成了一个个 crate——react_compiler_hir、react_compiler_ssa、react_compiler_lowering、react_compiler_optimization、react_compiler_reactive_scopes、react_compiler_typeinference、react_compiler_validation 等等。
变化的不是算法,而是数据表示方式。
The main differences are in the data representation - using arena-like structures (and indices into these arenas) to work within Rust's borrowing system.
TypeScript 里靠对象引用编织起来的图,没法原样搬进 Rust——借用检查器(borrow checker)不允许循环引用和可变别名。于是节点被统一塞进一个 arena,改成用索引来引用。这已经是把 CFG、SSA 这类节点互相指向的结构移植到 Rust 时,事实上的标准做法了。
公开 API 也有点特别。
The public API is basically "Rust Babel AST" + Scope Info in, Rust Babel AST out.
Babel AST 的 Rust 表示被当作共享接口,各个集成方(Babel、OXC、SWC)在边界上把它转换成自己的表示,再转换回去。理由很现实——"the conversion from any AST into our HIR is complex, and we can only maintain one version"(把任意 AST 转成我们的 HIR 是件复杂的事,我们只能维护一个版本)。而且,现在作用域信息需要由集成方自己序列化后传进来,因为 TypeScript 版本原本是搭在 Babel 的作用域分析之上的。文中提到有计划实现自己的作用域解析。
这也解释了前面提到的"序列化成本相当高"。把 Babel AST 传给 Rust 再传回来的这一趟往返,加上还要把作用域图也序列化一遍,这两项成本合起来,把转换逻辑本该有的 10 倍收益,啃到只剩下 3 倍。
合并之后的一个月 — 计划变了的地方,仍在漏的地方
从这里开始,是只读 PR 正文会漏掉的部分。提交历史要诚实得多。
序列化成本立刻就被动手处理了。 合并两天后,也就是 6 月 11 日,[rust-compiler] Return the compiled AST by value instead of JSON(#36729)和 [rust-compiler] Carry uninspected AST subtrees as raw JSON text(#36730)被合并了进来。编译好的 AST 现在按值返回而不是 JSON,不需要检查的 AST 子树则干脆以原始 JSON 文本的形式直接携带。前面说"那 3 倍已经过时了",说的就是这件事——产出那个数字的代码,合并之后立刻就开始变了。现在的倍数究竟是多少,没有人公布过。
OXC/SWC 的计划被推翻了。 PR 正文描绘的图景是"我们仓库里按集成负责人各自一个 crate(react_compiler_swc、react_compiler_oxc),大部分逻辑都住在那里"。可是 6 月 11 日,[compiler] Remove OXC and SWC plugins in favor of them being handled by those projects(#36743)被合并了。理由只有一句话——"I believe the consensus is that the OXC and SWC plugins should live in those projects, and instead consume the React compiler as a crate"(我认为大家的共识是,OXC 和 SWC 插件应该归属于那两个项目自身,转而把 React 编译器当作一个 crate 来消费)。事实上,现在的 compiler/crates 里已经没有 oxc/swc 的 crate 了。集成策略在合并两天之内就变了,只读 PR 正文会得到一幅错误的图景。
移植到 Rust 之后,也冒出了新问题。 6 月 16 日的 [rust-compiler] Represent string values as JsString (WTF-16 aware)(#36731)。JavaScript 字符串是 WTF-16 编码,Rust 的 String 是 UTF-8。如果直接把带有不成对代理项(surrogate)的字符串塞进 Rust 字符串,就没法无损地保留原始内容。这是一类 TypeScript 里原本不存在的 bug,随着换语言被带了进来。6 月 22 日还合入了 Use rustc-hash FxHasher for all maps and sets(#36811)这类典型的 Rust 性能调优。
"所有 fixture 都通过"需要加个脚注。 随合并提交一起进来的 compiler/crates/TODO.md 里,有一张 e2e 一致性(parity)快照表。
Variant | Score | Failures
Babel | 1792 / 1802 | 10
SWC | 1786 / 1802 | 16
OXC | 1704 / 1795 | 91
这和 PR 正文里"1725 个 fixture 全部通过"并不矛盾——它们是不同的测试套件。前者是 snap 套件(Rust 插件和现有插件的输出比对),这张表则是把三种集成变体拿去和 TypeScript 实现做比对的 e2e parity 套件。要点在于,按集成拆开看,现在还没有全部通过,文档自己也写着"the OXC row predates the fixtures and has not been re-measured"(OXC 那一行的测量早于这批 fixture,之后没有重新测过)。顺带一提,cargo test --workspace 是 84 个通过、0 个失败。这份文件自 6 月 9 日合并以来没有再改动过,所以现在的数字可能已经不一样了。
而且现在仍在不断地补漏洞。 7 月 8 日的 [compiler] Bail out with Todo on using and await using declarations(#36946)——遇到 using / await using 声明时,现在会直接放弃处理。类似 7 月 7 日的 [compiler] Port JSX tag classification fix to Rust (not-lowercase is a component)(#36951)这种把 TypeScript 那边的修复移植到 Rust 的提交,也一直在持续进来。这里能看到这次移植真正的代价——在相当一段时间里,得同时维护两套实现。
现在能用了吗 — npm 会给出答案
这个问题与其去搜索,不如直接用一条命令来回答,来得更快。
npm view babel-plugin-react-compiler dist-tags
# latest: 1.0.0
# experimental: 0.0.0-experimental-a1856f3-20260507
截至 2026 年 7 月 16 日,latest 仍然是1.0.0(2025 年 10 月的版本),最后一次发布是 2026 年 5 月 8 日的实验构建。也就是说,6 月 9 日合并的那个 Rust 移植,目前还没有对应的 npm 构建产物存在。 PR 里的"No builds available yet"到现在依然成立。
这一点还能交叉验证。OXC 的 React Compiler 文档把这个功能标记为"experimental and under active development",并警告"Options and behaviour may change",同时说明它把这个已经合并进 React、但仍未发布(unpublished) 的 Rust 移植,vendor(内嵌打包)成一个可发布的 crate,放在 oxc-project/forked-react-compiler 里使用。上游不发布,他们就只能 fork 出来自己用。
而且,react.dev 的博客上没有任何一篇关于这次移植的文章。目前最新的一篇是 2026 年 2 月 24 日的 The React Foundation。这是一次没有官方公告、悄悄合并进去的变更。
在这里可以实时看到民间传说是怎么被制造出来的。一搜索,就会看到"Next.js 16.4 搭载了 Rust 编译器,构建速度快了超过 40%"这类句子。查证一下是这样的。
npm view next dist-tags
# latest: 16.2.10
# preview: 16.3.0-preview.6
Next.js 16.4 根本不存在。 那个 40% 的数字也没有出处。一个不存在的版本、一份不存在的基准测试,已经以一句可供引用的话的样子在到处流传了。看框架性能数字时,一行 npm view 比十条搜索结果更靠谱。
编译器现在仍然咬人的地方 — 记忆化(memoization)的安全性
是 Rust 还是 TypeScript,并不会改变编译器在哪里咬人。 因为这只是同一套算法的移植。所以对实务者来说,更重要的其实是这一部分。
React Compiler 知道有些 API 用自动记忆化并不安全,会把这些模块排除在优化之外。这份名单在 DefaultModuleTypeProvider 里,截至 2026 年 7 月,一共只有三个。
react-hook-form—— 只有useForm()返回值里的watch()@tanstack/react-table@tanstack/react-virtual
原因源码注释解释得很清楚。问题出在内部可变性(interior mutability)。有些 API 不是创建一个新值放进 state,而是就地修改值本身,这样一来,即便函数对象没变,调用结果也会不同。React 做记忆化的前提是"内部变了,外部对象也会跟着变",而这个前提在这里被打破了。注释是这样总结这种情况的——在 Rules of React 被精确写成文档之前就已经存在的那些库,无意中发布了一些对记忆化不安全的 API;开发者如果手动给它们套上 memo,应用就会出问题;换成用编译器试试,又会得出"是编译器把我的代码搞坏了"这样的结论。
十天前发生的一件事说明,这不是一个抽象的担忧。7 月 6 日的 [compiler]: add useWindowVirtualizer to known incompatible libraries。早在 2025 年 9 月,@tanstack/react-virtual 的 useVirtualizer 就已经被加进了名单,但 useWindowVirtualizer 被漏掉了。两者是包在同一套内部实现外面的薄封装,所以有着同样的问题。结果很具体——在报告者的应用里,getVirtualItems() 被冻结在了首次渲染时的值上(也就是测量之前的空数组),导致虚拟列表永久性地空着。它就这样被悄悄地按错误的记忆化方式编译进去了。
这里面有两点值得读出来。
第一,这道安全网是一份写死的名单。 只挡得住已知的模块。名单之外、用了同样模式的库(或者你自己公司内部的 hook),会原样通过。OXC 的文档也说了同样的话——"Code that breaks the Rules of React is also skipped rather than optimized — for example interior mutability, or libraries built on observable mutation such as MobX's observer()." MobX 的 observer() 被拿来当例子。
第二,现在得改两个地方了。 #36912 把修复同时放进了 DefaultModuleTypeProvider.ts 和 Rust crate 里的 default_module_type_provider.rs。前面提到的双重维护成本,具体表现就是这样的。
顺带一提,OXC 文档里还写了一个集成顺序上的陷阱——"The React Compiler requires the original source: it must see JSX before any other transform." 如果有其他插件先对 JSX 动手,比如 emotion 的 CSS prop、Babel 的 constant-elements,就会出问题。
Meta 的 12% 和 2.5 倍,量的到底是什么
有一个数字经常和 Rust 这件事混在一起说,这里单独把它拎出来。这是 React Compiler v1.0 发布文章里的一句话。
We've seen initial loads and cross-page navigations improve by up to 12%, while certain interactions are more than 2.5× faster.
把前提精确地补上,是这样的。
- 谁测的:这是 React 团队(Meta)在自家应用上报告的数字,不是独立的基准测试。
- 测的是什么:首次加载和跨页导航最多(up to) 提升 12%,某些(certain) 交互快了 2.5 倍以上。"up to" 和 "certain" 不是装饰词,而是限定范围的词。这不是平均值。
- 什么时候测的:2025 年 10 月,基于的是TypeScript 版本 1.0,和 Rust 移植毫无关系。
- 文中还补充说内存占用是中性的。
而且发布文章自己也写道,"Although your mileage may vary, we recommend experimenting with the compiler in your app to see similar performance gains"。这里还容易搞混的一点是,12% 是运行时性能,而3 倍 / 10 倍是构建期的编译速度——这是两个维度上的数字。混在一起说出"编译器快了 10 倍,所以应用快了 12%"这种话,就是错了两次。
结语 — 现在你该做的事
归纳一下就是这样。
属实的部分:React Compiler 的 Rust 移植已在 2026 年 6 月 9 日合并。架构和 TypeScript 版本完全一致(HIR + CFG + SSA,按 pass 逐一移植),差异主要在于基于 arena 和索引的数据表示方式。架构和验证策略由人主导,实现大部分由 AI 完成,并且配备了一套相当扎实的验证机制——按 pass 逐一比对 IR。
很难称得上属实的部分:"10 倍"。这是一个由 AI 得出、作者本人声明没有验证过基准测试设置的数字;端到端算下来其实是 3 倍,而产出那个 3 倍的代码,从合并那一刻起序列化路径就已经在变,早就过时了。"Next.js 16.4 的 40%"这类说法,说的干脆是一个根本不存在的版本。
对你来说会改变的事:截至今天,什么都不会变。npm 上的 latest 是 1.0.0,Rust 构建也还没发布。如果你现在正在用 React Compiler,用的就是 TypeScript 1.0,这就是正常状态。如果说有什么值得提前准备的,也就是 v1.0 发布文章早就建议过的——锁定到精确版本(用 1.0.0 而不是 ^1.0.0),并配好 e2e 测试。这条建议背后的理由是,记忆化的生效方式可能会随版本而变化,而在整套实现被彻底替换的过渡期里,这一点会更加突出。
下次该什么时候再看:npm view babel-plugin-react-compiler dist-tags 会给出答案。该关注的时间点是 Rust 构建被正式发布、react.dev 上出现官方文章、以及 crates/TODO.md 里的 parity 表被整理清楚的那一刻。在那之前,说明真实状态的是仓库本身,不是某条基准测试推文。
最后,这整件事里最经得起时间考验的教训,好像不在性能,而在方法论。用 AI 移植一个 12 万行的编译器,真正建立起信任的不是代码评审,而是一套机械地比对每一个 pass 中间表示的装置。同时也值得记住这个方法的局限——它之所以能成立,是因为这个问题本来就有标准答案。而写下这个 PR 的人,明明给自己的数字打上了"没有验证过"的脚注,生态圈却把这条脚注去掉之后引用了它——这一点,或许才是那个会被反复重演更久的教训。
参考资料
- PR #36173 —
[compiler] Port React Compiler to Rust(josephsavona,2026-03-30 开启 / 2026-06-09 合并) - PR #36743 — 移除 OXC/SWC 插件,改由对应项目将其作为 crate 来消费
- PR #36912 — 把
useWindowVirtualizer加入已知的不兼容库名单 - PR #34493 — 把
@tanstack/react-virtual加入已知的不兼容库名单(2025-09) compiler/crates— Rust crate 的结构与TODO.md的 parity 快照DefaultModuleTypeProvider.ts— 内部可变性与不兼容库名单- React Compiler v1.0(react.dev,2025-10-07) — Quest Store 的 12% / 2.5 倍,以及版本锁定建议
- React 博客 — 最新一篇是 2026-02-24 的 The React Foundation(没有 Rust 移植的官方公告)
- OXC — React Compiler 集成文档(实验性,vendor 了未发布的移植版本)
- oxc-project/forked-react-compiler — OXC vendor 的那个 crate
- Next.js 16 架构 — 深入 Turbopack 与 Cache Components(相关文章)
현재 단락 (1/99)
React Compiler 看起来像是一个已经画上句号的故事。[React Compiler v1.0](https://react.dev/blog/2025/10/07/react-compil...