使用 Turborepo 管理你的 Monorepo 專案

5 分鐘

1. 為什麼選擇 Monorepo?

Monorepo(單一程式碼儲存庫)是一種將多個獨立專案或套件(package)存放在同一個程式碼儲存庫中的策略。與每個專案擁有獨立儲存庫的 Polyrepo 模式相比,Monorepo 具有一些顯著優勢:

  • 程式碼共享:在不同專案間共享元件、工具函式或型別定義變得非常容易。
  • 原子化提交:一個功能的修改如果涉及多個套件,可以透過一次提交完成,確保版本一致。
  • 簡化相依性管理:所有專案共享同一個 node_modules(或透過 pnpm/yarn workspace 最佳化),減少相依性衝突和版本不一致的問題。

然而,當儲存庫規模變大時,Monorepo 的挑戰也隨之而來:建置效能

2. Monorepo 的痛點

想像一下,你的儲存庫裡有 docswebapp,以及一個共享的 ui 元件庫。當你只修改了 docs 中的一個錯字時,你最不希望發生的事情就是整個儲存庫的所有專案(包括 webappui)都被重新建置和測試。

傳統的 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 絕對是一個能為你和團隊節省大量時間的利器。