Moduli Federati con Webpack 5

Un nuovo modo per approcciare ai micro-frontends?

Davide D'Antonio
weBeetle
13 min readOct 12, 2020

--

I Moduli federati di Webpack 5 ci offrono un modo completamente nuovo per condividere codice in fase di esecuzione tra diverse applicazioni. Per capirne correttamente il funzionamento facciamo un esempio:

Immaginiamo di avere un CMS dove vengono inseriti articoli ed un Sito Web che li mostra consentendone l’acquisto. Il codice sorgente del CMS e del Sito Web sono completamente separati e vengono sviluppati da due team distinti. Il CMS, inoltre, possiede una funzionalità di anteprima del prodotto prima che venga pubblicato e reso disponibile al cliente. Il problema di questa funzionalità di anteprima e che non è centralizzata, il Sito Web ed il CMS non condividono lo stesso codice sorgente e, ad un certo punto, le alte sfere richiedono che la funzionalità di anteprima e quella di visualizzazione devono essere esattamente le stesse.

Come possiamo fare?

Abbiamo a disposizione tre modi diversi per farlo:

  • Creiamo un modulo npm.
  • Utilizziamo i micro-frontends.
  • Utilizziamo i moduli federati di Webpack 5.

Adesso analizziamo le tre possibili soluzioni e valutiamone i pro ed i contro.

Modulo NPM

Il primo approccio, sicuramente anche quello più utilizzato, è quello di creare un modulo npm, in modo che venga installato su entrambe le applicazioni. Quindi in entrambe le applicazioni, tutto quello che riguarda il rendering del widget in questione viene sostituito con il modulo npm. Vediamo una semplice illustrazione che ci fa capire meglio quanto detto:

Pacchetto NPM

Questa soluzione ci fa pensare subito ai vantaggi:

  • Il codice è versionato e controllato;
  • Entrambe le applicazioni utilizzano lo stesso codice;
  • Non è possibile modificare il codice se non modificando il codice sorgente del modulo e avanzando la versione.

Come qualsiasi altra cosa, questo approccio ha anche degli svantaggi:

  • Il processo che intercorre tra la modifica al codice ed il rilascio è lento;
  • Ogniqualvolta si effettua un rilascio del modulo, bisogna effettuare un’aggiornamento e successivamente una build del CMS e del Sito Web;
  • Se non si fa attenzione si rischia di avere versioni del widget differenti sulle due applicazioni.

Micro-frontends

Con questo approccio, il codice del nostro amatissimo widget viene estratto e messo in un progetto separato a disposizione sul client o sul server a runtime. Graficamente accade questo:

I micro-frontend

I vantaggi di questo approccio sono i seguenti:

  • Possiamo effettuare l’aggiornamento del singolo micro-frontend senza effettuare un aggiornamento delle applicazioni che lo utilizzano;
  • La conseguenza del punto precedente è che le applicazioni che ne fanno utilizzo saranno sempre in sync.

Gli svantaggi invece:

  • Dovrete impacchettare il codice in modo che funzioni in tutte le applicazioni che lo ospitano;
  • Manutenere un altro micro-frontend all’interno del vostro sistema per un widget. Questo implica, per esempio, di aggiungere un ulteriore monitoring per fare in modo che vengano gestiti i down.

Moduli Federati

Con i moduli federati il CMS, o il Sito Web, semplicemente espone il widget ed il codice per renderizzarlo utilizzando il plugin ModuleFederationPlugin di Webpack 5. Le altre applicazioni possono utilizzarlo a loro volta, utilizzando lo stesso plugin. Graficamente ciò che avviene è quanto segue:

In questa nuova architettura, nella configurazione del CMS viene specificato un Sito Web esterno e definito come “remoto”. Il codice del componente viene importato come un qualsiasi altro componente React.

I vantaggi di questa metodologia sono i seguenti:

  • Il codice sorgente del widget rimane nel posto in cui dovrebbe essere;
  • Non abbiamo la necessita di utilizzare framework per micro-frontends;
  • Non abbiamo la necessità di loader personalizzati.

