Ajout et Structuration des Applications et Librairies dans un Monorepo
Bienvenue dans cette leçon dédiée à un aspect fondamental de la gestion des monorepos : l'ajout et la structuration efficace de vos applications et librairies. Dans le cadre de notre cours "Maîtriser les Monorepos : Optimisez votre Développement Web et Mobile", comprendre comment organiser votre code est aussi crucial que de choisir les bons outils. Une structure bien pensée garantit la maintenabilité, la performance et la scalabilité de votre projet sur le long terme.
Cette leçon vous guidera à travers les principes, les bonnes pratiques et les étapes concrètes pour intégrer harmonieusement de nouveaux projets dans votre monorepo.
Introduction : L'Importance Cruciale de la Structure
Un monorepo, par définition, est un dépôt unique contenant le code de multiples projets. Sans une organisation claire et cohérente, il peut rapidement devenir un "big ball of mud", perdant tous les avantages qu'il est censé offrir. L'objectif principal de cette leçon est de vous fournir les clés pour structurer votre monorepo de manière à :
- Améliorer la clarté et la découvrabilité des projets.
- Faciliter la collaboration entre les équipes.
- Optimiser les performances des outils de build et de test.
- Simplifier la gestion des dépendances internes et externes.
- Assurer la scalabilité de votre codebase à mesure qu'elle grandit.
Nous allons explorer les conventions de nommage, les outils d'aide à la structuration et, surtout, des exemples pratiques pour ajouter des applications web, mobiles ou des librairies partagées.
Pourquoi la Structure Compte dans un Monorepo ?
La structure de votre monorepo n'est pas qu'une question d'esthétique. Elle a un impact direct et profond sur l'efficacité de votre développement :
-
Clarté et Organisation
Une arborescence logique rend instantanément clair ce qui est une application déployable et ce qui est une librairie réutilisable. Cela réduit la charge cognitive pour les nouveaux arrivants et les développeurs travaillant sur plusieurs projets.
-
Maintenabilité Accrue
Des conventions claires minimisent les erreurs et facilitent la localisation du code pertinent pour une fonctionnalité ou une correction de bug. Un code bien organisé est plus facile à comprendre, à modifier et à tester.
-
Optimisation des Outils de Monorepo
Des outils comme Turborepo, Nx ou Lerna s'appuient fortement sur une structure prévisible pour des fonctionnalités clés comme :
- Caching intelligent : Identifier les projets qui n'ont pas changé pour éviter de les reconstruire.
- Détection d'impact : Savoir quels projets sont affectés par une modification dans une librairie partagée.
- Exécution parallèle : Orchestrer les tâches de build, de test ou de déploiement de manière efficiente.
-
Collaboration Simplifiée
Lorsque plusieurs équipes travaillent sur des projets différents au sein du même monorepo, une structure cohérente évite les conflits et permet à chacun de trouver rapidement les ressources dont il a besoin, favorisant ainsi le partage de code et la réutilisation.
-
Scalabilité et Évolution
Un monorepo est conçu pour grandir. Une bonne structure est la fondation qui permet d'ajouter de nouvelles applications et librairies sans que l'ensemble devienne un labyrinthe ingérable. Elle encourage un découpage logique et des responsabilités claires pour chaque module.
Principes Fondamentaux de Structuration
La plupart des monorepos modernes suivent des conventions de structuration assez similaires. Le principe directeur est de séparer les applications déployables des librairies réutilisables.
-
Découpage par Type (Convention
apps/etpackages/oulibs/)C'est la convention la plus courante et la plus recommandée.
-
apps/(Applications) : Ce répertoire contient toutes les unités de code qui sont destinées à être déployées et exécutées indépendamment.- Exemples :
web-dashboard,mobile-app,api-gateway,admin-panel,microservice-auth. - Chaque sous-dossier dans
apps/représente une application distincte.
- Exemples :
-
packages/oulibs/(Librairies / Packages) : Ce répertoire contient le code réutilisable qui n'est pas destiné à être déployé seul, mais plutôt à être consommé par une ou plusieurs applications (ou d'autres librairies).- Exemples :
ui-components(composants React/Vue partagés),utils(fonctions utilitaires génériques),design-system(tokens de style),api-client(clients HTTP pré-configurés),shared-types(interfaces TypeScript partagées). - Chaque sous-dossier dans
packages/oulibs/représente une librairie/un package distinct.
- Exemples :
-
-
Cohérence Nommage
Adoptez une convention de nommage claire et suivez-la scrupuleusement.
- Utilisez des tirets (
kebab-case) pour les noms de dossiers et de packages (ex:web-dashboard,ui-components). - Les noms doivent être descriptifs et concis.
- Utilisez des tirets (
-
Dépendances Explicites
Chaque application ou librairie doit explicitement déclarer ses dépendances dans son propre fichier
package.json. Les outils de monorepo (comme Yarn Workspaces ou pnpm Workspaces) gèrent ensuite les liens symboliques (symlinks) pour que les dépendances internes soient résolues correctement. -
Isolation des Responsabilités
Chaque package ou application doit avoir une responsabilité unique et bien définie. Cela favorise la modularité, réduit les couplages et simplifie les tests. Si un package commence à prendre trop de responsabilités, il est peut-être temps de le diviser en plusieurs packages plus petits.
Les Outils pour Gérer la Structure des Monorepos
La gestion des monorepos n'est pas uniquement une affaire de convention ; des outils spécialisés facilitent grandement la tâche :
-
Yarn Workspaces / pnpm Workspaces / npm Workspaces
Ce sont les gestionnaires de paquets natifs pour les monorepos. Ils permettent de :
- Installer les dépendances de tous les projets en une seule commande depuis la racine du monorepo.
- Lier les dépendances internes (entre les packages du monorepo) via des liens symboliques, évitant ainsi d'avoir à les publier séparément.
- Dédupliquer les dépendances externes pour économiser de l'espace disque. Ils sont la fondation de tout monorepo moderne.
-
Turborepo
Axé sur la performance, Turborepo excelle dans l'optimisation des tâches de build et de test grâce à un système de caching distribué et d'exécution parallèle. Il s'intègre parfaitement avec Yarn/pnpm Workspaces.
-
Nx (Nrwl Extensible)
Un framework de développement complet pour les monorepos. Nx offre :
- Génération de code (schematics) pour ajouter facilement de nouvelles applications/librairies.
- Graphes de dépendances pour visualiser les relations entre les projets.
- Détection d'impact pour savoir exactement quels projets sont affectés par un changement.
- Optimisation des tâches similaire à Turborepo, mais avec une approche plus prescriptive.
-
Lerna
Historiquement l'un des premiers outils de monorepo, Lerna est excellent pour la publication de multiples packages depuis un seul dépôt. Bien que certains de ses rôles (comme la gestion des dépendances) aient été repris par les gestionnaires de paquets natifs, il reste utile pour des workflows de versioning et de publication complexes.
Mise en Pratique : Ajout d'une Nouvelle Application ou Librairie
Maintenant, voyons comment ajouter concrètement un nouveau projet à votre monorepo. Nous utiliserons pnpm comme gestionnaire de paquets pour nos exemples, mais les principes sont similaires avec Yarn ou npm Workspaces.
Prérequis : Un Monorepo Initialisé
Assurez-vous que votre monorepo est déjà configuré avec un gestionnaire de paquets supportant les workspaces. Typiquement, cela signifie avoir un fichier pnpm-workspace.yaml (pour pnpm) ou un champ "workspaces" dans votre package.json (pour Yarn/npm) à la racine du monorepo.
Voici à quoi pourrait ressembler un pnpm-workspace.yaml typique à la racine de votre monorepo :
# monorepo-root/pnpm-workspace.yaml
packages:
- 'apps/*' # Inclut tous les dossiers directs dans 'apps/'
- 'packages/*' # Inclut tous les dossiers directs dans 'packages/'
# Vous pouvez ajouter d'autres patterns si nécessaire, ex: 'tools/*', 'tests/*'
Structure Exemple d'un Monorepo
Pour mieux visualiser, voici une structure de monorepo que nous allons manipuler :
monorepo-root/
├── apps/
│ ├── web-app/ # Application web Next.js principale
│ └── docs-site/ # Site de documentation (ex: Docusaurus, VitePress)
├── packages/
│ ├── ui-components/ # Librairie de composants UI React
│ ├── utils/ # Librairie de fonctions utilitaires génériques
│ └── api-client/ # Client API pour communiquer avec les services backend
├── pnpm-workspace.yaml # Configuration pnpm pour les workspaces
└── package.json # Fichier package.json racine (scripts globaux, etc.)
Étape 1: Créer le Répertoire du Nouveau Projet
Décidez si votre nouveau projet est une application (apps/) ou une librairie (packages/) et créez le dossier correspondant.
- Exemple : Ajout d'une nouvelle application web
admin-dashboard# Depuis la racine du monorepo mkdir -p apps/admin-dashboard cd apps/admin-dashboard - Exemple : Ajout d'une nouvelle librairie
shared-types# Depuis la racine du monorepo mkdir -p packages/shared-types cd packages/shared-types
Étape 2: Initialiser le Projet
Chaque projet au sein du monorepo doit être un package Node.js à part entière, avec son propre package.json.
-
Pour une application (ex: Next.js pour
admin-dashboard) Utilisez le CLI du framework si disponible. Cela créera lepackage.jsonet les fichiers de base.# Dans le répertoire apps/admin-dashboard/ npx create-next-app@latest . --typescript --eslint --tailwind --app --use-pnpm # Suivez les instructions, choisissez le nom du projet "admin-dashboard" -
Pour une librairie (ex:
shared-types) Initialisez simplement unpackage.json.# Dans le répertoire packages/shared-types/ pnpm init # Répondez aux questions, le nom doit être "shared-types"Ou plus simplement pour initialiser avec des valeurs par défaut :
# Dans le répertoire packages/shared-types/ pnpm init -y
Étape 3: Déclarer le Projet dans la Configuration du Monorepo
Si vous avez utilisé des glob patterns comme apps/* et packages/* dans votre pnpm-workspace.yaml (ce qui est le cas dans notre exemple), le nouveau répertoire sera automatiquement inclus. Sinon, vous devrez ajouter manuellement le chemin du nouveau projet.
Pas besoin de modifier pnpm-workspace.yaml si apps/* et packages/* sont déjà présents et couvrent votre nouveau projet.
Étape 4: Gérer les Dépendances
C'est là que la puissance des workspaces entre en jeu.
-
Installer les dépendances externes : Allez à la racine du monorepo et exécutez votre commande d'installation :
# Depuis la racine du monorepo pnpm install # Ou pour un package spécifique pnpm add react-query --filter admin-dashboardCela installera toutes les dépendances listées dans tous les
package.jsondes projets du monorepo, en les dédupliquant et en gérant les symlinks. -
Ajouter des dépendances internes (entre projets du monorepo) : C'est le scénario le plus courant et le plus puissant. Supposons que notre
admin-dashboardveuille utiliser la librairieui-componentsetapi-client.# Depuis la racine du monorepo pnpm add ui-components api-client --filter admin-dashboardCette commande va mettre à jour le
package.jsondeapps/admin-dashboardpour y inclureui-componentsetapi-clientcomme dépendances. Le gestionnaire de paquets utilisera un lien symbolique pour pointer vers les versions locales des packages, au lieu de les télécharger depuis un registre npm.Voici à quoi ressemblerait le
package.jsondeapps/admin-dashboardaprès l'ajout :// apps/admin-dashboard/package.json { "name": "admin-dashboard", "version": "0.1.0", "private": true, "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint" }, "dependencies": { "next": "14.x.x", "react": "^18.2.0", "react-dom": "^18.2.0", "ui-components": "workspace:^1.0.0", // <-- Dépendance interne vers packages/ui-components "api-client": "workspace:^1.0.0" // <-- Dépendance interne vers packages/api-client }, "devDependencies": { "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", "autoprefixer": "^10.0.1", "eslint": "^8", "eslint-config-next": "14.x.x", "postcss": "^8", "tailwindcss": "^3.3.0", "typescript": "^5" } }Notez la syntaxe
workspace:^1.0.0. Elle indique àpnpm(etYarn) que c'est une dépendance interne au monorepo et qu'il faut utiliser la version locale (symlink) du packageui-componentsavec une version compatible avec^1.0.0.
Étape 5: Scripts de Construction et de Test
Chaque projet doit avoir ses propres scripts build, test, dev (ou start) dans son package.json. Ces scripts définissent comment le projet est construit ou exécuté.
-
Exemple :
packages/ui-components/package.json// packages/ui-components/package.json { "name": "ui-components", "version": "1.0.0", "main": "dist/index.js", "module": "dist/index.mjs", "types": "dist/index.d.ts", "files": ["dist"], "scripts": { "build": "tsc --build", "test": "jest", "lint": "eslint .", "dev": "tsc --watch" }, "dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { "@types/react": "^18.2.0", "@types/react-dom": "^18.2.0", "typescript": "^5.0.0", "jest": "^29.0.0", "@swc/jest": "^0.2.29", "@types/jest": "^29.5.10", "eslint": "^8.56.0", "eslint-config-custom": "workspace:^1.0.0" } }Ici,
ui-componentsa des scripts pour sa propre compilation TypeScript, ses tests Jest, etc. -
Exécution de scripts depuis la racine du monorepo Les outils de monorepo vous permettent d'exécuter ces scripts pour un projet spécifique ou pour tous les projets qui en ont un :
# Exécuter le script 'build' pour le package 'ui-components' pnpm --filter ui-components build # Exécuter le script 'dev' pour l'application 'admin-dashboard' pnpm --filter admin-dashboard dev # Exécuter le script 'test' pour tous les packages qui l'ont pnpm test # (si un script 'test' est défini dans le package.json racine pour tous les workspaces) # Ou avec Turborepo/Nx pour une meilleure performance # turbo run test # nx run-many --target=test
Bonnes Pratiques et Pièges à Éviter
Une fois vos projets ajoutés, il est essentiel de suivre certaines pratiques pour maintenir l'ordre et l'efficacité de votre monorepo.
-
Granularité Adaptée
Ne découpez pas trop vos librairies. Un package pour trois fonctions utilitaires simples peut être excessif. L'objectif est un découpage logique et réutilisable, pas une micro-segmentation à outrance. Commencez plus large et découpez quand le besoin de réutilisation ou de responsabilité unique apparaît clairement.
-
Pas de Dépendances Cycliques
Un projet A ne doit pas dépendre d'un projet B, qui dépendrait à son tour du projet A. Cela crée des boucles de dépendances qui rendent le code difficile à comprendre, à tester et à maintenir. Les outils de monorepo (comme Nx) peuvent souvent détecter ces cycles.
-
Versions Cohérentes et Gestion du Versioning
Pour les dépendances internes, utilisez la syntaxe
workspace:*(ouworkspace:^1.0.0,workspace:~1.0.0) pour indiquer que vous voulez la version locale du package. Pour les dépendances externes, assurez-vous de maintenir une cohérence. Des outils commechangesetssont excellents pour gérer le versioning et la publication de multiples packages dans un monorepo. -
Documentation Claire
Chaque package/application doit avoir une documentation minimale dans son propre README.md expliquant son rôle, comment l'exécuter et comment y contribuer.
-
Conventions de Code Unifiées
Implémentez des outils comme ESLint, Prettier, et TypeScript (avec des configurations partagées) à la racine du monorepo. Cela garantit que tout le code suit les mêmes standards, quel que soit le contributeur ou le projet.
-
Tests Robustes
Chaque package doit avoir sa propre suite de tests (unitaires, d'intégration). Les outils de monorepo permettent d'exécuter ces tests de manière sélective ou parallèle, ce qui est crucial pour la fiabilité.
-
Gabarits (Templates)
Pour accélérer l'ajout de nouveaux projets, créez des gabarits ou utilisez des outils de génération de code (comme les schematics de Nx) pour initialiser des applications ou des librairies avec une structure et des configurations prédéfinies.
Conclusion
L'ajout et la structuration des applications et des librairies dans un monorepo sont des pierres angulaires de son succès. En adoptant des conventions claires, en tirant parti des gestionnaires de paquets avec des workspaces et en utilisant des outils comme Turborepo ou Nx, vous transformez un simple dépôt en un environnement de développement hautement optimisé et collaboratif.
Rappelez-vous : la cohérence est reine. Une structure bien pensée réduit la friction, améliore la productivité et prépare votre codebase à une croissance future. N'hésitez pas à expérimenter ces concepts dans votre propre environnement et à adapter les meilleures pratiques aux besoins spécifiques de vos projets. La maîtrise de ces techniques est un pas essentiel pour optimiser votre développement web et mobile avec les monorepos.