I punti di forza e i vantaggi dei micro frontend
Pubblicato: 2022-03-11L'architettura micro-frontend è un approccio di progettazione in cui un'app front-end viene scomposta in "microapp" individuali e semi-indipendenti che lavorano insieme liberamente. Il concetto di micro-frontend è vagamente ispirato e prende il nome dai microservizi.
I vantaggi del modello micro-frontend includono:
- Le architetture di micro-frontend possono essere più semplici e quindi più facili da ragionare e gestire.
- I team di sviluppo indipendenti possono collaborare più facilmente su un'app front-end.
- Possono fornire un mezzo per migrare da una "vecchia" app avendo una "nuova" app in esecuzione fianco a fianco.
Sebbene ultimamente i micro frontend abbiano ricevuto molta attenzione, al momento non esiste un'unica implementazione dominante e nessun quadro di micro frontend "migliore" chiaro. In effetti, esiste una varietà di approcci a seconda degli obiettivi e dei requisiti. Vedere la bibliografia per alcune delle implementazioni più note.
In questo articolo salteremo gran parte della teoria dei micro frontend. Ecco cosa non tratteremo:
- "Slicing" di un'app in microapp
- Problemi di distribuzione, incluso il modo in cui i micro frontend si inseriscono in un modello CI/CD
- Test
- Se le microapp devono essere allineate uno a uno con i microservizi sul back-end
- Critiche al concetto di micro-frontend
- La differenza tra micro frontend e una semplice architettura a componenti vecchia
Presenteremo invece un tutorial sul micro-frontend incentrato su un'implementazione concreta, evidenziando le questioni importanti nell'architettura del micro-frontend e le loro possibili soluzioni.
La nostra implementazione si chiama Yumcha. Il significato letterale di "yum cha" in cantonese è "bere il tè", ma il suo significato quotidiano è "uscire per un dim sum". L'idea qui è che le singole microapp all'interno di una macroapp (come chiameremo l'app composta di primo livello) sono analoghe ai vari cestini di porzioni da boccone tirati fuori a un pranzo dim sum.
A volte ci riferiremo a Yumcha come a un "framework micro-frontend". Nel mondo di oggi, il termine "framework" viene solitamente utilizzato per riferirsi a Angular, React, Vue.js o altre sovrastrutture simili per le app Web. Non stiamo affatto parlando di un quadro in questo senso. Chiamiamo Yumcha un framework solo per comodità: in realtà è più un insieme di strumenti e alcuni strati sottili per la creazione di app basate su micro frontend.
Tutorial micro-frontend Primi passi: markup per un'app composta
Immergiamoci pensando a come potremmo definire una macroapp e le microapp che la compongono. Il markup è sempre stato al centro del web. La nostra macroapp sarà quindi specificata da niente di più complicato di questo markup:
<html> <head> <script src="/yumcha.js"></script> </head> <body> <h1>Hello, micro-frontend app.</h1> <!-- HERE ARE THE MICROAPPS! --> <yumcha-portal name="microapp1" src="https://microapp1.example.com"></yumcha-portal> <yumcha-portal name="microapp2" src="https://microapp2.example.com"></yumcha-portal> </body> </html>
Definire la nostra macroapp utilizzando il markup ci dà pieno accesso alla potenza di HTML e CSS per il layout e la gestione delle nostre microapp. Ad esempio, una microapp potrebbe stare sopra un'altra, o di lato, o essere in un angolo della pagina, o essere in un riquadro di una fisarmonica, o rimanere nascosta finché non succede qualcosa, o rimanere permanentemente in background .
Abbiamo chiamato l'elemento personalizzato utilizzato per le microapp <yumcha-portal>
perché "portale" è un termine promettente per le microapp utilizzate nella proposta del portale, un primo tentativo di definire un elemento HTML standard da utilizzare nei micro frontend.
Implementazione dell'elemento personalizzato <yumcha-portal>
Come dovremmo implementare <yumcha-portal>
? Dal momento che è un elemento personalizzato, come componente web, ovviamente! Possiamo scegliere tra una serie di forti contendenti per la scrittura e la compilazione di componenti web micro-frontend; qui useremo LitElement, l'ultima iterazione del Polymer Project. LitElement supporta lo zucchero sintattico basato su TypeScript, che gestisce per noi la maggior parte degli elementi personalizzati. Per rendere <yumcha-portal>
disponibile sulla nostra pagina, dobbiamo includere il codice pertinente come <script>
, come abbiamo fatto sopra.
Ma cosa fa <yumcha-portal>
? Una prima approssimazione sarebbe creare un iframe
con l'origine specificata:
render() { return html`<iframe src=${this.src}></iframe>`; }
...dove render
è l'hook di rendering standard di LitElement, che utilizza il suo modello letterale con tag html
. Questa funzionalità minima potrebbe essere quasi sufficiente per alcuni casi d'uso banali.
Incorporamento di microapp in iframe
s
Gli iframe
sono l'elemento HTML che tutti amano odiare, ma in realtà forniscono un comportamento sandboxing estremamente utile e solido. Tuttavia, c'è ancora una lunga lista di problemi da tenere presente quando si utilizza iframe
s, con un potenziale impatto sul comportamento e sulla funzionalità della nostra app:
- Innanzitutto, gli
iframe
hanno stranezze ben note in termini di dimensioni e disposizione. - I CSS saranno ovviamente completamente isolati
iframe
, nel bene e nel male. - Il pulsante "indietro" del browser funzionerà abbastanza bene, anche se l'attuale stato di navigazione
iframe
non si rifletterà nell'URL della pagina , quindi non potremmo né tagliare e incollare gli URL per arrivare allo stesso stato dell'app composta, né link diretti a loro. - La comunicazione con l'
iframe
dall'esterno, a seconda della nostra configurazione CORS, potrebbe dover passare attraverso il protocollopostMessage
. - Dovranno essere presi accordi per l'autenticazione attraverso i confini
iframe
. - Alcuni lettori di schermo potrebbero inciampare nel limite
iframe
o aver bisogno che l'iframe
abbia un titolo che possono annunciare all'utente.
Alcuni di questi problemi possono essere evitati o mitigati non utilizzando iframe
s, un'alternativa che discuteremo più avanti nell'articolo.
Tra i lati positivi, l' iframe
avrà la propria Content-Security-Policy
(CSP) indipendente. Inoltre, se la microapp a cui punta l' iframe
utilizza un service worker o implementa il rendering lato server, tutto funzionerà come previsto. Possiamo anche specificare varie opzioni di sandbox per l' iframe
per limitarne le capacità, come la possibilità di passare al frame superiore.
Alcuni browser hanno spedito o stanno pianificando di spedire un attributo loading=lazy
per iframe
s, che rinvia il caricamento di iframe
sotto la piega finché l'utente non scorre vicino a loro, ma questo non fornisce il controllo granulare del caricamento lento che vogliamo.
Il vero problema con iframe
s è che il contenuto iframe
richiederà più richieste di rete per essere recuperato. Il index.html
di primo livello viene ricevuto, i suoi script vengono caricati e il suo HTML viene analizzato, ma poi il browser deve avviare un'altra richiesta per l'HTML iframe
, attendere di riceverlo, analizzare e caricare i suoi script ed eseguire il rendering contenuto di iframe
. In molti casi, il JavaScript iframe
dovrebbe quindi ancora essere avviato, effettuare le proprie chiamate API e mostrare dati significativi solo dopo che tali chiamate API sono state restituite e i dati sono stati elaborati per la visualizzazione.
Ciò comporterà probabilmente ritardi indesiderati e artefatti di rendering, soprattutto quando sono coinvolte diverse microapp. Se l'app iframe
implementa SSR, ciò aiuterà ma non eviterà la necessità di ulteriori round trip.
Quindi una delle sfide chiave che dobbiamo affrontare nella progettazione dell'implementazione del nostro portale è come affrontare questo problema di andata e ritorno. Il nostro obiettivo è che una singola richiesta di rete abbassi l'intera pagina con tutte le sue microapp, incluso qualsiasi contenuto che ciascuna di esse sia in grado di prepopolare. La soluzione a questo problema risiede nel server Yumcha.
Il server Yumcha
Un elemento chiave della soluzione micro-frontend qui presentata è la configurazione di un server dedicato per gestire la composizione delle microapp. Questo server inoltra le richieste ai server in cui è ospitata ogni microapp. Certo, sarà necessario uno sforzo per configurare e gestire questo server. Alcuni approcci micro-frontend (ad esempio, single-spa) tentano di fare a meno della necessità di tali configurazioni speciali del server, in nome della facilità di implementazione e configurazione.
Tuttavia, il costo della creazione di questo proxy inverso è più che compensato dai vantaggi che otteniamo; in effetti, ci sono comportamenti importanti delle app basate su micro frontend che semplicemente non possiamo ottenere senza di essa. Esistono molte alternative commerciali e gratuite alla configurazione di un tale proxy inverso.
Il proxy inverso, oltre a instradare le richieste di microapp al server appropriato, instrada anche le richieste di macroapp a un server di macroapp. Quel server prepara l'HTML per l'app composta in un modo speciale. Dopo aver ricevuto una richiesta di index.html
dal browser tramite il server proxy a un URL come http://macroapp.example.com
, recupera index.html
e quindi lo sottopone a una trasformazione semplice ma cruciale prima di restituire esso.
In particolare, l'HTML viene analizzato per <yumcha-portal>
, cosa che può essere eseguita facilmente con uno dei parser HTML competenti disponibili nell'ecosistema Node.js. Utilizzando l'attributo src
a <yumcha-portal>
, viene contattato il server che esegue la microapp e viene recuperato il relativo index.html
, incluso il contenuto renderizzato lato server, se presente. Il risultato viene inserito nella risposta HTML come <script>
o <template>
, in modo da non essere eseguito dal browser.

I vantaggi di questa configurazione includono, in primo luogo, che alla prima richiesta di index.html
per la pagina composta, il server può recuperare le singole pagine dai singoli server di microapp nella loro interezza, incluso il contenuto renderizzato SSR, se any—e consegnare una singola pagina completa al browser, incluso il contenuto che può essere utilizzato per popolare l' iframe
senza ulteriori round trip del server (usando l'attributo srcdoc
sottoutilizzato). Il server proxy garantisce inoltre che tutti i dettagli da cui vengono servite le microapp siano nascosti da occhi indiscreti. Infine, semplifica le questioni CORS, poiché le richieste di applicazione vengono inviate tutte alla stessa origine.
Al client, il <yumcha-portal>
viene istanziato e trova il contenuto in cui è stato inserito nel documento di risposta dal server, e al momento opportuno esegue il rendering iframe
e assegna il contenuto al suo attributo srcdoc
. Se non stiamo usando iframe
s (vedi sotto), il contenuto corrispondente a quel <yumcha-portal>
viene inserito nel DOM shadow dell'elemento personalizzato, se lo stiamo usando, o direttamente inline nel documento.
A questo punto, abbiamo già un'app basata su micro frontend parzialmente funzionante.
Questa è solo la punta dell'iceberg in termini di funzionalità interessanti per il server Yumcha. Ad esempio, vorremmo aggiungere funzionalità per controllare come vengono gestite le risposte agli errori HTTP dai server di microapp o come gestire le microapp che rispondono molto lentamente: non vogliamo aspettare per sempre per pubblicare la pagina se una microapp non lo è rispondendo! Questi e altri argomenti li lasceremo per un altro post.
La logica di trasformazione Yumcha macroapp index.html
può essere facilmente implementata in modalità lambda serverless o come middleware per framework server come Express o Koa.
Controllo Microapp basato su stub
Tornando al lato client, c'è un altro aspetto nel modo in cui implementiamo le microapp che è importante per l'efficienza, il caricamento lento e il rendering senza jank. Potremmo generare il tag iframe
per ogni microapp, sia con un attributo src
, che effettua un'altra richiesta di rete, sia con l'attributo srcdoc
compilato con il contenuto popolato per noi dal server. Ma in entrambi i casi, il codice in iframe
verrà avviato immediatamente, incluso il caricamento di tutti i relativi script e tag di collegamento, il bootstrap e qualsiasi chiamata API iniziale e relativa elaborazione dei dati, anche se l'utente non accede mai alla microapp in questione.
La nostra soluzione a questo problema è rappresentare inizialmente le microapp sulla pagina come piccoli stub inattivati, che possono quindi essere attivati. L'attivazione può essere guidata dalla visualizzazione dell'area della microapp, utilizzando l'API IntersectionObserver
sottoutilizzata o, più comunemente, da notifiche preliminari inviate dall'esterno. Ovviamente possiamo anche specificare che la microapp venga attivata immediatamente.
In ogni caso, quando e solo quando la microapp viene attivata, l' iframe
viene effettivamente renderizzato e il suo codice caricato ed eseguito. In termini di implementazione utilizzando LitElement e supponendo che lo stato di attivazione sia rappresentato da una variabile di istanza activated
, avremmo qualcosa del tipo:
render() { if (!this.activated) return html`{this.placeholder}`; else return html` <iframe srcdoc="${this.content}" @load="${this.markLoaded}"></iframe>`; }
Comunicazione tra microapp
Sebbene le microapp che compongono una macroapp siano per definizione accoppiate liberamente, devono comunque essere in grado di comunicare tra loro. Ad esempio, una microapp di navigazione dovrebbe inviare una notifica relativa all'attivazione di un'altra microapp appena selezionata dall'utente e l'app da attivare deve ricevere tali notifiche.
In linea con la nostra mentalità minimalista, vogliamo evitare di introdurre molti meccanismi di passaggio dei messaggi. Invece, nello spirito dei componenti web, utilizzeremo gli eventi DOM. Forniamo un'API di trasmissione banale che pre-notifica tutti gli stub di un evento imminente, attende tutti quelli che hanno richiesto di essere attivati per l'attivazione di quel tipo di evento, quindi invia l'evento sul documento, su cui qualsiasi microapp può ascoltare esso. Dato che tutti i nostri iframe
sono della stessa origine, possiamo raggiungere la pagina iframe
e viceversa per trovare elementi rispetto ai quali attivare gli eventi.
Instradamento
Al giorno d'oggi, tutti ci aspettiamo che la barra degli URL nelle SPA rappresenti lo stato di visualizzazione dell'applicazione, quindi possiamo tagliare, incollare, inviare messaggi di posta, testo e collegarci ad esso per passare direttamente a una pagina all'interno dell'app. In un'app micro-frontend, tuttavia, lo stato dell'applicazione è in realtà una combinazione di stati, uno per ogni microapp. Come dobbiamo rappresentare e controllare questo?
La soluzione è codificare lo stato di ogni microapp in un singolo URL composito e utilizzare un piccolo router di macroapp che sappia come mettere insieme quell'URL composito e separarlo. Sfortunatamente, ciò richiede la logica specifica di Yumcha in ogni microapp: ricevere messaggi dal router della macroapp e aggiornare lo stato della microapp e, al contrario, avvisare il router della macroapp delle modifiche in quello stato in modo che l'URL composito possa essere aggiornato. Ad esempio, si potrebbe immaginare una YumchaLocationStrategy
per Angular o un elemento <YumchaRouter>
per React.
Il caso non iframe
Come accennato in precedenza, l'hosting di microapp in iframe
s presenta alcuni aspetti negativi. Ci sono due alternative: includerli direttamente in linea nell'HTML della pagina o inserirli nel DOM ombra. Entrambe le alternative rispecchiano in qualche modo i pro ei contro di iframe
, ma a volte in modi diversi.
Ad esempio, i criteri CSP delle singole microapp dovrebbero essere in qualche modo uniti. Le tecnologie assistive come i lettori di schermo dovrebbero funzionare meglio che con iframe
s, supponendo che supportino il DOM ombra (cosa che non tutti lo fanno ancora). Dovrebbe essere semplice organizzare la registrazione degli operatori del servizio di una microapp utilizzando il concetto di "ambito" dell'operatore del servizio, sebbene l'app debba garantire che il suo lavoratore del servizio sia registrato con il nome dell'app, non con "/"
. Nessuno dei problemi di layout associati a iframe
si applica ai metodi DOM inline o shadow.
Tuttavia, è probabile che le applicazioni create utilizzando framework come Angular e React siano infelici in linea o nel DOM ombra. Per quelli, probabilmente vorremo usare iframe
s.
I metodi DOM inline e shadow differiscono quando si tratta di CSS. I CSS verranno incapsulati in modo pulito nel DOM ombra. Se per qualche motivo volessimo condividere CSS al di fuori con il DOM ombra, dovremmo usare fogli di stile costruibili o qualcosa di simile. Con le microapp integrate, tutti i CSS sarebbero condivisi in tutta la pagina.
Alla fine, l'implementazione della logica per le microapp DOM inline e shadow in <yumcha-portal>
è semplice. Recuperiamo il contenuto di una determinata microapp da dove è stata inserita nella pagina dalla logica del server come elemento HTML <template>
, clonarla, quindi aggiungerla a ciò che LitElement chiama renderRoot
, che normalmente è il DOM ombra dell'elemento, ma può essere impostato anche sull'elemento stesso ( this
) per il caso inline (non shadow DOM).
Ma aspetta! Il contenuto offerto dal server di microapp è un'intera pagina HTML. Non possiamo inserire la pagina HTML per la microapp, completa di tag html
, head
e body
, nel mezzo di quella per la macroapp, vero?
Risolviamo questo problema sfruttando una stranezza del tag del template
in cui è racchiuso il contenuto della microapp recuperato dal server della microapp. Si scopre che quando i browser moderni incontrano un tag template
, anche se non lo "eseguono", lo analizzano e, così facendo, rimuovono i contenuti non validi come i <html>
, <head>
e <body>
, preservandone il contenuto interiore. Quindi i <script>
e <link>
in <head>
, così come il contenuto di <body>
, vengono preservati. Questo è esattamente ciò che vogliamo ai fini dell'inserimento di contenuti di microapp nella nostra pagina.
Architettura micro-frontend: il diavolo è nei dettagli
I micro frontend metteranno radici nell'ecosistema delle webapp se (a) si riveleranno un approccio architetturale migliore e (b) saremo in grado di capire come implementarli in modi che soddisfino la miriade di requisiti pratici del web di oggi.
Per quanto riguarda la prima domanda, nessuno afferma che i micro frontend siano l'architettura giusta per tutti i casi d'uso. In particolare, ci sarebbero poche ragioni per lo sviluppo greenfield da parte di un singolo team per adottare micro frontend. Lascerò ad altri commentatori la domanda su quali tipi di app in quali tipi di contesti potrebbero trarre il massimo vantaggio da un modello di micro-frontend.
In termini di implementazione e fattibilità, abbiamo visto che ci sono molti dettagli di cui preoccuparsi, inclusi molti nemmeno menzionati in questo articolo, in particolare autenticazione e sicurezza, duplicazione del codice e SEO. Tuttavia, spero che questo articolo elabori un approccio di implementazione di base per i micro frontend che, con un ulteriore perfezionamento, possano resistere ai requisiti del mondo reale.
Bibliografia
- Micro front-end - Farlo in stile angolare - Parte 1
- Micro front-end - Farlo in stile angolare - Parte 2
- Evoluzione di un'applicazione AngularJS utilizzando microfrontend
- Micro-frontend
- Microservizi dell'interfaccia utente: invertire l'anti-pattern (micro frontend)
- Microservizi dell'interfaccia utente: un anti-pattern?
- La creazione di pagine utilizzando Micro-Frontend adotta un approccio simile a Yumcha di proxy inverso e SSI, che consiglio vivamente.
- Risorse per i micro frontend
- Podio
- Non capisco i micro-frontend. Questa è una panoramica abbastanza buona dei tipi di architetture e casi d'uso di micro-frontend.
- Micro-frontend serverless che utilizzano Vue.js, AWS Lambda e Hypernova
- Micro Frontend: un'ottima panoramica completa.