尤雨溪迴應:Vite 真的比 Turbopack 慢 10 倍嗎?

語言: CN / TW / HK

原文:https://github.com/yyx990803/vite-vs-next-turbo-hmr/discussions/8

作者:尤雨溪

首發於公眾號前端從進階到入院,歡迎關注。

一週前,Vercel 宣佈了 Webpack 的基於 Rust 的繼任者 Turbopack。

在公告中,Turbopack 宣稱“比 Vite 快 10 倍”。 Vercel 的各種營銷材料都重複宣揚這句話,包括推文,博客文章和發送給 Vercel 用户的營銷電子郵件。Turbopack 的文檔中還包括了 benchmark 圖,最初表明,使用 TurboPack 的 Next.js 13 可以在 0.01s 中執行 React HMR 熱更新,而對於 Vite 來説需要 0.09s。也有用於冷啟動性能的 benchmarks,但是由於沒有發現冷啟動速度是 Vite 10 倍的比較,因此我們只能假設“10 倍快”是基於 HMR 的性能。

Vercel 沒有在營銷材料或文檔中使用用於論證這些數字的 benchmarks 的任何鏈接。因此,我很好奇,並決定使用剛發佈的 Next 13 和 Vite 3.2 的 benchmark 來驗證自己的主張。代碼和方法在此處開源。

我的方法的要點是通過測量以下兩個時間戳之間的增量來比較 HMR 性能:

  1. 修改源文件的時間,通過單獨的 node.js 進程來觀測文件更改;

  2. 重新渲染更新的 React 組件的時間,通過直接在組件的 render 函數調用Date.now()來記錄。請注意,此調用發生在組件的虛擬 DOM render 階段,因此不會受到 React reconciliation 或實際 DOM 更新的影響。

benchmark 還測量了兩種不同情況下的數字:

  1. “根”案例,該組件會導入 1,000 個不同的 child 組件,並且一起渲染。
  2. “葉子”案例,該組件是由根導入,但自身沒有子組件。

差別

在聊數字之前,有幾個額外的差異值得一提:

  1. Next 是否使用 React Server Component(RSC)。
  2. Vite 是否使用 SWC 來替代 Babel 進行 React 轉義。

React Server Components

Next 13 引入了一個主要的架構轉變,因為現在組件默認為服務器組件,除非用户使用“use-client”指令明確選擇客户端模式。不僅是默認設置,Next 文檔還建議用户儘可能保持服務器組件模式,以提高終端用户的性能。

我的初始 benchmark 測試測了 Next 13 在服務器模式下的根組件和葉組件的 HMR 性能。結果表明,在這兩種情況下,Next 13 的速度實際上都較慢,並且葉組件的差異顯著。

Round 1 snapshot (Next w/ RSC, Vite w/ Babel)

當我在 Twitter 上發佈這些數字時,很快就有人指出,我應該在沒有 RSC 的情況下對 Next 組件進行 benchmark 測試,以使其相等。所以我在 Next 根組件中添加了“useclient”指令,以選擇進入客户端模式。事實上,在客户端模式下,Next HMR 顯著提高,比 Vite 快 2 倍:

Round 2 snapshot (Next w/o RSC, Vite w/ Babel)

SWC vs. Babel Transforms

我們的目標是使 benchmark 只關注 HMR 性能差異。為了確保我們確實在比較同一個東西,我們還應該消除另一個變量:Vite 的默認 React preset 使用 Babel 來轉換 React HMR 和 JSX。

React HMR 和 JSX 轉換不是與構建工具耦合的特性。可以通過 Babel(基於 js)或 SWC(基於 rust)完成。Esbuild 也可以轉換 JSX,但缺少對 HMR 的支持。SWC 明顯快於 Babel(單線程下 20 倍,多核心下 70 倍)。Vite 目前默認為 Babel 的原因是在安裝大小和實用性之間進行權衡。SWC 的安裝容量相當大(node_modules 中佔用 58MB,而 Vite 本身才 19MB),許多用户仍然依賴 Babel 進行其他轉換,因此 Babel pass 對他們來説是不可避免的。當然,這在未來可能會改變。

