兩大 bundler 交鋒:Vite 與 Turbopack 的技術角力賽

Leo Chiu
Starbugs Weekly 星巴哥技術專欄
15 min readJun 26, 2023

近幾年有很多在 bundler 領域的關鍵字

在 bundler 這個領域,近期最火的技術包括 Vite 與 Turbopack,而內部的技術包括像是 esbuild、SWC 等等,這幾個技術都是熱門的關鍵字。看著前端的各種技術不斷推陳出新,這時我就好奇為什麼公司和社群要推廣這些技術。例如,Vercel 不惜砸錢開發自己的 bundler。過去的 bundler 遇到什麼樣的問題呢?

基於這個出發點,想要了解目前 bundler 的生態系,這些技術之前的關聯又是什麼?還有,去年 11 月 Vercel 在 Twitter 上宣稱 Turbopack 比 Vite 快 10 倍,比 Webpack 快 700 倍。然後,尤雨溪(Vite 的作者)看到之後馬上開了一個 GitHub repo 來討論整件事情,並指出這篇推文的結果非常不公正。現在在 Turbopack 的官方網站也有許多針對 Vite 的比較文,看起來 Vercel 這間公司就是要和 Vite 競爭,想要讓 Turbopack 在社群上佔有一席之地。

有許多有趣的事情接二連三發生,我想多了解一下這兩個 bundler,抱著像是吃瓜群眾的心態。

Vite 簡介

Vite 是由 Vue.js 的作者尤雨溪在 2020 年 4 月所創建的前端建構工具,而 Vite 這個名字是來自法文的「快」,發音像是 veet,主要是用來改善前端的開發體驗

在開發環境使用 native ESM

在開發環境會利用瀏覽器支援 native ESM 的特性掛載到瀏覽器上,並且還有非常快的 Hot Module Replacement,以及許多其他很豐富的功能

Webpack 會在啟動伺服器就把所有的檔案都打包起來,這樣的缺點是隨著專案越來越大,打包的時間會越來越長,往往需要分鐘的時間才能啟動伺服器看到第一個頁面,這樣的開發體驗對於工程師來說並不是很好。

但是 Vite 也不是完全沒有問題,後面會提到這點

https://vitejs.dev/guide/why.html

Vite 的其中一個核心概念是「既然打包很久,那乾脆就不打包了」。在伺服器啟動後,Vite 直接以 native ESM 的形式將原始碼掛載到瀏覽器上。當使用者瀏覽一個頁面時,Vite 只需要轉譯該頁面對應的原始碼。這樣的好處是冷啟動伺服器的速度非常快,因為根本不需要執行轉譯跟打包;此外,當原始碼被修改時,因為使用 native ESM,只需要重新轉譯修改的那個檔案再重新透過 HTTP 請求該檔案。

https://vitejs.dev/guide/why.html

這時候會有另一個問題是 Vite 怎麼處理 dependencies,因為 Vite 是用 native ESM 掛載檔案,有些套件是 CommonJS 或 UMD,會造成格式之間無法相容的問題;除了 module 的格式不相容之外,一些套件甚至會有幾百個檔案,像是 lodash-es,在瀏覽器端直接發請求時幾個個檔案的請求勢必會造成網路阻塞。

所以 Vite 為了解決上述兩個問題,提出的解法是 pre-building dependencies。在伺服器啟動時,Vite 會把所有的 dependencies 都使用 esbuild 轉換成 ESM,並且將套件轉換成單一檔案儲存在 /node_modules/.vite,這樣一來請求像是 lodash-es 這種包含大量檔案的套件也不會造成網路阻塞。

Production 並非使用 native ESM

Vite 主要解決的是 DX 不佳的問題,在 production 使用 native ESM 反而可能會造成 UX 不好。這裡說的 UX 不好,並非是是指設計或是操作流程的問題,而是指 native ESM 會導致一個頁面需要請求數個檔案,最終造成網路阻塞的問題。

此外,為了加速開發環境下 HMR 的速度,Vite 採取的行為是基於 ESM 依需求編譯,並不會執行以往我們在打包時會執行 tree-shaking、lazy-loading、code splitting 等等的優化。因此,Vite 在 production 時仍需要使用 Rollup 打包,讓 Rollup 發揮其魔法。

至於 Vite 在 production 不使用 esbuild 的原因是 Rollup 在打包上的支援度還是比較好,Rollup 的 plugin 以及社群比 esbuild 還要好很多。目前 Vite 沒有把話說死,以後等 esbuild 越來越好用之後,也許在 production 也會改成使用 esbuild 也說不定。

Turbopack 簡介

Turbopack 是 Next.js 的公司 Vercel 在 2022 年 10 月發佈的一個建構工具,由 Webpack 的作者 Tobias Koppers 和 Next.js 團隊用 Rust 寫成,當時跟著 Next.js 13 一起發佈。

Introducing Turbopack: Rust-based successor to Webpack 這篇文章中提到,他們將 JavaScript-based 的建構工具換成 Rust-based 的版本後,在各個方面都得到提升,例如把 Babel 換掉之後,轉譯原始碼得到了 17 倍速度的提升,而把 Terser 換掉,則是在將程式碼壓縮上得到 6 倍速度的提升。甚至與 Webpack 比較,Turbopack 大約是 Webpack 的700 倍快。

