vatt'ghern jaskier's ballads
本文 1 個互動圖表在手機上以重點摘要呈現,互動版請以桌面瀏覽器開啟。

Cloudflare 說 tsrs 最高快 8 倍、記憶體少約 70%,但這兩個數字各自掛著條件:對照的是 TypeScript 7.0、type check worker 數相同,而且是與 TypeScript 團隊確認過的同一組設定。條件拿掉之後,倍數還剩多少意義,才是這篇要拆的事。

tsrs 的 8x 與 70% 該怎麼讀

十月二十日,Cloudflare 的部落格宣布它與 VoidZero 正和 Owner.com 的 Max Schwenk 一起做 tsrs,一個 TypeScript 編譯器的 Rust 移植版。文章給的賣點是兩個數字:最高快 8 倍,記憶體少約 70%,基準是 TypeScript 7.0。這篇整理的重點不在重述數字,而在數字附帶的條件,以及移植策略本身——不分叉、跟著上游走、靠 agent 每天同步——對編譯器與工具鏈設計有什麼含意。來源只有這一篇廠商部落格,沒有獨立重現,下面凡是「Cloudflare 說」的地方,都請照這個強度讀。

先看它不是什麼

文章第一句就把關係交代清楚:「Cloudflare and VoidZero are now working with Max Schwenk of Owner.com on tsrs, a Rust port of the TypeScript compiler.」tsrs 在 Oxc 這套 JavaScript 編譯器工具鏈裡孵化,文章的措辭是「incubating tsrs as part of the Oxc JavaScript compiler toolchain」。動機寫得很直接:「Our goal is to help developers spend less time waiting for their code to be checked and more time building.」要縮短的是等型別檢查的時間。

接著是一個聲明,也是整個專案的定位:「To be clear, we are not forking TypeScript.」理由是 TypeScript 團隊「should remain in full control of the language’s design and direction」,而移植版必須「follow those decisions closely, or it will eventually fall apart and become incompatible」。文章對自己的描述也一致:「TypeScript in Rust aims to be a faithful port of TypeScript.」

把這三句放在一起讀,合理的推測是:忠實移植是一種把設計權留在上游的工程選擇。語言的行為由 TypeScript 團隊決定,移植版的工作就變成追蹤與對照,而不是提出自己的型別系統規則。好處是使用者不必擔心兩套語意分岔;代價是移植版永遠在追人,任何上游變動都要在下游重做一遍。後面談維護的那一節,說的就是這筆代價怎麼付。這段推論是我的解讀,文章沒有這樣總結。

文章自己的分類可以借來用:slopforks、faithful ports、completely re-architected versions 是三種不同的做法。換成設計取向,可以沿兩條軸看——語意是否與上游一致,以及內部架構是否沿用上游的結構。忠實移植把兩條軸都釘在上游,換到的是可預期的相容行為,失去的是為 Rust 重新設計資料結構的自由;重新設計架構的版本相反,有機會更快,但每一個與上游不同的決定,都是未來相容性的負債。tsrs 把自己放在第一種,這解釋了為什麼文章一再強調跟隨與同步,而不是強調新的架構。這是我對分類的延伸,兩條軸線是我加的,文章只給了三個名詞。

文章也交代了時間背景。它提到 TypeScript 團隊自己做過把 TypeScript 從 JavaScript 移植到 Go 的工作(「the TypeScript team’s port of TypeScript from JavaScript to Go」),也提到最近出現的各種移植:「We have now seen slopforks, faithful ports, and completely re-architected versions of TypeScript in Rust or C++.」讓這類移植變得可行的原因,文章歸給 AI:「However, frontier AI models can now do large-scale ports almost independently in a matter of days.」

這句話要小心讀。它說的是大規模移植幾天就能做出來,沒有說做出來的東西品質如何,也沒有說 tsrs 本身是這樣做的。從這裡可以再推一步:如果做出一個能跑的移植版變便宜了,稀缺的就變成「哪一個移植版會被持續維護、跟得上上游」,而這正是文章後半花篇幅解釋的事。這個推論同樣是我的,不是文章的。

8x 與 70% 在量什麼

先交代一個背景,這段是一般知識,不是來源內容。型別檢查器要為整個專案的程式與依賴建立符號與型別資訊,所以它的耗時與記憶體都隨專案規模成長,大型專案的 CI 與編輯器回應常常卡在這裡。速度和記憶體是兩個不同的帳:速度影響等待,記憶體影響能不能在小機器、容器或瀏覽器分頁裡跑得動。文章把兩個數字並排放,反映的正是這兩種成本。

