核心概念
讓我們深入了解 Turbopack 的內部結構,找出它如此快速的原因。
Turbo 引擎
Turbopack 如此快速,是因為它建構在 Rust 的可重複使用函式庫上,此函式庫支援稱為 Turbo 引擎的增量運算。以下是它的運作方式
函式層級快取
在由 Turbo 引擎驅動的程式中,您可以將某些函式標記為「要記住」。當呼叫這些函式時,Turbo 引擎將記住呼叫它們時所使用的參數,以及它們傳回的內容。然後,它會將這些資訊儲存在記憶體快取中。
以下是此功能在套件管理工具中可能呈現的簡化範例
我們從對兩個檔案 api.ts
和 sdk.ts
呼叫 readFile
開始。然後,我們 bundle
這些檔案,將它們 concat
在一起,最後得到 fullBundle
。所有這些函式呼叫的結果都會儲存在快取中以供後續使用。
假設我們在開發伺服器上執行。您在電腦上儲存 sdk.ts
檔案。Turbopack 會收到檔案系統事件,並知道它需要重新運算 readFile("sdk.ts")
由於 sdk.ts
的結果已變更,我們需要再次 打包
它,然後需要再次串接。
最重要的是,api.ts
沒有變更。我們從快取中讀取其結果,並將其傳遞給 串接
。因此,我們不必再次讀取和重新打包它,從而節省時間。
現在,想像一下在一個真正的打包器中,有數千個檔案需要讀取和轉換要執行。心智模式是一樣的。透過記住函式呼叫的結果,並避免重複之前已完成的工作,你可以節省大量的時間。
快取
Turbo 引擎目前將其快取儲存在記憶體中。這表示快取將持續到執行它的程序為止,這對於開發伺服器來說很有效。當你在 Next.js 13+ 中執行 next dev --turbo
時,你將使用 Turbo 引擎啟動快取。當你取消開發伺服器時,快取將被清除。
未來,我們計畫保留這個快取,儲存在檔案系統中或 Turborepo 等遠端快取中。這表示 Turbopack 可以記住跨執行和機器完成的工作。
它如何提供協助?
這種方法讓 Turbopack 能夠極快速地計算應用程式的增量更新。這最佳化了 Turbopack 在開發中處理更新的方式,表示你的開發伺服器將始終對變更做出快速回應。
未來,永久快取將開啟更快速的生產建置。透過記住跨執行完成的工作,新的生產建置只能重新建置變更的檔案,這可能會大幅節省時間。
依據要求編譯
Turbo 引擎有助於在你的開發伺服器上提供極快的更新,但還有另一個重要的指標需要考量,那就是啟動時間。你的開發伺服器啟動執行的速度越快,你就能越快開始工作。
有兩種方法可以讓流程更快,一是工作更快,二是減少工作量。對於啟動開發伺服器,減少工作量的方法是編譯僅啟動所需的程式碼。
頁面級別編譯
2-3 年前的 Next.js 版本在顯示開發伺服器之前,會編譯整個應用程式。在 Next.js 11(在新分頁中開啟) 中,我們開始編譯僅你要求的頁面上的程式碼。
這樣比較好,但還不完美。當你導覽到 /users
時,我們會將所有用戶端和伺服器模組、動態匯入的模組,以及引用的 CSS 和圖片打包。這表示,如果你的頁面有很大一部分被隱藏,或隱藏在分頁後面,我們還是會將其編譯。
請求級別編譯
Turbopack 足夠聰明,可以編譯僅你要求的程式碼。這表示,如果瀏覽器要求 HTML,我們只編譯 HTML,而不是 HTML 參考的任何內容。
如果瀏覽器想要一些 CSS,我們只會編譯它,而不編譯引用的圖片。在 next/dynamic
後面有一個大型圖表程式庫嗎?在顯示圖表的標籤顯示之前,不會編譯它。Turbopack 甚至知道除非你的 Chrome DevTools 開啟,否則不編譯原始碼對應表。
如果我們使用原生 ESM,我們會得到類似的行為。除了原生 ESM 會產生大量對伺服器的請求,如我們在 為什麼使用 Turbopack 區段中所討論的。透過請求級別編譯,我們可以減少請求數量和使用原生速度來編譯它們。正如你在我們的 基準測試 中所看到的,這提供了顯著的效能提升。