mcpField
T
turborepo-caching

turborepo-caching

github.com/wshobson開発41.9k10.7k 回インストール2 週間前に更新
リポジトリ

turborepo-caching について

Turborepoのビルドキャッシュ最適化ガイドです。ビルドパイプライン、リモートキャッシュ、CIを設定する開発者向けに、設定テンプレートや対象の絞り込み、キャッシュの調査方法を紹介します。

主な機能

  1. 01ビルドパイプライン設定の指針
  2. 02ローカルとリモートのキャッシュ設定
  3. 03タスクのフィルタリングと範囲指定
  4. 04キャッシュミスの調査
  5. 05CI設定テンプレート

活用シーン

  1. 01新しいTurborepoプロジェクトのキャッシュ設定
  2. 02CIでのビルド最適化
  3. 03リモートキャッシュの構築
  4. 04キャッシュミスの調査

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/wshobson/agents --skill turborepo-caching

インストール後、Claude Code で/turborepo-cachingを入力して読み込まれたか確認します。

詳細

Turborepo のキャッシュ

Turborepo によるビルド最適化の本番運用パターン。

このスキルを使う場面

  • 新しい Turborepo プロジェクトをセットアップするとき
  • ビルドパイプラインを構成するとき
  • リモートキャッシュを実装するとき
  • CI/CD のパフォーマンスを最適化するとき
  • 他のモノレポツールから移行するとき
  • キャッシュミスをデバッグするとき

中核となる概念

1. Turborepo のアーキテクチャ
Workspace Root/
├── apps/
│   ├── web/
│   │   └── package.json
│   └── docs/
│       └── package.json
├── packages/
│   ├── ui/
│   │   └── package.json
│   └── config/
│       └── package.json
├── turbo.json
└── package.json
2. パイプラインの概念
概念説明
dependsOn先に完了する必要があるタスク
cache出力をキャッシュするかどうか
outputsキャッシュするファイル
inputsキャッシュキーに影響するファイル
persistent長時間実行されるタスク (開発サーバー)

テンプレート

