wu-framework
v1.1.4
Published
π Universal Microfrontends Framework - 8 frameworks, zero config, Shadow DOM isolation
Maintainers
Readme
π Wu Framework
Universal Microfrontends Made Simple
Wu Framework es una librerΓa de microfrontends con Shadow DOM nativo, aislamiento CSS avanzado, y comunicaciΓ³n cross-MFE integrada.
β¨ ΒΏPor quΓ© Wu Framework?
| CaracterΓstica | Module Federation | Single-SPA | Qiankun | Wu Framework |
|---|---|---|---|---|
| Shadow DOM nativo | β | β | Parcial | β
Completo |
| CSS Isolation Modes | Manual | Manual | BΓ‘sico | β
4 modos |
| fully-isolated mode | β | β | β | β
Γnico |
| Bloquea CSS Variables | β | β | β | β
Γnico |
| Framework Agnostic | Webpack only | β
| Vue-first | β
Cualquiera |
| Bundler Agnostic | Webpack 5 | β
| Webpack | β
Vite/Webpack |
| Cross-MFE Events | Manual | Manual | Props | β
Event Bus |
| Cross-MFE Store | β | β | Parcial | β
Reactive Store |
π‘οΈ CSS Isolation Modes (Feature Γnico)
Wu Framework ofrece 4 modos de aislamiento CSS. El modo fully-isolated es ΓΊnico - bloquea incluso las CSS variables del padre:
// header/wu.json - Aislamiento total (bloquea CSS vars del padre)
{
"name": "header",
"entry": "src/main.ts",
"styleMode": "fully-isolated",
"wu": {
"permissions": ["events", "store"]
}
}// container/wu.json - Aislamiento normal (hereda CSS vars)
{
"name": "content",
"folder": "container",
"entry": "src/main.tsx",
"styleMode": "isolated",
"wu": {
"permissions": ["events", "store"]
}
}| Modo | CSS Variables | Estilos Padre | Caso de Uso |
|------|--------------|---------------|-------------|
| isolated | β
Heredadas | β Bloqueados | MFE usa design system del host |
| fully-isolated | β Bloqueadas | β Bloqueados | MFE de terceros con su propio theme |
| shared | β
Heredadas | β
Compartidos | MFE necesita estilos del host |
| auto | β
Heredadas | β‘ Solo librerΓas | Compartir Element Plus, etc. |
π₯ Quick Start
1. Shell (React + Vite)
// shell/src/config/mfe.config.ts
const isDev = import.meta.env.DEV
const devUrls = {
header: 'http://localhost:3001',
content: 'http://localhost:3003'
}
const prodUrls = {
header: import.meta.env.VITE_HEADER_URL || 'https://cdn.example.com/mfe/header',
content: import.meta.env.VITE_CONTENT_URL || 'https://cdn.example.com/mfe/content'
}
export const urls = isDev ? devUrls : prodUrls
export const enabledApps = [
{ name: 'header', url: urls.header, enabled: true },
{ name: 'content', url: urls.content, enabled: true }
]// shell/src/hooks/useWuFramework.ts
import { useEffect, useState, useRef } from 'react'
import { wu } from 'wu-framework'
import { createUseWuEvents, createUseWuStore } from 'wu-framework/adapters/react'
import React from 'react'
import { enabledApps } from '../config/mfe.config'
// Crear hooks reactivos para el shell
export const useWuEvents = createUseWuEvents(React)
export const useWuStore = createUseWuStore(React)
export function useWuFramework() {
const [state, setState] = useState({
initialized: false,
loading: true,
error: null as Error | null
})
const initRef = useRef(false)
useEffect(() => {
if (initRef.current) return
initRef.current = true
const initWu = async () => {
try {
await wu.init({
apps: enabledApps.map(({ name, url }) => ({ name, url }))
})
setState({ initialized: true, loading: false, error: null })
} catch (error) {
setState({ initialized: false, loading: false, error: error as Error })
}
}
initWu()
}, [])
return { ...state, wu }
}// shell/src/App.tsx
import { useWuFramework, useWuEvents } from './hooks/useWuFramework'
import WuSlot from './components/WuSlot'
import ToastContainer from './components/ToastContainer'
function App() {
const { initialized, loading, error } = useWuFramework()
const { emit } = useWuEvents()
useEffect(() => {
if (initialized) {
emit('shell:ready', { timestamp: Date.now() })
}
}, [initialized, emit])
if (loading) return <div>Loading Wu Framework...</div>
if (error) return <div>Error: {error.message}</div>
return (
<>
<ToastContainer />
<WuSlot name="header" url={urls.header} />
<WuSlot name="content" url={urls.content} />
</>
)
}2. MFE Header (Vue 3)
// header/wu.json
{
"name": "header",
"entry": "src/main.ts",
"styleMode": "fully-isolated",
"wu": {
"exports": {
"NavBar": "src/components/NavBar.vue"
},
"permissions": ["events", "store"]
}
}// header/src/main.ts
import { createApp } from 'vue'
import { wuVue } from 'wu-framework/adapters/vue'
import App from './App.vue'
// Registrar con Wu Framework
wuVue.register('header', App, {
standalone: true,
standaloneContainer: '#app',
onMount: (container) => console.log('[Header] Mounted:', container),
onUnmount: (container) => console.log('[Header] Unmounted:', container)
})<!-- header/src/components/NavBar.vue -->
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { useWuEvents, useWuStore } from 'wu-framework/adapters/vue'
const { emit, on } = useWuEvents()
const { state: notificationState, setState } = useWuStore('notifications')
const notificationCount = ref(0)
let unsubscribe: (() => void) | null = null
onMounted(() => {
// Inicializar store compartido
if (!notificationState.value) {
setState({ count: 0, items: [] })
}
// Escuchar notificaciones desde CUALQUIER MFE (React, Angular, etc.)
unsubscribe = on('notification:new', (event) => {
notificationCount.value++
// Actualizar store compartido
const current = notificationState.value || { count: 0, items: [] }
setState({
count: current.count + 1,
items: [...current.items, {
id: Date.now(),
message: event.data?.message,
type: event.data?.type || 'info'
}]
})
})
})
onUnmounted(() => {
if (unsubscribe) unsubscribe()
})
const handleNavClick = (item: { id: string, label: string }) => {
emit('header:nav:click', { id: item.id, label: item.label })
}
</script>
<template>
<nav class="navbar">
<button class="navbar__notifications" @click="emit('header:notifications:open')">
π
<span v-if="notificationCount > 0" class="navbar__badge">
{{ notificationCount > 99 ? '99+' : notificationCount }}
</span>
</button>
</nav>
</template>3. MFE Content (React)
// container/wu.json
{
"name": "content",
"folder": "container",
"entry": "src/main.tsx",
"styleMode": "isolated",
"wu": {
"exports": {
"Dashboard": "src/components/Dashboard.tsx"
},
"permissions": ["events", "store"]
}
}// container/src/main.tsx
import { wuReact } from 'wu-framework/adapters/react'
import App from './App'
wuReact.register('content', App, {
strictMode: true,
standalone: true,
standaloneContainer: '#root',
onMount: (container) => console.log('[Content] Mounted:', container),
onUnmount: (container) => console.log('[Content] Unmounted:', container)
})// container/src/components/Dashboard.tsx
import { useState } from 'react'
import { useWuEvents } from '../hooks/useWu'
export function Dashboard() {
const { emit } = useWuEvents()
const [sent, setSent] = useState(0)
// Enviar notificaciΓ³n que el Header (Vue) y Shell (React) recibirΓ‘n
const sendNotification = (type: 'success' | 'warning' | 'error' | 'info', message: string) => {
emit('notification:new', { message, type })
setSent(prev => prev + 1)
}
return (
<div className="dashboard">
<h2>Cross-MFE Communication Demo</h2>
<p>Notifications sent: <strong>{sent}</strong></p>
<div className="notification-buttons">
<button onClick={() => sendNotification('success', 'Operation completed!')}>
β
Success
</button>
<button onClick={() => sendNotification('warning', 'Check your settings')}>
β οΈ Warning
</button>
<button onClick={() => sendNotification('error', 'Something went wrong!')}>
β Error
</button>
<button onClick={() => sendNotification('info', 'New message received')}>
π¬ Info
</button>
</div>
</div>
)
}4. Toast Container (Shell - escucha eventos de MFEs)
// shell/src/components/ToastContainer.tsx
import { useState, useEffect, useCallback } from 'react'
import { useWuEvents } from '../hooks/useWuFramework'
interface Toast {
id: string
message: string
type: 'success' | 'warning' | 'error' | 'info'
}
export function ToastContainer() {
const [toasts, setToasts] = useState<Toast[]>([])
const { on } = useWuEvents()
const addToast = useCallback((message: string, type: Toast['type']) => {
const id = `toast-${Date.now()}`
setToasts(prev => [...prev, { id, message, type }])
setTimeout(() => setToasts(prev => prev.filter(t => t.id !== id)), 5000)
}, [])
useEffect(() => {
// Escuchar notificaciones de CUALQUIER MFE
const unsubscribe = on('notification:new', (event) => {
const { message, type } = event.data || {}
if (message) addToast(message, type || 'info')
})
return () => unsubscribe?.()
}, [on, addToast])
return (
<div className="toast-container">
{toasts.map(toast => (
<div key={toast.id} className={`toast toast--${toast.type}`}>
{toast.message}
</div>
))}
</div>
)
}π‘ Cross-MFE Communication
Event Bus
import { emit, on, once, off } from 'wu-framework'
// React MFE emite
emit('notification:new', { message: 'Hello!', type: 'success' })
// Vue MFE escucha
const unsubscribe = on('notification:new', (event) => {
console.log(event.data.message) // 'Hello!'
})
// Wildcards - escuchar todos los eventos de notificaciΓ³n
on('notification:*', (event) => {
console.log('Event:', event.name, event.data)
})
// Una sola vez
once('app:ready', () => console.log('Ready!'))
// Limpiar
unsubscribe()Store Reactivo
import { getState, setState, onStateChange, batchState } from 'wu-framework'
// Vue MFE escribe
setState('notifications.count', 5)
setState('user.theme', 'dark')
// React MFE lee
const count = getState('notifications.count') // 5
// Angular MFE se suscribe
const unsub = onStateChange('notifications.*', (value, path) => {
console.log(`${path} changed:`, value)
})
// Batch updates
batchState({
'user.name': 'John',
'notifications.count': 0
})Hooks por Framework
React:
import { createUseWuEvents, createUseWuStore } from 'wu-framework/adapters/react'
const useWuEvents = createUseWuEvents(React)
const useWuStore = createUseWuStore(React)
function MyComponent() {
const { emit, on } = useWuEvents()
const { state, setState } = useWuStore('notifications')
useEffect(() => {
return on('user:login', (e) => console.log(e.data))
}, [])
return <span>Count: {state?.count || 0}</span>
}Vue:
<script setup>
import { useWuEvents, useWuStore } from 'wu-framework/adapters/vue'
const { emit, on } = useWuEvents()
const { state, setState } = useWuStore('cart')
onMounted(() => {
on('cart:updated', (data) => console.log(data))
})
</script>ποΈ Arquitectura
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β SHELL (React) β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β ToastContainer (escucha notification:new) β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β βββββββββββββββββββ βββββββββββββββββββββββ β
β β #shadow-root β β #shadow-root β β
β β βββββββββββββ β β βββββββββββββββββ β β
β β β Header β β βββββββΆ β β Content β β β
β β β (Vue) β β events β β (React) β β β
β β β β β βββββββ β β β β β
β β β π badge β β store β β [Send Notif] β β β
β β βββββββββββββ β β βββββββββββββββββ β β
β β fully-isolated β β isolated β β
β βββββββββββββββββββ βββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β WU FRAMEWORK CORE β
β ββββββββββββ ββββββββββββ βββββββββββββ ββββββββββββββββ β
β β EventBus β β Store β βStyleBridgeβ β Sandbox Pool β β
β ββββββββββββ ββββββββββββ βββββββββββββ ββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββFlujo de datos:
- React Dashboard (content) llama
emit('notification:new', {...}) - Wu EventBus propaga a todos los suscriptores
- Vue NavBar (header) incrementa badge
- React ToastContainer (shell) muestra toast flotante
- Store compartido actualiza
notifications.count
π¨ Adapters
| Framework | Adapter | Registro |
|-----------|---------|----------|
| βοΈ React | wuReact | wuReact.register('app', App) |
| π Vue 3 | wuVue | wuVue.register('app', App) |
| π
°οΈ Angular | wuAngular | wuAngular.register('app', AppModule) |
| π§‘ Svelte | wuSvelte | wuSvelte.register('app', App) |
| β‘ Preact | wuPreact | wuPreact.register('app', App) |
| π Solid.js | wuSolid | wuSolid.register('app', App) |
| π₯ Lit | wuLit | wuLit.register('app', MyElement) |
| π¦ Vanilla | wuVanilla | wuVanilla.register('app', config) |
βοΈ Core Systems
| Sistema | DescripciΓ³n | |---------|-------------| | WuStyleBridge | Detecta estilos por puerto/carpeta, inyecta en Shadow DOM, genera reset de CSS vars | | WuSandbox | Proxy Sandbox para JS isolation, cleanup automΓ‘tico | | WuEventBus | Wildcards, replay, mΓ©tricas | | WuStore | Dot notation, wildcard subscriptions, batch | | WuCache | Cache de manifests y mΓ³dulos | | WuPerformance | Tiempos de mount/unmount, reportes | | WuErrorBoundary | Auto-retry, fallback UI | | WuPluginSystem | Lifecycle hooks, middleware |
π API Reference
// Core
import { wu, init, mount, unmount, destroy } from 'wu-framework'
// Events
import { emit, on, once, off, replayEvents } from 'wu-framework'
// Store
import { getState, setState, onStateChange, batchState } from 'wu-framework'
// Performance
import { startMeasure, endMeasure, generatePerformanceReport } from 'wu-framework'
// Plugins
import { usePlugin, createPlugin, useHook } from 'wu-framework'
// Adapters
import { wuReact } from 'wu-framework/adapters/react'
import { wuVue } from 'wu-framework/adapters/vue'π License
MIT License - Copyright (c) 2025 Wu Framework Team