Come tutte le architetture software, esistono anche qui alcuni svantaggi:

  • L’applicazione non è completa in quanto il codice dei moduli federati viene caricato ed eseguito a runtime;
  • Se c’è un bug nel modulo, questo causerà malfunzionamenti in tutte le applicazioni che lo utilizzano.

Vediamo come funziona l’esempio appena descritto

Il modo più semplice per comprendere i moduli federati è utilizzandoli. Adesso analizzeremo il codice sorgente implementato dell’esempio appena descritto. Perfetto, iniziamo!

Il codice sorgente lo trovate su github a questo indirizzo https://github.com/davidedantonio/federated-module-example e clonate il repository in locale con il comando

A questo punto non ci resta che installare tutte le dipendenze per eseguire il codice in locale. Dalla root del progetto digitiamo:

e successivamente

Se tutto è andato a buon file, ed aprite il browser all’indirizzo http://localhost:9001 , troverete quello che secondo l’esempio è il Sito Web.

Ognuno dei riquadri evidenziati in blu rappresentano i prodotti che gli utenti addetti all’inserimento dei prodotti del CMS, dovrebbero visualizzare come anteprima, prima della pubblicazione sul Sito Web. Tecnicamente il Widget ha le seguenti caratteristiche: un titolo, una descrizione ed un colore che lo caratterizza (di default blu).

Perfetto! Adesso diamo uno sguardo al CMS. Con il browser navigate all’indirizzo http://localhost:9002 . Quello che vi ritroverete davanti è qualcosa del tipo:

Ora, visto che se state leggendo questo articolo, al 99,99% siete sviluppatori, prima di compilare il form e cliccare sul pulsante “Show Preview” apriamo la console per gli sviluppatori del browser e clicchiamo sul tab “Network”. Compiliamo i campi con le informazioni richieste e clicchiamo sul pulsante “Show Preview”. A seconda del colore e dei dati immessi ci ritroveremo di fronte qualcosa del genere:

Benissimo. Il Widget è esattamente come ce lo aspettavamo! Ma analizziamo cosa è accaduto nella console degli sviluppatori che avete aperto in precedenza. Dovreste ritrovarvi una richiesta http all’indirizzo:

o qualcosa del genere. Se analizziamo i dettagli della richiesta vedrete che sono stati scaricati pochissimi kb di codice:

Con questo piccolo esempio abbiamo appena visto come funzionano i Moduli federati di Webpack 5. In pratica, il codice sorgente del Widget del Prodotto risiede nel progetto website ma viene “condiviso” con chiunque voglia utilizzarlo.

Nel caso specifico l’applicazione cms conosce l’indirizzo ed il nome del componente del widget, vedremo fra poco come, e in fase di esecuzione, lo richiede nel momento in cui ne ha bisogno.

Analizziamo il progetto

Con il vostro editor preferito aprite il progetto. Vi ritroverete davanti il seguente albero di directory e files:

Notiamo che esiste una directory principale federated-module-example e al suo interno è stato creato il file package.json che contiene gli script descritti in precedenza:

  • install:all che installa tutte le dipendenze all’interno delle due sottodirectory website e cms
  • dev:all che avvia, in modo concorrente, i server di sviluppo per le due applicazioni.

È giunto il momento di capire come funzionano queste applicazioni. Partiamo dal Sito Web!

Il Sito Web

Analizziamo prima il progetto del Sito Web e quindi la directory website. In questa semplice applicazione, abbiamo creato un componente App che altro non è che il rendering della pagina del Sito Web che abbiamo visto in precedenza.

In pratica, Appnon fa altro che renderizzare una barra di navigazione ed i sei componenti ProductWidget (simulando in pratica il retrieve di sei prodotti da una ipotetica API). Il componente ProductWidget è specificato nel seguente modo:

