Skip to content
⚠️ This article was written in 2021. Some content may be outdated.

Turborepo 初探:高效能 Monorepo 構建

Vercel 收購了 Turborepo 並開源,這是一個用 Go 寫的高效能 Monorepo 構建工具。試用了一週,和 Lerna / pnpm workspace 對比了一下。

Monorepo 的痛點

用 pnpm workspace 管理 monorepo 專案,包管理沒問題,但構建編排很原始:

bash
# pnpm workspace 的構建方式
pnpm run build --filter=packages/core
pnpm run build --filter=packages/utils
pnpm run build --filter=packages/ui
pnpm run build --filter=apps/web

# 問題:
# 1. 手動排構建順序
# 2. 沒有快取(每次全量構建)
# 3. CI 上更慢(沒有本地快取)

Turborepo 解決什麼

  • 構建快取:相同輸入不重複構建(本地 + 遠端快取)
  • 並行排程:自動分析依賴圖,並行構建無依賴的包
  • 增量構建:只構建有變化的包及其下游依賴

基本配置

bash
# 安裝
npm install -D turbo
jsonc
// turbo.json
{
  "$schema": "https://turborepo.org/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],       // 先構建依賴的包
      "outputs": ["dist/**"]          // 快取這些產物
    },
    "dev": {
      "cache": false,                 // dev 不快取(持續執行)
      "persistent": true
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": []
    },
    "lint": {
      "outputs": []
    }
  }
}
jsonc
// packages/ui/package.json
{
  "name": "@myorg/ui",
  "scripts": {
    "build": "vite build",
    "dev": "vite build --watch",
    "lint": "eslint src/"
  }
}

// apps/web/package.json
{
  "name": "@myorg/web",
  "dependencies": {
    "@myorg/ui": "workspace:*",
    "@myorg/utils": "workspace:*"
  },
  "scripts": {
    "build": "vite build",
    "dev": "vite",
    "lint": "eslint src/"
  }
}

執行

bash
# 構建所有包(自動分析依賴圖,並行執行)
turbo run build

# 只構建某個包及其依賴
turbo run build --filter=@myorg/web

# 並行執行所有 dev
turbo run dev --parallel

# 執行所有 lint(無依賴,完全並行)
turbo run lint

快取機制

bash
# 第一次構建
turbo run build
# packages/core:   build (2.3s)
# packages/utils:  build (1.1s)
# packages/ui:     build (3.5s)
# apps/web:        build (5.2s)

# 沒有任何改動,第二次構建
turbo run build
# packages/core:   build >>> FULL TURBO (cached, 0.0s)
# packages/utils:  build >>> FULL TURBO (cached, 0.0s)
# packages/ui:     build >>> FULL TURBO (cached, 0.0s)
# apps/web:        build >>> FULL TURBO (cached, 0.0s)

# 只改了 utils,第三次構建
turbo run build
# packages/core:   build >>> FULL TURBO (cached, 0.0s)    # 沒變
# packages/utils:  build (1.2s)                             # 重新構建
# packages/ui:     build (3.4s)                             # 依賴 utils,重新構建
# apps/web:        build (5.1s)                             # 依賴 ui,重新構建

快取 key 基於:原始檔內容 + 環境變數 + lock 檔案 + package.json scripts。

遠端快取(CI 場景)

bash
# 登入 Vercel(免費提供遠端快取)
npx turbo login

# 連結遠端快取
npx turbo link

# 之後 turbo run build 自動同步快取到 Vercel
# 本地構建一次 → CI 直接用快取

CI 配置示例:

yaml
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: pnpm/action-setup@v2
        with:
          version: 6
      - uses: actions/setup-node@v3
        with:
          node-version: 16
          cache: 'pnpm'

      - run: pnpm install

      # Turborepo 自動使用遠端快取
      # 如果本地已經構建過,CI 直接用快取
      - run: pnpm turbo run build test lint
        env:
          TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
          TURBO_TEAM: ${{ secrets.TURBO_TEAM }}

和 Lerna 對比

| 特性 | Lerna | Turborepo | | ------|-------|-----------| | 構建編排 | 依賴 topo 排序 | 自動依賴圖 + 並行 | | 快取 | 無 | 本地 + 遠端 | | 增量構建 | 無 | 自動 | | 包釋出 | 有(核心功能) | 無(不管釋出) | | 配置複雜度 | 中等 | 極簡 |

Lerna 管釋出,Turborepo 管構建,可以一起用。

和 pnpm workspace 配合

bash
# pnpm 管依賴,Turborepo 管構建編排
# 這是目前最推薦的組合

# package.json
{
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev --parallel",
    "test": "turbo run test",
    "lint": "turbo run lint"
  },
  "devDependencies": {
    "turbo": "^1.0.0",
    "pnpm": "7.x"  // pnpm 用 workspace 協議管理依賴
  }
}

小結

  • Turborepo 解決 Monorepo 的構建編排和快取問題,不解決包釋出問題
  • 本地 + 遠端快取是最大賣點,CI 構建時間可以從分鐘級降到秒級
  • 和 pnpm workspace 配合是最優方案:pnpm 管依賴,Turborepo 管構建
  • 配置極簡(一個 turbo.json),學習成本低
  • 適合有 3+ 包的 Monorepo 專案;包少的話 pnpm workspace 夠用

MIT Licensed