← Retour aux articles
Tooling TurborepoMonorepoDevOpsTypeScript

Turborepo : des builds monorepo qui ne relancent que ce qui a changé

· 5 min de lecture

Un monorepo permet de partager du code entre applications et packages, mais le build ralentit à chaque package ajouté. Un script build à la racine relance tout, à chaque fois, même pour du code qui n’a pas bougé depuis le dernier commit, et une passe de CI complète finit par tout reconstruire et retester pour livrer un correctif d’une ligne. Turborepo s’attaque à ça en traitant vos scripts comme un graphe de tâches plutôt qu’une liste plate. Il sait quel package dépend de quel autre, exécute les tâches indépendantes en parallèle, et rejoue depuis le cache en quelques millisecondes tout ce qu’il a déjà construit.

Le graphe de tâches

Turborepo lit un turbo.json à la racine où chaque script devient une tâche avec ses dépendances déclarées. La syntaxe ^build signifie « construis d’abord chaque dépendance interne », ce qui suffit à Turborepo pour calculer un ordre d’exécution correct sur tout le workspace.

{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**", "!.next/cache/**"]
    },
    "test": {
      "dependsOn": ["build"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

Vous ne listez jamais les packages à la main. Turborepo déduit le graphe des manifests de votre workspace, donc ajouter un package ou une nouvelle dépendance redessine l’ordre de build automatiquement.

Un cache basé sur le contenu

Chaque exécution de tâche est hashée à partir de tout ce qui peut affecter sa sortie : fichiers source, dépendances, variables d’environnement et la configuration de la tâche elle-même. Si ce hash a déjà été vu, Turborepo saute complètement le travail et restaure les sorties et les logs précédents depuis le cache.

turbo run build
# premier run :  10 packages construits en 48s
# second run :   10 packages, FULL TURBO, 120ms (tout rejoué depuis le cache)

Le champ outputs indique à Turborepo quels fichiers capturer pour qu’un cache hit puisse les restaurer. Comme le hash repose sur le contenu, modifier un package n’invalide que ce package et les tâches en aval, jamais tout l’arbre.

Exécuter et filtrer les tâches

La plupart du temps vous lancez une tâche sur tout le repo, mais --filter restreint l’exécution à un package et, au besoin, à tout ce dont il dépend ou ce qui dépend de lui. C’est ce qui garde l’itération locale et la CI rapides quand vous n’avez touché qu’un coin du monorepo.

turbo run lint test --filter=web
turbo run build --filter=...@acme/ui
turbo run test --filter=[origin/main]

La dernière forme cible le travail sur les packages modifiés depuis une référence Git, donc une pull request ne construit et ne teste que ce que son diff affecte réellement. Les tâches indépendantes de l’ensemble résultant s’exécutent quand même en parallèle sur vos cœurs.

Le cache distant

Le cache local n’aide que la machine qui l’a produit. Le cache distant pousse ces artefacts hashés vers un store partagé, si bien qu’un cache rempli par un développeur ou un job de CI devient un cache hit pour tous les autres.

turbo login
turbo link
# la CI et les coéquipiers partagent désormais le même cache d'artefacts

Le gain est le plus net en CI : si un package a déjà été construit pour un commit antérieur avec des entrées identiques, le pipeline télécharge le résultat au lieu de le reconstruire. Un build vert sur un changement sans rapport peut passer de minutes à secondes.

Le pruning pour Docker

Copier un monorepo entier dans une image Docker casse le cache de layers, car un changement n’importe où invalide la layer d’installation des dépendances. turbo prune génère un monorepo partiel qui ne contient qu’une application cible et les packages dont elle a réellement besoin, avec un lockfile réduit.

turbo prune @acme/web --docker
# crée out/ avec un sous-ensemble minimal et installable du repo

Vous copiez d’abord le lockfile et les manifests réduits, vous installez, puis vous copiez le source. Docker met en cache la layer d’installation jusqu’à ce que de vraies dépendances changent, soit exactement le comportement qu’un monorepo casse sinon.

Où il se place

Turborepo n’est ni un bundler ni un gestionnaire de packages. Il se pose au-dessus de vos workspaces pnpm, npm ou Yarn existants et orchestre les scripts que vous avez déjà, ce qui rend l’adoption incrémentale : vous ajoutez un turbo.json, vous pointez vos scripts vers turbo run, et rien à la façon dont chaque package se construit n’a à changer. Il coordonne le travail au lieu de remplacer les outils qui l’exécutent.

Conclusion

Turborepo se rentabilise dès qu’un monorepo dépasse quelques packages, parce qu’il arrête de payer pour reconstruire du code qui n’a pas changé. Le graphe de tâches vous donne l’ordre correct gratuitement, le cache basé sur le contenu transforme la plupart des builds en replays instantanés, et le cache distant étend ce gain à toute votre équipe et votre CI. C’est une couche fine qui ne change rien à vos outils individuels et tout à la vitesse à laquelle ils tournent ensemble. Pour n’importe quel monorepo TypeScript sérieux, le marché est difficile à refuser.