Lab 26 : NX — Architecture Monorepo
📖 Ressources
🚀 Code de départ
Aucun — vous créerez un workspace NX depuis zéro.
Dans ce lab, vous mettrez en place un monorepo NX, générerez deux applications Angular, créerez une bibliothèque de composants partagés et utiliserez l'alias de chemin pour la consommer. Vous explorerez également le graphe de dépendances et la commande affected.
📝 Instructions
Étape 1 : Créer le workspace NX
npx create-nx-workspace@latest my-workspace
Quand demandé :
- Stack : Angular
- Type : Integrated Monorepo
- Nom de l'application : shop
- Format des styles : CSS (ou SCSS)
- Activer le cache distribué (Nx Cloud) : Non
my-workspace/
├── apps/
│ └── shop/
├── libs/
├── nx.json
├── tsconfig.base.json
└── package.json
Étape 2 : Démarrer l'application
cd my-workspace
nx serve shop
Ouvrez http://localhost:4200 — l'application Angular par défaut s'exécute.
Étape 3 : Générer une deuxième application
nx g @nx/angular:app admin
Vous avez maintenant deux applications indépendantes partageant le même dépôt :
apps/
├── shop/
└── admin/
Exécutez chacune indépendamment :
nx serve shop # http://localhost:4200
nx serve admin # http://localhost:4201
Étape 4 : Générer une bibliothèque UI partagée
nx g @nx/angular:lib ui --directory=libs/ui
Note (Nx v22+) : Passez
uicomme nom positionnel et--directory=libs/uicomme chemin de sortie complet. Évitez de combiner--name=uiet--directory=libs/uiensemble — Nx traite--directorycomme le chemin complet, et les deux flags peuvent produire un dossierlibs/ui/ui/imbriqué au lieu dulibs/ui/attendu.
Pourquoi libs/ et pas src/ ?
Un projet Angular standard garde tout dans src/. Dans un Integrated Monorepo Nx, la convention est de placer le code partageable dans le dossier racine libs/ afin que plusieurs applications (shop, admin, …) puissent l'importer sans artifices de chemins circulaires. Chaque bibliothèque est un projet TypeScript indépendant avec son propre project.json.
libs/ui vs libs/shop/shared-ui
| Chemin | Portée | À utiliser quand… |
|---|---|---|
libs/ui/ | Workspace entier | Primitives UI génériques (boutons, icônes, typographie) utilisées par toutes les applications |
libs/shop/shared-ui/ | Scope shop uniquement | Composants spécifiques au domaine shop qui ne doivent pas déborder dans d'autres scopes |
Utilisez libs/ui pour des briques pouvant appartenir à un design system. Utilisez libs/shop/shared-ui pour des widgets de panier, des cartes produit, ou tout ce qui est propre au shop.
Vérifier l'alias de chemin généré par Nx
Après l'exécution du générateur, ouvrez tsconfig.base.json et cherchez la nouvelle entrée sous paths :
{
"compilerOptions": {
"paths": {
"@my-workspace/ui": ["libs/ui/src/index.ts"]
}
}
}
C'est ainsi que TypeScript résout @my-workspace/ui vers les fichiers source réels dans libs/ui/src/index.ts. Aucun alias webpack ni résolveur personnalisé n'est nécessaire — il s'agit du mappage de chemins TypeScript standard.
⚠️ Le préfixe dépend du nom d'organisation saisi lors du
create-nx-workspace. Si vous avez choisiacme, l'alias sera@acme/ui. Lisez toujourstsconfig.base.json → compilerOptions.pathspour trouver l'alias exact de votre workspace avant de l'utiliser dans vos imports.
Tags et frontières de modules
Nx applique des règles d'import via @nx/enforce-module-boundaries dans eslint.config.mjs. Les bibliothèques doivent porter des tags pour que la règle sache quels scopes peuvent les importer.
Ouvrez libs/ui/project.json et vérifiez (ou ajoutez) le champ tags :
{
"tags": ["scope:shared", "type:ui"]
}
Sans scope:shared, ESLint bloquera toute application qui tente d'importer cette bibliothèque dès que des depConstraints sont configurées. Vous pouvez inspecter les règles de frontières dans eslint.config.mjs :
// eslint.config.mjs (extrait)
"@nx/enforce-module-boundaries": [
"error",
{
"depConstraints": [
{ "sourceTag": "scope:shop", "onlyDependOnLibsWithTags": ["scope:shop", "scope:shared"] },
{ "sourceTag": "scope:admin", "onlyDependOnLibsWithTags": ["scope:admin", "scope:shared"] }
]
}
]
Cela signifie que shop ne peut importer que des libs taguées scope:shop ou scope:shared. Taguez libs/ui avec scope:shared pour que les deux applications puissent l'utiliser librement.
Étape 5 : Créer un composant partagé
Générez un composant bouton dans la bibliothèque :
nx g @nx/angular:component libs/ui/src/lib/button/button --export
Note (Nx v22+) : Le flag
--projecta été supprimé du générateur de composants. Passez le chemin complet vers l'endroit où le composant doit être créé à la place.
Modifiez libs/ui/src/lib/button/button.component.ts :
import { Component, input } from '@angular/core';
@Component({
selector: 'lib-button',
standalone: true,
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 ButtonComponent {
variant = input<'primary' | 'secondary'>('primary');
}
Assurez-vous qu'il est exporté depuis libs/ui/src/index.ts :
export * from './lib/button/button.component';
Étape 6 : Utiliser la bibliothèque dans l'application shop
Dans apps/shop/src/app/app.component.ts, importez en utilisant l'alias trouvé dans tsconfig.base.json :
import { ButtonComponent } from '@my-workspace/ui'; // remplacez le préfixe si le vôtre est différent
@Component({
selector: 'app-root',
standalone: true,
imports: [ButtonComponent],
template: `
<h1>Boutique</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 shop — le bouton partagé s'affiche dans l'application shop.
Étape 7 : Visualiser le graphe de dépendances
nx graph
Le navigateur ouvre un graphe interactif. Vérifiez :
shopdépend deuiadminn'a pas encore de dépendances
Étape 8 : Exécuter les commandes affected
Faites une modification dans ButtonComponent, puis :
# Ne builder que ce qui a changé + ce qui en dépend
nx affected --target=build
# Ne tester que ce qui a changé + ce qui en dépend
nx affected --target=test
NX détermine que modifier ui affecte shop (qui en dépend) — les deux sont donc reconstruits. admin n'est pas touché.
C'est le principal avantage en CI/CD : vous ne payez que pour ce qui a changé.