Angular a-t-il encore besoin d’être présenté ? Probablement pas mais faisons-le quand même brièvement. Angular est un framework client-side open-source basé sur TypeScript (lui-même basé sur JavaScript) et développé par Google.
La philosophie d’Angular est de créer des applications « single-page », autrement dit une application complète tournant sur une seule page web (même s’il peut donner l’illusion du contraire avec son système de routing). L’application se décompose ensuite en components, des éléments gérant un fragment de la page, comme un component affichant un menu de navigation par exemple. Elle peut aussi être constituée de modules, basiquement divers components regroupés autour d’un thème, un module pour l’espace privé d’un client par exemple.
Dans cet article, nous allons voir ensemble une nouvelle façon d’organiser un projet Angular : les Micro-Frontends.
C’est quoi un Micro-Frontend ?
Comme expliqué avant, une application Angular peut être constituée de modules œuvrant chacun pour un but différent. Un « Micro-Frontend » ou « MFE » c’est pratiquement la même chose, à un détail près : un MFE est indépendant.
Par cela, j’entends qu’un module tourne au sein d’une application Angular spécifique et ne peut pas être lancé en standalone. Il est lancé en même temps que l’application principale (ou par elle au moment où on en a besoin grâce au lazyloading). Un MFE quant à lui ne fait pas à proprement partie de l’application car il s’agit d’une application à part pouvant être lancé seul.
« Mais c’est juste une autre application Angular alors ? » Et bien oui. Seulement elle diffère d’une application classique car elle va exposer un ou plusieurs de ces modules. Si cela ne change rien au fonctionnement de notre MFE, cela permet en revanche à notre application principale, que nous appellerons shell par convention, d’utiliser, d’afficher ces modules exposés.
Prenons un exemple concret. Nous avons un shell qui affiche une boutique en ligne. Nous avons décidé que la fiche produit serait un MFE. Quand l’utilisateur essaye d’accéder à une fiche, l’application va, au lieu d’appeler le component / le module correspondant, appeler le MFE et lui laisser la main en quelque sorte, affichant donc le MFE au sein de ce shell.
Pourquoi utiliser des Micro-Frontends ?
L’un des premiers avantages a finalement déjà été cité en amont : l’indépendance. Au sein d’une équipe de développement, il est parfois compliqué d’éviter de se marcher sur les pieds. Un développeur va devoir modifier tel fichier mais celui-ci est déjà modifié par un autre ce qui peut amener à quelques conflits (de code, pas de développeurs). Même si avec Angular cela peut être plus simple d’éviter les conflits en découpant l’application en modules, les MFE offrent une liberté plus importante car si vous travaillez sur le shell et votre collègue sur le MFE, vous n’avez aucune chance de créer des conflits.
Point de vue maintenance, là aussi les MFE peuvent simplifier la tâche aux équipes car en cas de mise à jour, plus besoin de recompiler, redéployer toute la solution et donc de devoir tout retester.
Evidemment, si les avantages se limitaient à cela, se serait peu. La grande force des MFE vient de la flexibilité que cela apporte à une application et surtout dans un cadre comme le SaaS, le Software-as-a-Service. On peut très bien imaginer une entreprise mettant à disposition de ces clients un shell personnalisé MAIS faisant appel à un certain catalogue de MFE qui eux sont plus génériques.
Dans cet exemple, si les MFE sont bien indépendants, comme ils sont ensuite utilisés par le shell comme s’il s’agissait de ces propres modules, le shell du client peut très bien adapter le design des MFE selon les demandes du client (couleurs, polices… tout ce que le CSS/SCSS permet de faire). Mais ceci n’est qu’un exemple et selon les projets, on peut tout aussi bien imaginer exactement l’inverse : un shell commun à tous et des MFE spécifiques pour chaque client.
Et comment ça marche ?
Entrons donc dans la partie un peu plus technique. Afin de mettre en place notre application et son MFE, nous allons utiliser un module d’Angular, le « Federation Module ».
Commençons donc par l’installer sur les deux projets (ou votre seul projet, un même projet Angular pouvant accueillir plusieurs applications).
npm i @angular-architects/module-federation
Nous allons ensuite initialiser le federation module sur ces projets. Pour le shell :
ng g @angular-architects/module-federation:init –port 4200 –type host
Et pour le MFE :
ng g @angular-architects/module-federation:init –port 4201 –type remote
Si vos deux applications sont dans le même projet, rajoutez simplement « –project PROJECT-NAME ». Ces deux commandes vont ajouter un fichier webpack.config.js à la racine des projets, l’un étant un squelette pour un projet de type shell (–type host) et l’autre pour un projet de type MFE (–type remote).
Voyons donc un peu ces deux fichiers. Tout d’abord côté shell :
const { shareAll, withModuleFederationPlugin } = require(‘@angular-architects/module-federation/webpack’);
module.exports = withModuleFederationPlugin({
remotes: {
« mfe »: « http://localhost:4201/remoteEntry.js »,
},
shared: {
…shareAll({ singleton: true, strictVersion: true, requiredVersion: ‘auto’ }),
},});
Le fichier configure ici les différents MFE que le shell va pouvoir utiliser (ici un seul). Pour cela, nous avons un tableau de « remotes » où nous définissons justement cette liste avec pour chaque entrée une désignation pour le MFE (ici tout simplement mfe) et l’URL du fichier remoteEntry.js qui est accessible quand le MFE est lancé. Dans cet exemple, le port correspond simplement à celui indiqué lors de l’étape précédente. Notez que ce port peut être changé, il se trouve à la racine du projet dans le angular.json, dans « projects » => « NOM-PROJET » => « architect » => « serve » => « options ».
La partie « shared » du fichier permet d’indiquer les packages npm partagés entre le shell et le(s) MFE. Par défaut, on partage tout (on a la fibre généreuse) mais cela peut donc être modifié selon vos besoins.
Côté MFE, le fichier ressemble à ceci :
const { shareAll, withModuleFederationPlugin } = require(‘@angular-architects/module-federation/webpack’);
module.exports = withModuleFederationPlugin({
name: ‘mfe’,
exposes: { ‘./Module’: ‘./projects/mfe/src/app/mymodule/mymodule.module.ts’,
},shared: {
…shareAll({ singleton: true, strictVersion: true, requiredVersion: ‘auto’ }),
},
});
Plutôt similaire n’est-ce pas ? La différence ici et qu’au lieu de faire une liste des MFE à récupérer, on expose ici les modules ou les components que l’on veut partager avec le shell.
Enfin, la dernière étape va être côté shell, plus précisément au niveau des routes :
export const APP_ROUTES: Routes = [
{
path: »,
component: HomeComponent,
pathMatch: ‘full’
},
{
path: mymfe,
loadChildren: () => import(‘mfe/Module’).then(m => m.MyModule)
},
];
Nous indiquons que la route « mymfe » (donc en local hhtp://localhost :4200/myfe) va charger le MFE défini dans le webpack.config et pointer vers le module « mymodule ». Toutefois, comme « mfe/Module » n’existe pas au sein du shell, il faut le déclarer dans un fichier decl.d.ts dans le dossier src du shell :
declare module ‘mfe/Module’;
Ensuite, tout fonctionne comme sur un projet « normal », le MFE pouvant recevoir des données du shell ou lui en envoyer grâce par exemple aux EventEmitter / Observables.
Nous espérons que cet article vous sera utile et vous donnera envie d’essayer cette autre façon d’organiser votre projet Angular.