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