目前文章撰寫的時間 Turbopack 還是只能與 Next.js 一起使用,但不確定未來有沒有機會變成跟 Vite 一樣成為可以跟其他框架整合的工具,從現階段 Vercel 從商業上的考量一定是會力推跟 Next.js 的整合性以及他們的雲端服務,在短期內要看到可以直接跟像是 create-react-app 之類的工具整合的可能性似乎不大。

為什麼 Turbopack 還是選擇 bundling

Native ESM 是目前 Vite 優化開發體驗的解決方案之一,使用 native ESM 的好處是在開發環境下可以按需解析和轉譯 module,加快了在瀏覽網頁時 HMR 的時間。相比之下,Webpack 需要在每次修改程式碼都需要重新 bundle,隨著專案越來越大每次 bundle 的時間會越來越久。

然而,Vite 的解決方案也不是完全沒有缺點,因為在瀏覽器中使用 native ESM 的緣故,所有的檔案結構將會維持原本的模樣,所以當專案越來越大時,module 的數量將會導致網路壅塞,因為網路可能需要請求數百或數千個檔案,即使在 local server 也可能會有這樣的問題。

綜合上述,Vercel 選擇還是透過 bundling 來避開在大型專案 native ESM 可能會導致網路壅塞的問題,他們使用 Rust 建立了類似於 Webpack 的建構工具 Turbopack,在開發環境下優化了打包流程。

Lazy bundling

由於 Vite 的冷啟動速度快是因為它不打包,因此 Turbopack 也採用「盡量」不打包的方式來優化啟動速度,當瀏覽 /users 頁面時,它才會打包該頁面需要的 module,有些區塊甚至在第一次請求時都不會被打包,例如隱藏的區塊或尚未顯示的 tab 等等,Turbopack 會分析原始碼,只打包最小量的程式碼。

Turbopack 跟 Webpack 一樣都會打包,跟 Vite 一樣都會需要轉譯程式碼,但 Turbopack 與其他建構工具的不同之處在於 Turbo engine 中的一項重要功能: function-level caching,它可以優化打包的過程,當有數千個 module 時,Turbopack 不會執行重複的事情,會從 cache 中讀取結果,這是目前 esbuild 做不到的事情。相較於每次更新都需要打包所有程式碼的 Webpack,Turbopack 必定快很多倍。

https://turbo.build/pack/docs/core-concepts

Turbopack 跟 Vite 的爭議點

Turbopack 之前在 Twitter 上發布了一篇官方推文,表示 HMR 比 Vite 快 10 倍。這篇貼完發布以後馬上引來 Vite 作者尤雨溪的回應,尤雨溪認為 Turbopack 比 Vite 快上 10 倍這個論點有許多不實之處,所以後來他開了一個 PR — Is Turbopack really 10x Faster than Vite? 來驗證跟討論整件事情,初期有提到 Turbopack 並沒有提到 benchmark 的連結以及數據的來源。

https://twitter.com/vercel/status/1584961746418208774

過沒幾天 Turbopack 就再次發文說明自己的 benchmark 是如何計算的,總結在 Turbopack Performance Benchmarks 這篇文章中。簡而言之,這篇文章提到 Turbopack 比 Vite 快 10 倍需要知道有幾個先決條件:

  • Vite 預設使用 babel 來轉換 React HMR (hot module replacement),如果沒有將預設的 babel 換成 SWC,這個比較是不公平的
  • Benchmark 包含超過 3 萬個 module,在實際的場景一個頁面要超過 3 萬個 module 基本上是不太可能的,這意味者 benchmark 有點過度誇大的嫌疑
  • Benchmark 測量的標準為 HMR 的時間,並非是畫面被修改的時間

在這件事情過了 8 個月之後,到了現在 Turbopack 官方的數據顯示最新的 benchmark,Vite 跟 Turbopack 的冷啟動速度在 1000 個 module 下已經相差差不多 0.3 秒。儘管隨著 module 數量增加,Vite 的冷啟動速度會比 Turbopack 更慢,但是在 5000 個 module 的測試中,Turbopack 與 Vite 的冷啟動速度分別為 4 秒和 5.5 秒,僅相差約 1.5 秒。在實際應用中,很難達到這個 module 數量的規模。

https://turbo.build/pack/docs/benchmarks

換言之,在現實中我們更在意的是社群、開發體驗、更新速度、issue 的修復程度等等,儘管兩者目前在 benchmark 的跑分都令人嘆為觀止,但只有微秒數的差距,並不是決定選擇使用哪種工具的關鍵。現在 Vercel 會如此的在意 Turbpack 數據除了展示能力之外,更多像是商業上的考量,透過一些炒熱話題的方法讓更多人知道 Vercel 這間公司旗下的產品,一則貼文引來各方熱烈的討論就是一種成功的廣告手段。

GitHub blame benchmark 的歷史紀錄

