Getting Started

Architettura MFE

Modello architetturale shell + microfrontend, lifecycle mount/unmount, isolation con createMemoryHistory, comunicazione via MFEContext.

Modello generale

@pzeta/mfe-contracts implementa il pattern Shell + Microfrontend (MFE): un'applicazione host (la "shell") carica dinamicamente moduli applicativi indipendenti, ciascuno con il proprio ciclo di vita, routing isolato e UI montata in uno slot DOM dedicato.

┌─────────────────────────────────────────────────────────────────┐
│                          SHELL (host Vue)                       │
│                                                                 │
│  ┌──────────────┐    ┌─────────────────────────────────────┐    │
│  │ Sidebar nav  │    │            MFE slot                 │    │
│  │ buildMenu... │    │    <div ref="mfeContainer" />       │    │
│  │ checkPerms.. │    │                                     │    │
│  └──────────────┘    │  ┌───────────────────────────────┐  │    │
│                      │  │ MFE Vue app (isolated router) │  │    │
│  ┌──────────────┐    │  │ provide(MFE_CONTEXT_KEY, ctx) │  │    │
│  │ MFELoader    │    │  │   └─ useMfeContext() in views │  │    │
│  │ - cache      │───►│  └───────────────────────────────┘  │    │
│  │ - CSS auto   │    └─────────────────────────────────────┘    │
│  │ - ESM import │                                               │
│  └──────────────┘                                               │
└─────────────────────────────────────────────────────────────────┘
                            ▲
                            │  ESM dynamic import
                            │
              ┌─────────────┴─────────────┐
              │  MFE bundle (remoto)      │
              │  - default: MFEModule     │
              │  - getManifest()          │
              │  - mount() / unmount()    │
              └───────────────────────────┘

Componenti del modello

Shell

Applicazione host che:

Microfrontend (MFE)

Modulo ESM autonomo che esporta (default) un oggetto MFEModule:

export default {
  getManifest(): MFEManifest { /* ... */ },
  async mount(container, context) { /* ... */ },
  async unmount() { /* ... */ },
} satisfies MFEModule

Ogni MFE è un'applicazione Vue completa: ha il proprio createApp, il proprio router (in memory history, non browser), i propri stili. La shell non sa nulla del suo interno: vede solo il manifest e i tre lifecycle hook.

MFEContext

Bridge fornito dalla shell al MFE in fase di mount(). Contiene:

  • basePath — prefisso URL del MFE nella shell
  • onNavigate(path) — callback per navigazione coordinata
  • http — client HTTP con autenticazione già gestita
  • eventBus — bus globale per comunicazione cross-MFE
  • theme — gestione tema condivisa (light/dark/system)
  • getUser() / getToken() — utente e token correnti

Vedi MFEContext per il contratto completo.

Lifecycle di un MFE

  ┌──────────────────────────────────────────────────────────────┐
  │  1. Shell.startup                                            │
  │     └─ MFELoader.loadRemote({ name, url, scope, module })    │
  │         ├─ valida URL (https:/http:)                         │
  │         ├─ inietta CSS sibling (se esiste)                   │
  │         ├─ dynamic ESM import(url)                           │
  │         ├─ valida MFEModule (getManifest/mount/unmount)      │
  │         └─ chiama optional onLoad()                          │
  │                                                              │
  │  2. Shell costruisce menu con manifest.routes                │
  │                                                              │
  │  3. Utente naviga su una route del MFE                       │
  │     └─ Shell chiama mfe.mount(container, ctx)                │
  │         └─ MFE: createApp + provide(MFE_CONTEXT_KEY, ctx)    │
  │             + memoryHistory(ctx.basePath)                    │
  │                                                              │
  │  4. (opzionale) Shell cambia rotta → mfe.onRouteChange(path) │
  │                                                              │
  │  5. Utente esce dal MFE                                      │
  │     └─ Shell chiama mfe.onBeforeUnmount?() poi mfe.unmount() │
  │         └─ MFE: app.unmount() + cleanup router               │
  └──────────────────────────────────────────────────────────────┘

Isolation del routing

Il punto chiave per evitare conflitti tra shell router e MFE router:

// Dentro il MFE
const router = createRouter({
  history: createMemoryHistory(context.basePath), // ⚠️ memory, NON web
  routes,
})

createMemoryHistory è obbligatorio: il browser ha un solo History API, già occupato dalla shell. Il MFE deve usare una history "in memoria" e sincronizzare la navigazione via context.onNavigate().

router.afterEach((to) => {
  context.onNavigate(to.path)
})

La shell intercetta la callback e aggiorna l'URL nel browser (via il proprio router).

Comunicazione cross-MFE

Tre canali, in ordine di accoppiamento crescente:

CanaleQuandoEsempio
EventBusNotifiche fire-and-forgetctx.eventBus.emit('user-created', { id })
HTTP sharedDati persistenti via APIctx.http.get('/api/users')
Componenti esposti (manifest.components)Riuso UI cross-MFE<UserCard :user="u" /> da MFE A in MFE B

Da evitare: dipendenze dirette tra MFE (import statici, type sharing). Tutto passa per il MFEContext o per il manifest.

Permission model

I permessi sono UI-only: nascondono/disabilitano elementi della UI in base ai permessi dell'utente, ma non sono un gate di sicurezza. La sicurezza reale è server-side.

  • Manifest-level: manifest.permissions indica i permessi richiesti per accedere al MFE
  • Route-level: route.permission filtra le voci nel menu
  • Element-level: direttiva v-can="'modulo:resource:write'" nasconde/disabilita componenti

Vedi checkPermissions e createCanDirective.

Sezione "settings"

Le route possono essere classificate in due sezioni tramite meta.section:

  • 'app' (default) — rail di navigazione principale
  • 'settings' — rail "Impostazioni" della shell
{
  path: '/utenti',
  label: 'Gestione utenti',
  meta: { section: 'settings', order: 10 },
}

La shell aggrega tutte le route settings dei MFE registrati con buildSettingsMenuFromManifests e le monta sotto /settings/{manifest.name}/....

Vincoli e best practice

DO
  • Usare createMemoryHistory nei MFE
  • Sincronizzare nav con context.onNavigate()
  • Tutti gli HTTP call via context.http (auth gestita dalla shell)
  • Validare manifest ricevuto da fonti esterne con validateManifest
  • Cleanup totale in unmount() (app.unmount, router, listener, intervals)
DON'T
  • Importare staticamente codice da altri MFE
  • Usare createWebHistory o createWebHashHistory dentro un MFE
  • Bypassare context.http per autenticazione custom
  • Fidarsi dei permessi client come gate di sicurezza
  • Lasciare side-effects globali dopo unmount()