Inoltre App è utilizzato dal componente bootstrap , il quale si occupa di renderizzarlo in una pagina HTML utilizzando ReactDOM nel seguente modo

Di conseguenza il componente App è a sua volta renderizzato in un div definito in una pagina HTML che si trova nella directory public/index.html

Non c’è altro da aggiungere per quanto riguarda l’applicazione. Per eseguire questo codice viene utilizzato babel per il transpiling del codice e Webpack per raggrupparlo e farlo girare su un server di sviluppo. Se diamo un occhiata al package.json è possibile vedere tutte le dipendenze necessarie:

Nulla di particolare fin qui, tranne per il fatto che stiamo utilizzando la versione di Webpack 5.0.0 o successive.

Le cose interessanti, invece, accadono nel file webpack.config.js mostrato di seguito:

All’inizio del file carichiamo il modulo HtmlWebpackPlugin che usa il template HTML public/index.html facendo in modo che ospiti la nostra applicazione React.

Successivamente viene importato il cuore della discussione di questo articolo ossia il modulo ModuleFederationPlugin utilizzato, insieme al precedente nella sezione plugins del file di configurazione.

La sezione rules in modules dice a Webpack come identificare ed interpretare i diversi files all’interno della nostra applicazione basandosi sull’estensione.

Nella sezione devServer vengono specificati tutti i parametri per l’esecuzione del server di sviluppo come, ad esempio, la porta.

Ora arriviamo alla parte più interessante ossia il nostro ModuleFederationPlugin , iniziando a descrivere tutte le voci che ne compongono la configurazione:

  • name : questo è il nome dell’applicazione. Non deve esserci nessun altro modulo federato con lo stesso nome all’interno del nostro sistema;
  • library : specifica come il modulo deve essere formattato per l’uso nel contesto del browser;
  • filename : il nome del file che l’applicazione utilizza per condividere con le altre applicazioni i moduli. È obbligatorio solo nel caso in cui la nostra applicazione espone moduli;
  • remotes : è un oggetto chiave/valore dove vengono specificati i moduli remoti che la nostra applicazione utilizza. Nel nostro caso website non utilizza moduli remoti pertanto rimane vuoto;
  • exposes : anche questo è un oggetto chiave/valore dove la chiave identifica il nome con il quale vogliamo che il nostro modulo viene condiviso con le altre applicazioni ed il valore è il path locale all’applicazione del modulo che vogliamo condividere;
  • shared : qui vengono definite tutte le dipendenze che vengono condivise con le altre applicazioni.

Quindi abbiamo definito un’applicazione React che, con l’utilizzo del plugin di Webpack 5 dei moduli federati, espone a tutte le altre applicazioni del nostro sistema il componente ProductWidget . Per capire ora come le altre applicazioni possono usufruire di questo modulo federato, dobbiamo analizzare anche l’applicazione CMS.

Il CMS

Per iniziare l’analisi di questa applicazione iniziamo dalla configurazione di Webpack. Quindi apriamo il file webpack.config.js e diamo uno sguardo alla configurazione:

La configurazione è pressoché uguale, tranne che per alcune cose. La prima fra tutte è la porta su cui verrà esposta l’applicazione. Le modifiche più importanti però avvengono nella sezione plugins ed in particolare nella configurazione di ModuleFederationPlugin .

Notiamo che:

  • il nome che viene esposto dall’applicazione, cms non è lo stesso della precedente website ;
  • filename , ossia il file che mettiamo a disposizione alle altre applicazioni remote non è lo stesso, ma potrebbe anche esserlo;
  • exposes è vuoto in quanto il CMS non espone moduli o componenti ad altre applicazioni.

Il vero cambiamento che dobbiamo notare è remotes . La configurazione della sezione contiene le coppie chiave/valore per usufruire dei moduli remoti. In particolare la chiave specifica come vogliamo mappare il nome remoto con quello interno (nel nostro caso lasciamo lo stesso) ed il valore indica il nome della variabile del modulo remoto di cui voglio usufruire e che è stata configurata nella sezione name dell’altra applicazione (nel nostro caso website).

