使用 Turborepo 管理你的 Monorepo 專案
1. 為什麼選擇 Monorepo?
Monorepo(單一程式碼儲存庫)是一種將多個獨立專案或套件(package)存放在同一個程式碼儲存庫中的策略。與每個專案擁有獨立儲存庫的 Polyrepo 模式相比,Monorepo 具有一些顯著優勢:
- 程式碼共享:在不同專案間共享元件、工具函式或型別定義變得非常容易。
- 原子化提交:一個功能的修改如果涉及多個套件,可以透過一次提交完成,確保版本一致。
- 簡化相依性管理:所有專案共享同一個
node_modules(或透過 pnpm/yarn workspace 最佳化),減少相依性衝突和版本不一致的問題。
然而,當儲存庫規模變大時,Monorepo 的挑戰也隨之而來:建置效能。
2. Monorepo 的痛點
想像一下,你的儲存庫裡有 docs、webapp,以及一個共享的 ui 元件庫。當你只修改了 docs 中的一個錯字時,你最不希望發生的事情就是整個儲存庫的所有專案(包括 webapp 和 ui)都被重新建置和測試。
傳統的 Monorepo 管理工具(如 Lerna)在任務編排和建置快取方面有所不足,導致:
- 重複工作:每次 CI/CD 執行時,都會從頭建置和測試所有內容,即使大部分程式碼沒有變化。
- 建置時間長:無法有效利用多核心 CPU 平行執行任務。
- 複雜的指令碼:需要編寫複雜的
package.json指令碼,手動控制任務的執行順序。
3. Turborepo:為速度而生
Turborepo 是一個專為 JavaScript/TypeScript Monorepo 設計的高效能建置系統(後被 Vercel 收購)。它透過兩大核心技術解決上述痛點:增量建置和遠端快取。
a. 增量建置與任務管線(Pipelines)
Turborepo 讓你在根目錄的 turbo.json 檔案中定義任務之間的相依關係。
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"]
},
"test": {
"dependsOn": ["build"],
"outputs": []
},
"lint": {
"outputs": []
},
"dev": {
"cache": false
}
}
}dependsOn: ["^build"]:^符號表示,一個套件的build任務依賴於其所依賴的所有套件的build任務。Turborepo 會根據這個關係圖,以最高效的平行方式執行任務。outputs:告訴 Turborepo 這個任務會產生哪些輸出檔案。
當你執行 pnpm turbo build 時,Turborepo 會計算哪些檔案被修改,並且只重新建置受影響的套件。如果一個套件的原始碼和相依性沒有變化,Turborepo 會直接使用上次的建置產物,這個過程是瞬間完成的。
b. 遠端快取(Remote Caching)
這是 Turborepo 的「殺手級」功能。它不只在你的本機上快取建置結果,還能將這些快取上傳到共享的遠端伺服器(如 Vercel 或你自己的 S3 儲存貯體)。
這表示:
- 你的同事在取得最新程式碼後,如果他要建置的部分你已經建置過,他可以直接下載快取,無須在本機重新建置。
- CI/CD 伺服器也能連線到這個遠端快取。一個 PR 在 CI 中被建置並快取後,其他開發者或後續 CI 任務都能立即享用這項成果。
這能將整個團隊的建置時間從數十分鐘縮短到幾分鐘,甚至幾十秒。
結論
Turborepo 並沒有發明 Monorepo,但它透過引入先進的快取和任務排程系統,大幅最佳化了 Monorepo 的開發體驗,解決其最核心的效能瓶頸。如果你正在使用或準備採用 Monorepo 架構,Turborepo 絕對是一個能為你和團隊節省大量時間的利器。