效能的主張只有一句話:「tsrs is up to 8x faster and uses ~70% less memory compared to TypeScript 7.0」,後面接著一個條件:「compared to TypeScript 7.0 when using the same number of type check workers」。整段的開頭語氣是「Early benchmarks of our Rust port of TypeScript are promising」,也就是早期結果。up to、~、early 三個修飾疊在一起,所以這兩個數字應該讀成「目前最好情況的約略上限」,而不是「平均會快八倍」。

兩個數字的說法方式不同,直接並排容易誤讀。「快 8 倍」換成時間是原來的八分之一,也就是 12.5%,等於省下 87.5% 的時間;「少約 70% 記憶體」是剩下約 30%,換成倍數約是 3.3 倍的差距。也就是說,若兩個上限同時成立,時間縮得比記憶體多得多。這些換算是我做的算術,來源沒有寫。另外 up to 的「最高」與 ~ 的「約」是兩種不同的不確定性,前者是範圍的上緣,後者是數值的誤差。

up to 是上限,代表所測案例裡最好的那一個,不是中位數。我讀到的文字沒有列出是哪些專案、哪些機器、跑了幾次;文章只交代了基準放在專案的 bench 目錄。所以「八倍」比較像一個值得拿自己的程式碼去驗證的起點。

拖動兩條滑桿,填入你自己的 TypeScript 7.0 數字 · 2 個輸入

60 秒
2.0 GB
宣稱上限下的對應值,基準是你填的假設數字,不是來源資料 型別檢查時間 TS 7.0 基準 60.0 秒 7.1 beta ÷2.5 24.0 秒 tsrs ÷8 7.5 秒 0100 秒200 秒300 秒 記憶體 TS 7.0 基準 2.0 GB tsrs 少約 70% 0.6 GB 08 GB16 GB 上限情境下,60 秒的檢查約 7.5 秒;實際落點介於兩者之間,要自己量。
上面的小工具把「最高」具體化:除以 8、除以 2.5、乘以 0.3 都是我拿來源倍數做的算術,輸入值是你的假設。它畫的是宣稱的上限,不是預測。

在上限情境下,如果你的 CI 型別檢查要 60 秒,tsrs 的對應值是 7.5 秒;記憶體 2 GB 的專案,對應值約 0.6 GB。實際會落在基準與上限之間的哪裡,沒有任何一篇部落格能替你回答,要靠自己的專案去量。

文章還放了另一個數字:TypeScript 團隊這邊,「making TypeScript 7.1 beta up to 2.5x faster than TypeScript 7.0」,並說「Some of these optimizations were upstreamed from tsrs, and we're hoping to find more.」tsrs 的一部分優化已經被回送到上游。把兩個上限放在同一個基準 7.0 上,8 除以 2.5 是 3.2;這是我對兩個上限相除的算術,來源沒有直接比較 tsrs 與 7.1 beta,也沒有說兩個上限出現在同一個專案上。比較保險的讀法是:移植版的價值有一部分,已經以「上游變快」的形式兌現。

如果要我選文章裡最不容易被基準細節動搖的一句,會是「Some of these optimizations were upstreamed」。它不依賴 8x 的測法,而是一個可以到上游紀錄裡核對的事實:哪些優化來自哪個提交。我沒有去核對,所以這裡只能說它可核對,不能說已核對。

「相同的 type check worker 數」這個條件要拆開看。type check worker 指並行跑型別檢查的工作單位;兩個實作若各自開不同數量的 worker,比的就不只是單一 worker 的效率,而是誰多開了幾個。把 worker 數對齊,等於把比較收窄到每個 worker 的實作效率與記憶體佈局,這也是它能同時談速度與記憶體的原因。以上對 worker 的解釋是背景知識加上我的推論,文章沒有展開這個條件。

比較條件是這段最有用的部分。文章寫道:「We worked with the TypeScript team to ensure the above benchmarks are comparing the same configuration.」它也點出別人的數字為什麼互有出入:近來各種 TypeScript 移植版的基準主張,文章的解釋是常常因為「often due to using larger machine types and different options compared to TypeScript's defaults」。機器規格與非預設選項,是它列出的兩個容易讓倍數失真的地方。tsrs 這邊的回應,是同一組設定、相同的 type check worker 數。

讀廠商基準時,我習慣先列幾個問題,再看文章答了哪幾個。基準版本是哪一版:答了,TypeScript 7.0。worker 數是否對齊:答了,相同。選項是否預設:答了,與 TypeScript 團隊確認過同一組設定。機器類型:文章批評別人用了更大的機器,自己的規格我讀到的文字沒有寫明。專案是哪些、多大:沒有列出。是中位數還是最好案例:寫的是 up to,也就是上限。冷啟動還是熱快取、跑了幾次、有沒有變異範圍:我讀到的文字沒有交代。七個問題裡,文章明確回答了三個,剩下的要靠自己的專案去補。