Notiamo che anche React è nell’elenco dei moduli condivisi. Questo è fondamentale perché dice a Webpack che l’applicazione host, il CMS, vuole condividere la sua istanza React con qualsiasi modulo federato in arrivo.

Un’altra cosa da notare è che nel file public/index.html abbiamo inserito il seguente tag script nell’header della pagina HTML.

Ora che abbiamo dato uno sguardo alla configurazione diamo uno sguardo al codice della nostra applicazione CMS. Aprendo il file App.js vi ritroverete il componente che renderizza il nostro modulo federato ProductWidget .

Notiamo che all’inizio del file, importiamo il nostro modulo federato utilizzando React.lazy , il quale ci consente di definire un componente che deve essere caricato dinamicamente. Questo ci aiuta a ridurre le dimensioni della nostra pagina in fase di caricamento.

La renderizzazione di un componente caricato con React.lazy richiede che sia presente un componente React.Suspense ad un livello più alto nell’albero di renderizzazione. A quest’ultimo dev’essere specificata la proprietà fallback a cui è possibile passare un componente, nel nostro caso la stringa Loading Widget , che verrà visualizzato finché il componente non verrà caricato all’interno del DOM.

Et voilà! Il gioco è fatto! Semplice no?

Come funziona la federazione dei moduli?

Ora che abbiamo visto come configurare Webpack nei nostri progetti, affinchè le nostre applicazioni possano condividere codice in fase di esecuzione, vediamo anche come funziona questo ModuleFederationPlugin .

Sostanzialmente ci sono due fasi: la fase di costruzione e la fase di esecuzione.

La fase di costruzione

Per dare uno sguardo più approfondito a ciò che viene creato da Webpack, un buon modo per farlo è compilare il progetto in modalità di sviluppo e guardare l’output. Se eseguite il progetto website in questo modo:

Sulla console verrà stampato qualcosa tipo:

dove:

  • index.html: è il file HTML compilato che include il file main.js che esegue l’applicazione;
  • main.js: è il codice compilato ed il punto di ingresso principale dell’applicazione;
  • website.js: è il manifest JavaScript manifest ed il runtime specializzato per i moduli remoti esportati e gli eventuali pacchetti condivisi;
  • src_ProductWidget_js.[id].js: è il file JavaScript compilato referenziato dal file manifest descritto in precedenza.

Ora che abbiamo analizzato il codice JavaScript compilato, vediamo cosa accade in fase di esecuzione.

La fase di esecuzione

Quando il file website.js viene caricato nella nostra pagina, registra una variabile con il nome specificato nella configurazione del plugin ModuleFederationPlugin . Questa variabile ha due cose al suo interno: una funzione get per accedere a tutti i moduli federati a cui ha accesso, e una funzione override con cui manipolare tutti i suoi moduli condivisi.

Ad esempio, se eseguiamo la nostra applicazione CMS, e dalla console di sviluppo digitiamo:

Ci ritroveremo il modulo così come definito nell’applicazione remota.

Se il nostro componente remoto esporta un valore predefinito, il valore restituito da factory() sarà un oggetto con una chiave denominata default. Se il modulo ha altre esportazioni, anche quelle verranno allegate all’oggetto.

Per Concludere

Il rilascio di Webpack 5 è avvenuto appena due giorni fa (il 10-10-2020)

mi aspetto di vedere molti più casi d’uso per questo plugin che contribuiranno a rendere l’utilizzo dei micro-frontend accessibile a sempre più sviluppatori. Nel frattempo…

Bibliografia e link utili

https://github.com/module-federation/module-federation-examples

https://twitter.com/ScriptedAlchemy

--

--

Davide D'Antonio
weBeetle

👨‍🎓Degree in computer science 💑 Married with Milena 🤓 Huge Nerd! 💻 Code lover 👨🏻‍💻 Fullstack developer