Lab 27 : NX — Monorepo from scratch
📖 Ressources
Section intitulée « 📖 Ressources »🚀 Code de départ
Section intitulée « 🚀 Code de départ »Pas de code de départ — vous allez créer un workspace NX from scratch.
Ce lab couvre les mêmes objectifs que le Lab 26 — bibliothèques partagées, alias de chemin, graphe de dépendances et affected — mais démarre depuis un workspace vide plutôt que depuis le preset Angular. Cette approche vous donne un contrôle total sur ce qui est installé et généré.
📝 Instructions
Section intitulée « 📝 Instructions »Étape 1 : Créer un workspace vide
Section intitulée « Étape 1 : Créer un workspace vide »npx create-nx-workspace@latest my-workspace --preset=appsQuand demandé, choisissez No pour Nx Cloud.
--preset=apps crée un workspace minimal sans code de démo, sans plugin de framework, sans application générée — juste la structure :
my-workspace/├── packages/ ← placeholder (racine npm workspaces)├── nx.json├── tsconfig.base.json├── tsconfig.json└── package.jsonLes dossiers apps/ et libs/ n’existent pas encore — ils sont créés lors de l’exécution des générateurs dans les étapes suivantes.
Comparez avec le preset Angular (Lab 26), qui pré-génère une application shop et installe @nx/angular pour vous. Ici, rien n’est supposé.
Étape 2 : Ajouter le support Angular
Section intitulée « Étape 2 : Ajouter le support Angular »Le workspace vide n’a aucun support de framework. Commencez par naviguer dans le workspace et exporter une variable d’environnement qui supprime un avertissement de compatibilité TypeScript :
cd my-workspaceexport NX_IGNORE_UNSUPPORTED_TS_SETUP=trueAjoutez maintenant le plugin Angular :
npx nx add @nx/angularCela installe @nx/angular et l’enregistre dans nx.json. Cela ne génère pas d’application — c’est votre prochaine étape.
Étape 3 : Générer la première application
Section intitulée « Étape 3 : Générer la première application »nx g @nx/angular:app shopQuand demandé, choisissez votre format de feuille de style (CSS ou SCSS).
Dans Nx 23, les applications sont générées à la racine du workspace (pas dans un sous-dossier apps/) :
shop/shop-e2e/Lancez-la :
nx serve shopOuvrez http://localhost:4200 — une application Angular vierge sans contenu de démo.
Étape 4 : Générer une deuxième application
Section intitulée « Étape 4 : Générer une deuxième application »nx g @nx/angular:app adminVous avez maintenant deux applications indépendantes dans le même dépôt :
shop/shop-e2e/admin/admin-e2e/Lancez-les indépendamment :
nx serve shop # http://localhost:4200nx serve admin # http://localhost:4201Étape 5 : Générer une bibliothèque UI partagée
Section intitulée « Étape 5 : Générer une bibliothèque UI partagée »nx g @nx/angular:lib --name=ui --directory=libs/uiNote (Nx v23+) : Les arguments positionnels ne sont plus supportés par ce générateur — utilisez toujours
--name=uiexplicitement.--directory=libs/uiplace la bibliothèque danslibs/ui/à la racine du workspace.
La bibliothèque est créée dans libs/ui/ avec son propre project.json. Le générateur crée aussi un composant par défaut dans libs/ui/src/lib/ui/ — vous pouvez le conserver ou le supprimer ; il n’est pas utilisé dans ce lab.
Pourquoi libs/ et non src/ ?
Dans un Integrated Monorepo Nx, la convention est de placer le code partageable dans le dossier libs/ racine afin que plusieurs applications (shop, admin, …) puissent toutes l’importer. Chaque bibliothèque est un projet TypeScript indépendant avec son propre project.json.
libs/ui vs libs/shop/shared-ui
| Chemin | Portée | Quand utiliser… |
|---|---|---|
libs/ui/ |
Workspace entier | Primitives UI génériques utilisées par toute app |
libs/shop/shared-ui/ |
Portée shop uniquement | Composants spécifiques au domaine shop |
Vérifiez l’alias de chemin généré par Nx
Après la génération, ouvrez tsconfig.base.json :
{ "compilerOptions": { "paths": { "@org/ui": ["./libs/ui/src/index.ts"] } }}TypeScript résout @org/ui vers libs/ui/src/index.ts. Pas besoin d’alias webpack.
⚠️ Le préfixe dépend de l’organisation npm définie à la création du workspace. Lisez toujours
tsconfig.base.json → compilerOptions.pathspour trouver votre alias exact avant de l’utiliser dans les imports.
Tags et frontières de modules
Ouvrez libs/ui/project.json et ajoutez des tags :
{ "tags": ["scope:shared", "type:ui"]}Les tags sont appliqués par @nx/enforce-module-boundaries dans eslint.config.mjs. Sans scope:shared, ESLint bloquera les imports depuis les apps une fois les contraintes configurées :
"@nx/enforce-module-boundaries": [ "error", { "depConstraints": [ { "sourceTag": "scope:shop", "onlyDependOnLibsWithTags": ["scope:shop", "scope:shared"] }, { "sourceTag": "scope:admin", "onlyDependOnLibsWithTags": ["scope:admin", "scope:shared"] } ] }]Étape 6 : Créer un composant partagé
Section intitulée « Étape 6 : Créer un composant partagé »nx g @nx/angular:component libs/ui/src/lib/button/button --exportNote (Nx v23+) : Passez le chemin complet — le flag
--projecta été supprimé. Le flag--exportajoute automatiquement l’export danslibs/ui/src/index.ts.
Dans Angular 20, les composants générés utilisent la convention de nommage courte : le fichier est button.ts (pas button.component.ts) et la classe est Button (pas ButtonComponent). standalone est la valeur par défaut et n’a pas besoin d’être déclaré.
Remplacez le contenu de libs/ui/src/lib/button/button.ts par :
import { Component, input } from '@angular/core';
@Component({ selector: 'lib-button', template: ` <button class="btn" [class]="variant()"> <ng-content /> </button> `, styles: [` .btn { padding: 8px 16px; border: none; cursor: pointer; border-radius: 4px; } .primary { background: #007bff; color: white; } .secondary { background: #6c757d; color: white; } `]})export class Button { variant = input<'primary' | 'secondary'>('primary');}Le flag --export a déjà ajouté l’export dans libs/ui/src/index.ts. Vérifiez qu’il pointe vers le bon fichier :
export * from './lib/button/button';Étape 7 : Utiliser la bibliothèque dans l’app shop
Section intitulée « Étape 7 : Utiliser la bibliothèque dans l’app shop »Dans shop/src/app/app.component.ts :
import { Button } from '@org/ui'; // vérifiez tsconfig.base.json pour votre préfixe exact
@Component({ selector: 'app-root', imports: [Button], template: ` <h1>Shop</h1> <lib-button variant="primary">Ajouter au panier</lib-button> <lib-button variant="secondary">Voir les détails</lib-button> `})export class AppComponent {}nx serve shopLe bouton partagé s’affiche dans l’app shop. L’app admin peut importer le même composant depuis le même chemin — une seule source de vérité.
Étape 8 : Visualiser le graphe de dépendances
Section intitulée « Étape 8 : Visualiser le graphe de dépendances »nx graphVérifiez :
shopdépend deuiadminn’a pas encore de dépendances
Étape 9 : Exécuter les commandes affected
Section intitulée « Étape 9 : Exécuter les commandes affected »Faites une modification dans Button, puis :
nx affected --target=buildnx affected --target=testNx détermine que modifier ui affecte shop — les deux sont reconstruits. admin n’est pas touché. En CI vous ne reconstruisez et retestez que ce qui a vraiment changé.
🔄 Comparaison des presets
Section intitulée « 🔄 Comparaison des presets »| Lab 26 (preset Angular) | Lab 27 (preset apps) | |
|---|---|---|
| Configuration | Prompts interactifs | Flag --preset=apps |
| Plugin Angular | Pré-installé | npx nx add @nx/angular |
| Première app | Auto-générée (shop) |
Générée manuellement |
| Emplacement des apps | shop/ à la racine du workspace |
shop/ à la racine du workspace |
| Code de démo | Inclus | Aucun |
| Idéal pour | Démarrage Angular rapide | Multi-framework ou setup contrôlé |