取消勾選任一條件,看 8x 與 70% 還能不能直接引用 · 3 個條件

三個條件都成立。這是文章比較 8x 與 70% 的情境,數字仍是 up to 的早期結果,可以引用,但要連條件一起引。

  • worker 數相同:文章的比較設定,8x 與 70% 都附著在這個條件上。
  • 預設選項、同一組設定:文章說它與 TypeScript 團隊確認過兩邊比較的是同一組設定。
  • 同級機器:文章沒有逐項列出機器規格,這一格是依它對其他基準的批評推出的前提(我的推測)。
三個條件裡,前兩個有文章的明文依據,第三個是推測,清單裡有標出來。

三個條件裡,前兩個有文章的明文依據,第三個是推測,清單裡有標出來

8x 與 70% 都附著在相同 worker 數與同一組預設設定上,條件一旦被換成更大的機器或非預設選項,這些倍數就不能直接拿來對照。

不過「相同 worker 數」有一個容易漏掉的後果。文章接著說,tsrs 預設出貨的是依機器類型調整的自適應設定,也就是「uses more memory but is even faster」。照字面讀,預設模式更快,但用更多記憶體;而 70% 的節省是在相同 worker 數的對照下得到的。合理的推測是:你直接跑 tsrs 看到的記憶體用量,不一定等於文章寫的那個 70%。我讀到的文字沒有給預設模式的具體數字,所以這只能是推測,要自己量。

移植版和上游的關係還有一個容易忽略的面向:版本號。文章的基準對象是 TypeScript 7.0,同時提到 7.1 beta;上游本身在動,移植版對照的就是一個移動的目標。每次上游釋出新版,過去量到的倍數就得重量一次。所以文章的數字帶著日期:2026 年 10 月 20 日發布的文章,對照的是當時的 7.0。

為什麼是 Rust,而且是現在

「為什麼是 Rust」那一節的論點集中在 WebAssembly。文章寫「Rust offers some unique advantages」,並以在 WebAssembly 中執行為例,連結指向 @tsrs/wasm 這個 npm 套件。對照的是 Go:「When you compile a Go program to Wasm, you have to compile Go’s garbage collector along with it.」編譯器若是 Go 寫的,要在瀏覽器裡跑,連垃圾回收器也得一起編進 Wasm 模組。

編譯器以 Go 寫成,編到 Wasm 編譯器以 Rust 寫成,編到 Wasm WASM 模組 WASM 模組 型別檢查邏輯編譯器本身的程式 型別檢查邏輯編譯器本身的程式 Go 的垃圾回收器文章說要一起編進去 沒有對應的方塊按文章的對照,不必帶垃圾回收器 示意圖,方塊不代表大小。文章沒有給 Go 版與 Rust 版 Wasm 模組的尺寸, 也沒有給垃圾回收帶來多少執行期開銷,圖只表示「有沒有這一塊」。
依據的是文章那一句關於 Go 與垃圾回收器的描述;右欄的「沒有」是對照的另一面,不是文章另外量測的結果。

這張圖是示意,沒有尺寸。文章沒有給出 Go 版 Wasm 模組比 Rust 版大多少,也沒有給出垃圾回收帶來多少執行期開銷,所以圖裡的方塊只表示「有沒有這一塊」,不表示大小。把它放在這裡,是因為這個論點本身是結構性的:同樣的型別檢查邏輯,Go 的路徑多帶一個執行期,Rust 的路徑按文章的對照則沒有。至於這個差別在實際的瀏覽器體驗裡有多大,來源沒有回答。

要公平地說,這個論點只涵蓋 Wasm 目標。原生執行時 Go 與 Rust 各有取捨,文章沒有討論,也不需要討論;它要回答的是「為什麼 Rust 對這個專案的目標更合適」,而不是「Rust 是否整體優於 Go」。把兩個問題分開,才不會把一個針對 Wasm 的論點讀成對語言的整體評價。

文章把這條路和自家平台接起來,提到 Workers 上的 Rust 支援與「the new Emscripten target for wasm-bindgen」有關。文章想描繪的目標,是讓完整的開發環境出現在更多地方,「including right in your browser」。型別檢查是完整開發環境裡最吃資源的一塊,所以它能不能在 Wasm 裡跑得動,是這個目標成不成立的關鍵一環——這句是我的推論,文章沒有這樣排序。

