wu-framework 1.1.1 β 1.1.3
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 +392 -988
- package/package.json +1 -1
- package/src/core/wu-core.js +25 -0
- package/src/core/wu-manifest.js +31 -2
- package/src/core/wu-style-bridge.js +323 -9
package/README.md
CHANGED
|
@@ -1,1033 +1,450 @@
|
|
|
1
|
-
# π Wu Framework
|
|
1
|
+
# π Wu Framework v1.1.1
|
|
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>
|