Helpers

Styles

Merge config Tailwind cross-MFE, sanitizzazione CSS, iniezione/rimozione di stili custom nel DOM.

API

function mergeTailwindConfig(
  baseConfig: TailwindConfig,
  mfeThemeExtension?: Record<string, unknown>,
): TailwindConfig

function sanitizeCss(css: string): void
function injectCustomStyles(mfeName: string, css: string): void
function removeCustomStyles(mfeName: string): void

Helper per comporre stili Tailwind cross-MFE e iniettare CSS custom in modo sicuro.

mergeTailwindConfig

Effettua un deep merge dell'estensione tema Tailwind di un MFE sopra una config base.

import { mergeTailwindConfig } from '@pzeta/mfe-contracts/helpers'

const baseConfig = {
  theme: {
    extend: {
      colors: {
        primary: { 500: '#3b82f6' },
      },
    },
  },
}

const mfeExt = {
  colors: {
    primary: { 600: '#2563eb' },   // aggiunge shade 600
    accent: { 500: '#f59e0b' },    // nuova palette
  },
  spacing: {
    '18': '4.5rem',
  },
}

const merged = mergeTailwindConfig(baseConfig, mfeExt)
/*
{
  theme: {
    extend: {
      colors: {
        primary: {
          500: '#3b82f6',  // preservato da base
          600: '#2563eb',  // aggiunto da MFE
        },
        accent: {
          500: '#f59e0b',  // nuovo
        },
      },
      spacing: {
        '18': '4.5rem',
      },
    },
  },
}
*/

Regole di merge

  • Oggetti plain: deep merge ricorsivo (fino a 4 livelli)
  • Array: il source sovrascrive il target (no merge array)
  • Primitivi: il source sovrascrive il target
  • Se mfeThemeExtension è undefined: ritorna baseConfig invariato

Pattern: aggregare più MFE

import { mergeTailwindConfig } from '@pzeta/mfe-contracts/helpers'

let config = baseTailwindConfig

for (const manifest of manifests) {
  const ext = manifest.tailwindStyles?.themeExtension
  if (ext) {
    config = mergeTailwindConfig(config, ext as Record<string, unknown>)
  }
}
Build-time o runtime? Tailwind compila le classi al build time. Se vuoi che un MFE possa aggiungere classi dinamicamente, devi:
  • includere i path dei MFE nel content[] del Tailwind base della shell, oppure
  • usare CSS custom (tailwindStyles.customClasses) iniettato via injectCustomStyles, oppure
  • usare CSS variables per overrides al volo (palette colori).

sanitizeCss

Valida una stringa CSS contro pattern pericolosi noti. Throw se rileva qualcosa di sospetto.

import { sanitizeCss } from '@pzeta/mfe-contracts/helpers'

// OK
sanitizeCss('.btn { background: red; padding: 10px; }')

// Throw: "CSS non sicuro: rilevato pattern pericoloso. url() può esfiltrare dati..."
sanitizeCss('.bg { background: url(https://evil.com/track.gif); }')

// Throw: "@import può caricare stylesheet esterni non controllati"
sanitizeCss('@import "https://evil.com/style.css";')

Pattern bloccati

PatternRischio
url(Esfiltrazione dati via richieste a server esterni
@importCaricamento stylesheet esterno non controllato
expression(Esecuzione JavaScript in CSS (IE legacy)
-moz-bindingXBL binding in Firefox (legacy)
behavior:Esecuzione HTC in IE (legacy)
javascript:Schema non consentito in CSS
Limitazione: la sanitizzazione opera sulla stringa CSS letterale e non copre CSS unicode escaping (es. \75rl( decodificato dal browser come url(). Per protezione completa, usa un parser CSS dedicato. Il CSS deve comunque provenire da fonte trusted (manifest del MFE, non input utente).

injectCustomStyles

Inietta una stringa CSS nel DOM come tag <style>, sanificandola prima.

import { injectCustomStyles } from '@pzeta/mfe-contracts/helpers'

injectCustomStyles('crm-module', `
  .crm-card { border-radius: 0.5rem; padding: 1rem; }
  .crm-badge--hot { background: #fee2e2; color: #991b1b; }
`)

// Risultato nel DOM:
// <style id="mfe-styles-crm-module">
//   .crm-card { ... }
//   .crm-badge--hot { ... }
// </style>

Comportamento

  1. Chiama sanitizeCss(css) (throw se pericoloso)
  2. Costruisce styleId = 'mfe-styles-{mfeName}'
  3. Se esiste già <style id={styleId}>, lo rimuove
  4. Crea <style id={styleId}>{css}</style> e lo appende a <head>

Idempotente: chiamarlo più volte con lo stesso mfeName sostituisce gli stili precedenti.

removeCustomStyles

Rimuove gli stili custom precedentemente iniettati.

import { removeCustomStyles } from '@pzeta/mfe-contracts/helpers'

removeCustomStyles('crm-module')
// Rimuove <style id="mfe-styles-crm-module">

Da chiamare in MFEModule.unmount() o quando il MFE viene scaricato dalla shell.

Esempio end-to-end

// Shell — al mount del MFE
import {
  injectCustomStyles,
  removeCustomStyles,
} from '@pzeta/mfe-contracts/helpers'

async function mountMfe(manifest: MFEManifest, container: HTMLElement) {
  // 1. Inietta CSS custom dichiarato nel manifest
  if (manifest.tailwindStyles?.customClasses) {
    try {
      injectCustomStyles(manifest.name, manifest.tailwindStyles.customClasses)
    } catch (err) {
      console.warn(`CSS custom del MFE "${manifest.name}" rifiutato:`, err.message)
    }
  }

  // 2. Monta il MFE
  await mfe.mount(container, context)
}

async function unmountMfe(manifest: MFEManifest) {
  await mfe.unmount()
  removeCustomStyles(manifest.name)
}

Tipi correlati