turborepo-caching

Par wshobson · agents

Configurez Turborepo pour des builds monorepo efficaces avec mise en cache locale et distante. À utiliser lors de la configuration de Turborepo, de l'optimisation des pipelines de build ou de la mise en place d'un cache distribué.

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

Mise en cache Turborepo

Modèles de production pour l'optimisation des builds Turborepo.

Quand utiliser cette compétence

  • Configurer de nouveaux projets Turborepo
  • Configurer des pipelines de build
  • Implémenter la mise en cache à distance
  • Optimiser les performances CI/CD
  • Migrer à partir d'autres outils monorepo
  • Déboguer les erreurs de cache

Concepts fondamentaux

1. Architecture Turborepo

Workspace Root/
├── apps/
│   ├── web/
│   │   └── package.json
│   └── docs/
│       └── package.json
├── packages/
│   ├── ui/
│   │   └── package.json
│   └── config/
│       └── package.json
├── turbo.json
└── package.json

2. Concepts de pipeline

Concept Description
dependsOn Tâches qui doivent se terminer d'abord
cache Mettre en cache les outputs ou non
outputs Fichiers à mettre en cache
inputs Fichiers affectant la clé de cache
persistent Tâches longue durée (serveurs dev)

Modèles

Modèle 1 : Configuration 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
    }
  }
}

Modèle 2 : Pipeline spécifique au package

// 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"]
    }
  }
}

Modèle 3 : Mise en cache à distance avec Vercel

# Connexion à Vercel
npx turbo login

# Lien vers le projet Vercel
npx turbo link

# Exécution avec cache à distance
turbo build --remote-only

# Variables d'environnement CI
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]'

Modèle 4 : Cache à distance auto-hébergé

// Serveur de cache à distance personnalisé (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";

// Récupérer un 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");
  }
});

// Mettre en cache un 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}`],
    });
  });
});

// Vérifier qu'un artifact existe
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);
// turbo.json pour le cache auto-hébergé
{
  "remoteCache": {
    "signature": false
  }
}
# Utiliser le cache auto-hébergé
turbo build --api="http://localhost:3000" --token="my-token" --team="my-team"

Modèle 5 : Filtrage et limitation de portée

# Builder un package spécifique
turbo build --filter=@myorg/web

# Builder un package et ses dépendances
turbo build --filter=@myorg/web...

# Builder un package et les packages qui en dépendent
turbo build --filter=...@myorg/ui

# Builder les packages modifiés depuis main
turbo build --filter='...[origin/main]'

# Builder les packages dans un répertoire
turbo build --filter='./apps/*'

# Combiner plusieurs filtres
turbo build --filter=@myorg/web --filter=@myorg/docs

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

# Inclure les dépendances des packages modifiés
turbo build --filter='...[HEAD^1]...'

Modèle 6 : Configuration de pipeline avancée

{
  "$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_*"]
    }
  }
}

Modèle 7 : Configuration package.json racine

{
  "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": "npm@10.0.0"
}

Déboguer le cache

# Simulation sèche pour voir ce qui s'exécuterait
turbo build --dry-run

# Output verbeux avec hashs
turbo build --verbosity=2

# Afficher le graphe de tâches
turbo build --graph

# Forcer l'absence de cache
turbo build --force

# Afficher l'état du cache
turbo build --summarize

# Déboguer une tâche spécifique
TURBO_LOG_VERBOSITY=debug turbo build --filter=@myorg/web

Bonnes pratiques

À faire

  • Définir des inputs explicites - Éviter l'invalidation du cache
  • Utiliser le protocole workspace - "@myorg/ui": "workspace:*"
  • Activer la mise en cache à distance - Partager entre CI et local
  • Filtrer dans CI - Builder uniquement les packages affectés
  • Mettre en cache les outputs de build - Pas les fichiers sources

À ne pas faire

  • Ne pas mettre en cache les serveurs dev - Utiliser persistent: true
  • Ne pas inclure les secrets dans env - Utiliser les variables d'environnement runtime
  • Ne pas ignorer dependsOn - Cause des conditions de course
  • Ne pas sur-filtrer - Peut oublier des dépendances

Skills similaires