Table des matières
Ouvrir table des matières
Introduction
Dans le monde effervescent de la programmation web, tu cherches sûrement des moyens pour booster ta productivité et gérer tes projets de manière optimale. Imagine un référentiel unique qui contient tous tes projets, facilitant le partage de code et la coordination d’équipe. C’est exactement ce que propose un monorepo.
Les frameworks populaires comme Next.js, Nuxt.js, Angular, ou encore Nest.js ont déjà adopté cette approche. S’ils l’ont fait, c’est qu’il y a de bonnes raisons ; il est donc temps que tu explores cette voie toi aussi.
Dans cet article, je vais te montrer les avantages d’un monorepo, te présenter l’outil incontournable comme Turbo, et te donner un exemple concret de création d’un monorepo depuis zéro ainsi que de migration de multi-repos à un monorepo.
Prépare-toi à découvrir une nouvelle façon de travailler, plus fluide et plus efficace.
Pourquoi Utiliser un Monorepo ?
Prenons un cas d’utilisation simple que tout le monde rencontre à un moment.
Imaginons que tu aies créé une application sous Next.js, contenu dans un dépôt GitHub. Tu as construit les composants, les fonctionnalités et les pages.
Bref, tout va bien : ton application = un dépôt GitHub.
Puis, l’idée te vient de créer une nouvelle application sous Next.js pour gérer la documentation de ton application principale, par souci de bien faire.
Ton application de documentation = un dépôt GitHub.
Et là, tu rencontres un problème : tu veux réutiliser des composants de ta première application dans cette nouvelle.
Alors, comment faire ?
Une solution serait d’isoler les composants de la première application dans un troisième dépôt GitHub, de les transformer en package NPM, et de les importer dans chacune des applications. Il faudra alors gérer le versionnage du package, incrémenter les versions dans les différents dépôts… Ta routine de travail va devenir de plus en plus complexe.
Heureusement, basculer cet exemple en monorepo va résoudre cette problématique de manière simple et efficace, car le partage du code entre les différentes applications deviendra trivial.
Donc, le principal intérêt d’utiliser un monorepo, c’est le partage de code entre diverses applications comme des composants, des interfaces, un domaine, etc.
Mais il y a bien d’autres raisons et bénéfices à utiliser un monorepo, comme le fait que toutes tes dépendances sont centralisées, donc mieux maîtrisées, ou encore une meilleure coordination entre les équipes.
Je t’ai préparé un tableau comparatif présentant des critères communs entre le multi-repo et le monorepo pour que tu puisses découvrir plus en détail les avantages de l’un et de l’autre.
Critère | Monorepo | Multirepo |
---|---|---|
Gestion des Dépendances | + Gestion centralisée des dépendances. | - Gestion des dépendances par projet, ce qui peut entraîner des duplications. |
+ Mise à jour des dépendances plus facile et uniforme. | + Chaque projet peut gérer ses propres dépendances indépendamment. | |
Partage de Code | + Partage de code simplifié entre projets. | - Partage de code plus complexe, nécessitant des packages externes. |
+ Réutilisation immédiate sans publication de paquets. | ||
Coordination d’Équipe | + Meilleure coordination et communication entre équipes. | - Coordination et synchronisation plus complexes. |
+ Changements visibles instantanément par toutes les équipes. | ||
Automatisation (CI/CD) | + Pipelines de CI/CD centralisés et optimisés. | - Pipelines de CI/CD séparés pour chaque projet. |
+ Tests et déploiements optimisés grâce au caching et à la détection de changements. | + Pipelines plus simples pour des projets individuels. | |
Scalabilité | - Peut devenir difficile à gérer avec la croissance des projets. | + Scalable car chaque projet est indépendant. |
+ Facilité d’ajout de nouveaux projets dans le même dépôt. | ||
Historique Git | - Historique git plus complexe et volumineux. | + Historique git plus clair et spécifique à chaque projet. |
Complexité de Configuration | - Nécessite une configuration initiale plus complexe pour les outils de build et CI/CD. | + Configuration initiale généralement plus simple. |
Isolation des Projets | - Moins d’isolation entre les projets, ce qui peut entraîner des conflits de version. | + Isolation totale entre les projets. |
+ Tests et builds peuvent affecter tous les projets si mal configurés. | - Changements dans un projet n’affectent pas les autres. | |
Mise à l’Échelle des Équipes | + Peut simplifier la collaboration entre plusieurs équipes travaillant sur différents projets. | - Chaque équipe peut travailler indépendamment sans affecter les autres. |
- Peut entraîner des conflits si plusieurs équipes travaillent sur des parties communes. | + Moins de conflits inter-équipes car les projets sont isolés. |
Turbo, l’outil parfait pour produire des monorepos TypeScript/Javascript
Il existe de bons outils pour gérer ses monorepos. J’ai personnellement utilisé Lerna 🔗 ainsi que Nx 🔗 produit par Nrwl 🔗 pour des projets d’envergure en entreprise.
Mais après avoir essayé et utilisé Turbo 🔗 de Vercel 🔗, je peux affirmer que c’est actuellement le meilleur outil pour créer un monorepo JavaScript/TypeScript selon moi.
Il est simple, rapide à prendre en main et surtout, efficace. Je n’ai jamais rencontré de problème avec cet outil, contrairement à Nx où j’ai eu quelques soucis.
De plus il fonctionne parfaitement bien pour mélanger des applications backend et frontend.
Petite aparté sur Vercel
Bien que très jeune (fondée en 2015), Vercel se distingue par son accent sur l’expérience développeur, offrant des outils et des services qui simplifient le processus de création, de déploiement et de maintenance des applications web.
On retrouve notamment le très populaire framework React, Next.js, mais également des plateformes comme V0 🔗 pour générer des interfaces UI React grâce à l’IA, ainsi que des outils comme Turbo pour structurer ton dépôt ou encore ncc 🔗 pour compiler un projet Node.js en un seul fichier.
Il te suffit de consulter leur GitHub 🔗 pour te rendre compte du nombre d’outils qu’ils ont produit et qu’ils mettent à notre disposition.
Utilisation de Turbo
Tu peux créer un nouveau projet from scratch avec Turbo en utilisant cette commande :
# If you use npm
npx create-turbo@latest
# If you use yarn
yarn dlx create-turbo@latest
# If you use pnpm
pnpm dl things turkey@latest
En lançant cette commande, Turbo va te poser deux questions : quel sera le nom de ton nouveau dossier et quel gestionnaire de paquets tu souhaites utiliser (npm, pnpm, yarn).
Note: Préférant désormais utiliser PNPM 🔗, j’afficherais les commandes avec pnpm, mais tu peux facilement trouver les équivalents pour yarn ou npm dans la documentation de Turbo 🔗.
Il faut savoir qu’en utilisant cette commande, Turbo va te créer une structure contenant :
Application packages
- apps/docs # next.js
- apps/web # next.js
Library packages
- packages/eslint-config
- packages/typescript-config
- packages/ui # component react/next.js
Tu peux retrouver le résultat sur ce commit 🔗.
Ce sera ma base pour la suite. Mais il existe d’autres exemples sur lesquels tu peux te baser pour ton propre monorepo.
Il y en a pour tous les goûts : Gatsby, Vite.js, Svelte, Vue.js, Prisma, TailwindCSS, Docker et même un Design System.
Tu peux retrouver tous les exemples dont il dispose ici 🔗.
Structure d’un dossier
La structure est relativement modeste : un dossier apps
, un dossier packages
et un fichier turbo.json
.
Techniquement, tu peux placer tes différentes applications et/ou packages dans un seul et même dossier : cela fonctionnerait. Toutefois, avoir un répertoire classique avec juste le fichier turbo.json
pourrait également fonctionner, comme tu peux le voir dans cet exemple ici 🔗.
Cependant, je te conseille de conserver la structure de l’exemple car elle permet d’avoir une organisation claire et cohérente dans ton dépôt.
Structure du turbo.json
$schema
:
"$schema": "https://turbo.build/schema.json"
Ce champ indique l’URL du schéma JSON utilisé pour valider ce fichier de configuration. Cela aide les éditeurs de code à offrir une autocomplétion et des vérifications syntaxiques.
tasks
:
"tasks": { ... }
Ce champ contient la définition des différentes tâches que tu peux exécuter avec Turborepo. Chaque tâche est définie par un nom (comme build
, lint
, ou dev
) et a ses propres propriétés.
Tâche build
:
"build": {
"dependsOn": ["^build"],
"inputs": ["$TURBO_DEFAULT$", ".env*"],
"outputs": [".next/**", "!.next/cache/**"]
}
dependsOn
: ["^build"]
- Cette tâche dépend des tâches
build
des projets dont ce projet dépend. Le ^ signifie que cela s’applique aux dépendances de niveau supérieur.
inputs
: ["$TURBO_DEFAULT$", ".env*"]
- Les fichiers ou dossiers qui déclenchent cette tâche s’ils sont modifiés.
$TURBO_DEFAULT$
inclut les entrées par défaut comme le code source, et.env*
englobe tous les fichiers commençant par.env
.
outputs
: [".next/**", "!.next/cache/**"]
- Les fichiers ou dossiers produits par cette tâche.
.next/**
inclut tous les fichiers dans le dossier.next
sauf ceux situés dans.next/cache
.
Tâche lint
:
"lint": {
"dependsOn": ["^lint"]
}
dependsOn
: ["^lint"]
- Cette tâche dépend des tâches
lint
des projets dont ce projet dépend, impliquant qu’elles doivent être exécutées avant celle-ci.
Tâche dev
:
"dev": {
"cache": false,
"persistent": true
}
cache
: false
- Cette tâche ne sera pas mise en cache. Cela signifie qu’elle s’exécutera à chaque fois que tu lances la tâche
dev
.
persistent
: true
- Cette tâche reste active (persistante). Elle ne se termine pas automatiquement et continue de fonctionner, ce qui est typique pour des tâches de développement comme les serveurs de développement.
Turbo dev, lint et build
Grâce au fichier turbo.json
configuré, tu vas pouvoir exploiter les commandes build
, lint
et dev
de Turbo qui sont présentes dans le fichier package.json
à la racine du monorepo :
"build": "turbo build",
"dev": "turbo dev",
"lint": "turbo lint",
Ces trois commandes sont des options que tu connais déjà dans tes projets web. Turbo va simplement exécuter ces commandes individuellement pour chaque package et chaque application globalement, à condition que le package ou l’application dispose de la commande correspondante.
Par exemple, si on exécute turbo lint
, le lint va s’exécuter sur le package ui
, l’application docs
et web
, car ils possèdent cette commande.
Si on exécute turbo build
, le build va s’exécuter uniquement sur l’application docs
et web
car ils possèdent cette commande.
Turbo va donc exécuter les commandes correspondantes si elles existent dans chacun de tes packages ou applications, ce qui te permet une grande aisance et flexibilité dans ton monorepo sans devoir modifier ta configuration.
Si tu ajoutes ou supprimes une application ou un package, Turbo va automatiquement les détecter et les prendre en compte.
Ajouter une nouvelle application
L’exemple mentionné ci-dessus de monorepo Turbo est une bonne base pour démarrer, surtout si tu aimes Next.js. Bien entendu, d’autres frameworks comme Vue.js ou Svelte pourraient te correspondre davantage.
Tu peux également utiliser cet exemple pour supprimer des applications ou des packages qui ne te conviennent pas.
Supposons que tu souhaites supprimer l’application docs
parce que tu n’en as pas besoin; il suffit de la supprimer depuis ton terminal avec la commande suivante :
rm -rf apps/docs
Voici le résultat sur ce commit 🔗.
Pour ajouter une nouvelle application, procède comme si tu créais une nouvelle application de manière classique.
Imaginons que tu souhaites installer une API NestJS (que j’adore 😍), rien de plus simple :
cd apps
nest new api
Et voilà ! Voici le résultat sur ce commit 🔗.
Si tu as supprimé l’application docs
, tu obtiendras ce résultat :
Application packages
- apps/api # NestJS
- apps/web # Next.js
Library packages
- packages/eslint-config
- packages/typescript-config
- packages/ui # Component React/Next.js
Utiliser un package dans un autre
Pour assurer la cohérence de ton repo, il serait judicieux d’importer les configurations Eslint et TypeScript déjà utilisées par ton application web Next.js.
Pour l’API, ajoute simplement ces deux lignes dans la section DevDependencies
de ton package.json
, pas dans dependencies
car il s’agit de dépendances de développement :
"@repo/eslint-config": "workspace:*",
"@repo/typescript-config": "workspace:*",
Si tu avais besoin du package ui
, tu aurais ajouté "@repo/ui": "workspace:*"
dans la section dependencies
du package.json
.
Pour exploiter ces configurations, ajoute les lignes suivantes :
- Dans
extends
du fichierapps/api/.eslintrc.js
:'@repo/eslint-config/library.js'
. - Dans
apps/api/tsconfig.json
:"extends": "@repo/typescript-config/base.json"
.
Voici le résultat dans ce commit 🔗.
Démonstration complète en vidéo
Conclusion
Félicitations! 🎉
Tu as maintenant un monorepo configuré avec TypeScript et Turbo de Vercel. C’est une base robuste sur laquelle tu peux continuer à construire et élargir selon les besoins de tes projets.
Tu peux retrouver le code sur ce dépôt GitHub : https://github.com/GaetanCottrez/tuto-monorepo-nextjs-nestjs-clerk-tinacms 🔗
Les monorepos avec Turbo permettent une flexibilité et une efficacité énormes, réduisant le temps passé sur les configurations et dépendances et te permettant de te concentrer sur l’innovation et le développement.
As-tu trouvé ceci utile? J’adorerais entendre tes retours et comment tu vas appliquer ces concepts dans tes projets!