Aller au contenu principal

Lab 27 : NX — Monorepo from scratch

📖 Ressources

🚀 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

Étape 1 : Créer un workspace vide

npx create-nx-workspace@latest my-workspace --preset=apps

Quand 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.json

Les 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é.

Pourquoi démarrer from scratch ?

L'approche --preset=apps est utile quand :

  • Vous voulez mixer des frameworks (Angular + React dans le même monorepo)
  • Vous voulez auditer exactement ce qui est installé
  • Vous configurez un monorepo et ajouterez les frameworks progressivement

É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-workspace
export NX_IGNORE_UNSUPPORTED_TS_SETUP=true
Pourquoi cette variable ?

Nx 23 crée les workspaces avec les project references TypeScript activées par défaut. Angular ne supporte pas les project references (voir angular#37276). Cette variable permet à tous les générateurs Angular de s'exécuter sans blocage. Gardez-la active pour toute la session du lab.

Ajoutez maintenant le plugin Angular :

npx nx add @nx/angular

Cela 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

nx g @nx/angular:app shop

Quand 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 shop

Ouvrez http://localhost:4200 — une application Angular vierge sans contenu de démo.

Étape 4 : Générer une deuxième application

nx g @nx/angular:app admin

Vous 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:4200
nx serve admin # http://localhost:4201

Étape 5 : Générer une bibliothèque UI partagée

nx g @nx/angular:lib --name=ui --directory=libs/ui

Note (Nx v23+) : Les arguments positionnels ne sont plus supportés par ce générateur — utilisez toujours --name=ui explicitement. --directory=libs/ui place la bibliothèque dans libs/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

CheminPortéeQuand utiliser…
libs/ui/Workspace entierPrimitives UI génériques utilisées par toute app
libs/shop/shared-ui/Portée shop uniquementComposants 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.paths pour 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é

nx g @nx/angular:component libs/ui/src/lib/button/button --export

Note (Nx v23+) : Passez le chemin complet — le flag --project a été supprimé. Le flag --export ajoute automatiquement l'export dans libs/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

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 shop

Le 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

nx graph

Vérifiez :

  • shop dépend de ui
  • admin n'a pas encore de dépendances

Étape 9 : Exécuter les commandes affected

Faites une modification dans Button, puis :

nx affected --target=build
nx affected --target=test

Nx 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

Lab 26 (preset Angular)Lab 27 (preset apps)
ConfigurationPrompts interactifsFlag --preset=apps
Plugin AngularPré-installénpx nx add @nx/angular
Première appAuto-générée (shop)Générée manuellement
Emplacement des appsshop/ à la racine du workspaceshop/ à la racine du workspace
Code de démoInclusAucun
Idéal pourDémarrage Angular rapideMulti-framework ou setup contrôlé