wu-framework 1.1.2 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +391 -987
- package/package.json +1 -1
- package/src/adapters/vue.js +2 -192
- package/src/core/wu-core.js +25 -0
- package/src/core/wu-manifest.js +31 -2
- package/src/core/wu-style-bridge.js +288 -12
package/README.md
CHANGED
|
@@ -2,1032 +2,449 @@
|
|
|
2
2
|
|
|
3
3
|
**Universal Microfrontends Made Simple**
|
|
4
4
|
|
|
5
|
-
Wu Framework es
|
|
6
|
-
|
|
7
|
-
## ✨ ¿Por qué Wu Framework?
|
|
8
|
-
|
|
9
|
-
| Característica | Module Federation | qiankun | **Wu Framework** |
|
|
10
|
-
|---|---|---|---|
|
|
11
|
-
| **Setup** | Webpack config complejo | Configuración manual | **Zero config** |
|
|
12
|
-
| **Framework Support** | React-first | Multi framework | **8 frameworks nativos** |
|
|
13
|
-
| **Bundler Required** | Solo Webpack 5 | Agnóstico pero complejo | **Ninguno** |
|
|
14
|
-
| **CSS Isolation** | Básico | CSS hacks | **Shadow DOM nativo** |
|
|
15
|
-
| **JS Isolation** | ❌ | Proxy básico | **Proxy Sandbox completo** |
|
|
16
|
-
| **Runtime Config** | ❌ | Limitado | **✅ Completamente dinámico** |
|
|
17
|
-
| **Self-Healing** | ❌ | ❌ | **✅ Auto-recovery** |
|
|
18
|
-
| **API Complexity** | Alta | Media | **1 línea de código** |
|
|
19
|
-
|
|
20
|
-
## 🎯 Instalación
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
npm install wu-framework
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
**¡Y ya está!** Sin webpack config, sin plugins, sin configuración.
|
|
5
|
+
Wu Framework es una librería de microfrontends con **Shadow DOM nativo**, **aislamiento CSS avanzado**, y **comunicación cross-MFE** integrada.
|
|
27
6
|
|
|
28
7
|
---
|
|
29
8
|
|
|
30
|
-
##
|
|
9
|
+
## ✨ ¿Por qué Wu Framework?
|
|
31
10
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
|
35
|
-
|
|
36
|
-
|
|
|
37
|
-
|
|
|
38
|
-
|
|
|
39
|
-
|
|
|
40
|
-
|
|
|
41
|
-
|
|
|
42
|
-
| 🔥 Lit | `wuLit` | `wuLit.register('app', MyElement)` |
|
|
43
|
-
| 📦 Vanilla JS | `wuVanilla` | `wuVanilla.register('app', config)` |
|
|
11
|
+
| Característica | Module Federation | Single-SPA | Qiankun | **Wu Framework** |
|
|
12
|
+
|---|---|---|---|---|
|
|
13
|
+
| **Shadow DOM nativo** | ❌ | ❌ | Parcial | **✅ Completo** |
|
|
14
|
+
| **CSS Isolation Modes** | Manual | Manual | Básico | **✅ 4 modos** |
|
|
15
|
+
| **`fully-isolated` mode** | ❌ | ❌ | ❌ | **✅ Único** |
|
|
16
|
+
| **Bloquea CSS Variables** | ❌ | ❌ | ❌ | **✅ Único** |
|
|
17
|
+
| **Framework Agnostic** | Webpack only | ✅ | Vue-first | **✅ Cualquiera** |
|
|
18
|
+
| **Bundler Agnostic** | Webpack 5 | ✅ | Webpack | **✅ Vite/Webpack** |
|
|
19
|
+
| **Cross-MFE Events** | Manual | Manual | Props | **✅ Event Bus** |
|
|
20
|
+
| **Cross-MFE Store** | ❌ | ❌ | Parcial | **✅ Reactive Store** |
|
|
44
21
|
|
|
45
22
|
---
|
|
46
23
|
|
|
47
|
-
##
|
|
48
|
-
|
|
49
|
-
### 1. Micro App: Crear `wu.json`
|
|
24
|
+
## 🛡️ CSS Isolation Modes (Feature Único)
|
|
50
25
|
|
|
51
|
-
|
|
26
|
+
Wu Framework ofrece **4 modos de aislamiento CSS**. El modo `fully-isolated` es **único** - bloquea incluso las CSS variables del padre:
|
|
52
27
|
|
|
53
28
|
```json
|
|
29
|
+
// header/wu.json - Aislamiento total (bloquea CSS vars del padre)
|
|
54
30
|
{
|
|
55
31
|
"name": "header",
|
|
56
|
-
"entry": "
|
|
32
|
+
"entry": "src/main.ts",
|
|
33
|
+
"styleMode": "fully-isolated",
|
|
57
34
|
"wu": {
|
|
58
|
-
"exports": {
|
|
59
|
-
"NavBar": "components/NavBar.js",
|
|
60
|
-
"UserMenu": "components/UserMenu.js"
|
|
61
|
-
},
|
|
62
|
-
"imports": [],
|
|
63
|
-
"routes": ["/", "/home"],
|
|
64
35
|
"permissions": ["events", "store"]
|
|
65
36
|
}
|
|
66
37
|
}
|
|
67
38
|
```
|
|
68
39
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
**React:**
|
|
81
|
-
```tsx
|
|
82
|
-
import { wuReact } from 'wu-framework/adapters/react';
|
|
83
|
-
import App from './App';
|
|
84
|
-
|
|
85
|
-
wuReact.register('header', App);
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
**Vue:**
|
|
89
|
-
```ts
|
|
90
|
-
import { wuVue } from 'wu-framework/adapters/vue';
|
|
91
|
-
import App from './App.vue';
|
|
92
|
-
|
|
93
|
-
wuVue.register('sidebar', App);
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
**Angular:**
|
|
97
|
-
```ts
|
|
98
|
-
import { wuAngular } from 'wu-framework/adapters/angular';
|
|
99
|
-
import { AppModule } from './app/app.module';
|
|
100
|
-
|
|
101
|
-
wuAngular.register('content', AppModule);
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
**¡Eso es todo!** El adapter se encarga de todo: detección de contexto, modo standalone, cleanup automático.
|
|
105
|
-
|
|
106
|
-
### 3. Shell (Host Application)
|
|
107
|
-
|
|
108
|
-
**Desarrollo:**
|
|
109
|
-
```js
|
|
110
|
-
import { wu } from 'wu-framework';
|
|
111
|
-
|
|
112
|
-
await wu.init({
|
|
113
|
-
apps: [
|
|
114
|
-
{ name: 'header', url: 'http://localhost:3001' },
|
|
115
|
-
{ name: 'sidebar', url: 'http://localhost:3002' },
|
|
116
|
-
{ name: 'content', url: 'http://localhost:3003' }
|
|
117
|
-
]
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
await wu.mount('header', '#header-container');
|
|
121
|
-
await wu.mount('sidebar', '#sidebar-container');
|
|
122
|
-
await wu.mount('content', '#content-container');
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
**Producción:**
|
|
126
|
-
```js
|
|
127
|
-
import { wu } from 'wu-framework';
|
|
128
|
-
|
|
129
|
-
await wu.init({
|
|
130
|
-
apps: [
|
|
131
|
-
{ name: 'header', url: 'https://cdn.mycompany.com/mfe/header' },
|
|
132
|
-
{ name: 'sidebar', url: 'https://cdn.mycompany.com/mfe/sidebar' },
|
|
133
|
-
{ name: 'content', url: 'https://cdn.mycompany.com/mfe/content' }
|
|
134
|
-
]
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
await wu.mount('header', '#header-container');
|
|
138
|
-
await wu.mount('sidebar', '#sidebar-container');
|
|
139
|
-
await wu.mount('content', '#content-container');
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
**Con configuración dinámica:**
|
|
143
|
-
```js
|
|
144
|
-
import { wu } from 'wu-framework';
|
|
145
|
-
|
|
146
|
-
const isDev = process.env.NODE_ENV !== 'production';
|
|
147
|
-
|
|
148
|
-
await wu.init({
|
|
149
|
-
apps: [
|
|
150
|
-
{ name: 'header', url: isDev ? 'http://localhost:3001' : 'https://cdn.mycompany.com/mfe/header' },
|
|
151
|
-
{ name: 'sidebar', url: isDev ? 'http://localhost:3002' : 'https://cdn.mycompany.com/mfe/sidebar' },
|
|
152
|
-
{ name: 'content', url: isDev ? 'http://localhost:3003' : 'https://cdn.mycompany.com/mfe/content' }
|
|
153
|
-
]
|
|
154
|
-
});
|
|
40
|
+
```json
|
|
41
|
+
// container/wu.json - Aislamiento normal (hereda CSS vars)
|
|
42
|
+
{
|
|
43
|
+
"name": "content",
|
|
44
|
+
"folder": "container",
|
|
45
|
+
"entry": "src/main.tsx",
|
|
46
|
+
"styleMode": "isolated",
|
|
47
|
+
"wu": {
|
|
48
|
+
"permissions": ["events", "store"]
|
|
49
|
+
}
|
|
50
|
+
}
|
|
155
51
|
```
|
|
156
52
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
├── App.jsx # ← Componente principal
|
|
164
|
-
├── components/
|
|
165
|
-
│ ├── NavBar.js
|
|
166
|
-
│ └── UserMenu.js
|
|
167
|
-
└── package.json
|
|
168
|
-
```
|
|
53
|
+
| Modo | CSS Variables | Estilos Padre | Caso de Uso |
|
|
54
|
+
|------|--------------|---------------|-------------|
|
|
55
|
+
| `isolated` | ✅ Heredadas | ❌ Bloqueados | MFE usa design system del host |
|
|
56
|
+
| `fully-isolated` | ❌ **Bloqueadas** | ❌ Bloqueados | MFE de terceros con su propio theme |
|
|
57
|
+
| `shared` | ✅ Heredadas | ✅ Compartidos | MFE necesita estilos del host |
|
|
58
|
+
| `auto` | ✅ Heredadas | ⚡ Solo librerías | Compartir Element Plus, etc. |
|
|
169
59
|
|
|
170
60
|
---
|
|
171
61
|
|
|
172
|
-
##
|
|
62
|
+
## 🔥 Quick Start
|
|
173
63
|
|
|
174
|
-
###
|
|
64
|
+
### 1. Shell (React + Vite)
|
|
175
65
|
|
|
176
66
|
```tsx
|
|
177
|
-
|
|
178
|
-
|
|
67
|
+
// shell/src/config/mfe.config.ts
|
|
68
|
+
const isDev = import.meta.env.DEV
|
|
179
69
|
|
|
180
|
-
|
|
181
|
-
|
|
70
|
+
const devUrls = {
|
|
71
|
+
header: 'http://localhost:3001',
|
|
72
|
+
content: 'http://localhost:3003'
|
|
73
|
+
}
|
|
182
74
|
|
|
183
|
-
|
|
75
|
+
const prodUrls = {
|
|
76
|
+
header: import.meta.env.VITE_HEADER_URL || 'https://cdn.example.com/mfe/header',
|
|
77
|
+
content: import.meta.env.VITE_CONTENT_URL || 'https://cdn.example.com/mfe/content'
|
|
78
|
+
}
|
|
184
79
|
|
|
185
|
-
|
|
186
|
-
wuReact.register('my-app', App, {
|
|
187
|
-
strictMode: true, // Envolver en React.StrictMode
|
|
188
|
-
props: { theme: 'dark' }, // Props iniciales
|
|
189
|
-
standalone: true, // Ejecutar independiente si Wu no está
|
|
190
|
-
standaloneContainer: '#root',
|
|
191
|
-
onMount: (container) => console.log('Mounted!'),
|
|
192
|
-
onUnmount: (container) => console.log('Unmounted!')
|
|
193
|
-
});
|
|
194
|
-
```
|
|
80
|
+
export const urls = isDev ? devUrls : prodUrls
|
|
195
81
|
|
|
196
|
-
|
|
82
|
+
export const enabledApps = [
|
|
83
|
+
{ name: 'header', url: urls.header, enabled: true },
|
|
84
|
+
{ name: 'content', url: urls.content, enabled: true }
|
|
85
|
+
]
|
|
86
|
+
```
|
|
197
87
|
|
|
198
88
|
```tsx
|
|
199
|
-
|
|
200
|
-
import {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
89
|
+
// shell/src/hooks/useWuFramework.ts
|
|
90
|
+
import { useEffect, useState, useRef } from 'react'
|
|
91
|
+
import { wu } from 'wu-framework'
|
|
92
|
+
import { createUseWuEvents, createUseWuStore } from 'wu-framework/adapters/react'
|
|
93
|
+
import React from 'react'
|
|
94
|
+
import { enabledApps } from '../config/mfe.config'
|
|
95
|
+
|
|
96
|
+
// Crear hooks reactivos para el shell
|
|
97
|
+
export const useWuEvents = createUseWuEvents(React)
|
|
98
|
+
export const useWuStore = createUseWuStore(React)
|
|
99
|
+
|
|
100
|
+
export function useWuFramework() {
|
|
101
|
+
const [state, setState] = useState({
|
|
102
|
+
initialized: false,
|
|
103
|
+
loading: true,
|
|
104
|
+
error: null as Error | null
|
|
105
|
+
})
|
|
106
|
+
const initRef = useRef(false)
|
|
208
107
|
|
|
209
108
|
useEffect(() => {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
109
|
+
if (initRef.current) return
|
|
110
|
+
initRef.current = true
|
|
111
|
+
|
|
112
|
+
const initWu = async () => {
|
|
113
|
+
try {
|
|
114
|
+
await wu.init({
|
|
115
|
+
apps: enabledApps.map(({ name, url }) => ({ name, url }))
|
|
116
|
+
})
|
|
117
|
+
setState({ initialized: true, loading: false, error: null })
|
|
118
|
+
} catch (error) {
|
|
119
|
+
setState({ initialized: false, loading: false, error: error as Error })
|
|
120
|
+
}
|
|
121
|
+
}
|
|
215
122
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
);
|
|
123
|
+
initWu()
|
|
124
|
+
}, [])
|
|
125
|
+
|
|
126
|
+
return { ...state, wu }
|
|
221
127
|
}
|
|
222
128
|
```
|
|
223
129
|
|
|
224
|
-
### Cargar Microfrontends en React (Shell)
|
|
225
|
-
|
|
226
130
|
```tsx
|
|
227
|
-
|
|
228
|
-
import {
|
|
131
|
+
// shell/src/App.tsx
|
|
132
|
+
import { useWuFramework, useWuEvents } from './hooks/useWuFramework'
|
|
133
|
+
import WuSlot from './components/WuSlot'
|
|
134
|
+
import ToastContainer from './components/ToastContainer'
|
|
135
|
+
|
|
136
|
+
function App() {
|
|
137
|
+
const { initialized, loading, error } = useWuFramework()
|
|
138
|
+
const { emit } = useWuEvents()
|
|
229
139
|
|
|
230
|
-
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
if (initialized) {
|
|
142
|
+
emit('shell:ready', { timestamp: Date.now() })
|
|
143
|
+
}
|
|
144
|
+
}, [initialized, emit])
|
|
231
145
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const CONTENT_URL = process.env.REACT_APP_CONTENT_URL || 'http://localhost:3002';
|
|
146
|
+
if (loading) return <div>Loading Wu Framework...</div>
|
|
147
|
+
if (error) return <div>Error: {error.message}</div>
|
|
235
148
|
|
|
236
|
-
function Shell() {
|
|
237
149
|
return (
|
|
238
|
-
|
|
239
|
-
<
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
/>
|
|
245
|
-
<WuSlot name="content" url={CONTENT_URL} />
|
|
246
|
-
</div>
|
|
247
|
-
);
|
|
150
|
+
<>
|
|
151
|
+
<ToastContainer />
|
|
152
|
+
<WuSlot name="header" url={urls.header} />
|
|
153
|
+
<WuSlot name="content" url={urls.content} />
|
|
154
|
+
</>
|
|
155
|
+
)
|
|
248
156
|
}
|
|
249
157
|
```
|
|
250
158
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
## 💚 Vue Adapter
|
|
254
|
-
|
|
255
|
-
### Registro Básico
|
|
256
|
-
|
|
257
|
-
```ts
|
|
258
|
-
import { wuVue } from 'wu-framework/adapters/vue';
|
|
259
|
-
import App from './App.vue';
|
|
159
|
+
### 2. MFE Header (Vue 3)
|
|
260
160
|
|
|
261
|
-
|
|
161
|
+
```json
|
|
162
|
+
// header/wu.json
|
|
163
|
+
{
|
|
164
|
+
"name": "header",
|
|
165
|
+
"entry": "src/main.ts",
|
|
166
|
+
"styleMode": "fully-isolated",
|
|
167
|
+
"wu": {
|
|
168
|
+
"exports": {
|
|
169
|
+
"NavBar": "src/components/NavBar.vue"
|
|
170
|
+
},
|
|
171
|
+
"permissions": ["events", "store"]
|
|
172
|
+
}
|
|
173
|
+
}
|
|
262
174
|
```
|
|
263
175
|
|
|
264
|
-
### Con Plugins (Pinia, Router, etc.)
|
|
265
|
-
|
|
266
176
|
```ts
|
|
267
|
-
|
|
268
|
-
import {
|
|
269
|
-
import
|
|
270
|
-
import App from './App.vue'
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
177
|
+
// header/src/main.ts
|
|
178
|
+
import { createApp } from 'vue'
|
|
179
|
+
import { wuVue } from 'wu-framework/adapters/vue'
|
|
180
|
+
import App from './App.vue'
|
|
181
|
+
|
|
182
|
+
// Registrar con Wu Framework
|
|
183
|
+
wuVue.register('header', App, {
|
|
184
|
+
standalone: true,
|
|
185
|
+
standaloneContainer: '#app',
|
|
186
|
+
onMount: (container) => console.log('[Header] Mounted:', container),
|
|
187
|
+
onUnmount: (container) => console.log('[Header] Unmounted:', container)
|
|
188
|
+
})
|
|
278
189
|
```
|
|
279
190
|
|
|
280
|
-
### Composables para Vue
|
|
281
|
-
|
|
282
191
|
```vue
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
import {
|
|
286
|
-
|
|
287
|
-
const { emit, on, cleanup } = useWuEvents();
|
|
288
|
-
const { state, setState } = useWuStore('cart');
|
|
192
|
+
<!-- header/src/components/NavBar.vue -->
|
|
193
|
+
<script setup lang="ts">
|
|
194
|
+
import { ref, onMounted, onUnmounted } from 'vue'
|
|
195
|
+
import { useWuEvents, useWuStore } from 'wu-framework/adapters/vue'
|
|
289
196
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
});
|
|
197
|
+
const { emit, on } = useWuEvents()
|
|
198
|
+
const { state: notificationState, setState } = useWuStore('notifications')
|
|
293
199
|
|
|
294
|
-
|
|
295
|
-
|
|
200
|
+
const notificationCount = ref(0)
|
|
201
|
+
let unsubscribe: (() => void) | null = null
|
|
296
202
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
</template>
|
|
303
|
-
```
|
|
203
|
+
onMounted(() => {
|
|
204
|
+
// Inicializar store compartido
|
|
205
|
+
if (!notificationState.value) {
|
|
206
|
+
setState({ count: 0, items: [] })
|
|
207
|
+
}
|
|
304
208
|
|
|
305
|
-
|
|
209
|
+
// Escuchar notificaciones desde CUALQUIER MFE (React, Angular, etc.)
|
|
210
|
+
unsubscribe = on('notification:new', (event) => {
|
|
211
|
+
notificationCount.value++
|
|
212
|
+
|
|
213
|
+
// Actualizar store compartido
|
|
214
|
+
const current = notificationState.value || { count: 0, items: [] }
|
|
215
|
+
setState({
|
|
216
|
+
count: current.count + 1,
|
|
217
|
+
items: [...current.items, {
|
|
218
|
+
id: Date.now(),
|
|
219
|
+
message: event.data?.message,
|
|
220
|
+
type: event.data?.type || 'info'
|
|
221
|
+
}]
|
|
222
|
+
})
|
|
223
|
+
})
|
|
224
|
+
})
|
|
306
225
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
226
|
+
onUnmounted(() => {
|
|
227
|
+
if (unsubscribe) unsubscribe()
|
|
228
|
+
})
|
|
310
229
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
230
|
+
const handleNavClick = (item: { id: string, label: string }) => {
|
|
231
|
+
emit('header:nav:click', { id: item.id, label: item.label })
|
|
232
|
+
}
|
|
314
233
|
</script>
|
|
315
234
|
|
|
316
235
|
<template>
|
|
317
|
-
<
|
|
318
|
-
<
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
</div>
|
|
236
|
+
<nav class="navbar">
|
|
237
|
+
<button class="navbar__notifications" @click="emit('header:notifications:open')">
|
|
238
|
+
🔔
|
|
239
|
+
<span v-if="notificationCount > 0" class="navbar__badge">
|
|
240
|
+
{{ notificationCount > 99 ? '99+' : notificationCount }}
|
|
241
|
+
</span>
|
|
242
|
+
</button>
|
|
243
|
+
</nav>
|
|
326
244
|
</template>
|
|
327
245
|
```
|
|
328
246
|
|
|
329
|
-
###
|
|
330
|
-
|
|
331
|
-
```ts
|
|
332
|
-
import { createApp } from 'vue';
|
|
333
|
-
import { wuVuePlugin } from 'wu-framework/adapters/vue';
|
|
334
|
-
|
|
335
|
-
const app = createApp(App);
|
|
336
|
-
app.use(wuVuePlugin); // Registra <WuSlot> globalmente
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
---
|
|
340
|
-
|
|
341
|
-
## 🅰️ Angular Adapter
|
|
342
|
-
|
|
343
|
-
### Registro con NgModule
|
|
344
|
-
|
|
345
|
-
```ts
|
|
346
|
-
import { wuAngular } from 'wu-framework/adapters/angular';
|
|
347
|
-
import { AppModule } from './app/app.module';
|
|
348
|
-
|
|
349
|
-
wuAngular.register('my-app', AppModule);
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
### Registro con Standalone Components (Angular 14+)
|
|
353
|
-
|
|
354
|
-
```ts
|
|
355
|
-
import { wuAngular } from 'wu-framework/adapters/angular';
|
|
356
|
-
import { AppComponent } from './app/app.component';
|
|
357
|
-
import { appConfig } from './app/app.config';
|
|
358
|
-
|
|
359
|
-
wuAngular.registerStandalone('my-app', AppComponent, {
|
|
360
|
-
appConfig
|
|
361
|
-
});
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
### Servicio para Componentes
|
|
365
|
-
|
|
366
|
-
```ts
|
|
367
|
-
import { Component, OnInit, OnDestroy } from '@angular/core';
|
|
368
|
-
import { createWuService } from 'wu-framework/adapters/angular';
|
|
247
|
+
### 3. MFE Content (React)
|
|
369
248
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
sendEvent() {
|
|
384
|
-
this.wuService.emit('app:ready', { timestamp: Date.now() });
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
ngOnDestroy() {
|
|
388
|
-
this.wuService.destroy();
|
|
249
|
+
```json
|
|
250
|
+
// container/wu.json
|
|
251
|
+
{
|
|
252
|
+
"name": "content",
|
|
253
|
+
"folder": "container",
|
|
254
|
+
"entry": "src/main.tsx",
|
|
255
|
+
"styleMode": "isolated",
|
|
256
|
+
"wu": {
|
|
257
|
+
"exports": {
|
|
258
|
+
"Dashboard": "src/components/Dashboard.tsx"
|
|
259
|
+
},
|
|
260
|
+
"permissions": ["events", "store"]
|
|
389
261
|
}
|
|
390
262
|
}
|
|
391
263
|
```
|
|
392
264
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
### Registro Básico
|
|
398
|
-
|
|
399
|
-
```js
|
|
400
|
-
import { wuSvelte } from 'wu-framework/adapters/svelte';
|
|
401
|
-
import App from './App.svelte';
|
|
402
|
-
|
|
403
|
-
wuSvelte.register('my-app', App);
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
### Svelte 5 (con runes)
|
|
407
|
-
|
|
408
|
-
```js
|
|
409
|
-
import { wuSvelte } from 'wu-framework/adapters/svelte';
|
|
410
|
-
import App from './App.svelte';
|
|
411
|
-
|
|
412
|
-
wuSvelte.registerSvelte5('my-app', App);
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
### Stores Reactivos
|
|
416
|
-
|
|
417
|
-
```svelte
|
|
418
|
-
<script>
|
|
419
|
-
import { createWuStore, useWuEvents } from 'wu-framework/adapters/svelte';
|
|
420
|
-
import { onDestroy } from 'svelte';
|
|
421
|
-
|
|
422
|
-
// Store reactivo (compatible con $store)
|
|
423
|
-
const userStore = createWuStore('user');
|
|
424
|
-
|
|
425
|
-
// Eventos
|
|
426
|
-
const { emit, on, cleanup } = useWuEvents();
|
|
427
|
-
|
|
428
|
-
on('cart:updated', (data) => {
|
|
429
|
-
console.log('Cart updated:', data);
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
onDestroy(cleanup);
|
|
433
|
-
</script>
|
|
434
|
-
|
|
435
|
-
<p>Hello, {$userStore?.name}</p>
|
|
436
|
-
<button on:click={() => emit('user:logout')}>Logout</button>
|
|
437
|
-
```
|
|
438
|
-
|
|
439
|
-
---
|
|
440
|
-
|
|
441
|
-
## ⚡ Preact Adapter
|
|
442
|
-
|
|
443
|
-
### Registro Básico
|
|
444
|
-
|
|
445
|
-
```jsx
|
|
446
|
-
import { wuPreact } from 'wu-framework/adapters/preact';
|
|
447
|
-
import App from './App';
|
|
448
|
-
|
|
449
|
-
wuPreact.register('my-app', App);
|
|
450
|
-
```
|
|
451
|
-
|
|
452
|
-
### Compatible con React (preact/compat)
|
|
453
|
-
|
|
454
|
-
```jsx
|
|
455
|
-
import { wuPreact } from 'wu-framework/adapters/preact';
|
|
456
|
-
import App from './App'; // Componente estilo React
|
|
457
|
-
|
|
458
|
-
wuPreact.registerCompat('my-app', App);
|
|
459
|
-
```
|
|
460
|
-
|
|
461
|
-
### Hooks
|
|
462
|
-
|
|
463
|
-
```jsx
|
|
464
|
-
import { h } from 'preact';
|
|
465
|
-
import { useState, useEffect, useCallback, useRef } from 'preact/hooks';
|
|
466
|
-
import { createUseWuEvents, createUseWuStore } from 'wu-framework/adapters/preact';
|
|
467
|
-
|
|
468
|
-
const useWuEvents = createUseWuEvents({ useCallback, useEffect, useRef });
|
|
469
|
-
const useWuStore = createUseWuStore({ useState, useCallback, useEffect });
|
|
470
|
-
|
|
471
|
-
function MyComponent() {
|
|
472
|
-
const { emit, on } = useWuEvents();
|
|
473
|
-
const { state } = useWuStore('user');
|
|
474
|
-
|
|
475
|
-
return <div>Hello {state?.name}</div>;
|
|
476
|
-
}
|
|
477
|
-
```
|
|
478
|
-
|
|
479
|
-
---
|
|
480
|
-
|
|
481
|
-
## 💎 Solid.js Adapter
|
|
482
|
-
|
|
483
|
-
### Registro Básico
|
|
484
|
-
|
|
485
|
-
```jsx
|
|
486
|
-
import { wuSolid } from 'wu-framework/adapters/solid';
|
|
487
|
-
import App from './App';
|
|
265
|
+
```tsx
|
|
266
|
+
// container/src/main.tsx
|
|
267
|
+
import { wuReact } from 'wu-framework/adapters/react'
|
|
268
|
+
import App from './App'
|
|
488
269
|
|
|
489
|
-
|
|
270
|
+
wuReact.register('content', App, {
|
|
271
|
+
strictMode: true,
|
|
272
|
+
standalone: true,
|
|
273
|
+
standaloneContainer: '#root',
|
|
274
|
+
onMount: (container) => console.log('[Content] Mounted:', container),
|
|
275
|
+
onUnmount: (container) => console.log('[Content] Unmounted:', container)
|
|
276
|
+
})
|
|
490
277
|
```
|
|
491
278
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
import {
|
|
496
|
-
|
|
497
|
-
function
|
|
498
|
-
|
|
499
|
-
const [
|
|
500
|
-
|
|
501
|
-
//
|
|
502
|
-
const
|
|
279
|
+
```tsx
|
|
280
|
+
// container/src/components/Dashboard.tsx
|
|
281
|
+
import { useState } from 'react'
|
|
282
|
+
import { useWuEvents } from '../hooks/useWu'
|
|
283
|
+
|
|
284
|
+
export function Dashboard() {
|
|
285
|
+
const { emit } = useWuEvents()
|
|
286
|
+
const [sent, setSent] = useState(0)
|
|
287
|
+
|
|
288
|
+
// Enviar notificación que el Header (Vue) y Shell (React) recibirán
|
|
289
|
+
const sendNotification = (type: 'success' | 'warning' | 'error' | 'info', message: string) => {
|
|
290
|
+
emit('notification:new', { message, type })
|
|
291
|
+
setSent(prev => prev + 1)
|
|
292
|
+
}
|
|
503
293
|
|
|
504
294
|
return (
|
|
505
|
-
<div>
|
|
506
|
-
<
|
|
507
|
-
<
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
295
|
+
<div className="dashboard">
|
|
296
|
+
<h2>Cross-MFE Communication Demo</h2>
|
|
297
|
+
<p>Notifications sent: <strong>{sent}</strong></p>
|
|
298
|
+
|
|
299
|
+
<div className="notification-buttons">
|
|
300
|
+
<button onClick={() => sendNotification('success', 'Operation completed!')}>
|
|
301
|
+
✅ Success
|
|
302
|
+
</button>
|
|
303
|
+
<button onClick={() => sendNotification('warning', 'Check your settings')}>
|
|
304
|
+
⚠️ Warning
|
|
305
|
+
</button>
|
|
306
|
+
<button onClick={() => sendNotification('error', 'Something went wrong!')}>
|
|
307
|
+
❌ Error
|
|
308
|
+
</button>
|
|
309
|
+
<button onClick={() => sendNotification('info', 'New message received')}>
|
|
310
|
+
💬 Info
|
|
311
|
+
</button>
|
|
312
|
+
</div>
|
|
513
313
|
</div>
|
|
514
|
-
)
|
|
515
|
-
}
|
|
516
|
-
```
|
|
517
|
-
|
|
518
|
-
---
|
|
519
|
-
|
|
520
|
-
## 🔥 Lit Adapter (Web Components)
|
|
521
|
-
|
|
522
|
-
### Registro con LitElement
|
|
523
|
-
|
|
524
|
-
```js
|
|
525
|
-
import { LitElement, html, css } from 'lit';
|
|
526
|
-
import { wuLit } from 'wu-framework/adapters/lit';
|
|
527
|
-
|
|
528
|
-
class MyApp extends LitElement {
|
|
529
|
-
static styles = css`
|
|
530
|
-
:host { display: block; padding: 16px; }
|
|
531
|
-
`;
|
|
532
|
-
|
|
533
|
-
render() {
|
|
534
|
-
return html`<h1>Hello from Lit!</h1>`;
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
wuLit.register('my-app', MyApp);
|
|
539
|
-
```
|
|
540
|
-
|
|
541
|
-
### Con WuMixin
|
|
542
|
-
|
|
543
|
-
```js
|
|
544
|
-
import { LitElement, html } from 'lit';
|
|
545
|
-
import { WuMixin } from 'wu-framework/adapters/lit';
|
|
546
|
-
|
|
547
|
-
class MyApp extends WuMixin(LitElement) {
|
|
548
|
-
connectedCallback() {
|
|
549
|
-
super.connectedCallback();
|
|
550
|
-
|
|
551
|
-
// Usar eventos de Wu
|
|
552
|
-
this.wuOn('user:login', (data) => {
|
|
553
|
-
console.log('User logged in:', data);
|
|
554
|
-
});
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
handleClick() {
|
|
558
|
-
this.wuEmit('button:clicked', { id: 'my-button' });
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
render() {
|
|
562
|
-
return html`
|
|
563
|
-
<button @click=${this.handleClick}>Click me</button>
|
|
564
|
-
`;
|
|
565
|
-
}
|
|
314
|
+
)
|
|
566
315
|
}
|
|
567
|
-
|
|
568
|
-
wuLit.register('my-app', MyApp);
|
|
569
|
-
```
|
|
570
|
-
|
|
571
|
-
### Web Component Vanilla (sin Lit)
|
|
572
|
-
|
|
573
|
-
```js
|
|
574
|
-
import { wuLit } from 'wu-framework/adapters/lit';
|
|
575
|
-
|
|
576
|
-
class MyComponent extends HTMLElement {
|
|
577
|
-
connectedCallback() {
|
|
578
|
-
this.attachShadow({ mode: 'open' });
|
|
579
|
-
this.shadowRoot.innerHTML = '<h1>Hello Web Component!</h1>';
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
wuLit.registerWebComponent('my-component', MyComponent);
|
|
584
|
-
```
|
|
585
|
-
|
|
586
|
-
---
|
|
587
|
-
|
|
588
|
-
## 📦 Vanilla JS Adapter
|
|
589
|
-
|
|
590
|
-
### Registro con Objeto
|
|
591
|
-
|
|
592
|
-
```js
|
|
593
|
-
import { wuVanilla } from 'wu-framework/adapters/vanilla';
|
|
594
|
-
|
|
595
|
-
wuVanilla.register('my-app', {
|
|
596
|
-
state: { count: 0 },
|
|
597
|
-
|
|
598
|
-
render: (container, state) => {
|
|
599
|
-
container.innerHTML = `
|
|
600
|
-
<div>
|
|
601
|
-
<h1>Count: ${state.count}</h1>
|
|
602
|
-
<button id="increment">+</button>
|
|
603
|
-
</div>
|
|
604
|
-
`;
|
|
605
|
-
container.querySelector('#increment').onclick = () => {
|
|
606
|
-
state.count++;
|
|
607
|
-
// Re-render manual
|
|
608
|
-
};
|
|
609
|
-
},
|
|
610
|
-
|
|
611
|
-
destroy: (container) => {
|
|
612
|
-
container.innerHTML = '';
|
|
613
|
-
}
|
|
614
|
-
});
|
|
615
316
|
```
|
|
616
317
|
|
|
617
|
-
###
|
|
618
|
-
|
|
619
|
-
```js
|
|
620
|
-
import { wuVanilla } from 'wu-framework/adapters/vanilla';
|
|
621
|
-
|
|
622
|
-
class TodoApp {
|
|
623
|
-
constructor(container) {
|
|
624
|
-
this.container = container;
|
|
625
|
-
this.todos = [];
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
render() {
|
|
629
|
-
this.container.innerHTML = `
|
|
630
|
-
<ul>
|
|
631
|
-
${this.todos.map(t => `<li>${t}</li>`).join('')}
|
|
632
|
-
</ul>
|
|
633
|
-
<input type="text" id="input" />
|
|
634
|
-
<button id="add">Add</button>
|
|
635
|
-
`;
|
|
636
|
-
|
|
637
|
-
this.container.querySelector('#add').onclick = () => {
|
|
638
|
-
const input = this.container.querySelector('#input');
|
|
639
|
-
this.todos.push(input.value);
|
|
640
|
-
this.render();
|
|
641
|
-
};
|
|
642
|
-
}
|
|
318
|
+
### 4. Toast Container (Shell - escucha eventos de MFEs)
|
|
643
319
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
320
|
+
```tsx
|
|
321
|
+
// shell/src/components/ToastContainer.tsx
|
|
322
|
+
import { useState, useEffect, useCallback } from 'react'
|
|
323
|
+
import { useWuEvents } from '../hooks/useWuFramework'
|
|
324
|
+
|
|
325
|
+
interface Toast {
|
|
326
|
+
id: string
|
|
327
|
+
message: string
|
|
328
|
+
type: 'success' | 'warning' | 'error' | 'info'
|
|
647
329
|
}
|
|
648
330
|
|
|
649
|
-
|
|
650
|
-
|
|
331
|
+
export function ToastContainer() {
|
|
332
|
+
const [toasts, setToasts] = useState<Toast[]>([])
|
|
333
|
+
const { on } = useWuEvents()
|
|
651
334
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
wuVanilla.registerTemplate('header', `
|
|
658
|
-
<header>
|
|
659
|
-
<h1>My Header</h1>
|
|
660
|
-
<nav>
|
|
661
|
-
<a href="/">Home</a>
|
|
662
|
-
<a href="/about">About</a>
|
|
663
|
-
</nav>
|
|
664
|
-
</header>
|
|
665
|
-
`);
|
|
666
|
-
|
|
667
|
-
// Template dinámico
|
|
668
|
-
wuVanilla.registerTemplate('greeting',
|
|
669
|
-
(data) => `<h1>Hello, ${data.name}!</h1>`,
|
|
670
|
-
{ data: { name: 'World' } }
|
|
671
|
-
);
|
|
672
|
-
```
|
|
335
|
+
const addToast = useCallback((message: string, type: Toast['type']) => {
|
|
336
|
+
const id = `toast-${Date.now()}`
|
|
337
|
+
setToasts(prev => [...prev, { id, message, type }])
|
|
338
|
+
setTimeout(() => setToasts(prev => prev.filter(t => t.id !== id)), 5000)
|
|
339
|
+
}, [])
|
|
673
340
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
341
|
+
useEffect(() => {
|
|
342
|
+
// Escuchar notificaciones de CUALQUIER MFE
|
|
343
|
+
const unsubscribe = on('notification:new', (event) => {
|
|
344
|
+
const { message, type } = event.data || {}
|
|
345
|
+
if (message) addToast(message, type || 'info')
|
|
346
|
+
})
|
|
347
|
+
return () => unsubscribe?.()
|
|
348
|
+
}, [on, addToast])
|
|
681
349
|
|
|
682
|
-
|
|
683
|
-
<div>
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
350
|
+
return (
|
|
351
|
+
<div className="toast-container">
|
|
352
|
+
{toasts.map(toast => (
|
|
353
|
+
<div key={toast.id} className={`toast toast--${toast.type}`}>
|
|
354
|
+
{toast.message}
|
|
355
|
+
</div>
|
|
356
|
+
))}
|
|
687
357
|
</div>
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
actions: {
|
|
691
|
-
increment: (state) => ({ count: state.count + 1 }),
|
|
692
|
-
decrement: (state) => ({ count: state.count - 1 })
|
|
693
|
-
}
|
|
694
|
-
});
|
|
695
|
-
|
|
696
|
-
wuVanilla.register('counter', Counter);
|
|
358
|
+
)
|
|
359
|
+
}
|
|
697
360
|
```
|
|
698
361
|
|
|
699
362
|
---
|
|
700
363
|
|
|
701
|
-
## 📡
|
|
702
|
-
|
|
703
|
-
Sistema de eventos para comunicación entre microfrontends con soporte para wildcards y replay.
|
|
364
|
+
## 📡 Cross-MFE Communication
|
|
704
365
|
|
|
705
|
-
|
|
706
|
-
import { emit, on, once, off, replayEvents } from 'wu-framework';
|
|
366
|
+
### Event Bus
|
|
707
367
|
|
|
708
|
-
|
|
709
|
-
|
|
368
|
+
```ts
|
|
369
|
+
import { emit, on, once, off } from 'wu-framework'
|
|
710
370
|
|
|
711
|
-
//
|
|
712
|
-
|
|
713
|
-
console.log('User logged in:', event.data);
|
|
714
|
-
});
|
|
371
|
+
// React MFE emite
|
|
372
|
+
emit('notification:new', { message: 'Hello!', type: 'success' })
|
|
715
373
|
|
|
716
|
-
//
|
|
717
|
-
on('
|
|
718
|
-
console.log(
|
|
719
|
-
})
|
|
374
|
+
// Vue MFE escucha
|
|
375
|
+
const unsubscribe = on('notification:new', (event) => {
|
|
376
|
+
console.log(event.data.message) // 'Hello!'
|
|
377
|
+
})
|
|
720
378
|
|
|
721
|
-
//
|
|
722
|
-
|
|
723
|
-
console.log('
|
|
724
|
-
})
|
|
379
|
+
// Wildcards - escuchar todos los eventos de notificación
|
|
380
|
+
on('notification:*', (event) => {
|
|
381
|
+
console.log('Event:', event.name, event.data)
|
|
382
|
+
})
|
|
725
383
|
|
|
726
|
-
//
|
|
727
|
-
|
|
728
|
-
unsubscribe(); // También funciona
|
|
384
|
+
// Una sola vez
|
|
385
|
+
once('app:ready', () => console.log('Ready!'))
|
|
729
386
|
|
|
730
|
-
//
|
|
731
|
-
|
|
732
|
-
console.log('Past event:', event);
|
|
733
|
-
});
|
|
387
|
+
// Limpiar
|
|
388
|
+
unsubscribe()
|
|
734
389
|
```
|
|
735
390
|
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
## 🗄️ Store (State Management)
|
|
739
|
-
|
|
740
|
-
Store de alto rendimiento con pattern matching.
|
|
741
|
-
|
|
742
|
-
```js
|
|
743
|
-
import {
|
|
744
|
-
getState,
|
|
745
|
-
setState,
|
|
746
|
-
onStateChange,
|
|
747
|
-
batchState,
|
|
748
|
-
clearState,
|
|
749
|
-
getStoreMetrics
|
|
750
|
-
} from 'wu-framework';
|
|
391
|
+
### Store Reactivo
|
|
751
392
|
|
|
752
|
-
|
|
753
|
-
setState
|
|
754
|
-
setState('user.preferences.theme', 'dark');
|
|
393
|
+
```ts
|
|
394
|
+
import { getState, setState, onStateChange, batchState } from 'wu-framework'
|
|
755
395
|
|
|
756
|
-
//
|
|
757
|
-
|
|
758
|
-
|
|
396
|
+
// Vue MFE escribe
|
|
397
|
+
setState('notifications.count', 5)
|
|
398
|
+
setState('user.theme', 'dark')
|
|
759
399
|
|
|
760
|
-
//
|
|
761
|
-
const
|
|
762
|
-
console.log(`${path} changed to:`, value);
|
|
763
|
-
});
|
|
400
|
+
// React MFE lee
|
|
401
|
+
const count = getState('notifications.count') // 5
|
|
764
402
|
|
|
765
|
-
//
|
|
766
|
-
onStateChange('
|
|
767
|
-
console.log(
|
|
768
|
-
})
|
|
403
|
+
// Angular MFE se suscribe
|
|
404
|
+
const unsub = onStateChange('notifications.*', (value, path) => {
|
|
405
|
+
console.log(`${path} changed:`, value)
|
|
406
|
+
})
|
|
769
407
|
|
|
770
|
-
// Batch updates
|
|
408
|
+
// Batch updates
|
|
771
409
|
batchState({
|
|
772
|
-
'user.name': '
|
|
773
|
-
'
|
|
774
|
-
|
|
775
|
-
});
|
|
776
|
-
|
|
777
|
-
// Métricas de performance
|
|
778
|
-
const metrics = getStoreMetrics();
|
|
779
|
-
```
|
|
780
|
-
|
|
781
|
-
---
|
|
782
|
-
|
|
783
|
-
## 🔌 Sistema de Plugins
|
|
784
|
-
|
|
785
|
-
```js
|
|
786
|
-
import { usePlugin, createPlugin, uninstallPlugin } from 'wu-framework';
|
|
787
|
-
|
|
788
|
-
const analyticsPlugin = createPlugin({
|
|
789
|
-
name: 'analytics',
|
|
790
|
-
permissions: ['events', 'store'],
|
|
791
|
-
|
|
792
|
-
install: (api, options) => {
|
|
793
|
-
api.on('app:mounted', (event) => {
|
|
794
|
-
trackEvent('mount', event.data.appName);
|
|
795
|
-
});
|
|
796
|
-
},
|
|
797
|
-
|
|
798
|
-
beforeMount: async (context) => {
|
|
799
|
-
console.log('Before mount:', context.appName);
|
|
800
|
-
},
|
|
801
|
-
|
|
802
|
-
afterMount: async (context) => {
|
|
803
|
-
console.log('Mounted in', context.mountTime, 'ms');
|
|
804
|
-
}
|
|
805
|
-
});
|
|
806
|
-
|
|
807
|
-
usePlugin(analyticsPlugin, { trackingId: 'UA-123456' });
|
|
808
|
-
```
|
|
809
|
-
|
|
810
|
-
---
|
|
811
|
-
|
|
812
|
-
## 🪝 Lifecycle Hooks
|
|
813
|
-
|
|
814
|
-
```js
|
|
815
|
-
import { useHook, createGuardHook, createTimedHook } from 'wu-framework';
|
|
816
|
-
|
|
817
|
-
// Hook básico
|
|
818
|
-
useHook('beforeMount', async (context, next) => {
|
|
819
|
-
console.log('Before mounting:', context.appName);
|
|
820
|
-
await next();
|
|
821
|
-
}, { name: 'my-hook', priority: 10 });
|
|
822
|
-
|
|
823
|
-
// Guard hook (puede cancelar)
|
|
824
|
-
const authGuard = createGuardHook(async (ctx) => {
|
|
825
|
-
return await isUserAuthenticated();
|
|
826
|
-
});
|
|
827
|
-
useHook('beforeMount', authGuard);
|
|
828
|
-
|
|
829
|
-
// Hook con timeout
|
|
830
|
-
const timedHook = createTimedHook(async (ctx) => {
|
|
831
|
-
await slowOperation();
|
|
832
|
-
}, 5000);
|
|
833
|
-
```
|
|
834
|
-
|
|
835
|
-
### Fases Disponibles
|
|
836
|
-
|
|
837
|
-
| Fase | Descripción |
|
|
838
|
-
|------|-------------|
|
|
839
|
-
| `beforeInit` | Antes de inicializar framework |
|
|
840
|
-
| `afterInit` | Después de inicializar |
|
|
841
|
-
| `beforeLoad` | Antes de cargar una app |
|
|
842
|
-
| `afterLoad` | Después de cargar |
|
|
843
|
-
| `beforeMount` | Antes de montar |
|
|
844
|
-
| `afterMount` | Después de montar |
|
|
845
|
-
| `beforeUnmount` | Antes de desmontar |
|
|
846
|
-
| `afterUnmount` | Después de desmontar |
|
|
847
|
-
|
|
848
|
-
---
|
|
849
|
-
|
|
850
|
-
## 🎯 Loading Strategies
|
|
851
|
-
|
|
852
|
-
```js
|
|
853
|
-
await wu.init({
|
|
854
|
-
apps: [
|
|
855
|
-
{ name: 'header', url: '/mfe/header', strategy: 'lazy' }, // Default - carga cuando se monta
|
|
856
|
-
{ name: 'sidebar', url: '/mfe/sidebar', strategy: 'eager' }, // Precarga inmediata
|
|
857
|
-
{ name: 'footer', url: '/mfe/footer', strategy: 'preload' }, // <link prefetch>
|
|
858
|
-
{ name: 'analytics', url: '/mfe/analytics', strategy: 'idle' } // requestIdleCallback
|
|
859
|
-
]
|
|
860
|
-
});
|
|
861
|
-
```
|
|
862
|
-
|
|
863
|
-
| Estrategia | Descripción | Uso recomendado |
|
|
864
|
-
|------------|-------------|-----------------|
|
|
865
|
-
| `lazy` | Carga cuando se monta (default) | Contenido below-the-fold |
|
|
866
|
-
| `eager` | Precarga inmediata | Componentes críticos |
|
|
867
|
-
| `preload` | Usa `<link rel="prefetch">` | Navegación anticipada |
|
|
868
|
-
| `idle` | Usa `requestIdleCallback` | Analytics, features secundarias |
|
|
869
|
-
|
|
870
|
-
---
|
|
871
|
-
|
|
872
|
-
## ⚡ Performance Monitoring
|
|
873
|
-
|
|
874
|
-
```js
|
|
875
|
-
import {
|
|
876
|
-
startMeasure,
|
|
877
|
-
endMeasure,
|
|
878
|
-
getPerformanceMetrics,
|
|
879
|
-
generatePerformanceReport
|
|
880
|
-
} from 'wu-framework';
|
|
881
|
-
|
|
882
|
-
// Medición personalizada
|
|
883
|
-
startMeasure('data-fetch', 'my-app');
|
|
884
|
-
await fetchData();
|
|
885
|
-
const duration = endMeasure('data-fetch', 'my-app');
|
|
886
|
-
|
|
887
|
-
// Reporte completo
|
|
888
|
-
const report = generatePerformanceReport();
|
|
889
|
-
```
|
|
890
|
-
|
|
891
|
-
---
|
|
892
|
-
|
|
893
|
-
## 🛡️ Error Handling
|
|
894
|
-
|
|
895
|
-
```js
|
|
896
|
-
import { registerErrorHandler, configureErrorBoundary } from 'wu-framework';
|
|
897
|
-
|
|
898
|
-
registerErrorHandler({
|
|
899
|
-
name: 'api-error',
|
|
900
|
-
canHandle: (error) => error.message.includes('API'),
|
|
901
|
-
handle: async (error, context) => {
|
|
902
|
-
if (context.retryCount < 3) {
|
|
903
|
-
return { recovered: true, action: 'retry' };
|
|
904
|
-
}
|
|
905
|
-
return { recovered: false, action: 'fallback' };
|
|
906
|
-
}
|
|
907
|
-
});
|
|
908
|
-
|
|
909
|
-
configureErrorBoundary({
|
|
910
|
-
maxRetries: 3,
|
|
911
|
-
retryDelay: 1000,
|
|
912
|
-
showErrorUI: true
|
|
913
|
-
});
|
|
914
|
-
```
|
|
915
|
-
|
|
916
|
-
---
|
|
917
|
-
|
|
918
|
-
## 🎯 API Simplificada (wu.app)
|
|
919
|
-
|
|
920
|
-
```js
|
|
921
|
-
import { wu } from 'wu-framework';
|
|
922
|
-
|
|
923
|
-
// URL puede ser localhost (dev) o CDN (prod)
|
|
924
|
-
const header = wu.app('header', {
|
|
925
|
-
url: 'http://localhost:3001', // o 'https://cdn.mycompany.com/mfe/header'
|
|
926
|
-
container: '#header-container'
|
|
927
|
-
});
|
|
928
|
-
|
|
929
|
-
await header.mount();
|
|
930
|
-
console.log(header.isMounted); // true
|
|
931
|
-
|
|
932
|
-
await header.unmount();
|
|
933
|
-
await header.remount();
|
|
934
|
-
await header.reload(); // Limpia cache
|
|
935
|
-
|
|
936
|
-
const health = await header.verify();
|
|
937
|
-
await header.destroy();
|
|
410
|
+
'user.name': 'John',
|
|
411
|
+
'notifications.count': 0
|
|
412
|
+
})
|
|
938
413
|
```
|
|
939
414
|
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
## 🚀 Deployment
|
|
943
|
-
|
|
944
|
-
### Estructura de Producción
|
|
415
|
+
### Hooks por Framework
|
|
945
416
|
|
|
946
|
-
|
|
417
|
+
**React:**
|
|
418
|
+
```tsx
|
|
419
|
+
import { createUseWuEvents, createUseWuStore } from 'wu-framework/adapters/react'
|
|
947
420
|
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
├── mfe/
|
|
951
|
-
│ ├── header/
|
|
952
|
-
│ │ ├── wu.json # Manifest
|
|
953
|
-
│ │ ├── index.js # Entry point
|
|
954
|
-
│ │ └── assets/
|
|
955
|
-
│ ├── sidebar/
|
|
956
|
-
│ │ ├── wu.json
|
|
957
|
-
│ │ ├── index.js
|
|
958
|
-
│ │ └── assets/
|
|
959
|
-
│ └── content/
|
|
960
|
-
│ ├── wu.json
|
|
961
|
-
│ ├── index.js
|
|
962
|
-
│ └── assets/
|
|
963
|
-
```
|
|
421
|
+
const useWuEvents = createUseWuEvents(React)
|
|
422
|
+
const useWuStore = createUseWuStore(React)
|
|
964
423
|
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
// config.js
|
|
969
|
-
const config = {
|
|
970
|
-
development: {
|
|
971
|
-
header: 'http://localhost:3001',
|
|
972
|
-
sidebar: 'http://localhost:3002',
|
|
973
|
-
content: 'http://localhost:3003'
|
|
974
|
-
},
|
|
975
|
-
staging: {
|
|
976
|
-
header: 'https://staging-cdn.mycompany.com/mfe/header',
|
|
977
|
-
sidebar: 'https://staging-cdn.mycompany.com/mfe/sidebar',
|
|
978
|
-
content: 'https://staging-cdn.mycompany.com/mfe/content'
|
|
979
|
-
},
|
|
980
|
-
production: {
|
|
981
|
-
header: 'https://cdn.mycompany.com/mfe/header',
|
|
982
|
-
sidebar: 'https://cdn.mycompany.com/mfe/sidebar',
|
|
983
|
-
content: 'https://cdn.mycompany.com/mfe/content'
|
|
984
|
-
}
|
|
985
|
-
};
|
|
986
|
-
|
|
987
|
-
const env = process.env.NODE_ENV || 'development';
|
|
988
|
-
const urls = config[env];
|
|
989
|
-
|
|
990
|
-
await wu.init({
|
|
991
|
-
apps: [
|
|
992
|
-
{ name: 'header', url: urls.header },
|
|
993
|
-
{ name: 'sidebar', url: urls.sidebar },
|
|
994
|
-
{ name: 'content', url: urls.content }
|
|
995
|
-
]
|
|
996
|
-
});
|
|
997
|
-
```
|
|
998
|
-
|
|
999
|
-
### CORS Configuration
|
|
424
|
+
function MyComponent() {
|
|
425
|
+
const { emit, on } = useWuEvents()
|
|
426
|
+
const { state, setState } = useWuStore('notifications')
|
|
1000
427
|
|
|
1001
|
-
|
|
428
|
+
useEffect(() => {
|
|
429
|
+
return on('user:login', (e) => console.log(e.data))
|
|
430
|
+
}, [])
|
|
1002
431
|
|
|
1003
|
-
|
|
1004
|
-
# nginx.conf para CDN
|
|
1005
|
-
location /mfe/ {
|
|
1006
|
-
add_header Access-Control-Allow-Origin *;
|
|
1007
|
-
add_header Access-Control-Allow-Methods "GET, OPTIONS";
|
|
1008
|
-
add_header Access-Control-Allow-Headers "Content-Type";
|
|
432
|
+
return <span>Count: {state?.count || 0}</span>
|
|
1009
433
|
}
|
|
1010
434
|
```
|
|
1011
435
|
|
|
1012
|
-
|
|
436
|
+
**Vue:**
|
|
437
|
+
```vue
|
|
438
|
+
<script setup>
|
|
439
|
+
import { useWuEvents, useWuStore } from 'wu-framework/adapters/vue'
|
|
1013
440
|
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
{
|
|
1017
|
-
"name": "header",
|
|
1018
|
-
"version": "1.2.0",
|
|
1019
|
-
"entry": "index.js"
|
|
1020
|
-
}
|
|
1021
|
-
```
|
|
441
|
+
const { emit, on } = useWuEvents()
|
|
442
|
+
const { state, setState } = useWuStore('cart')
|
|
1022
443
|
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
{ name: 'header', url: 'https://cdn.mycompany.com/mfe/header/v1.2.0' },
|
|
1028
|
-
{ name: 'sidebar', url: 'https://cdn.mycompany.com/mfe/sidebar/v2.0.0' }
|
|
1029
|
-
]
|
|
1030
|
-
});
|
|
444
|
+
onMounted(() => {
|
|
445
|
+
on('cart:updated', (data) => console.log(data))
|
|
446
|
+
})
|
|
447
|
+
</script>
|
|
1031
448
|
```
|
|
1032
449
|
|
|
1033
450
|
---
|
|
@@ -1036,106 +453,93 @@ await wu.init({
|
|
|
1036
453
|
|
|
1037
454
|
```
|
|
1038
455
|
┌─────────────────────────────────────────────────────────────┐
|
|
1039
|
-
│
|
|
456
|
+
│ SHELL (React) │
|
|
457
|
+
│ ┌──────────────────────────────────────────────────────┐ │
|
|
458
|
+
│ │ ToastContainer (escucha notification:new) │ │
|
|
459
|
+
│ └──────────────────────────────────────────────────────┘ │
|
|
1040
460
|
├─────────────────────────────────────────────────────────────┤
|
|
1041
|
-
│ ┌─────────────────┐
|
|
1042
|
-
│ │ #shadow-root │
|
|
1043
|
-
│ │ ┌───────────┐ │
|
|
1044
|
-
│ │ │ Header │ │ │ │
|
|
1045
|
-
│ │ │ (
|
|
1046
|
-
│ │
|
|
1047
|
-
│ │
|
|
1048
|
-
│ │
|
|
1049
|
-
│
|
|
461
|
+
│ ┌─────────────────┐ ┌─────────────────────┐ │
|
|
462
|
+
│ │ #shadow-root │ │ #shadow-root │ │
|
|
463
|
+
│ │ ┌───────────┐ │ │ ┌───────────────┐ │ │
|
|
464
|
+
│ │ │ Header │ │ ──────▶ │ │ Content │ │ │
|
|
465
|
+
│ │ │ (Vue) │ │ events │ │ (React) │ │ │
|
|
466
|
+
│ │ │ │ │ ◀────── │ │ │ │ │
|
|
467
|
+
│ │ │ 🔔 badge │ │ store │ │ [Send Notif] │ │ │
|
|
468
|
+
│ │ └───────────┘ │ │ └───────────────┘ │ │
|
|
469
|
+
│ │ fully-isolated │ │ isolated │ │
|
|
470
|
+
│ └─────────────────┘ └─────────────────────┘ │
|
|
1050
471
|
├─────────────────────────────────────────────────────────────┤
|
|
1051
|
-
│ WU FRAMEWORK CORE
|
|
1052
|
-
│ ┌──────────┐ ┌──────────┐
|
|
1053
|
-
│ │ EventBus │ │ Store │ │
|
|
1054
|
-
│ └──────────┘ └──────────┘
|
|
472
|
+
│ WU FRAMEWORK CORE │
|
|
473
|
+
│ ┌──────────┐ ┌──────────┐ ┌───────────┐ ┌──────────────┐ │
|
|
474
|
+
│ │ EventBus │ │ Store │ │StyleBridge│ │ Sandbox Pool │ │
|
|
475
|
+
│ └──────────┘ └──────────┘ └───────────┘ └──────────────┘ │
|
|
1055
476
|
└─────────────────────────────────────────────────────────────┘
|
|
1056
477
|
```
|
|
1057
478
|
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
479
|
+
**Flujo de datos:**
|
|
480
|
+
1. React Dashboard (content) llama `emit('notification:new', {...})`
|
|
481
|
+
2. Wu EventBus propaga a todos los suscriptores
|
|
482
|
+
3. Vue NavBar (header) incrementa badge
|
|
483
|
+
4. React ToastContainer (shell) muestra toast flotante
|
|
484
|
+
5. Store compartido actualiza `notifications.count`
|
|
1061
485
|
|
|
1062
|
-
|
|
486
|
+
---
|
|
1063
487
|
|
|
1064
|
-
|
|
1065
|
-
import { wu, init, mount, unmount, destroy, getStats } from 'wu-framework';
|
|
1066
|
-
```
|
|
488
|
+
## 🎨 Adapters
|
|
1067
489
|
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
490
|
+
| Framework | Adapter | Registro |
|
|
491
|
+
|-----------|---------|----------|
|
|
492
|
+
| ⚛️ React | `wuReact` | `wuReact.register('app', App)` |
|
|
493
|
+
| 💚 Vue 3 | `wuVue` | `wuVue.register('app', App)` |
|
|
494
|
+
| 🅰️ Angular | `wuAngular` | `wuAngular.register('app', AppModule)` |
|
|
495
|
+
| 🧡 Svelte | `wuSvelte` | `wuSvelte.register('app', App)` |
|
|
496
|
+
| ⚡ Preact | `wuPreact` | `wuPreact.register('app', App)` |
|
|
497
|
+
| 💎 Solid.js | `wuSolid` | `wuSolid.register('app', App)` |
|
|
498
|
+
| 🔥 Lit | `wuLit` | `wuLit.register('app', MyElement)` |
|
|
499
|
+
| 📦 Vanilla | `wuVanilla` | `wuVanilla.register('app', config)` |
|
|
1075
500
|
|
|
1076
|
-
|
|
1077
|
-
import { getState, setState, onStateChange, batchState, clearState } from 'wu-framework';
|
|
1078
|
-
```
|
|
501
|
+
---
|
|
1079
502
|
|
|
1080
|
-
|
|
503
|
+
## ⚙️ Core Systems
|
|
1081
504
|
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
505
|
+
| Sistema | Descripción |
|
|
506
|
+
|---------|-------------|
|
|
507
|
+
| **WuStyleBridge** | Detecta estilos por puerto/carpeta, inyecta en Shadow DOM, genera reset de CSS vars |
|
|
508
|
+
| **WuSandbox** | Proxy Sandbox para JS isolation, cleanup automático |
|
|
509
|
+
| **WuEventBus** | Wildcards, replay, métricas |
|
|
510
|
+
| **WuStore** | Dot notation, wildcard subscriptions, batch |
|
|
511
|
+
| **WuCache** | Cache de manifests y módulos |
|
|
512
|
+
| **WuPerformance** | Tiempos de mount/unmount, reportes |
|
|
513
|
+
| **WuErrorBoundary** | Auto-retry, fallback UI |
|
|
514
|
+
| **WuPluginSystem** | Lifecycle hooks, middleware |
|
|
1085
515
|
|
|
1086
|
-
|
|
516
|
+
---
|
|
1087
517
|
|
|
1088
|
-
|
|
1089
|
-
import { startMeasure, endMeasure, generatePerformanceReport } from 'wu-framework';
|
|
1090
|
-
import { registerErrorHandler, configureErrorBoundary } from 'wu-framework';
|
|
1091
|
-
```
|
|
518
|
+
## 📋 API Reference
|
|
1092
519
|
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
import { wuReact } from 'wu-framework/adapters/react';
|
|
1097
|
-
import { wuVue } from 'wu-framework/adapters/vue';
|
|
1098
|
-
import { wuAngular } from 'wu-framework/adapters/angular';
|
|
1099
|
-
import { wuSvelte } from 'wu-framework/adapters/svelte';
|
|
1100
|
-
import { wuPreact } from 'wu-framework/adapters/preact';
|
|
1101
|
-
import { wuSolid } from 'wu-framework/adapters/solid';
|
|
1102
|
-
import { wuLit } from 'wu-framework/adapters/lit';
|
|
1103
|
-
import { wuVanilla } from 'wu-framework/adapters/vanilla';
|
|
1104
|
-
```
|
|
520
|
+
```ts
|
|
521
|
+
// Core
|
|
522
|
+
import { wu, init, mount, unmount, destroy } from 'wu-framework'
|
|
1105
523
|
|
|
1106
|
-
|
|
524
|
+
// Events
|
|
525
|
+
import { emit, on, once, off, replayEvents } from 'wu-framework'
|
|
1107
526
|
|
|
1108
|
-
|
|
1109
|
-
import {
|
|
527
|
+
// Store
|
|
528
|
+
import { getState, setState, onStateChange, batchState } from 'wu-framework'
|
|
1110
529
|
|
|
1111
|
-
//
|
|
1112
|
-
|
|
1113
|
-
await wu.init(presets.production([{ name: 'app', url: 'https://...' }]));
|
|
530
|
+
// Performance
|
|
531
|
+
import { startMeasure, endMeasure, generatePerformanceReport } from 'wu-framework'
|
|
1114
532
|
|
|
1115
|
-
//
|
|
1116
|
-
|
|
1117
|
-
dev.inspect();
|
|
1118
|
-
await dev.reload('app');
|
|
533
|
+
// Plugins
|
|
534
|
+
import { usePlugin, createPlugin, useHook } from 'wu-framework'
|
|
1119
535
|
|
|
1120
|
-
//
|
|
1121
|
-
wu
|
|
1122
|
-
wu
|
|
536
|
+
// Adapters
|
|
537
|
+
import { wuReact } from 'wu-framework/adapters/react'
|
|
538
|
+
import { wuVue } from 'wu-framework/adapters/vue'
|
|
1123
539
|
```
|
|
1124
540
|
|
|
1125
541
|
---
|
|
1126
542
|
|
|
1127
|
-
## 🤝 Contributing
|
|
1128
|
-
|
|
1129
|
-
¡Las contribuciones son bienvenidas!
|
|
1130
|
-
|
|
1131
|
-
1. **Fork** el repositorio
|
|
1132
|
-
2. **Crear** branch (`git checkout -b feature/amazing-feature`)
|
|
1133
|
-
3. **Commit** tus cambios (`git commit -m 'Add amazing feature'`)
|
|
1134
|
-
4. **Push** al branch (`git push origin feature/amazing-feature`)
|
|
1135
|
-
5. **Abrir** un Pull Request
|
|
1136
|
-
|
|
1137
|
-
---
|
|
1138
|
-
|
|
1139
543
|
## 📄 License
|
|
1140
544
|
|
|
1141
545
|
MIT License - Copyright (c) 2025 Wu Framework Team
|
|
@@ -1145,5 +549,5 @@ MIT License - Copyright (c) 2025 Wu Framework Team
|
|
|
1145
549
|
<p align="center">
|
|
1146
550
|
<b>🚀 Wu Framework - Universal Microfrontends Made Simple</b>
|
|
1147
551
|
<br><br>
|
|
1148
|
-
<i>
|
|
552
|
+
<i>Shadow DOM • 4 CSS Isolation Modes • Cross-MFE Events & Store • Zero Config</i>
|
|
1149
553
|
</p>
|