Introduction aux monorepos avec TypeScript
Avec la complexité croissante des applications web modernes, les développeurs sont de plus en plus confrontés au problème de la gestion du code. Lorsqu'un projet est composé de nombreux modules interconnectés — frontend, backend, bibliothèques d'usage général — l'approche traditionnelle avec des dépôts séparés (polyrepo) peut entraîner une duplication de code, une désynchronisation des versions et des difficultés de refactoring.
Un monorepo est une stratégie dans laquelle le code de plusieurs projets est stocké dans un seul dépôt. Combiné avec TypeScript, le monorepo devient un outil puissant : le typage strict garantit la sécurité des modifications au sein de l'ensemble de la stack, et un système de build unifié simplifie la gestion des dépendances.
Dans cet article, nous verrons comment organiser un monorepo avec TypeScript : de la structure de base aux outils avancés comme Nx et Turborepo.
Avantages du monorepo pour les projets TypeScript
Avant de passer à la pratique, voyons pourquoi le monorepo est particulièrement avantageux pour les projets TypeScript :
- Typage unifié — TypeScript permet de réutiliser les interfaces et les types entre les packages sans duplication.
- Commits atomiques — les modifications de l'API et de ses consommateurs peuvent être effectuées en un seul commit, ce qui simplifie la revue et le rollback.
- Refactoring simplifié — l'IDE (par exemple, VS Code) gère correctement les renommages et les déplacements de fichiers dans l'ensemble du dépôt.
- Mise en cache des builds — des outils comme Nx mettent en cache les résultats de compilation, accélérant ainsi le CI/CD.
Structure de base d'un monorepo
Commençons par une structure simple mais efficace. Créons un projet racine et plusieurs packages :
my-monorepo/├── packages/│ ├── shared/ # Types et utilitaires communs│ │ ├── src/│ │ ├── package.json│ │ └── tsconfig.json│ ├── server/ # Backend (par exemple, Express)│ │ ├── src/│ │ ├── package.json│ │ └── tsconfig.json│ └── web/ # Frontend (React)│ ├── src/│ ├── package.json│ └── tsconfig.json├── package.json # package.json racine├── tsconfig.base.json # tsconfig de base pour tous les packages└── .gitignoreLe package.json racine doit contenir la configuration des workspaces. Pour npm et Yarn, cela ressemble à ceci :
{ "name": "my-monorepo", "private": true, "workspaces": [ "packages/*" ], "scripts": { "build": "tsc --build", "clean": "tsc --build --clean" }}Le tsconfig.base.json définit les paramètres de compilation communs :
{ "compilerOptions": { "target": "ES2020", "module": "commonjs", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "declaration": true, "declarationMap": true, "sourceMap": true, "composite": true }}Chaque package étend la configuration de base :
{ "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./dist", "rootDir": "./src" }, "include": ["src"]}Gestion des dépendances et TypeScript paths
L'un des problèmes clés des monorepos est la configuration correcte des imports entre les packages. Au lieu de publier les packages sur npm, nous utilisons les TypeScript Project References et les paths.
Configuration des Project References
Ajoutons dans le tsconfig.json racine des références aux projets :
{ "files": [], "references": [ { "path": "./packages/shared" }, { "path": "./packages/server" }, { "path": "./packages/web" } ]}Maintenant, lors de l'exécution