Construisez une architecture micro-frontends Angular avec Module Federation : découpage, partage de dépendances, routing et stratégie de déploiement.
Concept Module Federation et micro-frontends
Les micro-frontends divisent une Single Page Application en plusieurs applications indépendantes déployables séparément. Chaque équipe possède son domaine métier de bout en bout — du style au déploiement — sans coordination de release avec les autres équipes.
Webpack Module Federation (introduit dans Webpack 5) est le mécanisme technique qui permet à un host de charger à la volée des modules JavaScript compilés par un autre projet (remote), partageant optionnellement des dépendances communes comme Angular ou RxJS.
Architecture cible typique :
- Shell (host) — navigation globale, auth, layout principal
- Remote catalogue — domaine produits, déployé indépendamment
- Remote panier — domaine commandes, déployé indépendamment
- Shared design system — composants UI communs (optionnel)
Mise en place avec @angular-architects/module-federation
Le package @angular-architects/module-federation encapsule la configuration
Webpack Module Federation et fournit des schémas Angular CLI pour générer host et remotes
automatiquement.
# Dans le projet shell (host)
ng add @angular-architects/module-federation --project shell --port 4200 --type host
# Dans chaque remote
ng add @angular-architects/module-federation --project catalogue --port 4201 --type remote
ng add @angular-architects/module-federation --project panier --port 4202 --type remote
Ces commandes génèrent un webpack.config.js dans chaque projet et modifient
angular.json pour utiliser @angular-architects/module-federation/webpack
comme builder custom.
Angular 17+ et esbuild
Module Federation repose sur Webpack. Si votre projet Angular 17+ utilise esbuild
(builder par défaut), vous devez revenir à @angular-devkit/build-angular:browser
ou utiliser @angular-architects/native-federation, l'alternative basée
sur les ES Modules natifs.
Configuration host et remote Webpack
Le remote déclare les modules qu'il expose. Le host déclare les remotes dont il consomme les modules. Voici la configuration minimale pour chacun.
webpack.config.js du remote (catalogue) :
// apps/catalogue/webpack.config.js
const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');
module.exports = withModuleFederationPlugin({
name: 'catalogue',
exposes: {
// Alias public → fichier source exposé
'./Module': './apps/catalogue/src/app/catalogue/catalogue.module.ts',
},
shared: {
...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }),
},
});
webpack.config.js du host (shell) :
// apps/shell/webpack.config.js
const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');
module.exports = withModuleFederationPlugin({
remotes: {
// Clé locale → URL distante (runtime ou statique)
catalogue: 'http://localhost:4201/remoteEntry.js',
panier: 'http://localhost:4202/remoteEntry.js',
},
shared: {
...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }),
},
});
localhost:4201 par l'URL CDN/S3 du remote déployé.
Vous pouvez aussi charger l'URL dynamiquement depuis une API de configuration.
Routing dynamique et chargement lazy
Le shell charge les remotes en lazy loading via le router Angular. La fonction
loadRemoteModule récupère le module exposé par le remote à la demande.
// apps/shell/src/app/app.routes.ts
import { Routes } from '@angular/router';
import { loadRemoteModule } from '@angular-architects/module-federation';
export const APP_ROUTES: Routes = [
{
path: '',
loadComponent: () => import('./home/home.component').then(m => m.HomeComponent),
},
{
path: 'catalogue',
loadChildren: () =>
loadRemoteModule({
type: 'module',
remoteEntry: 'http://localhost:4201/remoteEntry.js',
exposedModule: './Module',
}).then(m => m.CatalogueModule),
},
{
path: 'panier',
loadChildren: () =>
loadRemoteModule({
type: 'module',
remoteEntry: 'http://localhost:4202/remoteEntry.js',
exposedModule: './Module',
}).then(m => m.PanierModule),
},
];
Pour charger l'URL du remote depuis une API (configuration dynamique) :
// apps/shell/src/app/mf-config.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { firstValueFrom } from 'rxjs';
export interface MfManifest {
[key: string]: string; // ex: { catalogue: 'https://cdn.example.com/catalogue/remoteEntry.js' }
}
@Injectable({ providedIn: 'root' })
export class MfConfigService {
private manifest: MfManifest = {};
async load(): Promise {
this.manifest = await firstValueFrom(
this.http.get<MfManifest>('/assets/mf-manifest.json'),
);
}
getRemoteUrl(name: string): string {
return this.manifest[name] ?? '';
}
constructor(private http: HttpClient) {}
}
Checklist micro-frontends en production
Avant de déployer votre architecture micro-frontends, validez ces points critiques pour garantir stabilité, performance et maintenabilité.
- Toutes les dépendances critiques (Angular, RxJS) partagées en
singleton: true - URLs des remotes externalisées dans un manifeste JSON (pas en dur dans le code)
- Fallback UI en cas d'échec de chargement d'un remote (try/catch sur
loadRemoteModule) - CORS configuré sur chaque remote pour autoriser le domaine du shell
- Headers cache-control adaptés sur
remoteEntry.js(courte durée) vs chunks (longue durée) - Tests d'intégration e2e couvrant la navigation cross-remote (Cypress)
- Monitoring distinct par remote en production (erreurs, perf, déploiements)
- Convention de versioning des interfaces exposées entre équipes (breaking changes)