把「為什麼是現在」和「為什麼是 Rust」接起來看,文章給了兩個獨立的理由:移植的門檻被 AI 降低,以及 Wasm 這個目標平台對 Rust 比較有利。一個關於供給,一個關於需求,缺一個都說不通:沒有便宜的移植,Rust 版不會出現;沒有 Wasm 與瀏覽器內開發環境的需求,移植到 Rust 而不是沿用 Go 版的理由就薄了。這個歸納是我的,文章把兩個理由分在兩節講。

如果文章那句關於 AI 的話成立,影響不止於 tsrs。任何一個定義清楚、有大量測試可對照的專案,移植成本都會下降,工具鏈裡「這個工具有沒有 Rust 版」會變成常態問題。同時,維護成本不會同樣下降:每個移植版都得面對同步問題。供給變多,篩選標準會從「能不能做出來」移到「誰維護得久」,這也是我前面說的稀缺轉移。這段是推論,建立在文章那句 AI 能力的主張上,而那句主張本身,我讀到的文字沒有給出實測。

不分叉的代價,是每天都要跟上游

忠實移植的難處不在第一版,在第二十版。文章對維護的說法分三塊。第一塊是合併:「we are working with Theo Browne to merge tsc-rs and tsrs into a single package」,目標是一個「supports all of TypeScript's features including Language Server Protocol (LSP) and content mappers」的套件。換句話說,現在的 tsrs 還不是這個完整形態;LSP 與 content mapper 屬於合併後要達到的範圍,這是我從「目標」這個詞讀出來的,文章沒有逐項列出目前缺什麼。

Cloudflare VoidZero Max Schwenk(Owner.com) Oxc 工具鏈tsrs 的孵化處 tsrsTypeScript 編譯器的 Rust 移植版 TypeScript 團隊掌握語言設計與方向 Oxlint tsc-rs TypeScript Go 程式碼庫 協作 孵化 跟隨 計畫整合 合併 嘗試回送 示意關係圖,箭頭只表示文章描述的方向,不表示程式碼依賴。
圖中每一條邊都對應文章裡的一句話:協作、Oxc 孵化、TypeScript 團隊掌握設計、與 tsc-rs 合併、整合進 Oxlint、嘗試把效能改進回送。

不分叉還帶來一個結構上的後果:移植版的 bug 有兩種來源,一種是移植時翻錯,一種是上游改了而移植版還沒跟上。前者靠測試抓,後者靠同步流程抓。文章描述的 agent 流程對準的是第二種;第一種,文章這一段沒有談,可以預期它靠相容性測試之類的手段,但這只是我的推測。

第二塊是同步。文章拿 Vinext 的做法當先例:「Each morning, an agent reviews the changes, fetches diffs, and opens tracking issues for anything that could affect Vinext.」然後說「We will apply the same process to TypeScript」。也就是每天早上有 agent 看上游的變更、抓 diff、對可能影響移植版的改動開追蹤 issue。人在這個流程裡做判斷與修正,agent 做篩選與記錄——這個分工是我從句子結構推的,文章沒有說人做什麼。

agent 審閱變更並開 issue,優點是不會漏看,缺點是會產生雜訊:每天都有一批「可能影響」的 issue,需要有人分級。文章沒有說明分級標準。我猜它會落在兩個極端之間——全開 issue 然後靠人過濾,或是只開高信心的項目——但這是猜測。對外部使用者而言,影響最大的是這個流程的延遲:上游修了一個行為,下游多久之後跟上,而這個數字文章沒有給。

Vinext 的既有流程,文章說 tsrs 會沿用到 TypeScript 1上游 TypeScript出現變更 2agent 每天早上審閱變更 3抓取 difffetches diffs 4對可能影響的改動開追蹤 issue 文章沒有說明的部分 誰來修、怎麼驗證、目前落後上游多少 實線方框是文章逐字描述的步驟,虛線方框是我讀到的文字沒有交代的部分。
方框 2 到 4 對應 Vinext 那句原文的三個動詞:reviews、fetches、opens。

第三塊是承認現況還有差:「there are some differences to the upstream version of TypeScript right now」,例子是診斷訊息輸出的順序(「the order in which diagnostics are printed」),目標是「We aim to reduce those differences to a minimum by the time we publish a stable release.」換成工程語言:現在是實驗版,穩定版之前行為還會收斂。如果你的 CI 會比對 tsc 的輸出文字,診斷順序不同就會讓 diff 變髒,這是我對使用情境的推演,不是文章的警告。