テンプレート 1: turbo.json の構成
{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": [".env", ".env.local"],
  "globalEnv": ["NODE_ENV", "VERCEL_URL"],
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**", "!.next/cache/**"],
      "env": ["API_URL", "NEXT_PUBLIC_*"]
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": ["coverage/**"],
      "inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts"]
    },
    "lint": {
      "outputs": [],
      "cache": true
    },
    "typecheck": {
      "dependsOn": ["^build"],
      "outputs": []
    },
    "dev": {
      "cache": false,
      "persistent": true
    },
    "clean": {
      "cache": false
    }
  }
}
テンプレート 2: パッケージ固有のパイプライン
// apps/web/turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "extends": ["//"],
  "pipeline": {
    "build": {
      "outputs": [".next/**", "!.next/cache/**"],
      "env": ["NEXT_PUBLIC_API_URL", "NEXT_PUBLIC_ANALYTICS_ID"]
    },
    "test": {
      "outputs": ["coverage/**"],
      "inputs": ["src/**", "tests/**", "jest.config.js"]
    }
  }
}
テンプレート 3: Vercel によるリモートキャッシュ
# Login to Vercel
npx turbo login

# Link to Vercel project
npx turbo link

# Run with remote cache
turbo build --remote-only

# CI environment variables
TURBO_TOKEN=your-token
TURBO_TEAM=your-team
# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:

env:
  TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
  TURBO_TEAM: ${{ vars.TURBO_TEAM }}

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npx turbo build --filter='...[origin/main]'

      - name: Test
        run: npx turbo test --filter='...[origin/main]'
テンプレート 4: セルフホストのリモートキャッシュ
// Custom remote cache server (Express)
import express from "express";
import { createReadStream, createWriteStream } from "fs";
import { mkdir } from "fs/promises";
import { join } from "path";

const app = express();
const CACHE_DIR = "./cache";

// Get artifact
app.get("/v8/artifacts/:hash", async (req, res) => {
  const { hash } = req.params;
  const team = req.query.teamId || "default";
  const filePath = join(CACHE_DIR, team, hash);

  try {
    const stream = createReadStream(filePath);
    stream.pipe(res);
  } catch {
    res.status(404).send("Not found");
  }
});

// Put artifact
app.put("/v8/artifacts/:hash", async (req, res) => {
  const { hash } = req.params;
  const team = req.query.teamId || "default";
  const dir = join(CACHE_DIR, team);
  const filePath = join(dir, hash);

  await mkdir(dir, { recursive: true });

  const stream = createWriteStream(filePath);
  req.pipe(stream);

  stream.on("finish", () => {
    res.json({
      urls: [`${req.protocol}://${req.get("host")}/v8/artifacts/${hash}`],
    });
  });
});

// Check artifact exists
app.head("/v8/artifacts/:hash", async (req, res) => {
  const { hash } = req.params;
  const team = req.query.teamId || "default";
  const filePath = join(CACHE_DIR, team, hash);

  try {
    await fs.access(filePath);
    res.status(200).end();
  } catch {
    res.status(404).end();
  }
});

app.listen(3000, "127.0.0.1");
// turbo.json for the local cache demo
{
  "remoteCache": {
    "signature": false
  }
}
# This local demo does not authenticate the dummy token.
# Use only disposable artifacts on a trusted machine; do not expose the server.
turbo build --api="http://127.0.0.1:3000" --token="my-token" --team="my-team"
テンプレート 5: フィルタリングとスコープ設定
# Build specific package
turbo build --filter=@myorg/web

# Build package and its dependencies
turbo build --filter=@myorg/web...

# Build package and its dependents
turbo build --filter=...@myorg/ui

# Build changed packages since main
turbo build --filter='...[origin/main]'

# Build packages in directory
turbo build --filter='./apps/*'

# Combine filters
turbo build --filter=@myorg/web --filter=@myorg/docs

# Exclude package
turbo build --filter='!@myorg/docs'

# Include dependencies of changed
turbo build --filter='...[HEAD^1]...'
テンプレート 6: 高度なパイプライン構成
{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**"],
      "inputs": ["$TURBO_DEFAULT$", "!**/*.md", "!**/*.test.*"]
    },
    "test": {
      "dependsOn": ["^build"],
      "outputs": ["coverage/**"],
      "inputs": ["src/**", "tests/**", "*.config.*"],
      "env": ["CI", "NODE_ENV"]
    },
    "test:e2e": {
      "dependsOn": ["build"],
      "outputs": [],
      "cache": false
    },
    "deploy": {
      "dependsOn": ["build", "test", "lint"],
      "outputs": [],
      "cache": false
    },
    "db:generate": {
      "cache": false
    },
    "db:push": {
      "cache": false,
      "dependsOn": ["db:generate"]
    },
    "@myorg/web#build": {
      "dependsOn": ["^build", "@myorg/db#db:generate"],
      "outputs": [".next/**"],
      "env": ["NEXT_PUBLIC_*"]
    }
  }
}
テンプレート 7: ルートの package.json のセットアップ
{
  "name": "my-turborepo",
  "private": true,
  "workspaces": ["apps/*", "packages/*"],
  "scripts": {
    "build": "turbo build",
    "dev": "turbo dev",
    "lint": "turbo lint",
    "test": "turbo test",
    "clean": "turbo clean && rm -rf node_modules",
    "format": "prettier --write \"**/*.{ts,tsx,md}\"",
    "changeset": "changeset",
    "version-packages": "changeset version",
    "release": "turbo build --filter=./packages/* && changeset publish"
  },
  "devDependencies": {
    "turbo": "^1.10.0",
    "prettier": "^3.0.0",
    "@changesets/cli": "^2.26.0"
  },
  "packageManager": "[email protected]"
}

キャッシュのデバッグ

# Dry run to see what would run
turbo build --dry-run

# Verbose output with hashes
turbo build --verbosity=2

# Show task graph
turbo build --graph

# Force no cache
turbo build --force

# Show cache status
turbo build --summarize

# Debug specific task
TURBO_LOG_VERBOSITY=debug turbo build --filter=@myorg/web

ベストプラクティス

やるべきこと
  • 入力 (inputs) を明示的に定義する - キャッシュの無効化を避ける
  • ワークスペースプロトコルを使う - "@myorg/ui": "workspace:*"
  • リモートキャッシュを有効にする - CI とローカルで共有する
  • CI でフィルタリングする - 影響を受けたパッケージだけをビルドする
  • ビルド出力をキャッシュする - ソースファイルではなく
やってはいけないこと
  • 開発サーバーをキャッシュしない - persistent: true を使う
  • env にシークレットを含めない - 実行時の環境変数を使う
  • dependsOn を無視しない - 競合状態を引き起こす
  • 過度にフィルタリングしない - 依存関係を見落とす可能性がある

コメント