Cloudflare 用 Rust 重写了 TypeScript:我拿同一批代码跑了跑,报错一模一样,退出码不一样

TS7(Go 原生编译器)7 月初发布的时候,我写过一次它的升级体验,当时觉得这事已经翻篇了:微软自己下场重写,10 倍速度,生态大结局。 结果 10 月 20 号 Cloudflare 发了篇博客,标题就叫 "Introducing tsrs: A Port of TypeScript to Rust"。我第一反应是揉了揉眼睛------不是已经有 Go 版了吗,为什么又有人把一个编译器重写了一遍?还是 Cloudflare 牵头,跟 VoidZero 一起做的,孵化在 Oxc 工具链里,Hacker News 上挂着 371 分和一百多条评论。 点进去看完博客,又翻了一遍 GitHub 上的兼容性文档,我觉得这事比"又一个重写"有意思。当天就在我那台 1核2G 的小机器上装了 tsrs,拿同一批代码跟 tsc 对着跑了一遍。这篇记录一下过程和两个我没想到的发现。

先说清楚这是什么,不是什么

博客里专门强调了一句:不是 fork。tsrs 是对 TypeScript 7 编译器(Go 实现)逐函数的移植,GitHub 上的兼容性文档写得很直白:"Go code is the spec"------只要诊断、诊断顺序、消息文本、退出码跟 Go 版对不上,就算 tsrs 的 bug,哪怕 tsrs 打印出来的东西看起来更好。

这个表态我觉得挺关键的。前几个月 AI 批量移植 TypeScript 的"实验项目"满天飞,微软都没搭理。而这次 TypeScript 团队是明确合作的,npm 包的版本号也带上了上游 commit:我装到的 0.18.0-ts7.1.0-dev.20260929,前面那串日期就是它移植的上游提交快照,后缀对应的正是 microsoft/TypeScript@b85298b6a81f 这个 7.1.0-dev 提交。仓库里每晚会跑 TypeScript 官方测试套件生成兼容性矩阵,玩法跟 Cloudflare 维护 Vinext 同步 Next.js 是同一套。

那为什么有了 Go 版还要 Rust 版?官方给的理由我挑实际的说:Go 编译成 WebAssembly 时得把 GC 一起塞进去,Rust 不用。也就是说 TS7 想进浏览器基本没戏,tsrs 可以 。他们已经发了 @tsrs/wasm 包,还有一个跑在浏览器里的 playground。剩下的理由(性能、内存)博客里给了官方数据:同配置下比 TS 7.0 快至多 8 倍、省约 70% 内存。 数字先放这,我自己跑出来的体感跟 8 倍有差距,写在后面。

我最关心的不是快不快

编译器慢一点,无非是多等几秒。但类型检查器换血,最怕的是它报的错和原来不是同一套------CI 里把 tsc 换成另一个编译器,如果有个本来该报的错它没报,那不是优化,是放行了一个线上 bug。 所以我没先跑分,先干的事是造一个"审计靶场":一个分层的小项目,models、utils、services 加一堆生成的 catalog 模块,最后扩到 199 个 .ts 文件,然后在里面埋了 6 类我平时真实见过的类型错误:

  • 给 string 字段塞 number(TS2322)
  • 可能为 undefined 的值直接取属性(TS18048)
  • 把 number 当函数调用(TS2349)
  • 访问接口上不存在的属性(TS2339)
  • 联合类型收窄失败再赋值(TS2322)
  • 泛型约束违反,传了个缺字段的类型进去(TS2741)

最后这条我埋得比较刁钻:函数签名是 <T extends Auditable>(a: T, b: T) => T,实参却是个没有 auditLog 字段的 StockRecord。这种情况下编译器得先把 T 推断失败回退成 Auditable,然后后面取 sku、warehouse 会连带报出三条 TS2339。连带错误报不报、怎么报,是最能看出两个编译器类型推断是不是同一套逻辑的------单个错误两边都报不奇怪,推断回退的涟漪都一致才算真像。 这 6 处错误一共牵出 10 条诊断。我把两边的原始输出 diff 了一下:

css 复制代码
$ diff <(tsc -p . 2>&1) <(tsrs -p . 2>&1) && echo SAME
SAME

逐字符一致。错误码、文件行号、消息文本、打印顺序,全都一样。10 条里有 4 条是那个泛型推断回退连出来的(1 条 TS2741 加 3 条 TS2339),连这种推断失败的余波都一模一样。

这个结果跟官方兼容性文档里的数字对得上:TypeScript 一致性测试套件里,tsrs 对齐了 13,458 / 13,462 个错误基线,差 4 个。我这点样本肯定撞不到那 4 个角落,但 10 条全中至少说明日常错误场景下它确实是"同一套"。顺带说一句,博客里提到"诊断打印顺序目前有差异"这个已知问题,在我这个规模的项目里没有复现出来,可能要更大的仓库或者并行 worker 下才会分叉------这条我没验证,不瞎下结论。

一个我没想到的差异:退出码

一致性验证完,我顺手看了眼脚本的退出码,结果发现一个有意思的事。 同一个埋错的项目,跑完之后:

ruby 复制代码
$ tsc -p tsconfig.json; echo $?
1
$ tsrs -p tsconfig.json; echo $?
2

tsc 7.0.2 退出码是 1,tsrs 是 2。换成 @tsrs/wasm 跑,也是 2,它的 README 里甚至直接用注释标了 result.exitCode; // 2。而一个没有任何错误的项目,两边都规规矩矩返回 0。 也就是说"有没有错"这个大方向两边没问题------非零就是有错,大多数 CI 判断 if ! tsc 不受影响。但如果你的流水线里有那种写死 -eq 1 的老脚本,或者靠退出码区分"编译错误"和"配置错误"的工具链,升级前真得确认一下。tsrs 的 wasm 文档里写了它的退出码语义:0 到 4 是编译器常规状态,5 是 wasm 层面的崩溃或内存不足。