回送的方向也寫了:效能改進「we’ll attempt to contribute them back to the TypeScript Go codebase」。要看的是 attempt 這個字,它是嘗試,不是承諾。前面 7.1 beta 已經收到一部分來自 tsrs 的優化,目前文章能指出的已回送內容只有那一批,後續取決於上游願不願意收,這是我從 attempt 這個措辭做的推測。

接下來可以觀察的事,文章本身已經點出幾項:tsc-rs 與 tsrs 是否真的合併成單一套件、LSP 與 content mapper 是否出現、診斷順序等差異是否在穩定版前收斂、效能改進有多少被上游收下。每一項都是可以驗證的事實,不需要靠猜。我會特別看最後一項,因為它同時反映兩件事:移植版的優化是否通用,以及上游願不願意把外部的成果納入。

對編譯器與工具鏈設計的含意

文章的下一步清單透露了它想把型別檢查放在哪裡:整合進自家的堆疊,例如「for example for type-aware lint rules in Oxlint」。型別感知的 lint 規則需要型別資訊,傳統上這代表要呼叫 tsc 或 TypeScript 的語言服務;如果型別檢查器是同一條工具鏈裡的 Rust 元件,lint 與型別檢查就有機會共用解析結果與記憶體。「有機會」是我的推測:文章只說了要整合,沒有說共用什麼。

Oxc 這邊的動作是:「The Oxc team has already begun refactoring the codebase to make it faster, use less memory, and make it maintainable.」TypeScript 團隊也沒有停:「they are experimenting with memory and performance improvements for tsc.」幾方各自在動,結果之一是 7.1 beta 的 2.5x。對選型的人,這代表「tsc 比現在快」與「tsrs 比 tsc 快」這兩個差距都會變動,今天量到的倍數不是永久的。這是推論;文章沒有預測倍數會怎麼變。

如果只留三個設計上的觀察,我會留這三個,都是推論。一個是比較不同實作的效能時,先對齊並行度與設定,再談倍數;文章的條件寫法值得照抄。另一個是忠實移植的產品包含它的同步流程,審閱上游與其說是維護細節,不如說是這個專案能不能存活的核心機制。還有一個是目標平台會反過來決定語言:想在 Wasm 與瀏覽器裡跑完整工具鏈,垃圾回收器要不要一起帶,會變成選語言時的第一批問題之一。

不同的讀者要看的欄位也不同。管 monorepo CI 的人,關心的是相同 worker 數下的時間與記憶體峰值,以及診斷輸出順序不同會不會讓比對失效。用編輯器的人,要等 tsc-rs 與 tsrs 合併後 LSP 的支援,文章把 LSP 列在合併目標裡,現況沒有說明。想把型別檢查搬進瀏覽器或 Workers 的人,要看 @tsrs/wasm 這個套件,文章只用連結帶到它,沒有給模組大小或載入時間。三種人讀同一篇文章,答案的完整度不一樣,這本身就是文章目前的狀態。

給實際要決定的人,我會這樣用這篇文章。文章給的試用方式很單純,從 npm 安裝 tsrs 套件;它稱這是「an experimental release」,所以先別放進會擋合併的 CI 流程。要驗證八倍與七成,就照文章的比較條件做:固定 type check worker 數、用 TypeScript 的預設選項、同一台機器,在你自己的專案上兩邊各跑一遍,同時記錄時間、記憶體峰值與診斷輸出的差異。再單獨跑一次 tsrs 預設的自適應模式,看它用多少記憶體、快多少——這是文章沒給數字的那一格。

跑自己的基準時,有幾個做法能讓結果可比:兩邊都先暖機再計時,固定同一份 lockfile 與同一個 commit,記錄記憶體用的是峰值而不是某一刻的值,重複多次並留下範圍而不是只留一個數字。這些是一般基準實務,不是文章的要求;用在 tsrs 上,它們能補上文章沒有交代的變異範圍。

證據強度也要說清楚。來源是 Cloudflare 自己的部落格,數字是自己的基準;文章說明與 TypeScript 團隊確認過比較的是同一組設定,但本篇只讀了這一份來源,沒有第三方重現可對照。Rust 對 Go 的論點只講了 Wasm 這一個方向,沒有談原生執行時的取捨。維護模式能不能持續,要看之後幾個月上游變動進來時的實際表現,現在只有流程描述,沒有同步落後多少的資料。

The call:把 tsrs 當成值得在自己專案上量一次的實驗版。8x 與 70% 是 Cloudflare 在相同 worker 數、同一組設定下,對 TypeScript 7.0 量到的上限;預設自適應模式的數字、穩定版的行為、與上游的同步落後程度,來源都還沒有給。