wu-framework 1.0.6 → 1.1.1
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 +948 -358
- package/package.json +34 -9
- package/src/adapters/angular.d.ts +154 -0
- package/src/adapters/angular.js +642 -0
- package/src/adapters/index.js +157 -0
- package/src/adapters/lit.d.ts +120 -0
- package/src/adapters/lit.js +726 -0
- package/src/adapters/preact.d.ts +108 -0
- package/src/adapters/preact.js +665 -0
- package/src/adapters/react.d.ts +212 -0
- package/src/adapters/react.js +513 -0
- package/src/adapters/solid.d.ts +101 -0
- package/src/adapters/solid.js +591 -0
- package/src/adapters/svelte.d.ts +166 -0
- package/src/adapters/svelte.js +803 -0
- package/src/adapters/vanilla.d.ts +179 -0
- package/src/adapters/vanilla.js +791 -0
- package/src/adapters/vue.d.ts +299 -0
- package/src/adapters/vue.js +570 -0
package/README.md
CHANGED
|
@@ -9,11 +9,13 @@ Wu Framework es la librería de microfrontends más simple y poderosa del mercad
|
|
|
9
9
|
| Característica | Module Federation | qiankun | **Wu Framework** |
|
|
10
10
|
|---|---|---|---|
|
|
11
11
|
| **Setup** | Webpack config complejo | Configuración manual | **Zero config** |
|
|
12
|
-
| **Framework Support** | React-first | Multi framework | **
|
|
12
|
+
| **Framework Support** | React-first | Multi framework | **8 frameworks nativos** |
|
|
13
13
|
| **Bundler Required** | Solo Webpack 5 | Agnóstico pero complejo | **Ninguno** |
|
|
14
14
|
| **CSS Isolation** | Básico | CSS hacks | **Shadow DOM nativo** |
|
|
15
|
+
| **JS Isolation** | ❌ | Proxy básico | **Proxy Sandbox completo** |
|
|
15
16
|
| **Runtime Config** | ❌ | Limitado | **✅ Completamente dinámico** |
|
|
16
|
-
| **
|
|
17
|
+
| **Self-Healing** | ❌ | ❌ | **✅ Auto-recovery** |
|
|
18
|
+
| **API Complexity** | Alta | Media | **1 línea de código** |
|
|
17
19
|
|
|
18
20
|
## 🎯 Instalación
|
|
19
21
|
|
|
@@ -23,15 +25,91 @@ npm install wu-framework
|
|
|
23
25
|
|
|
24
26
|
**¡Y ya está!** Sin webpack config, sin plugins, sin configuración.
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 🎨 Frameworks Soportados
|
|
31
|
+
|
|
32
|
+
Wu Framework incluye **adapters nativos** para los frameworks más populares:
|
|
33
|
+
|
|
34
|
+
| Framework | Adapter | Registro |
|
|
35
|
+
|-----------|---------|----------|
|
|
36
|
+
| ⚛️ React | `wuReact` | `wuReact.register('app', App)` |
|
|
37
|
+
| 💚 Vue | `wuVue` | `wuVue.register('app', App)` |
|
|
38
|
+
| 🅰️ Angular | `wuAngular` | `wuAngular.register('app', AppModule)` |
|
|
39
|
+
| 🧡 Svelte | `wuSvelte` | `wuSvelte.register('app', App)` |
|
|
40
|
+
| ⚡ Preact | `wuPreact` | `wuPreact.register('app', App)` |
|
|
41
|
+
| 💎 Solid.js | `wuSolid` | `wuSolid.register('app', App)` |
|
|
42
|
+
| 🔥 Lit | `wuLit` | `wuLit.register('app', MyElement)` |
|
|
43
|
+
| 📦 Vanilla JS | `wuVanilla` | `wuVanilla.register('app', config)` |
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 🔥 Quick Start
|
|
48
|
+
|
|
49
|
+
### 1. Micro App: Crear `wu.json`
|
|
50
|
+
|
|
51
|
+
Cada microfrontend necesita un archivo `wu.json` en su raíz:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"name": "header",
|
|
56
|
+
"entry": "index.js",
|
|
57
|
+
"wu": {
|
|
58
|
+
"exports": {
|
|
59
|
+
"NavBar": "components/NavBar.js",
|
|
60
|
+
"UserMenu": "components/UserMenu.js"
|
|
61
|
+
},
|
|
62
|
+
"imports": [],
|
|
63
|
+
"routes": ["/", "/home"],
|
|
64
|
+
"permissions": ["events", "store"]
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
| Campo | Descripción |
|
|
70
|
+
|-------|-------------|
|
|
71
|
+
| `name` | Nombre único del microfrontend |
|
|
72
|
+
| `entry` | Archivo JS principal (default: `index.js`) |
|
|
73
|
+
| `wu.exports` | Componentes que expone a otros microfrontends |
|
|
74
|
+
| `wu.imports` | Componentes que importa de otros microfrontends |
|
|
75
|
+
| `wu.routes` | Rutas que maneja este microfrontend |
|
|
76
|
+
| `wu.permissions` | Permisos requeridos (`events`, `store`, `dom`) |
|
|
77
|
+
|
|
78
|
+
### 2. Micro App: Registrar con Adapter (1 línea)
|
|
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';
|
|
27
92
|
|
|
28
|
-
|
|
93
|
+
wuVue.register('sidebar', App);
|
|
94
|
+
```
|
|
29
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:**
|
|
30
109
|
```js
|
|
31
110
|
import { wu } from 'wu-framework';
|
|
32
111
|
|
|
33
|
-
|
|
34
|
-
wu.init({
|
|
112
|
+
await wu.init({
|
|
35
113
|
apps: [
|
|
36
114
|
{ name: 'header', url: 'http://localhost:3001' },
|
|
37
115
|
{ name: 'sidebar', url: 'http://localhost:3002' },
|
|
@@ -39,521 +117,1033 @@ wu.init({
|
|
|
39
117
|
]
|
|
40
118
|
});
|
|
41
119
|
|
|
42
|
-
|
|
43
|
-
wu.mount('
|
|
44
|
-
wu.mount('
|
|
45
|
-
wu.mount('content', '#content-container');
|
|
120
|
+
await wu.mount('header', '#header-container');
|
|
121
|
+
await wu.mount('sidebar', '#sidebar-container');
|
|
122
|
+
await wu.mount('content', '#content-container');
|
|
46
123
|
```
|
|
47
124
|
|
|
48
|
-
|
|
49
|
-
|
|
125
|
+
**Producción:**
|
|
50
126
|
```js
|
|
51
127
|
import { wu } from 'wu-framework';
|
|
52
128
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
ReactDOM.unmountComponentAtNode(container);
|
|
60
|
-
}
|
|
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
|
+
]
|
|
61
135
|
});
|
|
62
136
|
|
|
63
|
-
|
|
64
|
-
wu.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
},
|
|
68
|
-
unmount: (container) => {
|
|
69
|
-
// Vue cleanup
|
|
70
|
-
}
|
|
71
|
-
});
|
|
137
|
+
await wu.mount('header', '#header-container');
|
|
138
|
+
await wu.mount('sidebar', '#sidebar-container');
|
|
139
|
+
await wu.mount('content', '#content-container');
|
|
140
|
+
```
|
|
72
141
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
+
]
|
|
84
154
|
});
|
|
85
155
|
```
|
|
86
156
|
|
|
87
|
-
|
|
157
|
+
### Estructura de Archivos (Microfrontend)
|
|
88
158
|
|
|
89
|
-
|
|
159
|
+
```
|
|
160
|
+
my-header-mfe/
|
|
161
|
+
├── wu.json # ← Manifest requerido
|
|
162
|
+
├── index.js # ← Entry point (registra con adapter)
|
|
163
|
+
├── App.jsx # ← Componente principal
|
|
164
|
+
├── components/
|
|
165
|
+
│ ├── NavBar.js
|
|
166
|
+
│ └── UserMenu.js
|
|
167
|
+
└── package.json
|
|
168
|
+
```
|
|
90
169
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
"shared.Theme"
|
|
103
|
-
],
|
|
104
|
-
"routes": ["/header", "/nav"],
|
|
105
|
-
"permissions": ["localStorage", "eventBus"]
|
|
106
|
-
}
|
|
107
|
-
}
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## ⚛️ React Adapter
|
|
173
|
+
|
|
174
|
+
### Registro Básico
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
import { wuReact } from 'wu-framework/adapters/react';
|
|
178
|
+
import App from './App';
|
|
179
|
+
|
|
180
|
+
wuReact.register('my-app', App);
|
|
108
181
|
```
|
|
109
182
|
|
|
110
|
-
###
|
|
183
|
+
### Con Opciones
|
|
111
184
|
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
185
|
+
```tsx
|
|
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!')
|
|
118
193
|
});
|
|
119
|
-
|
|
120
|
-
// Usar componentes compartidos
|
|
121
|
-
const Button = await wu.use('shared.Button');
|
|
122
|
-
const Theme = await wu.use('shared.Theme');
|
|
123
194
|
```
|
|
124
195
|
|
|
125
|
-
|
|
196
|
+
### Hooks para React
|
|
197
|
+
|
|
198
|
+
```tsx
|
|
199
|
+
import React from 'react';
|
|
200
|
+
import { createUseWuEvents, createUseWuStore } from 'wu-framework/adapters/react';
|
|
201
|
+
|
|
202
|
+
const useWuEvents = createUseWuEvents(React);
|
|
203
|
+
const useWuStore = createUseWuStore(React);
|
|
126
204
|
|
|
127
|
-
|
|
205
|
+
function MyComponent() {
|
|
206
|
+
const { emit, on } = useWuEvents();
|
|
207
|
+
const { state, setState } = useWuStore('user');
|
|
128
208
|
|
|
209
|
+
useEffect(() => {
|
|
210
|
+
const unsub = on('cart:updated', (data) => {
|
|
211
|
+
console.log('Cart updated:', data);
|
|
212
|
+
});
|
|
213
|
+
return unsub;
|
|
214
|
+
}, [on]);
|
|
215
|
+
|
|
216
|
+
return (
|
|
217
|
+
<button onClick={() => emit('user:logout')}>
|
|
218
|
+
Logout {state?.name}
|
|
219
|
+
</button>
|
|
220
|
+
);
|
|
221
|
+
}
|
|
129
222
|
```
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
223
|
+
|
|
224
|
+
### Cargar Microfrontends en React (Shell)
|
|
225
|
+
|
|
226
|
+
```tsx
|
|
227
|
+
import React from 'react';
|
|
228
|
+
import { createWuSlot } from 'wu-framework/adapters/react';
|
|
229
|
+
|
|
230
|
+
const WuSlot = createWuSlot(React);
|
|
231
|
+
|
|
232
|
+
// URLs pueden ser localhost (dev) o CDN (prod)
|
|
233
|
+
const HEADER_URL = process.env.REACT_APP_HEADER_URL || 'http://localhost:3001';
|
|
234
|
+
const CONTENT_URL = process.env.REACT_APP_CONTENT_URL || 'http://localhost:3002';
|
|
235
|
+
|
|
236
|
+
function Shell() {
|
|
237
|
+
return (
|
|
238
|
+
<div>
|
|
239
|
+
<WuSlot
|
|
240
|
+
name="header"
|
|
241
|
+
url={HEADER_URL}
|
|
242
|
+
onLoad={() => console.log('Header loaded!')}
|
|
243
|
+
onError={(err) => console.error(err)}
|
|
244
|
+
/>
|
|
245
|
+
<WuSlot name="content" url={CONTENT_URL} />
|
|
246
|
+
</div>
|
|
247
|
+
);
|
|
248
|
+
}
|
|
141
249
|
```
|
|
142
250
|
|
|
143
|
-
|
|
251
|
+
---
|
|
144
252
|
|
|
145
|
-
|
|
146
|
-
- ✅ **JavaScript scope aislado** - Variables globales separadas
|
|
147
|
-
- ✅ **DOM encapsulado** - No hay interferencia entre apps
|
|
148
|
-
- ✅ **Performance nativo** - Sin overhead de hacks CSS
|
|
149
|
-
- ✅ **Debugging fácil** - Cada app en su propio DevTools tree
|
|
253
|
+
## 💚 Vue Adapter
|
|
150
254
|
|
|
151
|
-
|
|
255
|
+
### Registro Básico
|
|
152
256
|
|
|
153
|
-
|
|
257
|
+
```ts
|
|
258
|
+
import { wuVue } from 'wu-framework/adapters/vue';
|
|
259
|
+
import App from './App.vue';
|
|
154
260
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
import React from 'react';
|
|
158
|
-
import ReactDOM from 'react-dom';
|
|
159
|
-
import { wu } from 'wu-framework';
|
|
261
|
+
wuVue.register('my-app', App);
|
|
262
|
+
```
|
|
160
263
|
|
|
161
|
-
|
|
162
|
-
<header className="app-header">
|
|
163
|
-
<h1>Header App (React)</h1>
|
|
164
|
-
</header>
|
|
165
|
-
);
|
|
264
|
+
### Con Plugins (Pinia, Router, etc.)
|
|
166
265
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
266
|
+
```ts
|
|
267
|
+
import { wuVue } from 'wu-framework/adapters/vue';
|
|
268
|
+
import { createPinia } from 'pinia';
|
|
269
|
+
import router from './router';
|
|
270
|
+
import App from './App.vue';
|
|
271
|
+
|
|
272
|
+
wuVue.register('my-app', App, {
|
|
273
|
+
setup: (app) => {
|
|
274
|
+
app.use(createPinia());
|
|
275
|
+
app.use(router);
|
|
173
276
|
}
|
|
174
277
|
});
|
|
175
278
|
```
|
|
176
279
|
|
|
177
|
-
###
|
|
280
|
+
### Composables para Vue
|
|
178
281
|
|
|
179
282
|
```vue
|
|
180
|
-
|
|
283
|
+
<script setup>
|
|
284
|
+
import { onMounted, onUnmounted } from 'vue';
|
|
285
|
+
import { useWuEvents, useWuStore } from 'wu-framework/adapters/vue';
|
|
286
|
+
|
|
287
|
+
const { emit, on, cleanup } = useWuEvents();
|
|
288
|
+
const { state, setState } = useWuStore('cart');
|
|
289
|
+
|
|
290
|
+
onMounted(() => {
|
|
291
|
+
on('user:login', (data) => console.log('Login:', data));
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
onUnmounted(() => cleanup());
|
|
295
|
+
</script>
|
|
296
|
+
|
|
181
297
|
<template>
|
|
182
|
-
<
|
|
183
|
-
<
|
|
184
|
-
<
|
|
185
|
-
|
|
186
|
-
<li v-for="item in items" :key="item.id">
|
|
187
|
-
{{ item.name }}
|
|
188
|
-
</li>
|
|
189
|
-
</ul>
|
|
190
|
-
</nav>
|
|
191
|
-
</aside>
|
|
298
|
+
<div>
|
|
299
|
+
<p>Items: {{ state?.items?.length }}</p>
|
|
300
|
+
<button @click="emit('cart:clear')">Clear Cart</button>
|
|
301
|
+
</div>
|
|
192
302
|
</template>
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Cargar Microfrontends en Vue (Shell)
|
|
193
306
|
|
|
307
|
+
```vue
|
|
194
308
|
<script setup>
|
|
195
|
-
import {
|
|
309
|
+
import { WuSlot } from 'wu-framework/adapters/vue';
|
|
196
310
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
{ id: 3, name: 'Contact' }
|
|
201
|
-
]);
|
|
311
|
+
// URLs: localhost en dev, CDN en prod
|
|
312
|
+
const headerUrl = import.meta.env.VITE_HEADER_URL || 'http://localhost:3001';
|
|
313
|
+
const sidebarUrl = import.meta.env.VITE_SIDEBAR_URL || 'http://localhost:3002';
|
|
202
314
|
</script>
|
|
315
|
+
|
|
316
|
+
<template>
|
|
317
|
+
<div>
|
|
318
|
+
<WuSlot
|
|
319
|
+
name="header"
|
|
320
|
+
:url="headerUrl"
|
|
321
|
+
@load="onLoad"
|
|
322
|
+
@error="onError"
|
|
323
|
+
/>
|
|
324
|
+
<WuSlot name="sidebar" :url="sidebarUrl" />
|
|
325
|
+
</div>
|
|
326
|
+
</template>
|
|
203
327
|
```
|
|
204
328
|
|
|
205
|
-
|
|
206
|
-
// sidebar-app/src/index.js
|
|
207
|
-
import { createApp } from 'vue';
|
|
208
|
-
import { wu } from 'wu-framework';
|
|
209
|
-
import App from './App.vue';
|
|
329
|
+
### Plugin Global
|
|
210
330
|
|
|
211
|
-
|
|
331
|
+
```ts
|
|
332
|
+
import { createApp } from 'vue';
|
|
333
|
+
import { wuVuePlugin } from 'wu-framework/adapters/vue';
|
|
212
334
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
app = createApp(App);
|
|
216
|
-
app.mount(container);
|
|
217
|
-
},
|
|
218
|
-
unmount: (container) => {
|
|
219
|
-
if (app) {
|
|
220
|
-
app.unmount();
|
|
221
|
-
app = null;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
});
|
|
335
|
+
const app = createApp(App);
|
|
336
|
+
app.use(wuVuePlugin); // Registra <WuSlot> globalmente
|
|
225
337
|
```
|
|
226
338
|
|
|
227
|
-
|
|
339
|
+
---
|
|
228
340
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
341
|
+
## 🅰️ Angular Adapter
|
|
342
|
+
|
|
343
|
+
### Registro con NgModule
|
|
344
|
+
|
|
345
|
+
```ts
|
|
346
|
+
import { wuAngular } from 'wu-framework/adapters/angular';
|
|
233
347
|
import { AppModule } from './app/app.module';
|
|
234
348
|
|
|
235
|
-
|
|
349
|
+
wuAngular.register('my-app', AppModule);
|
|
350
|
+
```
|
|
236
351
|
|
|
237
|
-
|
|
238
|
-
mount: async (container: HTMLElement) => {
|
|
239
|
-
// Crear elemento para Angular
|
|
240
|
-
const appElement = document.createElement('app-root');
|
|
241
|
-
container.appendChild(appElement);
|
|
352
|
+
### Registro con Standalone Components (Angular 14+)
|
|
242
353
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
container.innerHTML = '';
|
|
252
|
-
}
|
|
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
|
|
253
361
|
});
|
|
254
362
|
```
|
|
255
363
|
|
|
256
|
-
###
|
|
364
|
+
### Servicio para Componentes
|
|
257
365
|
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
import {
|
|
261
|
-
import App from './App.svelte';
|
|
366
|
+
```ts
|
|
367
|
+
import { Component, OnInit, OnDestroy } from '@angular/core';
|
|
368
|
+
import { createWuService } from 'wu-framework/adapters/angular';
|
|
262
369
|
|
|
263
|
-
|
|
370
|
+
@Component({
|
|
371
|
+
selector: 'app-root',
|
|
372
|
+
template: `<button (click)="sendEvent()">Send Event</button>`
|
|
373
|
+
})
|
|
374
|
+
export class AppComponent implements OnInit, OnDestroy {
|
|
375
|
+
private wuService = createWuService();
|
|
264
376
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
target: container,
|
|
269
|
-
props: {
|
|
270
|
-
name: 'Footer App'
|
|
271
|
-
}
|
|
377
|
+
ngOnInit() {
|
|
378
|
+
this.wuService.on('user:login', (data) => {
|
|
379
|
+
console.log('User logged in:', data);
|
|
272
380
|
});
|
|
273
|
-
},
|
|
274
|
-
unmount: (container) => {
|
|
275
|
-
if (app) {
|
|
276
|
-
app.$destroy();
|
|
277
|
-
app = null;
|
|
278
|
-
}
|
|
279
381
|
}
|
|
280
|
-
|
|
382
|
+
|
|
383
|
+
sendEvent() {
|
|
384
|
+
this.wuService.emit('app:ready', { timestamp: Date.now() });
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
ngOnDestroy() {
|
|
388
|
+
this.wuService.destroy();
|
|
389
|
+
}
|
|
390
|
+
}
|
|
281
391
|
```
|
|
282
392
|
|
|
283
|
-
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## 🧡 Svelte Adapter
|
|
284
396
|
|
|
285
|
-
###
|
|
397
|
+
### Registro Básico
|
|
286
398
|
|
|
287
399
|
```js
|
|
288
|
-
import {
|
|
400
|
+
import { wuSvelte } from 'wu-framework/adapters/svelte';
|
|
401
|
+
import App from './App.svelte';
|
|
289
402
|
|
|
290
|
-
|
|
291
|
-
|
|
403
|
+
wuSvelte.register('my-app', App);
|
|
404
|
+
```
|
|
292
405
|
|
|
293
|
-
|
|
294
|
-
await wu.mount('appName', '#container');
|
|
406
|
+
### Svelte 5 (con runes)
|
|
295
407
|
|
|
296
|
-
|
|
297
|
-
|
|
408
|
+
```js
|
|
409
|
+
import { wuSvelte } from 'wu-framework/adapters/svelte';
|
|
410
|
+
import App from './App.svelte';
|
|
298
411
|
|
|
299
|
-
|
|
300
|
-
|
|
412
|
+
wuSvelte.registerSvelte5('my-app', App);
|
|
413
|
+
```
|
|
301
414
|
|
|
302
|
-
|
|
303
|
-
const Component = await wu.use('app.Component');
|
|
415
|
+
### Stores Reactivos
|
|
304
416
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
417
|
+
```svelte
|
|
418
|
+
<script>
|
|
419
|
+
import { createWuStore, useWuEvents } from 'wu-framework/adapters/svelte';
|
|
420
|
+
import { onDestroy } from 'svelte';
|
|
308
421
|
|
|
309
|
-
//
|
|
310
|
-
|
|
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>
|
|
311
437
|
```
|
|
312
438
|
|
|
313
|
-
|
|
439
|
+
---
|
|
314
440
|
|
|
315
|
-
|
|
316
|
-
|
|
441
|
+
## ⚡ Preact Adapter
|
|
442
|
+
|
|
443
|
+
### Registro Básico
|
|
317
444
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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);
|
|
323
459
|
```
|
|
324
460
|
|
|
325
|
-
###
|
|
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';
|
|
488
|
+
|
|
489
|
+
wuSolid.register('my-app', App);
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
### Stores Reactivos
|
|
493
|
+
|
|
494
|
+
```jsx
|
|
495
|
+
import { createWuStore, createWuEvent } from 'wu-framework/adapters/solid';
|
|
496
|
+
|
|
497
|
+
function MyComponent() {
|
|
498
|
+
// Store reactivo
|
|
499
|
+
const [user, setUser] = createWuStore('user');
|
|
500
|
+
|
|
501
|
+
// Eventos reactivos
|
|
502
|
+
const lastEvent = createWuEvent('notification:*');
|
|
503
|
+
|
|
504
|
+
return (
|
|
505
|
+
<div>
|
|
506
|
+
<p>Hello, {user()?.name}</p>
|
|
507
|
+
<Show when={lastEvent()}>
|
|
508
|
+
<p>Last notification: {lastEvent()?.data}</p>
|
|
509
|
+
</Show>
|
|
510
|
+
<button onClick={() => setUser('name', 'John')}>
|
|
511
|
+
Set Name
|
|
512
|
+
</button>
|
|
513
|
+
</div>
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
---
|
|
519
|
+
|
|
520
|
+
## 🔥 Lit Adapter (Web Components)
|
|
521
|
+
|
|
522
|
+
### Registro con LitElement
|
|
326
523
|
|
|
327
524
|
```js
|
|
328
|
-
import {
|
|
525
|
+
import { LitElement, html, css } from 'lit';
|
|
526
|
+
import { wuLit } from 'wu-framework/adapters/lit';
|
|
329
527
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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
|
+
}
|
|
333
537
|
|
|
334
|
-
|
|
335
|
-
await dev.reload('appName');
|
|
538
|
+
wuLit.register('my-app', MyApp);
|
|
336
539
|
```
|
|
337
540
|
|
|
338
|
-
|
|
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
|
+
}
|
|
339
560
|
|
|
340
|
-
|
|
561
|
+
render() {
|
|
562
|
+
return html`
|
|
563
|
+
<button @click=${this.handleClick}>Click me</button>
|
|
564
|
+
`;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
wuLit.register('my-app', MyApp);
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### Web Component Vanilla (sin Lit)
|
|
341
572
|
|
|
342
573
|
```js
|
|
343
|
-
import {
|
|
574
|
+
import { wuLit } from 'wu-framework/adapters/lit';
|
|
344
575
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
576
|
+
class MyComponent extends HTMLElement {
|
|
577
|
+
connectedCallback() {
|
|
578
|
+
this.attachShadow({ mode: 'open' });
|
|
579
|
+
this.shadowRoot.innerHTML = '<h1>Hello Web Component!</h1>';
|
|
580
|
+
}
|
|
581
|
+
}
|
|
350
582
|
|
|
351
|
-
|
|
352
|
-
await wu.init(presets.production([
|
|
353
|
-
{ name: 'header', url: 'https://header.myapp.com' },
|
|
354
|
-
{ name: 'sidebar', url: 'https://sidebar.myapp.com' }
|
|
355
|
-
]));
|
|
583
|
+
wuLit.registerWebComponent('my-component', MyComponent);
|
|
356
584
|
```
|
|
357
585
|
|
|
358
|
-
|
|
586
|
+
---
|
|
587
|
+
|
|
588
|
+
## 📦 Vanilla JS Adapter
|
|
589
|
+
|
|
590
|
+
### Registro con Objeto
|
|
359
591
|
|
|
360
592
|
```js
|
|
361
|
-
import {
|
|
593
|
+
import { wuVanilla } from 'wu-framework/adapters/vanilla';
|
|
362
594
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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
|
+
},
|
|
367
610
|
|
|
368
|
-
|
|
369
|
-
|
|
611
|
+
destroy: (container) => {
|
|
612
|
+
container.innerHTML = '';
|
|
613
|
+
}
|
|
370
614
|
});
|
|
371
615
|
```
|
|
372
616
|
|
|
373
|
-
|
|
617
|
+
### Registro con Clase
|
|
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
|
+
}
|
|
374
627
|
|
|
375
|
-
|
|
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
|
+
`;
|
|
376
636
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
637
|
+
this.container.querySelector('#add').onclick = () => {
|
|
638
|
+
const input = this.container.querySelector('#input');
|
|
639
|
+
this.todos.push(input.value);
|
|
640
|
+
this.render();
|
|
641
|
+
};
|
|
642
|
+
}
|
|
381
643
|
|
|
382
|
-
|
|
644
|
+
destroy() {
|
|
645
|
+
this.container.innerHTML = '';
|
|
646
|
+
}
|
|
647
|
+
}
|
|
383
648
|
|
|
384
|
-
-
|
|
385
|
-
|
|
386
|
-
- ✅ **Sandbox más ligero** - Menos overhead
|
|
387
|
-
- ✅ **Component sharing** - Sistema declarativo
|
|
649
|
+
wuVanilla.registerClass('todo-app', TodoApp);
|
|
650
|
+
```
|
|
388
651
|
|
|
389
|
-
###
|
|
652
|
+
### Registro con Template
|
|
390
653
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
- ✅ **Manifest system** - Configuración declarativa
|
|
394
|
-
- ✅ **API moderna** - ES modules nativo
|
|
654
|
+
```js
|
|
655
|
+
import { wuVanilla } from 'wu-framework/adapters/vanilla';
|
|
395
656
|
|
|
396
|
-
|
|
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
|
+
`);
|
|
397
666
|
|
|
398
|
-
|
|
667
|
+
// Template dinámico
|
|
668
|
+
wuVanilla.registerTemplate('greeting',
|
|
669
|
+
(data) => `<h1>Hello, ${data.name}!</h1>`,
|
|
670
|
+
{ data: { name: 'World' } }
|
|
671
|
+
);
|
|
672
|
+
```
|
|
399
673
|
|
|
400
|
-
###
|
|
674
|
+
### Componente Reactivo
|
|
401
675
|
|
|
402
676
|
```js
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
677
|
+
import { wuVanilla } from 'wu-framework/adapters/vanilla';
|
|
678
|
+
|
|
679
|
+
const Counter = wuVanilla.createComponent({
|
|
680
|
+
state: { count: 0 },
|
|
681
|
+
|
|
682
|
+
template: (state) => `
|
|
683
|
+
<div>
|
|
684
|
+
<h1>Count: ${state.count}</h1>
|
|
685
|
+
<button data-action="increment">+</button>
|
|
686
|
+
<button data-action="decrement">-</button>
|
|
687
|
+
</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);
|
|
408
697
|
```
|
|
409
698
|
|
|
410
|
-
|
|
699
|
+
---
|
|
700
|
+
|
|
701
|
+
## 📡 Event Bus
|
|
702
|
+
|
|
703
|
+
Sistema de eventos para comunicación entre microfrontends con soporte para wildcards y replay.
|
|
411
704
|
|
|
412
705
|
```js
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
706
|
+
import { emit, on, once, off, replayEvents } from 'wu-framework';
|
|
707
|
+
|
|
708
|
+
// Emitir evento
|
|
709
|
+
emit('user:login', { userId: 123, name: 'John' });
|
|
710
|
+
|
|
711
|
+
// Suscribirse a evento
|
|
712
|
+
const unsubscribe = on('user:login', (event) => {
|
|
713
|
+
console.log('User logged in:', event.data);
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
// Suscribirse con wildcards
|
|
717
|
+
on('user:*', (event) => {
|
|
718
|
+
console.log('User event:', event.name, event.data);
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
// Suscribirse una sola vez
|
|
722
|
+
once('app:ready', (event) => {
|
|
723
|
+
console.log('App is ready!');
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
// Desuscribirse
|
|
727
|
+
off('user:login', callback);
|
|
728
|
+
unsubscribe(); // También funciona
|
|
729
|
+
|
|
730
|
+
// Replay eventos del historial
|
|
731
|
+
replayEvents('user:*', (event) => {
|
|
732
|
+
console.log('Past event:', event);
|
|
733
|
+
});
|
|
418
734
|
```
|
|
419
735
|
|
|
420
|
-
|
|
736
|
+
---
|
|
421
737
|
|
|
422
|
-
|
|
423
|
-
<!-- Funciona directamente en el browser -->
|
|
424
|
-
<script type="module">
|
|
425
|
-
import { wu } from './node_modules/wu-framework/src/index.js';
|
|
738
|
+
## 🗄️ Store (State Management)
|
|
426
739
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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';
|
|
751
|
+
|
|
752
|
+
// Establecer estado (dot notation)
|
|
753
|
+
setState('user.name', 'John');
|
|
754
|
+
setState('user.preferences.theme', 'dark');
|
|
755
|
+
|
|
756
|
+
// Obtener estado
|
|
757
|
+
const name = getState('user.name'); // 'John'
|
|
758
|
+
const user = getState('user'); // { name: 'John', preferences: { theme: 'dark' } }
|
|
759
|
+
|
|
760
|
+
// Suscribirse a cambios
|
|
761
|
+
const unsubscribe = onStateChange('user.name', (value, path) => {
|
|
762
|
+
console.log(`${path} changed to:`, value);
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
// Suscribirse con wildcards
|
|
766
|
+
onStateChange('user.*', (value, path) => {
|
|
767
|
+
console.log(`User property changed: ${path} =`, value);
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
// Batch updates (más eficiente)
|
|
771
|
+
batchState({
|
|
772
|
+
'user.name': 'Jane',
|
|
773
|
+
'user.email': 'jane@example.com',
|
|
774
|
+
'settings.notifications': true
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
// Métricas de performance
|
|
778
|
+
const metrics = getStoreMetrics();
|
|
431
779
|
```
|
|
432
780
|
|
|
433
|
-
|
|
781
|
+
---
|
|
434
782
|
|
|
435
|
-
|
|
783
|
+
## 🔌 Sistema de Plugins
|
|
436
784
|
|
|
437
|
-
```
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
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' });
|
|
442
808
|
```
|
|
443
809
|
|
|
810
|
+
---
|
|
811
|
+
|
|
812
|
+
## 🪝 Lifecycle Hooks
|
|
813
|
+
|
|
444
814
|
```js
|
|
445
|
-
|
|
446
|
-
|
|
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);
|
|
447
828
|
|
|
448
|
-
|
|
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({
|
|
449
854
|
apps: [
|
|
450
|
-
{ name: 'header', url: '
|
|
451
|
-
{ name: '
|
|
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
|
|
452
859
|
]
|
|
453
860
|
});
|
|
454
|
-
|
|
455
|
-
wu.mount('header', '#header');
|
|
456
|
-
wu.mount('content', '#content');
|
|
457
861
|
```
|
|
458
862
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
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
|
|
469
873
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
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();
|
|
473
889
|
```
|
|
474
890
|
|
|
475
|
-
|
|
891
|
+
---
|
|
476
892
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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
|
+
});
|
|
482
914
|
```
|
|
483
915
|
|
|
916
|
+
---
|
|
917
|
+
|
|
918
|
+
## 🎯 API Simplificada (wu.app)
|
|
919
|
+
|
|
484
920
|
```js
|
|
485
|
-
// index.js
|
|
486
|
-
import React from 'react';
|
|
487
|
-
import ReactDOM from 'react-dom';
|
|
488
921
|
import { wu } from 'wu-framework';
|
|
489
922
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
);
|
|
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();
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
---
|
|
941
|
+
|
|
942
|
+
## 🚀 Deployment
|
|
495
943
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
944
|
+
### Estructura de Producción
|
|
945
|
+
|
|
946
|
+
Cada microfrontend se despliega de forma independiente:
|
|
947
|
+
|
|
948
|
+
```
|
|
949
|
+
https://cdn.mycompany.com/
|
|
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
|
+
```
|
|
964
|
+
|
|
965
|
+
### Configuración por Entorno
|
|
966
|
+
|
|
967
|
+
```js
|
|
968
|
+
// config.js
|
|
969
|
+
const config = {
|
|
970
|
+
development: {
|
|
971
|
+
header: 'http://localhost:3001',
|
|
972
|
+
sidebar: 'http://localhost:3002',
|
|
973
|
+
content: 'http://localhost:3003'
|
|
499
974
|
},
|
|
500
|
-
|
|
501
|
-
|
|
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'
|
|
502
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
|
+
]
|
|
503
996
|
});
|
|
504
997
|
```
|
|
505
998
|
|
|
999
|
+
### CORS Configuration
|
|
1000
|
+
|
|
1001
|
+
Los microfrontends deben permitir CORS desde el shell:
|
|
1002
|
+
|
|
1003
|
+
```nginx
|
|
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";
|
|
1009
|
+
}
|
|
1010
|
+
```
|
|
1011
|
+
|
|
1012
|
+
### Versionado
|
|
1013
|
+
|
|
506
1014
|
```json
|
|
507
|
-
// wu.json
|
|
1015
|
+
// wu.json con versión
|
|
508
1016
|
{
|
|
509
|
-
"name": "header
|
|
510
|
-
"
|
|
511
|
-
"
|
|
512
|
-
"exports": {
|
|
513
|
-
"HeaderComponent": "./components/Header"
|
|
514
|
-
},
|
|
515
|
-
"imports": [],
|
|
516
|
-
"routes": ["/header"]
|
|
517
|
-
}
|
|
1017
|
+
"name": "header",
|
|
1018
|
+
"version": "1.2.0",
|
|
1019
|
+
"entry": "index.js"
|
|
518
1020
|
}
|
|
519
1021
|
```
|
|
520
1022
|
|
|
521
|
-
|
|
1023
|
+
```js
|
|
1024
|
+
// Shell con versiones específicas
|
|
1025
|
+
await wu.init({
|
|
1026
|
+
apps: [
|
|
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
|
+
});
|
|
1031
|
+
```
|
|
522
1032
|
|
|
523
|
-
|
|
524
|
-
# Terminal 1 - Container
|
|
525
|
-
cd my-container-app
|
|
526
|
-
npx serve -p 3000
|
|
1033
|
+
---
|
|
527
1034
|
|
|
528
|
-
|
|
529
|
-
cd header-app
|
|
530
|
-
npx serve -p 3001
|
|
1035
|
+
## 🏗️ Arquitectura
|
|
531
1036
|
|
|
532
|
-
|
|
1037
|
+
```
|
|
1038
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
1039
|
+
│ HOST APPLICATION │
|
|
1040
|
+
├─────────────────────────────────────────────────────────────┤
|
|
1041
|
+
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────┐ │
|
|
1042
|
+
│ │ #shadow-root │ │ #shadow-root │ │ #shadow-root│ │
|
|
1043
|
+
│ │ ┌───────────┐ │ │ ┌───────────┐ │ │ ┌─────────┐ │ │
|
|
1044
|
+
│ │ │ Header │ │ │ │ Sidebar │ │ │ │ Content │ │ │
|
|
1045
|
+
│ │ │ (React) │ │ │ │ (Vue) │ │ │ │(Angular)│ │ │
|
|
1046
|
+
│ │ └───────────┘ │ │ └───────────┘ │ │ └─────────┘ │ │
|
|
1047
|
+
│ │ CSS Isolated │ │ CSS Isolated │ │ CSS Isolated│ │
|
|
1048
|
+
│ │ JS Sandboxed │ │ JS Sandboxed │ │ JS Sandboxed│ │
|
|
1049
|
+
│ └─────────────────┘ └─────────────────┘ └─────────────┘ │
|
|
1050
|
+
├─────────────────────────────────────────────────────────────┤
|
|
1051
|
+
│ WU FRAMEWORK CORE │
|
|
1052
|
+
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────────────┐ │
|
|
1053
|
+
│ │ EventBus │ │ Store │ │ Plugins │ │ Sandbox Pool │ │
|
|
1054
|
+
│ └──────────┘ └──────────┘ └──────────┘ └────────────────┘ │
|
|
1055
|
+
└─────────────────────────────────────────────────────────────┘
|
|
533
1056
|
```
|
|
534
1057
|
|
|
535
|
-
|
|
1058
|
+
---
|
|
536
1059
|
|
|
537
|
-
|
|
1060
|
+
## 📋 API Reference
|
|
538
1061
|
|
|
539
|
-
|
|
540
|
-
2. Crear feature branch (`git checkout -b feature/amazing-feature`)
|
|
541
|
-
3. Commit cambios (`git commit -m 'Add amazing feature'`)
|
|
542
|
-
4. Push branch (`git push origin feature/amazing-feature`)
|
|
543
|
-
5. Abrir Pull Request
|
|
1062
|
+
### Core
|
|
544
1063
|
|
|
545
|
-
|
|
1064
|
+
```js
|
|
1065
|
+
import { wu, init, mount, unmount, destroy, getStats } from 'wu-framework';
|
|
1066
|
+
```
|
|
546
1067
|
|
|
547
|
-
|
|
1068
|
+
### Event Bus
|
|
548
1069
|
|
|
549
|
-
|
|
1070
|
+
```js
|
|
1071
|
+
import { emit, on, once, off, replayEvents, getEventBusStats } from 'wu-framework';
|
|
1072
|
+
```
|
|
1073
|
+
|
|
1074
|
+
### Store
|
|
1075
|
+
|
|
1076
|
+
```js
|
|
1077
|
+
import { getState, setState, onStateChange, batchState, clearState } from 'wu-framework';
|
|
1078
|
+
```
|
|
550
1079
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
1080
|
+
### Plugins & Hooks
|
|
1081
|
+
|
|
1082
|
+
```js
|
|
1083
|
+
import { usePlugin, createPlugin, useHook, removeHook } from 'wu-framework';
|
|
1084
|
+
```
|
|
1085
|
+
|
|
1086
|
+
### Performance & Errors
|
|
1087
|
+
|
|
1088
|
+
```js
|
|
1089
|
+
import { startMeasure, endMeasure, generatePerformanceReport } from 'wu-framework';
|
|
1090
|
+
import { registerErrorHandler, configureErrorBoundary } from 'wu-framework';
|
|
1091
|
+
```
|
|
1092
|
+
|
|
1093
|
+
### Adapters
|
|
1094
|
+
|
|
1095
|
+
```js
|
|
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
|
+
```
|
|
1105
|
+
|
|
1106
|
+
### Utilities
|
|
1107
|
+
|
|
1108
|
+
```js
|
|
1109
|
+
import { presets, dev, events } from 'wu-framework';
|
|
1110
|
+
|
|
1111
|
+
// Presets
|
|
1112
|
+
await wu.init(presets.development([{ name: 'app', port: 3001 }]));
|
|
1113
|
+
await wu.init(presets.production([{ name: 'app', url: 'https://...' }]));
|
|
1114
|
+
|
|
1115
|
+
// Dev tools
|
|
1116
|
+
dev.enableDebug();
|
|
1117
|
+
dev.inspect();
|
|
1118
|
+
await dev.reload('app');
|
|
1119
|
+
|
|
1120
|
+
// Silenciar logs
|
|
1121
|
+
wu.silence();
|
|
1122
|
+
wu.verbose();
|
|
1123
|
+
```
|
|
1124
|
+
|
|
1125
|
+
---
|
|
1126
|
+
|
|
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
|
|
554
1136
|
|
|
555
1137
|
---
|
|
556
1138
|
|
|
557
|
-
|
|
1139
|
+
## 📄 License
|
|
1140
|
+
|
|
1141
|
+
MIT License - Copyright (c) 2025 Wu Framework Team
|
|
1142
|
+
|
|
1143
|
+
---
|
|
558
1144
|
|
|
559
|
-
|
|
1145
|
+
<p align="center">
|
|
1146
|
+
<b>🚀 Wu Framework - Universal Microfrontends Made Simple</b>
|
|
1147
|
+
<br><br>
|
|
1148
|
+
<i>Zero dependencies • 8 Frameworks • Shadow DOM • Proxy Sandbox • Self-Healing</i>
|
|
1149
|
+
</p>
|