MFE Contracts

Libreria TypeScript di contratti condivisi per integrazione microfrontend nella shell Vue. Tipi, loader ESM, composables CRUD, helpers di routing e permission check.

Cos'è @pzeta/mfe-contracts

@pzeta/mfe-contracts è la libreria di contratti TypeScript che fa da interfaccia stabile tra la shell Vue e i microfrontend (MFE) caricati dinamicamente. Definisce manifest, lifecycle, routing, permessi e contesto runtime — più un set di composables CRUD riutilizzabili e utilities (loader ESM, debounce, route path helpers).

Questa documentazione si riferisce alla v1.2.x. Peer dependency: vue@^3.4. Engine: node>=22.0.0.

Funzionalità principali

  • Contratti tipizzatiMFEManifest, MFEModule, MFEContext, MFERoute, MFEComponent per integrazione type-safe
  • Loader ESMMFELoader per caricamento dinamico dei moduli con cache, retry e validazione manifest
  • Composables CRUDuseCrudResource, useCrudView, useDetailForm, useDialog, useErrorToast per ridurre boilerplate nelle viste CRUD degli MFE
  • Helpers RBACcheckPermissions, checkRouteAccess, direttiva v-can via createCanDirective
  • Helpers menubuildMenuFromManifest, buildSettingsMenuFromManifests per costruire la navigazione della shell aggregando i manifest
  • Helpers stilimergeTailwindConfig, sanitizeCss, injectCustomStyles per isolamento CSS cross-MFE
  • Subpath exports — import selettivi (/types, /loader, /helpers, /composables, /utils) per ottimizzare il bundle size

Architettura

@pzeta/mfe-contracts
├── Types.ts           # Manifest · Module · Context · Route · Component · User · EventBus · HttpClient · Config · Theme
├── Loader.ts          # MFELoader (caricamento ESM dinamico + cache)
├── Helpers.ts         # validateManifest · buildMenu* · checkPermissions · sanitizeCss · ...
├── Directive.ts       # createCanDirective (v-can)
├── composables/       # useMfeContext · useCrudResource · useCrudView · useDetailForm
│                      # useDialog · useErrorToast
└── utils/             # extractErrorMessage · debounce · stripShellPrefix · buildSettingsFullPath

Modello dei contratti

interface MFEManifest {
  name: string
  version: string
  description: string
  icon?: string
  routes: MFERoute[]
  permissions?: string[]
  components?: MFEComponent[]
}

interface MFEModule {
  getManifest(): MFEManifest
  mount(container: HTMLElement, context: MFEContext): Promise<void>
  unmount(): Promise<void>
}

interface MFEContext {
  user: MFEUser
  basePath: string
  http: MFEHttpClient
  eventBus: MFEEventBus
  theme: MFETheme
  onNavigate(path: string): void
  // ... config + utilities
}

Sotto-pagine

Quick start

Lato microfrontend — implementa MFEModule

// src/mfe-entry.ts
import { createApp } from 'vue'
import { createRouter, createMemoryHistory } from 'vue-router'
import type { MFEModule, MFEManifest, MFEContext } from '@pzeta/mfe-contracts'
import { MFE_CONTEXT_KEY } from '@pzeta/mfe-contracts'
import App from './App.vue'
import routes from './router/routes'

let app: any = null

export const mfeModule: MFEModule = {
  getManifest(): MFEManifest {
    return {
      name: 'crm-module',
      version: '1.0.0',
      description: 'Gestione clienti, ticket e pipeline CRM',
      icon: 'pi-building',
      routes: [
        {
          path: '/crm/dashboard',
          label: 'Dashboard',
          icon: 'pi-chart-line',
          permission: 'crm.dashboard.read',
          meta: { requiresAuth: true, order: 1 }
        }
      ],
      permissions: ['crm.read']
    }
  },

  async mount(container, context) {
    const router = createRouter({
      history: createMemoryHistory(context.basePath),
      routes
    })
    router.afterEach((to) => context.onNavigate(to.path))

    app = createApp(App)
    app.use(router)
    app.provide(MFE_CONTEXT_KEY, context)
    app.mount(container)
  },

  async unmount() {
    app?.unmount()
    app = null
  }
}

export default mfeModule

Lato shell — carica e monta MFE

import { MFELoader, validateManifest, buildMenuFromManifest } from '@pzeta/mfe-contracts'

const loader = new MFELoader()
const module = await loader.load('https://mfe-crm.example.com/index.js')

if (validateManifest(module.getManifest())) {
  await module.mount(document.getElementById('mfe-slot')!, mfeContext)
}

Lato componente MFE — composables CRUD

<script setup lang="ts">
import { useCrudView, useMfeContext } from '@pzeta/mfe-contracts/composables'

const ctx = useMfeContext()
const { items, loading, selected, load, save, remove } = useCrudView({
  list: () => ctx.http.get('/api/clienti'),
  create: (c) => ctx.http.post('/api/clienti', c),
  update: (id, c) => ctx.http.put(`/api/clienti/${id}`, c),
  remove: (id) => ctx.http.delete(`/api/clienti/${id}`),
})
</script>

Subpath exports

Per minimizzare il bundle dei consumer:

// Solo tipi (zero runtime cost)
import type { MFEManifest, MFEModule } from '@pzeta/mfe-contracts/types'

// Solo loader
import { MFELoader } from '@pzeta/mfe-contracts/loader'

// Solo composables CRUD
import { useCrudView, useDetailForm } from '@pzeta/mfe-contracts/composables'

// Solo utilities
import { debounce, extractErrorMessage } from '@pzeta/mfe-contracts/utils'

// Solo helpers
import { validateManifest, checkPermissions } from '@pzeta/mfe-contracts/helpers'

Quando usare cosa

ScenarioAPI
Esporre un MFE alla shellimplementa MFEModule + esporta come default
Caricare dinamicamente un MFE dalla shellMFELoader.load(url)
Validare un manifest prima del mountvalidateManifest / isValidManifest
Costruire menu nav dalla lista di manifestbuildMenuFromManifest / buildSettingsMenuFromManifests
Check permessi inline in templatedirettiva v-can (registrata con createCanDirective)
Check permessi programmaticocheckPermissions / checkRouteAccess
CRUD list + form completouseCrudView
Solo lista CRUD genericauseCrudResource
Form di dettaglio (load/save/reset)useDetailForm
Gestione stato dialoguseDialog
Toast errori standardizzatouseErrorToast
Accesso al contesto shell dal MFEuseMfeContext (richiede app.provide(MFE_CONTEXT_KEY, ctx))

Stack tecnico

  • TypeScript ~6.0 — strict, type-first
  • Vue 3 Composition API (>=3.4)
  • ESM only"type": "module", build via tsc
  • Subpath exports in package.json
  • Vitest + happy-dom per i test