这个差异说不好该不该算 tsrs 的锅------它 track 的是 7.1.0-dev,也可能是上游 7.0 到 7.1 之间行为就变了。我没有去考古微软的 changelog,只把现象记在这里。但"退出码变了"这种事,恰恰是换检查器时最容易漏看的缝隙,因为它不体现在编译输出里。

速度:1核2G 上的体感

跑分的事放到后面说,因为它真不是重点。而且我的机器条件跟官方 bench 完全不可比:1 核 2G 的容器,项目也就是 199 个文件、一万多行的规模,不是 monorepo。 用 bash 的 time 简单测了几轮,取稳定值:

项目规模 tsc 7.0.2 tsrs 0.18.0
35 个文件 ~125ms ~70ms
199 个文件 ~195ms ~87ms

大概 1.8 到 2.2 倍的样子,跟官方的 8 倍差距很大。原因不难想:官方是大型仓库在大机器上同配置对比,类型检查本身占大头;我这种小项目,进程冷启动占了将近一半时间(两边 --version 都要 35-45ms),怎么算都快不起来。所以如果你跟我一样是中小项目,别指望装完 tsrs 编译时间除以 8------快一倍上下是更现实的预期。

另外必须交代口径:tsrs track 的是 7.1.0-dev,tsc 是 7.0.2,严格说不是同一版源码的对比。官方那张 8 倍的图也是跟 TS 7.0 比的,而 TS 7.1 beta 自己又比 7.0 快 2.5 倍,其中一部分优化还是从 tsrs 反向贡献回去的。这条赛道现在是你追我赶的状态,谁也别急着下"谁碾压谁"的结论。

WASM 是真正的差异化,但也别神化

@tsrs/wasm 我单独试了一下。同版本号,wasm32-wasip1 编译,Node 22 以上和浏览器都能跑。用它的 API 检查我那个 199 文件的项目:

ini 复制代码
run1: exit=2 1075ms mem=110MB
run2: exit=2 417ms mem=110MB

首次运行带 wasm 初始化,一秒出头,之后四百多毫秒,110MB 内存,10 条错误一条不少。它还支持内存文件系统,浏览器里扔几个字符串进去就能拿到带 UTF-16 坐标的 JSON 诊断,完全不用服务端。官方那个 playground 就是这么跑的。

这事 TS7 确实做不到------Go 的运行时进不了 Wasm。所以"浏览器里直接做类型检查""在 Cloudflare Worker 沙箱里跑完整开发环境"这类场景,目前只有 tsrs 这条路。不过它现在是单线程的,大仓库上 wasm 版跟原生版的差距会很明显,按官方文档的说法这是当前的限制,不是设计终点。

泼冷水时间

测完之后我的整体判断是:方向对、底子硬,但现在把它当生产工具还有几个坎,有几个我直接劝退:

最硬的一条:Windows 用户现在装不了。COMPATIBILITY.md 写得很清楚,0.18.0 及之前只有 macOS arm64 和 Linux x64/arm64(glibc/musl)的二进制,Windows x64 在"下个版本"的配置里,Intel Mac 也没有。Wasm 版倒是平台无关,但那是另一回事。

再往下试,坑就零碎了。--init、--showConfig 直接不支持,--locale 被忽略(只有英文消息),--generateTrace 也没有。--watch 在 Linux 和 macOS 上能用,官方说 39/42 个 watch 场景基线通过,但 Windows 上会直接报不支持。

版本口径也是个实际麻烦:npm 上 latest 标签现在指向 7.1.0-dev 的移植,它跟着上游 dev 分支走,今天装和下周装可能就是不同的快照。用在 CI 里意味着你的类型检查结果会随上游漂移------虽然理论上"Go 版什么样它就该什么样",但 dev 分支本身的语义就可能变。

所以我的结论是分场景的:Linux/macOS 上的 CI、想省内存的构建环境、以及任何要做浏览器内类型检查的场景,值得现在就上手试;Windows 开发机为主、或者对退出码和诊断格式有硬依赖的老流水线,等它发稳定版再说。两个都是实验版本,输了也就是回退一条 npm 命令的事。

至于"以后要不要从 tsc 迁出去",我暂时没有答案。tsrs 和 TS7 本来就是一家亲(优化互相回流),两边稳定版都还没影,现在选边没意义。除非你卡在 WASM 这个场景上,那种情况下眼下确实只有 tsrs 能干这件事。

相关推荐
落花看风雪1 小时前
从零开始搭建后台系统(vue3+TypeScript+Vite)01
typescript·vue·js
行者全栈架构师6 小时前
我让 AI 独立开发了一款游戏:零依赖、188 个测试全绿、工期压到四分之一
typescript·canvas·vibecoding
Eyfcom7 小时前
〖深度〗鸿蒙 ArkTS 移动应用开发经验:从 TypeScript 心智模型到声明式运行时的六层认知重构
typescript·移动开发·harmonyos·arkts
miofly8 小时前
GitHub 今日推荐|pandacode:本地桌面统一管理多个AI编码代理
rust·开源·github
怪我冷i9 小时前
Rust 核心语法与内存安全机制
开发语言·安全·rust
程工造Agent1 天前
从 0 到 1 学 Codex 源码:顺着 main 读懂 Rust 闭包与 async move
rust·ai编程
shimly1234561 天前
(done) RUST 指针详解
rust
geovindu1 天前
rust: Template Method Pattern
开发语言·后端·设计模式·rust·行为模式·模板方法模
Patrick_Wilson1 天前
悬浮提示从哪来:LSP、tsserver 与 .d.ts
typescript·npm·前端工程化