增量計算
Turbopack 使用自動化的需求驅動增量計算架構,為大型 Next.js 和 React 應用程式提供 React 快速重新整理。
此架構使用快取來記住函式呼叫時使用的值以及傳回的值。增量建置的規模取決於您在本機所做的變更大小,而不是應用程式的大小。


Turbopack 的架構基於十多年的經驗和先前的研究。它從 webpack、Salsa(為 Rust-Analyzer 和 Ruff 提供動力)、Parcel、Rust 編譯器的查詢系統、Adapton 以及許多其他專案中汲取靈感。
背景:手動增量計算
許多建置系統都包含顯式的相依性圖,在評估建置規則時必須手動填入。顯式宣告您的相依性圖在理論上可以提供最佳結果,但實際上卻會留下錯誤的空間。
指定顯式相依性圖的困難意味著通常快取是以粗略的檔案層級粒度進行的。這種粒度確實有一些好處:更少的增量結果意味著更少的快取資料,如果您有有限的磁碟空間或記憶體,這可能是值得的。
這種架構的一個範例是 GNU Make,其中輸出目標和先決條件是手動設定和表示為檔案的。由於粒度粗略,像 GNU Make 這樣的系統會錯失快取機會:它們不了解也無法快取編譯器內的內部資料結構。
函式層級細粒度自動增量計算
在 Turbopack 中,輸入檔案和產生的建置成品之間的關係並非直接。打包器採用全程式分析來進行無用程式碼消除(「tree shaking」)和模組圖中常見相依性的叢集處理。因此,建置成品(跨多個應用程式路由共用的 JavaScript 檔案)與輸入檔案形成複雜的多對多關係。
Turbopack 使用非常細粒度的快取架構。由於手動宣告相依性並將其新增至圖中容易發生人為錯誤,Turbopack 需要一種可以擴展的自動化解決方案。
值單元格
為了促進自動快取和相依性追蹤,Turbopack 引入了「值單元格」(Vc<…>
) 的概念。每個值單元格都代表一小段細粒度的執行,就像試算表中的單元格一樣。讀取單元格時,它會記錄目前正在執行的函式及其所有單元格作為該單元格的相依項。


藉由在讀取單元格之前不將其標記為相依項,Turbopack 實現了比 傳統由上而下記憶化方法更細粒度的快取。例如,引數可能是多個值單元格的物件或對應。當物件或對應的任何部分變更時,並不需要重新計算我們追蹤的函式,而只需要在實際讀取的單元格變更時重新計算追蹤的函式。
值單元格代表 Turbopack 內幾乎所有的東西,例如磁碟上的檔案、抽象語法樹 (AST)、有關模組的匯入和匯出中繼資料,或用於分塊和打包的叢集資訊。


標記髒資料和傳播變更
當單元格的值變更時,Turbopack 必須判斷要重新計算哪些工作。它使用 Adapton 的兩階段髒資料和傳播演算法。


通常,原始程式碼檔案位於相依性圖的底部。當增量執行引擎發現檔案的內容已變更時,它會將讀取該檔案的函式及其相關的值單元格標記為「髒資料」。為了監看檔案系統變更,Turbopack 使用 inotify
(Linux) 或透過 notify
crate的對等平台 API。
接下來是傳播,從底層向上重新執行打包器,向上傳播任何產生新結果的計算。此傳播是「需求驅動」的,表示系統只會在它是「作用中查詢」的一部分時重新計算相依的單元格。「作用中查詢」可能是目前已啟用熱重新載入的開啟網頁,甚至是建置完整生產應用程式的請求。
如果單元格不是作用中查詢的一部分,則其髒資料旗標的傳播會延遲,直到相依性圖變更或建立新的作用中查詢。
其他最佳化:匯總樹狀結構
相依性圖可以包含數十萬個小型追蹤函式的獨特調用,且增量執行引擎必須經常遍歷圖來檢查和更新髒資料旗標。
Turbopack 使用「匯總樹狀結構」來最佳化這些操作。匯總樹狀結構的每個節點都代表一組追蹤的函式呼叫,減少與相依性追蹤相關的一些記憶體額外負荷,並減少必須遍歷的節點數。
使用 Rust 和 Tokio 進行平行和非同步執行
為了平行執行,Turbopack 使用 Rust 與 Tokio 非同步執行階段。每個追蹤的函式都會作為 Tokio 工作產生到 Tokio 的執行緒集區中。這讓 Turbopack 可以受益於 Rust 的低額外負荷平行處理和 Tokio 的工作竊取排程器。
雖然在大多數情況下,打包是受 CPU 限制的,但在從慢速硬碟、網路磁碟機建置時,或在讀取或寫入持續快取時,它可能會變成受 IO 限制。Tokio 允許 Turbopack 比我們原本能夠更優雅地處理這些效能降低的情況。