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