Vite core 不依賴 Babel。只需要用 vite-plugin-swc-react-refresh 來替換默認的 React 插件即可。切換後,我們看到了根案例中 Vite 的顯著改進,超過了 Next:

有趣的是,這裏的成長曲線顯示,Next/turbo 在根情況下比葉情況下慢 4 倍,而 Vite 只慢 2.4 倍。這意味着 Vite HMR 在更大型的組件中表現更好。

此外,切換到 SWC 也應改善 Vercel benchmark 測試中 Vite 的冷啟動指標。

在不同的硬件上的性能

因為這是一個涉及 Node.js 和和原生 Rust 部分的複合測試,在不同的硬件上會有非凡的差異。我發佈的結果是在我的 M1 MacBook Pro 上收集的。其他用户在不同的硬件上運行了相同的 benchmark 測試,並報告了不同的結果。

在某些情況下,根案例下的 Vite 更快。

而在另外一些情況下,兩種情況下 Vite 都明顯更快。

Vercel 的澄清

在我發佈了我的 benchmark 之後,Vercel 發佈了一篇博文,澄清了他們的 benchmark 方法,並將其 benchmark 提供給公眾驗證。雖然這可能是第一天就做的事兒,但這絕對是朝着正確方向邁出的一步。

讀完帖子和 benchmark 代碼後,這裏有幾個關鍵要點:

  1. Vite 實現仍然使用默認的基於 Babel 的 React 插件。
  2. 1k 組件的案例下有數字的四捨五入問題,Turbopack 的 15ms 被舍入城 0.01s,Vite 的 87ms 被舍入城 0.09s。這把本來接近 6 倍的差距擴大到了 10 倍。
  3. Vercel 的 benchmark 使用更新模塊的“瀏覽器 eval 時間”作為結束時間戳,而不是 React 組件重新渲染時間。
  4. 該帖子包括一張圖表,顯示當模塊總數超過 30k 時,Turbopack 可以比 Vite 快 10 倍。

總結下來,“比 Vite 快 10 倍”必須在以下條件下才成立:

  1. Vite 未使用相同的 SWC 轉換。

  2. 該應用程序包含超過30k個模塊

  3. Benchmark 只測量熱更新模塊被評估的時間,而不是實際應用更改的時間。

什麼是“公平”比較?

由於 Vercel 的 benchmark 測試測量“模塊評估時間”,以排除 React 的 HMR 運行時引起的差異,我們可以假設 benchmark 測試的目標是對 Vite 和 Turbopack 固有的 HMR 機制進行公正的比較。

不幸的是,在這個前提下,Vite 仍然在 benchmark 測試中使用 Babel,這並不平等,這讓 10 倍速度的聲明無效了。在使用 SWC 轉換的 Vite 來矯正數字之前,應將其視為不準確的測試。

此外,我相信大多數人都會同意:

  • 對於絕大多數用户來説,30k 模塊數量是一個極不可能的場景。隨着 Vite 使用 SWC,達到 10 倍要求所需的模塊數量可能會變得更加不切實際。雖然這在理論上是可能的,但用它來證明 Vercel 一直營銷的成績,是很虛偽的。

  • 用户更關心端到端的 HMR 性能,即從保存到看到反映的更改的時間,而不是理論上的“模塊評估”時間。當看到“更新速度快 10 倍”時,一般用户會考慮前者而不是後者。Vercel 在其營銷中圖方便省略了這一警告。實際上,Next 中服務器組件的端到端 HMR(默認值)比 Vite 中的

作為 Vite 的作者,我很高興看到像 Vercel 這樣資金雄厚的公司在改進前端工具方面進行了大量投資。如果適用,我們甚至可以在未來在 Vite 中利用 Turbopack。我相信 OSS 領域的健康競爭最終會讓所有開發者受益。

然而,我也認為開放源碼軟件的競爭應該建立在公開溝通、公平比較和相互尊重的基礎上。令人失望和擔憂的是,看到激進的營銷使用了精心挑選的、未經同行評審的、邊緣誤導性的數字,這些數字通常只在商業競爭中出現。作為一家建立在 OSS 成功之上的公司,我相信 Vercel 可以做得更好。

首發於公眾號前端從進階到入院,歡迎關注。