為什么 Next.js 不用 Vite 而要自造輪子 Turbopack?
Next.js 的 Github issues 中有一個(gè)帖子,反饋了 Next.js 的開發(fā)模式編譯很慢[1],自 2023 年 4 月 23 日提問以來,現(xiàn)在已經(jīng)有 468 多條討論!看來這個(gè)問題不只一個(gè)人遇到,做為一個(gè)使用過 Next.js 的用戶來說,Next.js 的其它方面還可以,但是開發(fā)體驗(yàn)真是挺糟糕的...
用戶 @roonie007 提出了自己的疑問,為什么 Next.js 不使用 Vite,而要重新發(fā)明輪子?[2]
做為 Next.js 和 Turbopack @vercel 的技術(shù)主管 @timneutkens[3] 對此進(jìn)行了回復(fù),如下所示:
我會盡量簡短,因?yàn)槲铱梢詫?談?wù)撨@個(gè)話題幾個(gè)小時(shí) ??
幾年前,在 Vite 得到廣泛應(yīng)用之前,我們開始看到越來越多基于 Next.js 構(gòu)建的大型 Web 應(yīng)用程序,包括企業(yè)級團(tuán)隊(duì)中超過 100 名開發(fā)>人員的采用。這些代碼庫擴(kuò)展到數(shù)萬個(gè)自定義組件,并且還從 npm 導(dǎo)入包。簡言之,盡管我們當(dāng)時(shí)使用的 webpack(如果沒有選擇 Turbopack)實(shí)際上相當(dāng)快速,但對于這些不斷增長的代碼庫規(guī)模來說仍然不夠快。
我們還看到了一種趨勢,即通用應(yīng)用程序變得更加依賴編譯,主要是由組件庫/圖標(biāo)庫的興起所致。今天,正如您在這個(gè)帖子中看到的,由于已發(fā)布的設(shè)計(jì)系統(tǒng)和圖標(biāo)庫的使用,即使是一個(gè)非常小的應(yīng)用程序也可能編譯出 20K 個(gè)或更多模塊。
我們發(fā)現(xiàn)的問題是,即使我們將 webpack 優(yōu)化到最大,它仍然存在可以處理的模塊數(shù)量上限,因?yàn)槿绻阌?20,000 個(gè)模塊,即使每個(gè)模塊只花費(fèi) 1 毫秒,也會導(dǎo)致 20 秒的處理時(shí)間,如果不能并行處理的話。
除此之外,我們不僅運(yùn)行一個(gè) webpack 編譯器,而是運(yùn)行了三個(gè):一個(gè)用于服務(wù)器,一個(gè)用于瀏覽器,一個(gè)用于邊緣運(yùn)行時(shí)。這會帶來復(fù)雜性,因?yàn)檫@些獨(dú)立的編譯器必須協(xié)調(diào)工作,因?yàn)樗鼈儧]有共享的模塊圖。
在同一時(shí)間段,我們還開始探索 React Server Components、App Router,以及我們希望未來 5-10 年內(nèi) Next.js 開發(fā)應(yīng)該是什么樣子的總體情況。其中一個(gè)主要議題是關(guān)于代碼如何從服務(wù)器->客戶端->服務(wù)器->客戶端,簡言之,如果您熟悉的話,就是服務(wù)器操作,特別是服務(wù)器操作可以返回包含額外客戶端組件的 JSX。為了使其工作,我們發(fā)現(xiàn)擁有一個(gè)統(tǒng)一的模塊圖,可以在同一個(gè)打包器/編譯器中同時(shí)容納服務(wù)器、客戶端和邊緣代碼,將會非常有益。這是像 Parcel 這樣的打包器長期探索的內(nèi)容。
那時(shí)候,我們評估了所有現(xiàn)有的解決方案,并發(fā)現(xiàn)它們各自都有權(quán)衡之處,我不會像“拋棄其他人”一樣說它們,因?yàn)檫@些權(quán)衡都是有意義的,只是對于像 Next.js 這樣的框架,尤其是未來的 Next.js(如果我記得正確的話,這大概是在 ~2020 年左右)來說,它們不合適。
總體上來說,讓我們談一談一些目標(biāo),其中一些對用戶有益,一些對維護(hù)有益:
更快的 HMR
- Webpack 在模塊圖中的模塊數(shù)量上有性能限制。一旦達(dá)到 30K 模塊,每次代碼變更的開銷至少需要 ~1 秒的處理時(shí)間,無論您是進(jìn)行小的 CSS 更改還是其他更改。
更快的路由初始編譯
- 具有 20-30K 模塊的 Webpack 通常需要 15-30 秒的處理時(shí)間,因?yàn)樗鼰o法跨 CPU 進(jìn)行并行處理。
無破壞性變更
- 我們希望將所有這些改進(jìn)帶給現(xiàn)有應(yīng)用程序。作為其中的一部分,有很多 Next.js 特定的編譯器功能,如 next/font,需要添加進(jìn)來。
可伸縮到最大的 TS/JS 代碼庫
- 如上所述,我們看到越來越大的代碼庫,為了優(yōu)化這些,需要一種不同的架構(gòu)。我認(rèn)為在其他打包器中我們采用的架構(gòu)最接近的是 Parcel。
持久性緩存
- Turbopack 擁有一個(gè)廣泛的緩存機(jī)制,可以與 Facebook 的 Metro 打包器(用于 react-native 和 instagram.com)相媲美,它能夠持久地緩存之前完成的工作,因此當(dāng)您重新啟動(dòng)開發(fā)服務(wù)器時(shí),它只需恢復(fù)上次會話的緩存。目前正在積極地開發(fā)中。
與開發(fā)密切匹配的生產(chǎn)構(gòu)建
- 目前在 Next.js 中,以及其他打包器中,dev/prod 之間存在差異,我們希望盡量減少這些差異。
超越當(dāng)前打包器的生產(chǎn)優(yōu)化
- 我們一直在開發(fā)先進(jìn)的搖樹功能,允許在導(dǎo)入/導(dǎo)出級別而不是模塊級別進(jìn)行代碼拆分,受到 Closure Compiler 的啟發(fā)。目前的打包器操作是在模塊級別上進(jìn)行的。
減少編譯器/編譯時(shí)間中的不穩(wěn)定性
- 目前由于服務(wù)器/客戶端/邊緣 webpack 編譯器之間的協(xié)調(diào),有時(shí)會導(dǎo)致編譯時(shí)間較長。主要目標(biāo)之一是減少實(shí)現(xiàn)的復(fù)雜性,并在一個(gè)編譯通道中輸出所有必需的文件。
(以后)Next.js 感知的打包器工具
- 比如大大改進(jìn)的 bundle 分析,了解布局/頁面/路由情況。
(以后)Next.js / RSC 感知的打包優(yōu)化
- 例如,優(yōu)化客戶端組件以盡可能高效地捆綁。
維護(hù)者的完整可觀察性
- Next.js 的使用很廣泛,因此會產(chǎn)生大量的 bug 報(bào)告和功能請求。其中一種特別難以調(diào)查的 bug 報(bào)告與減速相關(guān)(這個(gè)問題就是一個(gè)很好的例子),以及內(nèi)存使用("Next.js 泄漏內(nèi)存"報(bào)告)。這些問題難以調(diào)查,因?yàn)樗鼈冃枰钊肓私鈭?bào)告者的性能分析/內(nèi)存轉(zhuǎn)儲,而他們通常不愿意共享可運(yùn)行的代碼。
這是為什么構(gòu)建我們自己的工具對于我們是有益的一個(gè)重要原因,它使我們能夠調(diào)查報(bào)告的問題,而無需訪問您的代碼庫。如果我們使用其他任何打包器,我們將不得不說"很遺憾,這是您的問題,嘗試將其報(bào)告給該打包器的 GitHub 存儲庫",這不是我們想要做的事情,也不是我們之前在 webpack 中做過的。
就我個(gè)人而言,我很高興看到 Vite 在生態(tài)系統(tǒng)中做得很好。他們也從其他打包器中吸取了經(jīng)驗(yàn)教訓(xùn)。如果您看看他們最近在 Rolldown 上的工作,您會發(fā)現(xiàn)有很多相似之處,這回歸到打包而不是“解包”以提高編譯性能,例如。
猜測寫這篇文章花了比我想要花的時(shí)間還要多,但希望對您有所幫助!
TLDR:其他打包器很棒,但它們不適合像 Next.js 這樣的框架。我們希望將這些改進(jìn)帶給現(xiàn)有用戶,為此我們不得不構(gòu)建一個(gè)新的打包器,吸取了之前嘗試過的許多不同方法的經(jīng)驗(yàn)教訓(xùn)。
感興趣的可以通過以下參考資料閱讀原帖子。對此你怎么看?歡迎評論區(qū)討論!
參考資料
[1]Next.js 的開發(fā)模式編譯很慢: https://github.com/vercel/next.js/issues/48748
[2]為什么 Next.js 不使用 Vite,而要重新發(fā)明輪子?: https://github.com/vercel/next.js/issues/48748#issuecomment-2151880231
[3]技術(shù)主管 @timneutkens: https://github.com/vercel/next.js/issues/48748#issuecomment-2199941311