順帶一提很有趣的事情,現在用 Google 找 Turbopack 跟 Vite 比較的文章,有看到許多篇提到 Vite 在 3000 個 module 的 benchmark 冷啟動速度需要 11.4 秒,當下我看到這個數字的時候就想說跟現在官網上的數字差有點多,然後我就有個起心動念想去 GitHub blame 一下 benchmark 的歷史記錄,因為 Turbopack 的官方網站是開源的,因此可以查看到改動的紀錄。

在看過數據之後,發現像是 3000 個 module 的測試紀錄沒有在 GitHub 歷史紀錄上看到,而且最初的 Introducing Turbopack: Rust-based successor to Webpack 這篇文章上面只有提到 5000 個 module 的比較,這樣許多文章提到的 3000 個 module 是哪裡來的?🤔

不過這個 benchmark 資料確實是有在持續更新,也有陸續把資料回歸校正,就不會讓人感覺像是一開始說的比 Vite 10 倍快這麼誇張。

兩個 bundler 如何抉擇?

在大致上看完 Vite 跟 Turbopack 的簡介以及一些 benchmark 之後,其中一個大家會很在意的點就是要怎麼抉擇兩個建構工具,因為 benchmark 看起來不相上下,有沒有一些可以做為決策的關鍵點呢?

Vite 初次載入速度過慢的問題

在 Vite 4.3 版本以前(~ 2023/04)有許多 issue 都提到初次載入頁面過慢的問題:

雖然 benchmark 的冷啟動速度非常快,但是 benchmark 並沒有包含第一次在瀏覽器看到畫面的時間,而 Vite 的冷啟動速度快的原因是把 webpack 做的一些事情移動到了瀏覽器請求的時間點,而且會有大量的 http 請求。

不過我在 M1 Macbook pro 無法重現大量 component 很慢的情境,不確定是不是因為 M1 的緣故,又或者是 issue 作者提供的範例 repo 不夠複雜,所以才無法重現。但有,許多人回報他們有遇過一樣的問題,尤其是在 Windows 作業系統上,這個問題是曾經真的存在過。

在 Vite 4.3 版本之後,效能得到了大量的改善。上述第一次看到畫面很慢的 issue 也被官方關掉了。後續也沒有人提出相應的問題,看起來應該是已經解決了。

https://vitejs.dev/blog/announcing-vite4-3.html

Turbopack 目前僅支援 Next.js

如果原本的專案並非使用 CRA(create-react-app)或是自己設置 Webpack 等等,要轉移到 Turbopack 的困難度比較高,因為要先把原本的專案轉移到 Next.js,才能使用 Turbopack。

但是換句話說,新的專案只有基於 Next.js 才能使用 Turbopack,這點應該會是需要考量的原因,畢竟 Next.js 也是另一個學習成本跟維護成本。

我會如何抉擇?

如果公司的專案原本使用 CRA 或是 Webpack,也許可以考慮轉移到 Vite。但是要注意的是 Vite 在 production 是用 Rollup 打包,原本使用的 Webpack plugin 都要找到對應的解決方案才可能轉移到 Vite,而且 chunk splitting、tree shaking 的邏輯都跟原本專案的會不一樣,這些都是需要考量的因素。

至於使用 Turbopack 的話基本上應該是要看對於 Next.js 的需求程度,因為 Turbopack 是作為 Next.js 的附加功能之一,更應該要看導入 Next.js 對於專案的優勢跟劣勢,而並非因為 Turbopack 看起來速度很快就盲目導入。

一些額外的小知識

  • Vite 的專案是用 pnpm 跟 pnpm workspaces (monorepo)
  • 在做比較的都是 React 社群,而且 SWC 目前也只有 react-swc,沒有支援 Vue
  • 最新的 Vite 4.3 benchmark 大致上看起來比 Turbopack 快,有興趣可以從這篇推文開始看起
  • Vite 在 production 使用 Rollup 打包的靈感自於 Preact 的 WMR
  • Snowpack 因為 Vite 出現以後決定不再維護了,現在整個團隊跑去做一個叫做 Astro 的框架,這個框架底層就是 Vite
  • Turbopack 的官方文件是用 MDX 寫的

總結

在這篇文章中我們提到了 Vite 跟 Turbopack 這兩個工具主要都是用來優化開發體驗,並且提及了各自截然不同的解決方案。明明都是啟動器,卻採取不一樣的方法達到令人嘆為觀止的速度,覺得是非常有趣的一件事。

後續因為吃瓜心態在追 Vite 跟 Turbopack 的爭議點也是很有趣的事情,如果只有一方出現就不會出現兩者的競爭心態,也無法促進後續大量的討論,在看這些 issue 時也讓自己看到更多知識,雖然 Vercel 更像是把 Vite 當作是商業上競爭對手,或是吸引大眾目光的方法。

除了 Vite 跟 Turbopack 之外,也陸續看了 esbuild、SWC、Preact、Snowpack 等等專案的一些東西,總而言之技術真的追不完,而且變化真的很快速,不知道下半年還有什麼新奇的技術呢?

--

--

Leo Chiu
Starbugs Weekly 星巴哥技術專欄

每天進步一點點,在終點遇見更好的自己。 Instragram 小帳:@leo.web.dev