wu-framework 1.1.7 → 1.1.8
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 +511 -977
- package/dist/wu-framework.cjs.js +3 -1
- package/dist/wu-framework.cjs.js.map +1 -0
- package/dist/wu-framework.dev.js +7533 -2761
- package/dist/wu-framework.dev.js.map +1 -1
- package/dist/wu-framework.esm.js +3 -0
- package/dist/wu-framework.esm.js.map +1 -0
- package/dist/wu-framework.umd.js +3 -1
- package/dist/wu-framework.umd.js.map +1 -0
- package/integrations/astro/README.md +127 -0
- package/integrations/astro/WuApp.astro +63 -0
- package/integrations/astro/WuShell.astro +39 -0
- package/integrations/astro/index.js +68 -0
- package/integrations/astro/package.json +38 -0
- package/integrations/astro/types.d.ts +53 -0
- package/package.json +89 -71
- package/src/adapters/angular/ai.js +30 -0
- package/src/adapters/angular/index.d.ts +154 -0
- package/src/adapters/angular/index.js +932 -0
- package/src/adapters/angular.d.ts +3 -154
- package/src/adapters/angular.js +3 -813
- package/src/adapters/index.js +35 -24
- package/src/adapters/lit/ai.js +20 -0
- package/src/adapters/lit/index.d.ts +120 -0
- package/src/adapters/lit/index.js +721 -0
- package/src/adapters/lit.d.ts +3 -120
- package/src/adapters/lit.js +3 -726
- package/src/adapters/preact/ai.js +33 -0
- package/src/adapters/preact/index.d.ts +108 -0
- package/src/adapters/preact/index.js +661 -0
- package/src/adapters/preact.d.ts +3 -108
- package/src/adapters/preact.js +3 -665
- package/src/adapters/react/ai.js +135 -0
- package/src/adapters/react/index.d.ts +246 -0
- package/src/adapters/react/index.js +689 -0
- package/src/adapters/react.d.ts +3 -212
- package/src/adapters/react.js +3 -513
- package/src/adapters/shared.js +64 -0
- package/src/adapters/solid/ai.js +32 -0
- package/src/adapters/solid/index.d.ts +101 -0
- package/src/adapters/solid/index.js +586 -0
- package/src/adapters/solid.d.ts +3 -101
- package/src/adapters/solid.js +3 -591
- package/src/adapters/svelte/ai.js +31 -0
- package/src/adapters/svelte/index.d.ts +166 -0
- package/src/adapters/svelte/index.js +798 -0
- package/src/adapters/svelte.d.ts +3 -166
- package/src/adapters/svelte.js +3 -803
- package/src/adapters/vanilla/ai.js +30 -0
- package/src/adapters/vanilla/index.d.ts +179 -0
- package/src/adapters/vanilla/index.js +785 -0
- package/src/adapters/vanilla.d.ts +3 -179
- package/src/adapters/vanilla.js +3 -791
- package/src/adapters/vue/ai.js +52 -0
- package/src/adapters/vue/index.d.ts +299 -0
- package/src/adapters/vue/index.js +608 -0
- package/src/adapters/vue.d.ts +3 -299
- package/src/adapters/vue.js +3 -611
- package/src/ai/wu-ai-actions.js +261 -0
- package/src/ai/wu-ai-browser.js +663 -0
- package/src/ai/wu-ai-context.js +332 -0
- package/src/ai/wu-ai-conversation.js +554 -0
- package/src/ai/wu-ai-permissions.js +381 -0
- package/src/ai/wu-ai-provider.js +605 -0
- package/src/ai/wu-ai-schema.js +225 -0
- package/src/ai/wu-ai-triggers.js +396 -0
- package/src/ai/wu-ai.js +474 -0
- package/src/core/wu-app.js +50 -8
- package/src/core/wu-cache.js +1 -1
- package/src/core/wu-core.js +645 -677
- package/src/core/wu-html-parser.js +121 -211
- package/src/core/wu-iframe-sandbox.js +328 -0
- package/src/core/wu-mcp-bridge.js +647 -0
- package/src/core/wu-overrides.js +510 -0
- package/src/core/wu-prefetch.js +414 -0
- package/src/core/wu-proxy-sandbox.js +398 -75
- package/src/core/wu-sandbox.js +86 -268
- package/src/core/wu-script-executor.js +79 -182
- package/src/core/wu-snapshot-sandbox.js +149 -106
- package/src/core/wu-strategies.js +13 -0
- package/src/core/wu-style-bridge.js +0 -2
- package/src/index.js +139 -665
- package/dist/wu-framework.hex.js +0 -23
- package/dist/wu-framework.min.js +0 -1
- package/dist/wu-framework.obf.js +0 -1
- package/scripts/build-protected.js +0 -366
- package/scripts/build.js +0 -212
- package/scripts/rollup-plugin-hex.js +0 -143
- package/src/core/wu-registry.js +0 -60
- package/src/core/wu-sandbox-pool.js +0 -390
package/README.md
CHANGED
|
@@ -1,893 +1,570 @@
|
|
|
1
1
|
# Wu Framework
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**The easiest way to turn any app into a microfrontend — and the only one that doesn't force you to stop working as a standalone app.**
|
|
4
4
|
|
|
5
|
-
Wu
|
|
6
|
-
|
|
7
|
-
## Por que Wu Framework?
|
|
8
|
-
|
|
9
|
-
| Caracteristica | Module Federation | qiankun | single-spa | **Wu Framework** |
|
|
10
|
-
|---|---|---|---|---|
|
|
11
|
-
| **Setup** | Webpack config complejo | Configuracion manual | Config extensa | **Zero config** |
|
|
12
|
-
| **Framework Support** | React-first | Multi framework | Multi framework | **8 frameworks nativos** |
|
|
13
|
-
| **Bundler Required** | Solo Webpack 5 | Agnostico | Agnostico | **Ninguno requerido** |
|
|
14
|
-
| **CSS Isolation** | Basico | CSS hacks | Manual | **Shadow DOM nativo** |
|
|
15
|
-
| **JS Isolation** | No | Proxy basico | No | **Proxy + Snapshot Sandbox** |
|
|
16
|
-
| **Style Modes** | No | No | No | **3 modos (shared/isolated/fully-isolated)** |
|
|
17
|
-
| **Runtime Config** | No | Limitado | Limitado | **Completamente dinamico** |
|
|
18
|
-
| **Self-Healing** | No | No | No | **Auto-recovery** |
|
|
19
|
-
| **Build Protection** | No | No | No | **Minify + Obfuscate + Base64** |
|
|
20
|
-
| **API Complexity** | Alta | Media | Alta | **1 linea de codigo** |
|
|
21
|
-
|
|
22
|
-
## Instalacion
|
|
5
|
+
Wu speaks the language of your framework. One line to register a React component, a Vue app, an Angular module. Your app works inside a microfrontend shell AND as a standalone app — same code, zero changes.
|
|
23
6
|
|
|
24
7
|
```bash
|
|
25
8
|
npm install wu-framework
|
|
26
9
|
```
|
|
27
10
|
|
|
28
|
-
Sin webpack config, sin plugins, sin configuracion adicional.
|
|
29
|
-
|
|
30
11
|
---
|
|
31
12
|
|
|
32
|
-
##
|
|
13
|
+
## Why Wu?
|
|
14
|
+
|
|
15
|
+
Every microfrontend framework forces you to learn its language. Single-spa makes you export `bootstrap`, `mount`, `unmount`. Module Federation requires webpack config. Qiankun needs manual registration with lifecycle callbacks.
|
|
16
|
+
|
|
17
|
+
Wu doesn't. You write React code. You write Vue code. You write Angular code. Wu adapts to you.
|
|
18
|
+
|
|
19
|
+
```jsx
|
|
20
|
+
// That's it. Your React app is now a microfrontend.
|
|
21
|
+
wuReact.register('header', App);
|
|
22
|
+
```
|
|
33
23
|
|
|
34
|
-
Wu
|
|
24
|
+
If Wu isn't running? Your app mounts directly to `#root` and works by itself. No lock-in. No dependency on a shell being alive. **Microfrontends that don't chain you.**
|
|
35
25
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
|
39
|
-
|
|
40
|
-
|
|
|
41
|
-
|
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
|
45
|
-
|
|
|
46
|
-
|
|
|
47
|
-
|
|
|
26
|
+
### What makes Wu different
|
|
27
|
+
|
|
28
|
+
| | single-spa | qiankun | Module Federation | **Wu** |
|
|
29
|
+
|---|---|---|---|---|
|
|
30
|
+
| **Registration** | Export lifecycle functions | Manual lifecycle callbacks | Webpack config | **One line, framework-native** |
|
|
31
|
+
| **CSS isolation** | Manual | CSS prefixing (fragile) | None | **Shadow DOM + 3 configurable modes** |
|
|
32
|
+
| **JS isolation** | None | Basic Proxy | None | **Proxy sandbox with side-effect cleanup** |
|
|
33
|
+
| **Standalone mode** | No | No | No | **Automatic — works without shell** |
|
|
34
|
+
| **Framework API** | Generic | Generic | Generic | **Native hooks, composables, stores per framework** |
|
|
35
|
+
| **Prefetch** | Manual | Manual preload | None | **Speculation Rules API + fallbacks** |
|
|
36
|
+
| **QA Overrides** | None | None | None | **Cookie-based URL overrides (secure)** |
|
|
37
|
+
| **Config required** | Extensive | Moderate | Webpack plugin | **Zero** |
|
|
48
38
|
|
|
49
39
|
---
|
|
50
40
|
|
|
51
|
-
##
|
|
41
|
+
## WuCommerce — Real-World Example
|
|
52
42
|
|
|
53
|
-
|
|
43
|
+
Wu Framework ships with **WuCommerce**, a Shopify-like merchant dashboard where every section is a real micro-app built with a different framework. The narrative: each module was built by a different team or acquired, unified under wu-framework.
|
|
54
44
|
|
|
55
|
-
|
|
45
|
+
| Micro-app | Framework | What it does |
|
|
46
|
+
|-----------|-----------|-------------|
|
|
47
|
+
| **Topbar** | Preact | Store name, nav tabs, search, notifications, theme toggle |
|
|
48
|
+
| **Dashboard** | Svelte 5 | KPI cards, sparklines, recent orders, revenue chart |
|
|
49
|
+
| **Orders** | React | Order table with filters, search, status badges |
|
|
50
|
+
| **Products** | Vue 3 | Product catalog grid, stock badges, category filter |
|
|
51
|
+
| **Customers** | Solid.js | Customer list, segment badges, click-to-filter-orders |
|
|
52
|
+
| **Analytics** | Lit | Revenue bar chart, traffic donut, top products |
|
|
53
|
+
| **Chat** | Vanilla JS | Floating chat widget with conversations and messages |
|
|
54
|
+
| **Settings** | Angular 21 | Store config form, shipping zones, payment methods |
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"styleMode": "shared",
|
|
63
|
-
"wu": {
|
|
64
|
-
"exports": {
|
|
65
|
-
"NavBar": "components/NavBar.js",
|
|
66
|
-
"UserMenu": "components/UserMenu.js"
|
|
67
|
-
},
|
|
68
|
-
"imports": [],
|
|
69
|
-
"routes": ["/", "/home"],
|
|
70
|
-
"permissions": ["events", "store"]
|
|
71
|
-
}
|
|
72
|
-
}
|
|
56
|
+
All 8 apps communicate through Wu's event bus and shared store. Click a customer → orders filter by that customer. Toggle theme → all 8 apps update. Change store name in settings → topbar updates.
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
npm run dev # starts all 9 servers (1 shell + 8 micro-apps)
|
|
60
|
+
open http://localhost:4321
|
|
73
61
|
```
|
|
74
62
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
| `styleMode` | Modo de estilos: `shared`, `isolated`, `fully-isolated` |
|
|
81
|
-
| `wu.exports` | Componentes que expone a otros microfrontends |
|
|
82
|
-
| `wu.imports` | Componentes que importa de otros microfrontends |
|
|
83
|
-
| `wu.routes` | Rutas que maneja este microfrontend |
|
|
84
|
-
| `wu.permissions` | Permisos requeridos (`events`, `store`, `dom`) |
|
|
63
|
+
Source: `examples/astro-multi-framework/`
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Quick Start
|
|
85
68
|
|
|
86
|
-
###
|
|
69
|
+
### 1. Register your micro app
|
|
87
70
|
|
|
88
71
|
**React:**
|
|
89
|
-
```
|
|
72
|
+
```jsx
|
|
90
73
|
import { wuReact } from 'wu-framework/adapters/react';
|
|
91
74
|
import App from './App';
|
|
92
|
-
|
|
93
|
-
wuReact.register('header', App);
|
|
75
|
+
wuReact.register('orders', App);
|
|
94
76
|
```
|
|
95
77
|
|
|
96
78
|
**Vue:**
|
|
97
|
-
```
|
|
79
|
+
```js
|
|
98
80
|
import { wuVue } from 'wu-framework/adapters/vue';
|
|
99
81
|
import App from './App.vue';
|
|
100
|
-
|
|
101
|
-
wuVue.register('sidebar', App);
|
|
82
|
+
wuVue.register('products', App);
|
|
102
83
|
```
|
|
103
84
|
|
|
104
|
-
**Angular:**
|
|
85
|
+
**Angular (standalone component):**
|
|
105
86
|
```ts
|
|
87
|
+
import 'zone.js';
|
|
88
|
+
import '@angular/compiler';
|
|
89
|
+
import { createApplication } from '@angular/platform-browser';
|
|
90
|
+
import { createComponent, provideZoneChangeDetection } from '@angular/core';
|
|
106
91
|
import { wuAngular } from 'wu-framework/adapters/angular';
|
|
107
|
-
import {
|
|
92
|
+
import { AppComponent } from './app/app.component';
|
|
108
93
|
|
|
109
|
-
wuAngular.
|
|
94
|
+
wuAngular.registerStandalone('settings', AppComponent, {
|
|
95
|
+
createApplication,
|
|
96
|
+
createComponent,
|
|
97
|
+
provideZoneChangeDetection,
|
|
98
|
+
});
|
|
110
99
|
```
|
|
111
100
|
|
|
112
|
-
|
|
101
|
+
**Svelte 5:**
|
|
102
|
+
```js
|
|
103
|
+
import { wuSvelte } from 'wu-framework/adapters/svelte';
|
|
104
|
+
import App from './App.svelte';
|
|
105
|
+
wuSvelte.registerSvelte5('dashboard', App);
|
|
106
|
+
```
|
|
113
107
|
|
|
114
|
-
|
|
108
|
+
**Solid:**
|
|
109
|
+
```jsx
|
|
110
|
+
import { wuSolid } from 'wu-framework/adapters/solid';
|
|
111
|
+
import App from './App';
|
|
112
|
+
wuSolid.register('customers', App);
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Preact:**
|
|
116
|
+
```jsx
|
|
117
|
+
import { wuPreact } from 'wu-framework/adapters/preact';
|
|
118
|
+
import App from './App';
|
|
119
|
+
wuPreact.register('topbar', App);
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Lit:**
|
|
123
|
+
```js
|
|
124
|
+
import { wuLit } from 'wu-framework/adapters/lit';
|
|
125
|
+
import { MyApp } from './my-app.js';
|
|
126
|
+
wuLit.register('analytics', MyApp);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Vanilla JS:**
|
|
130
|
+
```js
|
|
131
|
+
import wuVanilla from 'wu-framework/adapters/vanilla';
|
|
132
|
+
// use wuVanilla.useWuEvents() and wuVanilla.useWuStore() inside mount/unmount
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### 2. Mount from the shell
|
|
115
136
|
|
|
116
|
-
**Desarrollo:**
|
|
117
137
|
```js
|
|
118
138
|
import { wu } from 'wu-framework';
|
|
119
139
|
|
|
120
140
|
await wu.init({
|
|
121
141
|
apps: [
|
|
122
|
-
{ name: 'header',
|
|
142
|
+
{ name: 'header', url: 'http://localhost:3001' },
|
|
123
143
|
{ name: 'sidebar', url: 'http://localhost:3002' },
|
|
124
144
|
{ name: 'content', url: 'http://localhost:3003' }
|
|
125
145
|
]
|
|
126
146
|
});
|
|
127
147
|
|
|
128
|
-
await wu.mount('header',
|
|
148
|
+
await wu.mount('header', '#header-container');
|
|
129
149
|
await wu.mount('sidebar', '#sidebar-container');
|
|
130
150
|
await wu.mount('content', '#content-container');
|
|
131
151
|
```
|
|
132
152
|
|
|
133
|
-
|
|
134
|
-
```js
|
|
135
|
-
import { wu } from 'wu-framework';
|
|
153
|
+
Each app runs inside its own Shadow DOM with a JS sandbox that tracks and cleans up all side effects on unmount.
|
|
136
154
|
|
|
137
|
-
|
|
138
|
-
apps: [
|
|
139
|
-
{ name: 'header', url: 'https://cdn.mycompany.com/mfe/header' },
|
|
140
|
-
{ name: 'sidebar', url: 'https://cdn.mycompany.com/mfe/sidebar' },
|
|
141
|
-
{ name: 'content', url: 'https://cdn.mycompany.com/mfe/content' }
|
|
142
|
-
]
|
|
143
|
-
});
|
|
144
|
-
```
|
|
155
|
+
---
|
|
145
156
|
|
|
146
|
-
|
|
157
|
+
## 8 Framework Adapters
|
|
147
158
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
159
|
+
Wu doesn't give you a generic API and tell you to figure it out. Each adapter speaks the native language of its framework.
|
|
160
|
+
|
|
161
|
+
| Framework | Adapter | Register | Native APIs |
|
|
162
|
+
|-----------|---------|----------|-------------|
|
|
163
|
+
| React | `wuReact` | `.register(name, Component)` | `createUseWuEvents(React)`, `createUseWuStore(React)` |
|
|
164
|
+
| Vue 3 | `wuVue` | `.register(name, Component)` | `useWuEvents()`, `useWuStore()`, `WuSlot`, plugin |
|
|
165
|
+
| Angular 14+ | `wuAngular` | `.registerStandalone(name, Component, opts)` | `createWuService()` |
|
|
166
|
+
| Angular (NgModule) | `wuAngular` | `.register(name, Module)` | Same |
|
|
167
|
+
| Angular Elements | `wuAngular` | `.registerElement(name, Component)` | Same |
|
|
168
|
+
| Svelte 3/4 | `wuSvelte` | `.register(name, Component)` | `createWuStore()`, `useWuEvents()` |
|
|
169
|
+
| Svelte 5 | `wuSvelte` | `.registerSvelte5(name, Component)` | Same + `createWuEventStore()` |
|
|
170
|
+
| Preact | `wuPreact` | `.register(name, Component)` | `createUseWuEvents(hooks)`, `createUseWuStore(hooks)` |
|
|
171
|
+
| Preact (compat) | `wuPreact` | `.registerCompat(name, Component)` | Same |
|
|
172
|
+
| Solid.js | `wuSolid` | `.register(name, Component)` | `useWuEvents()`, `createWuStore()`, `createWuEvent()` |
|
|
173
|
+
| Lit | `wuLit` | `.register(name, Element)` | `WuMixin(LitElement)`, `wuProperty()` decorator |
|
|
174
|
+
| Web Component | `wuLit` | `.registerWebComponent(name, Element)` | Same |
|
|
175
|
+
| Vanilla JS | `wuVanilla` | `.register(name, config)` | `useWuEvents()`, `useWuStore()`, `createComponent()` |
|
|
176
|
+
| Vanilla (class) | `wuVanilla` | `.registerClass(name, Class)` | Same |
|
|
177
|
+
| Vanilla (template) | `wuVanilla` | `.registerTemplate(name, html)` | Same |
|
|
178
|
+
|
|
179
|
+
> **Note:** React and Preact hooks are *factory functions* — pass `React` or `{ useCallback, useEffect, useRef }` to get the actual hooks. Vue composables work directly with no arguments. Angular's `registerStandalone` is recommended over `register` because it works inside Shadow DOM.
|
|
180
|
+
|
|
181
|
+
Every adapter detects if Wu is present. If not, the app mounts standalone. Your microfrontend never stops working.
|
|
158
182
|
|
|
159
183
|
---
|
|
160
184
|
|
|
161
|
-
##
|
|
185
|
+
## 3 CSS Isolation Modes
|
|
162
186
|
|
|
163
|
-
|
|
187
|
+
This is something no other microfrontend framework offers: **per-app configurable style isolation**.
|
|
164
188
|
|
|
165
|
-
|
|
|
166
|
-
|
|
167
|
-
| `shared` |
|
|
168
|
-
| `isolated` |
|
|
169
|
-
| `fully-isolated` |
|
|
189
|
+
| Mode | What happens | When to use |
|
|
190
|
+
|------|-------------|-------------|
|
|
191
|
+
| `shared` | Host document styles are injected into Shadow DOM | Apps sharing a design system (Tailwind, Element Plus) |
|
|
192
|
+
| `isolated` | Pure Shadow DOM — no external styles enter | Fully independent apps with their own CSS |
|
|
193
|
+
| `fully-isolated` | Only the app's own styles are injected | Apps that need their CSS but not global CSS |
|
|
170
194
|
|
|
171
|
-
|
|
195
|
+
Set it in `wu.json`:
|
|
172
196
|
|
|
173
197
|
```json
|
|
174
198
|
{
|
|
175
199
|
"name": "my-app",
|
|
200
|
+
"entry": "index.js",
|
|
176
201
|
"styleMode": "isolated"
|
|
177
202
|
}
|
|
178
203
|
```
|
|
179
204
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
- Inyecta estilos de librerias (Element Plus, Tailwind, Vue Flow, etc.)
|
|
183
|
-
- Inyecta estilos globales del documento
|
|
184
|
-
- Observer HMR para cambios en desarrollo
|
|
185
|
-
- **Ideal para:** Apps que comparten un design system comun
|
|
186
|
-
|
|
187
|
-
### isolated
|
|
188
|
-
|
|
189
|
-
- NO inyecta ningun estilo externo
|
|
190
|
-
- Usa encapsulamiento nativo del Shadow DOM
|
|
191
|
-
- La app debe incluir sus propios estilos (CSS-in-JS, scoped, imports)
|
|
192
|
-
- **Ideal para:** Apps con estilos completamente independientes
|
|
193
|
-
|
|
194
|
-
### fully-isolated
|
|
195
|
-
|
|
196
|
-
- Detecta y inyecta SOLO estilos propios de la app especifica
|
|
197
|
-
- Patron de deteccion: `packages/appName/src/`
|
|
198
|
-
- MutationObserver persistente para HMR de Vite
|
|
199
|
-
- **Ideal para:** Apps que necesitan sus estilos pero no los globales
|
|
205
|
+
In a real team, your header might need `shared` (same design system as the shell), your analytics dashboard might need `isolated` (completely independent), and your sidebar might need `fully-isolated` (own styles only). Wu handles all three in the same page.
|
|
200
206
|
|
|
201
207
|
---
|
|
202
208
|
|
|
203
|
-
##
|
|
209
|
+
## JS Sandbox — Three Modes, Real Isolation
|
|
204
210
|
|
|
205
|
-
|
|
211
|
+
Wu offers three sandbox modes. Pick the one that fits your workflow:
|
|
206
212
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
import
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
```
|
|
213
|
+
| Mode | How it works | Tree shaking | Source maps | HMR | JS isolation |
|
|
214
|
+
|------|-------------|:---:|:---:|:---:|:---:|
|
|
215
|
+
| `module` (default) | `import()` + Proxy side-effect tracking | ✅ | ✅ | ✅ | Side effects only |
|
|
216
|
+
| `strict` | Hidden iframe + real `import()` | ✅ | ✅ | ✅ | **Full** (separate window) |
|
|
217
|
+
| `eval` | Fetch HTML → parse → `with(proxy){}` | ❌ | ❌ | ❌ | **Full** (proxy traps) |
|
|
213
218
|
|
|
214
|
-
|
|
219
|
+
```js
|
|
220
|
+
// Global setting
|
|
221
|
+
await wu.init({
|
|
222
|
+
sandbox: 'strict', // or 'module' (default) or 'eval'
|
|
223
|
+
apps: [...]
|
|
224
|
+
});
|
|
215
225
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
onUnmount: (container) => console.log('Unmounted!')
|
|
226
|
+
// Per-app override — mix modes in the same page
|
|
227
|
+
await wu.init({
|
|
228
|
+
apps: [
|
|
229
|
+
{ name: 'header', url: '...', sandbox: 'module' }, // Fast dev, no isolation
|
|
230
|
+
{ name: 'analytics', url: '...', sandbox: 'strict' }, // Full isolation + modern DX
|
|
231
|
+
{ name: 'legacy', url: '...', sandbox: 'eval' }, // UMD bundle, max isolation
|
|
232
|
+
]
|
|
224
233
|
});
|
|
225
234
|
```
|
|
226
235
|
|
|
227
|
-
|
|
236
|
+
**Module mode** loads your app via `import()`. Side effects (timers, listeners, storage) are tracked through the Proxy and cleaned on unmount. App code runs in the global scope — the proxy is a cleanup tracker. Works with ES modules, Vite HMR, and all modern tooling.
|
|
228
237
|
|
|
229
|
-
|
|
230
|
-
import React from 'react';
|
|
231
|
-
import { createUseWuEvents, createUseWuStore } from 'wu-framework/adapters/react';
|
|
238
|
+
**Strict mode** creates a hidden iframe as an isolated execution context, then runs `import()` inside it. Your app gets a **separate `window`** — globals, timers, and listeners are isolated by the browser itself. Document operations (`querySelector`, `createElement`, `body`) are proxied to the Shadow DOM container. Destroying the iframe nukes all side effects at once. If `import()` fails (CORS), it falls back to eval mode automatically.
|
|
232
239
|
|
|
233
|
-
|
|
234
|
-
const useWuStore = createUseWuStore(React);
|
|
240
|
+
**Eval mode** fetches your app's HTML, parses out `<script>` and `<style>` tags, and executes all scripts inside the Proxy sandbox using `with(proxy){}`. Every unqualified identifier (`setTimeout`, `document`, `fetch`) resolves through the proxy's traps. This is qiankun-level isolation. Requires bundled apps (UMD/IIFE), not ES modules.
|
|
235
241
|
|
|
236
|
-
|
|
237
|
-
const { emit, on } = useWuEvents();
|
|
238
|
-
const { state, setState } = useWuStore('user');
|
|
239
|
-
|
|
240
|
-
useEffect(() => {
|
|
241
|
-
const unsub = on('cart:updated', (data) => {
|
|
242
|
-
console.log('Cart updated:', data);
|
|
243
|
-
});
|
|
244
|
-
return unsub;
|
|
245
|
-
}, [on]);
|
|
242
|
+
### How strict mode works
|
|
246
243
|
|
|
247
|
-
return (
|
|
248
|
-
<button onClick={() => emit('user:logout')}>
|
|
249
|
-
Logout {state?.name}
|
|
250
|
-
</button>
|
|
251
|
-
);
|
|
252
|
-
}
|
|
253
244
|
```
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
return (
|
|
267
|
-
<div>
|
|
268
|
-
<WuSlot
|
|
269
|
-
name="header"
|
|
270
|
-
url={HEADER_URL}
|
|
271
|
-
onLoad={() => console.log('Header loaded!')}
|
|
272
|
-
onError={(err) => console.error(err)}
|
|
273
|
-
/>
|
|
274
|
-
</div>
|
|
275
|
-
);
|
|
276
|
-
}
|
|
245
|
+
┌── Main Window ──────────────────────────────────┐
|
|
246
|
+
│ ┌── Shadow DOM ──────────────────────────────┐ │
|
|
247
|
+
│ │ App renders here (CSS isolated) │ │
|
|
248
|
+
│ │ document.querySelector → scoped here │ │
|
|
249
|
+
│ └────────────────────────────────────────────┘ │
|
|
250
|
+
│ ┌── Hidden iframe ──────────────────────────┐ │
|
|
251
|
+
│ │ import() runs here (REAL ES modules) │ │
|
|
252
|
+
│ │ window = separate context (ISOLATED) │ │
|
|
253
|
+
│ │ document.createElement → main document │ │
|
|
254
|
+
│ │ Destroy iframe = kill everything │ │
|
|
255
|
+
│ └────────────────────────────────────────────┘ │
|
|
256
|
+
└──────────────────────────────────────────────────┘
|
|
277
257
|
```
|
|
278
258
|
|
|
279
|
-
|
|
259
|
+
Strict mode preserves your entire development workflow:
|
|
260
|
+
- **Tree shaking** — real `import()`, bundler optimizations work as normal
|
|
261
|
+
- **Source maps** — browser loads real modules, maps resolve correctly
|
|
262
|
+
- **Vite HMR** — HMR client runs inside iframe, WebSocket connects to dev server
|
|
263
|
+
- **Full isolation** — separate `window` context, no global leaks between apps
|
|
280
264
|
|
|
281
|
-
|
|
265
|
+
**What gets tracked and auto-cleaned (all modes):**
|
|
266
|
+
- `setTimeout` / `setInterval` / `requestAnimationFrame` — all IDs tracked, all cleared
|
|
267
|
+
- `addEventListener` / `removeEventListener` — all listeners tracked, all removed
|
|
268
|
+
- `document.querySelector` — scoped to the app's Shadow DOM
|
|
269
|
+
- `localStorage` / `sessionStorage` — keys prefixed with `wu_{appName}_`
|
|
282
270
|
|
|
283
|
-
|
|
271
|
+
**277 tests verify this works.** Unit tests for every isolation feature, integration tests proving two sandboxes don't contaminate each other.
|
|
284
272
|
|
|
285
|
-
|
|
286
|
-
import { wuVue } from 'wu-framework/adapters/vue';
|
|
287
|
-
import App from './App.vue';
|
|
288
|
-
|
|
289
|
-
wuVue.register('my-app', App);
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
### Con Plugins (Pinia, Router, etc.)
|
|
273
|
+
---
|
|
293
274
|
|
|
294
|
-
|
|
295
|
-
import { wuVue } from 'wu-framework/adapters/vue';
|
|
296
|
-
import { createPinia } from 'pinia';
|
|
297
|
-
import router from './router';
|
|
298
|
-
import App from './App.vue';
|
|
275
|
+
## Cross-App Communication
|
|
299
276
|
|
|
300
|
-
|
|
301
|
-
setup: (app) => {
|
|
302
|
-
app.use(createPinia());
|
|
303
|
-
app.use(router);
|
|
304
|
-
}
|
|
305
|
-
});
|
|
306
|
-
```
|
|
307
|
-
|
|
308
|
-
### Composables para Vue
|
|
277
|
+
### Event Bus
|
|
309
278
|
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
import { onMounted, onUnmounted } from 'vue';
|
|
313
|
-
import { useWuEvents, useWuStore } from 'wu-framework/adapters/vue';
|
|
279
|
+
```js
|
|
280
|
+
import { emit, on, once, off } from 'wu-framework';
|
|
314
281
|
|
|
315
|
-
|
|
316
|
-
const { state, setState } = useWuStore('cart');
|
|
282
|
+
emit('user:login', { userId: 123 });
|
|
317
283
|
|
|
318
|
-
|
|
319
|
-
|
|
284
|
+
const unsub = on('user:*', (event) => {
|
|
285
|
+
console.log(event.name, event.data);
|
|
320
286
|
});
|
|
321
287
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
<template>
|
|
326
|
-
<div>
|
|
327
|
-
<p>Items: {{ state?.items?.length }}</p>
|
|
328
|
-
<button @click="emit('cart:clear')">Clear Cart</button>
|
|
329
|
-
</div>
|
|
330
|
-
</template>
|
|
288
|
+
once('app:ready', (e) => console.log('Ready'));
|
|
289
|
+
unsub();
|
|
331
290
|
```
|
|
332
291
|
|
|
333
|
-
|
|
292
|
+
Features: wildcard patterns (`user:*`, `*.error`, `app.*.ready`), event replay for late-joining apps, strict mode (only registered apps can emit), history buffer.
|
|
334
293
|
|
|
335
|
-
|
|
336
|
-
<script setup>
|
|
337
|
-
import { WuSlot } from 'wu-framework/adapters/vue';
|
|
338
|
-
|
|
339
|
-
const headerUrl = import.meta.env.VITE_HEADER_URL || 'http://localhost:3001';
|
|
340
|
-
</script>
|
|
341
|
-
|
|
342
|
-
<template>
|
|
343
|
-
<div>
|
|
344
|
-
<WuSlot
|
|
345
|
-
name="header"
|
|
346
|
-
:url="headerUrl"
|
|
347
|
-
@load="onLoad"
|
|
348
|
-
@error="onError"
|
|
349
|
-
/>
|
|
350
|
-
</div>
|
|
351
|
-
</template>
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
### Plugin Global
|
|
355
|
-
|
|
356
|
-
```ts
|
|
357
|
-
import { createApp } from 'vue';
|
|
358
|
-
import { wuVuePlugin } from 'wu-framework/adapters/vue';
|
|
359
|
-
|
|
360
|
-
const app = createApp(App);
|
|
361
|
-
app.use(wuVuePlugin); // Registra <WuSlot> globalmente
|
|
362
|
-
```
|
|
294
|
+
### Store
|
|
363
295
|
|
|
364
|
-
|
|
296
|
+
```js
|
|
297
|
+
import { getState, setState, onStateChange } from 'wu-framework';
|
|
365
298
|
|
|
366
|
-
|
|
299
|
+
setState('user.name', 'John');
|
|
300
|
+
setState('user.preferences.theme', 'dark');
|
|
367
301
|
|
|
368
|
-
|
|
302
|
+
getState('user.name'); // 'John'
|
|
369
303
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
304
|
+
const unsub = onStateChange('user.*', (value, path) => {
|
|
305
|
+
console.log(`${path} = ${value}`);
|
|
306
|
+
});
|
|
373
307
|
|
|
374
|
-
|
|
308
|
+
// Batch updates
|
|
309
|
+
wu.store.batch({ 'cart.count': 5, 'cart.total': 99.99 });
|
|
375
310
|
```
|
|
376
311
|
|
|
377
|
-
|
|
312
|
+
Ring buffer event history. Wildcard subscriptions. Dot-notation paths with auto-creation of nested objects.
|
|
378
313
|
|
|
379
|
-
|
|
380
|
-
import { wuAngular } from 'wu-framework/adapters/angular';
|
|
381
|
-
import { AppComponent } from './app/app.component';
|
|
382
|
-
import { appConfig } from './app/app.config';
|
|
314
|
+
---
|
|
383
315
|
|
|
384
|
-
|
|
385
|
-
appConfig
|
|
386
|
-
});
|
|
387
|
-
```
|
|
316
|
+
## Framework-Native Integration Examples
|
|
388
317
|
|
|
389
|
-
|
|
318
|
+
> All examples below are from **WuCommerce**, a real merchant dashboard built with Wu Framework.
|
|
319
|
+
> 8 micro-apps, 8 different frameworks, running together in one shell.
|
|
320
|
+
> See `examples/astro-multi-framework/` for the full source.
|
|
390
321
|
|
|
391
|
-
|
|
322
|
+
### React — Orders (mf-eventlab)
|
|
392
323
|
|
|
393
|
-
```
|
|
394
|
-
|
|
395
|
-
import {
|
|
396
|
-
import
|
|
397
|
-
|
|
398
|
-
wuAngular.registerElement('my-app', AppComponent, {
|
|
399
|
-
elementTag: 'my-app-element', // Tag del custom element
|
|
400
|
-
appConfig, // Configuracion de Angular
|
|
401
|
-
standaloneContainer: '#root', // Container para modo standalone
|
|
402
|
-
onMount: (container, element) => {
|
|
403
|
-
console.log('Angular Element mounted');
|
|
404
|
-
},
|
|
405
|
-
onUnmount: (container, element) => {
|
|
406
|
-
console.log('Angular Element unmounted');
|
|
407
|
-
}
|
|
408
|
-
});
|
|
409
|
-
```
|
|
410
|
-
|
|
411
|
-
El adapter se encarga de:
|
|
412
|
-
- Crear la aplicacion Angular
|
|
413
|
-
- Registrar el Custom Element con `customElements.define()`
|
|
414
|
-
- Gestionar el ciclo de vida mount/unmount
|
|
415
|
-
- Soportar modo standalone si Wu Framework no esta disponible
|
|
324
|
+
```jsx
|
|
325
|
+
// main.jsx
|
|
326
|
+
import { wuReact } from 'wu-framework/adapters/react';
|
|
327
|
+
import App from './App';
|
|
328
|
+
wuReact.register('orders', App);
|
|
416
329
|
|
|
417
|
-
|
|
330
|
+
// App.jsx — hooks are factories, pass React to get the hook
|
|
331
|
+
import React, { useState, useEffect, useMemo } from 'react';
|
|
332
|
+
import { createUseWuEvents, createUseWuStore } from 'wu-framework/adapters/react';
|
|
418
333
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
import { createWuService } from 'wu-framework/adapters/angular';
|
|
334
|
+
const useWuEvents = createUseWuEvents(React);
|
|
335
|
+
const useWuStore = createUseWuStore(React);
|
|
422
336
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
})
|
|
427
|
-
export class AppComponent implements OnInit, OnDestroy {
|
|
428
|
-
private wuService = createWuService();
|
|
337
|
+
function Orders() {
|
|
338
|
+
const { emit, on } = useWuEvents();
|
|
339
|
+
const [filter, setFilter] = useState('all');
|
|
429
340
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
341
|
+
useEffect(() => {
|
|
342
|
+
const unsub = on('customer:selected', (e) => {
|
|
343
|
+
setFilter(e.data?.customerName || 'all');
|
|
433
344
|
});
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
sendEvent() {
|
|
437
|
-
this.wuService.emit('app:ready', { timestamp: Date.now() });
|
|
438
|
-
}
|
|
345
|
+
return unsub;
|
|
346
|
+
}, []);
|
|
439
347
|
|
|
440
|
-
|
|
441
|
-
this.wuService.destroy();
|
|
442
|
-
}
|
|
348
|
+
return <button onClick={() => emit('order:new', { id: 1001, total: 49.99 })}>New Order</button>;
|
|
443
349
|
}
|
|
444
350
|
```
|
|
445
351
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
## Svelte Adapter
|
|
352
|
+
### Vue 3 — Products (mf-statebridge)
|
|
449
353
|
|
|
450
|
-
|
|
354
|
+
```vue
|
|
355
|
+
<!-- main.js -->
|
|
356
|
+
<!-- import { wuVue } from 'wu-framework/adapters/vue'; -->
|
|
357
|
+
<!-- import App from './App.vue'; -->
|
|
358
|
+
<!-- wuVue.register('products', App); -->
|
|
451
359
|
|
|
452
|
-
|
|
453
|
-
import {
|
|
454
|
-
import
|
|
360
|
+
<script setup>
|
|
361
|
+
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
|
362
|
+
import { wuVue } from 'wu-framework/adapters/vue';
|
|
455
363
|
|
|
456
|
-
|
|
457
|
-
|
|
364
|
+
const { useWuEvents, useWuStore } = wuVue;
|
|
365
|
+
const events = useWuEvents();
|
|
458
366
|
|
|
459
|
-
|
|
367
|
+
onMounted(() => {
|
|
368
|
+
events.on('search:global', (e) => {
|
|
369
|
+
search.value = e.data?.query || '';
|
|
370
|
+
});
|
|
371
|
+
});
|
|
460
372
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
373
|
+
function handleStockAlert(product) {
|
|
374
|
+
events.emit('product:stockAlert', { productId: product.id, stock: product.stock });
|
|
375
|
+
}
|
|
464
376
|
|
|
465
|
-
|
|
377
|
+
onUnmounted(() => events.cleanup());
|
|
378
|
+
</script>
|
|
466
379
|
```
|
|
467
380
|
|
|
468
|
-
###
|
|
381
|
+
### Svelte 5 — Dashboard (mf-hero)
|
|
469
382
|
|
|
470
383
|
```svelte
|
|
384
|
+
<!-- main.js: wuSvelte.registerSvelte5('dashboard', App) -->
|
|
471
385
|
<script>
|
|
472
|
-
import {
|
|
473
|
-
import { onDestroy } from 'svelte';
|
|
386
|
+
import { wuSvelte } from 'wu-framework/adapters/svelte';
|
|
474
387
|
|
|
475
|
-
const
|
|
388
|
+
const { createWuStore, createWuEventStore, useWuEvents } = wuSvelte;
|
|
476
389
|
const { emit, on, cleanup } = useWuEvents();
|
|
477
390
|
|
|
478
|
-
|
|
479
|
-
console.log('Cart updated:', data);
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
onDestroy(cleanup);
|
|
483
|
-
</script>
|
|
391
|
+
let stats = $state({ revenue: 12847, orders: 156, visitors: 2341, conversion: 4.2 });
|
|
484
392
|
|
|
485
|
-
|
|
486
|
-
<button on:click={() => emit('user:logout')}>Logout</button>
|
|
487
|
-
```
|
|
393
|
+
on('theme:toggle', (e) => console.log('Theme:', e.data?.theme));
|
|
488
394
|
|
|
489
|
-
|
|
395
|
+
function handleNewOrder() {
|
|
396
|
+
emit('order:new', { orderId: `ORD-${Date.now()}`, total: 49.99, status: 'pending' });
|
|
397
|
+
}
|
|
490
398
|
|
|
491
|
-
|
|
399
|
+
// cleanup is called automatically on component destroy
|
|
400
|
+
</script>
|
|
401
|
+
```
|
|
492
402
|
|
|
493
|
-
###
|
|
403
|
+
### Solid.js — Customers (mf-features)
|
|
494
404
|
|
|
495
405
|
```jsx
|
|
496
|
-
|
|
497
|
-
import
|
|
406
|
+
// main.jsx: wuSolid.register('customers', App)
|
|
407
|
+
import { createSignal, onMount } from 'solid-js';
|
|
408
|
+
import { wuSolid } from 'wu-framework/adapters/solid';
|
|
498
409
|
|
|
499
|
-
|
|
500
|
-
|
|
410
|
+
export default function Customers() {
|
|
411
|
+
const [search, setSearch] = createSignal('');
|
|
412
|
+
const { emit, on } = wuSolid.useWuEvents(); // auto-cleanup via onCleanup
|
|
501
413
|
|
|
502
|
-
|
|
414
|
+
onMount(() => {
|
|
415
|
+
on('search:global', (e) => setSearch(e.data?.query || ''));
|
|
416
|
+
});
|
|
503
417
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
418
|
+
const handleRowClick = (customer) => {
|
|
419
|
+
emit('customer:selected', { customerId: customer.id, customerName: customer.name });
|
|
420
|
+
emit('nav:section', { section: 'orders' });
|
|
421
|
+
};
|
|
507
422
|
|
|
508
|
-
|
|
423
|
+
return <div>...</div>;
|
|
424
|
+
}
|
|
509
425
|
```
|
|
510
426
|
|
|
511
|
-
###
|
|
427
|
+
### Preact — Topbar (mf-header)
|
|
512
428
|
|
|
513
429
|
```jsx
|
|
514
|
-
|
|
515
|
-
import { useState,
|
|
516
|
-
import {
|
|
430
|
+
// main.jsx: wuPreact.register('topbar', App)
|
|
431
|
+
import { useState, useCallback, useEffect, useRef } from 'preact/hooks';
|
|
432
|
+
import { wuPreact } from 'wu-framework/adapters/preact';
|
|
517
433
|
|
|
518
|
-
|
|
519
|
-
const
|
|
434
|
+
// Factory pattern — pass hooks as argument
|
|
435
|
+
const useWuEvents = wuPreact.createUseWuEvents({ useCallback, useEffect, useRef });
|
|
436
|
+
const useWuStore = wuPreact.createUseWuStore({ useState, useCallback, useEffect });
|
|
520
437
|
|
|
521
|
-
function
|
|
438
|
+
function Topbar() {
|
|
522
439
|
const { emit, on } = useWuEvents();
|
|
523
|
-
const
|
|
440
|
+
const storeData = useWuStore('store');
|
|
441
|
+
|
|
442
|
+
const handleSearch = (query) => emit('search:global', { query });
|
|
443
|
+
const toggleTheme = () => emit('theme:toggle', { theme: isDark ? 'light' : 'dark' });
|
|
524
444
|
|
|
525
|
-
return <
|
|
445
|
+
return <header>...</header>;
|
|
526
446
|
}
|
|
527
447
|
```
|
|
528
448
|
|
|
529
|
-
|
|
449
|
+
### Angular 21 — Settings (mf-pluginperf)
|
|
530
450
|
|
|
531
|
-
|
|
451
|
+
```ts
|
|
452
|
+
// main.ts — full setup (JIT mode, no AnalogJS needed)
|
|
453
|
+
import 'zone.js';
|
|
454
|
+
import '@angular/compiler';
|
|
455
|
+
import { createApplication } from '@angular/platform-browser';
|
|
456
|
+
import { createComponent, provideZoneChangeDetection } from '@angular/core';
|
|
457
|
+
import { wuAngular } from 'wu-framework/adapters/angular';
|
|
458
|
+
import { SettingsComponent } from './app/settings.component';
|
|
532
459
|
|
|
533
|
-
|
|
460
|
+
wuAngular.registerStandalone('settings', SettingsComponent, {
|
|
461
|
+
standaloneContainer: '#app',
|
|
462
|
+
createApplication, // pass Angular APIs to avoid bundler issues
|
|
463
|
+
createComponent,
|
|
464
|
+
provideZoneChangeDetection,
|
|
465
|
+
});
|
|
534
466
|
|
|
535
|
-
|
|
536
|
-
import {
|
|
537
|
-
import
|
|
467
|
+
// settings.component.ts — use createWuService for events/store
|
|
468
|
+
import { Component, OnInit, OnDestroy } from '@angular/core';
|
|
469
|
+
import { createWuService } from 'wu-framework/adapters/angular';
|
|
538
470
|
|
|
539
|
-
|
|
540
|
-
|
|
471
|
+
@Component({ selector: 'app-settings', standalone: true, template: '...' })
|
|
472
|
+
export class SettingsComponent implements OnInit, OnDestroy {
|
|
473
|
+
private wu = createWuService();
|
|
474
|
+
storeName = '';
|
|
541
475
|
|
|
542
|
-
|
|
476
|
+
ngOnInit() {
|
|
477
|
+
this.storeName = this.wu.getState('store')?.name || '';
|
|
478
|
+
this.wu.on('theme:toggle', (e) => console.log('Theme:', e.data?.theme));
|
|
479
|
+
}
|
|
543
480
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
return (
|
|
552
|
-
<div>
|
|
553
|
-
<p>Hello, {user()?.name}</p>
|
|
554
|
-
<Show when={lastEvent()}>
|
|
555
|
-
<p>Last notification: {lastEvent()?.data}</p>
|
|
556
|
-
</Show>
|
|
557
|
-
<button onClick={() => setUser('name', 'John')}>
|
|
558
|
-
Set Name
|
|
559
|
-
</button>
|
|
560
|
-
</div>
|
|
561
|
-
);
|
|
481
|
+
save() {
|
|
482
|
+
this.wu.setState('store', { name: this.storeName });
|
|
483
|
+
this.wu.emit('settings:saved', { name: this.storeName });
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
ngOnDestroy() { this.wu.destroy(); }
|
|
562
487
|
}
|
|
563
488
|
```
|
|
564
489
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
490
|
+
> **Angular + Vite setup:** No AnalogJS or Angular CLI required. Plain Vite with
|
|
491
|
+
> `esbuild: { target: 'es2022' }` and `tsconfig.json` with `experimentalDecorators: true`.
|
|
492
|
+
> `@angular/compiler` import enables JIT mode at runtime. Pass `createApplication`,
|
|
493
|
+
> `createComponent`, `provideZoneChangeDetection` as options to `registerStandalone`
|
|
494
|
+
> so the bundler resolves them from your app's `node_modules`.
|
|
568
495
|
|
|
569
|
-
###
|
|
496
|
+
### Lit — Analytics (mf-showcase)
|
|
570
497
|
|
|
571
498
|
```js
|
|
499
|
+
// main.js: wuLit.register('analytics', AnalyticsApp)
|
|
572
500
|
import { LitElement, html, css } from 'lit';
|
|
573
|
-
import { wuLit } from 'wu-framework/adapters/lit';
|
|
574
|
-
|
|
575
|
-
class MyApp extends LitElement {
|
|
576
|
-
static styles = css`
|
|
577
|
-
:host { display: block; padding: 16px; }
|
|
578
|
-
`;
|
|
579
|
-
|
|
580
|
-
render() {
|
|
581
|
-
return html`<h1>Hello from Lit!</h1>`;
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
wuLit.register('my-app', MyApp);
|
|
586
|
-
```
|
|
587
|
-
|
|
588
|
-
### Con WuMixin
|
|
589
|
-
|
|
590
|
-
```js
|
|
591
|
-
import { LitElement, html } from 'lit';
|
|
592
501
|
import { WuMixin } from 'wu-framework/adapters/lit';
|
|
593
502
|
|
|
594
|
-
class
|
|
503
|
+
class AnalyticsApp extends WuMixin(LitElement) {
|
|
504
|
+
static styles = css`...`;
|
|
505
|
+
|
|
595
506
|
connectedCallback() {
|
|
596
507
|
super.connectedCallback();
|
|
597
|
-
this.wuOn('
|
|
598
|
-
|
|
599
|
-
});
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
handleClick() {
|
|
603
|
-
this.wuEmit('button:clicked', { id: 'my-button' });
|
|
508
|
+
this.wuOn('theme:toggle', () => this.requestUpdate());
|
|
509
|
+
const stats = this.wuGetState('stats');
|
|
604
510
|
}
|
|
605
511
|
|
|
606
512
|
render() {
|
|
607
513
|
return html`
|
|
608
|
-
<button @click=${this.
|
|
514
|
+
<button @click=${() => this.wuEmit('analytics:export')}>Export</button>
|
|
609
515
|
`;
|
|
610
516
|
}
|
|
611
517
|
}
|
|
612
|
-
|
|
613
|
-
wuLit.register('my-app', MyApp);
|
|
518
|
+
customElements.define('analytics-app', AnalyticsApp);
|
|
614
519
|
```
|
|
615
520
|
|
|
616
|
-
###
|
|
521
|
+
### Vanilla JS — Chat Widget (mf-sandbox)
|
|
617
522
|
|
|
618
523
|
```js
|
|
619
|
-
|
|
524
|
+
// main.js — default import
|
|
525
|
+
import wuVanilla from 'wu-framework/adapters/vanilla';
|
|
620
526
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
this.shadowRoot.innerHTML = '<h1>Hello Web Component!</h1>';
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
wuLit.registerWebComponent('my-component', MyComponent);
|
|
629
|
-
```
|
|
630
|
-
|
|
631
|
-
---
|
|
527
|
+
const { useWuEvents, useWuStore } = wuVanilla;
|
|
528
|
+
const events = useWuEvents();
|
|
529
|
+
const store = useWuStore();
|
|
632
530
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
```js
|
|
638
|
-
import { wuVanilla } from 'wu-framework/adapters/vanilla';
|
|
639
|
-
|
|
640
|
-
wuVanilla.register('my-app', {
|
|
641
|
-
state: { count: 0 },
|
|
642
|
-
|
|
643
|
-
render: (container, state) => {
|
|
644
|
-
container.innerHTML = `
|
|
645
|
-
<div>
|
|
646
|
-
<h1>Count: ${state.count}</h1>
|
|
647
|
-
<button id="increment">+</button>
|
|
648
|
-
</div>
|
|
649
|
-
`;
|
|
650
|
-
container.querySelector('#increment').onclick = () => {
|
|
651
|
-
state.count++;
|
|
652
|
-
};
|
|
653
|
-
},
|
|
654
|
-
|
|
655
|
-
destroy: (container) => {
|
|
656
|
-
container.innerHTML = '';
|
|
657
|
-
}
|
|
658
|
-
});
|
|
659
|
-
```
|
|
660
|
-
|
|
661
|
-
### Registro con Clase
|
|
662
|
-
|
|
663
|
-
```js
|
|
664
|
-
import { wuVanilla } from 'wu-framework/adapters/vanilla';
|
|
665
|
-
|
|
666
|
-
class TodoApp {
|
|
667
|
-
constructor(container) {
|
|
668
|
-
this.container = container;
|
|
669
|
-
this.todos = [];
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
render() {
|
|
673
|
-
this.container.innerHTML = `
|
|
674
|
-
<ul>
|
|
675
|
-
${this.todos.map(t => `<li>${t}</li>`).join('')}
|
|
676
|
-
</ul>
|
|
677
|
-
<input type="text" id="input" />
|
|
678
|
-
<button id="add">Add</button>
|
|
679
|
-
`;
|
|
680
|
-
|
|
681
|
-
this.container.querySelector('#add').onclick = () => {
|
|
682
|
-
const input = this.container.querySelector('#input');
|
|
683
|
-
this.todos.push(input.value);
|
|
684
|
-
this.render();
|
|
685
|
-
};
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
destroy() {
|
|
689
|
-
this.container.innerHTML = '';
|
|
690
|
-
}
|
|
531
|
+
function mount(container) {
|
|
532
|
+
events.on('theme:toggle', (e) => applyTheme(e.data?.theme));
|
|
533
|
+
const user = store.get('user');
|
|
534
|
+
// render chat UI into container...
|
|
691
535
|
}
|
|
692
536
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
```js
|
|
699
|
-
import { wuVanilla } from 'wu-framework/adapters/vanilla';
|
|
700
|
-
|
|
701
|
-
wuVanilla.registerTemplate('header', `
|
|
702
|
-
<header>
|
|
703
|
-
<h1>My Header</h1>
|
|
704
|
-
<nav>
|
|
705
|
-
<a href="/">Home</a>
|
|
706
|
-
<a href="/about">About</a>
|
|
707
|
-
</nav>
|
|
708
|
-
</header>
|
|
709
|
-
`);
|
|
710
|
-
|
|
711
|
-
// Template dinamico
|
|
712
|
-
wuVanilla.registerTemplate('greeting',
|
|
713
|
-
(data) => `<h1>Hello, ${data.name}!</h1>`,
|
|
714
|
-
{ data: { name: 'World' } }
|
|
715
|
-
);
|
|
716
|
-
```
|
|
717
|
-
|
|
718
|
-
### Componente Reactivo
|
|
719
|
-
|
|
720
|
-
```js
|
|
721
|
-
import { wuVanilla } from 'wu-framework/adapters/vanilla';
|
|
722
|
-
|
|
723
|
-
const Counter = wuVanilla.createComponent({
|
|
724
|
-
state: { count: 0 },
|
|
725
|
-
|
|
726
|
-
template: (state) => `
|
|
727
|
-
<div>
|
|
728
|
-
<h1>Count: ${state.count}</h1>
|
|
729
|
-
<button data-action="increment">+</button>
|
|
730
|
-
<button data-action="decrement">-</button>
|
|
731
|
-
</div>
|
|
732
|
-
`,
|
|
733
|
-
|
|
734
|
-
actions: {
|
|
735
|
-
increment: (state) => ({ count: state.count + 1 }),
|
|
736
|
-
decrement: (state) => ({ count: state.count - 1 })
|
|
737
|
-
}
|
|
738
|
-
});
|
|
739
|
-
|
|
740
|
-
wuVanilla.register('counter', Counter);
|
|
741
|
-
```
|
|
742
|
-
|
|
743
|
-
---
|
|
744
|
-
|
|
745
|
-
## Event Bus
|
|
746
|
-
|
|
747
|
-
Sistema de eventos para comunicacion entre microfrontends con soporte para wildcards y replay.
|
|
748
|
-
|
|
749
|
-
```js
|
|
750
|
-
import { emit, on, once, off, replayEvents } from 'wu-framework';
|
|
751
|
-
|
|
752
|
-
// Emitir evento
|
|
753
|
-
emit('user:login', { userId: 123, name: 'John' });
|
|
754
|
-
|
|
755
|
-
// Suscribirse a evento
|
|
756
|
-
const unsubscribe = on('user:login', (event) => {
|
|
757
|
-
console.log('User logged in:', event.data);
|
|
758
|
-
});
|
|
759
|
-
|
|
760
|
-
// Suscribirse con wildcards
|
|
761
|
-
on('user:*', (event) => {
|
|
762
|
-
console.log('User event:', event.name, event.data);
|
|
763
|
-
});
|
|
764
|
-
|
|
765
|
-
// Suscribirse una sola vez
|
|
766
|
-
once('app:ready', (event) => {
|
|
767
|
-
console.log('App is ready!');
|
|
768
|
-
});
|
|
769
|
-
|
|
770
|
-
// Desuscribirse
|
|
771
|
-
off('user:login', callback);
|
|
772
|
-
unsubscribe();
|
|
773
|
-
|
|
774
|
-
// Replay eventos del historial
|
|
775
|
-
replayEvents('user:*', (event) => {
|
|
776
|
-
console.log('Past event:', event);
|
|
777
|
-
});
|
|
778
|
-
```
|
|
779
|
-
|
|
780
|
-
---
|
|
781
|
-
|
|
782
|
-
## Store (State Management)
|
|
783
|
-
|
|
784
|
-
Store de alto rendimiento con pattern matching.
|
|
785
|
-
|
|
786
|
-
```js
|
|
787
|
-
import {
|
|
788
|
-
getState,
|
|
789
|
-
setState,
|
|
790
|
-
onStateChange,
|
|
791
|
-
batchState,
|
|
792
|
-
clearState,
|
|
793
|
-
getStoreMetrics
|
|
794
|
-
} from 'wu-framework';
|
|
795
|
-
|
|
796
|
-
// Establecer estado (dot notation)
|
|
797
|
-
setState('user.name', 'John');
|
|
798
|
-
setState('user.preferences.theme', 'dark');
|
|
799
|
-
|
|
800
|
-
// Obtener estado
|
|
801
|
-
const name = getState('user.name'); // 'John'
|
|
802
|
-
const user = getState('user'); // { name: 'John', preferences: { theme: 'dark' } }
|
|
803
|
-
|
|
804
|
-
// Suscribirse a cambios
|
|
805
|
-
const unsubscribe = onStateChange('user.name', (value, path) => {
|
|
806
|
-
console.log(`${path} changed to:`, value);
|
|
807
|
-
});
|
|
808
|
-
|
|
809
|
-
// Suscribirse con wildcards
|
|
810
|
-
onStateChange('user.*', (value, path) => {
|
|
811
|
-
console.log(`User property changed: ${path} =`, value);
|
|
812
|
-
});
|
|
813
|
-
|
|
814
|
-
// Batch updates (mas eficiente)
|
|
815
|
-
batchState({
|
|
816
|
-
'user.name': 'Jane',
|
|
817
|
-
'user.email': 'jane@example.com',
|
|
818
|
-
'settings.notifications': true
|
|
819
|
-
});
|
|
820
|
-
|
|
821
|
-
// Metricas de performance
|
|
822
|
-
const metrics = getStoreMetrics();
|
|
537
|
+
function unmount(container) {
|
|
538
|
+
events.cleanup();
|
|
539
|
+
container.innerHTML = '';
|
|
540
|
+
}
|
|
823
541
|
```
|
|
824
542
|
|
|
825
543
|
---
|
|
826
544
|
|
|
827
|
-
##
|
|
545
|
+
## Plugins & Lifecycle Hooks
|
|
828
546
|
|
|
829
547
|
```js
|
|
830
|
-
import { usePlugin, createPlugin,
|
|
548
|
+
import { usePlugin, createPlugin, useHook, createGuardHook } from 'wu-framework';
|
|
831
549
|
|
|
832
|
-
|
|
550
|
+
// Plugin
|
|
551
|
+
usePlugin(createPlugin({
|
|
833
552
|
name: 'analytics',
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
api.on('app:mounted', (event) => {
|
|
838
|
-
trackEvent('mount', event.data.appName);
|
|
839
|
-
});
|
|
840
|
-
},
|
|
553
|
+
install: (api) => api.on('app:mounted', (e) => track(e)),
|
|
554
|
+
afterMount: async (ctx) => log('mounted in', ctx.mountTime, 'ms')
|
|
555
|
+
}));
|
|
841
556
|
|
|
842
|
-
|
|
843
|
-
console.log('Before mount:', context.appName);
|
|
844
|
-
},
|
|
845
|
-
|
|
846
|
-
afterMount: async (context) => {
|
|
847
|
-
console.log('Mounted in', context.mountTime, 'ms');
|
|
848
|
-
}
|
|
849
|
-
});
|
|
850
|
-
|
|
851
|
-
usePlugin(analyticsPlugin, { trackingId: 'UA-123456' });
|
|
852
|
-
```
|
|
853
|
-
|
|
854
|
-
---
|
|
855
|
-
|
|
856
|
-
## Lifecycle Hooks
|
|
857
|
-
|
|
858
|
-
```js
|
|
859
|
-
import { useHook, createGuardHook, createTimedHook } from 'wu-framework';
|
|
860
|
-
|
|
861
|
-
// Hook basico
|
|
557
|
+
// Lifecycle hook
|
|
862
558
|
useHook('beforeMount', async (context, next) => {
|
|
863
|
-
console.log('
|
|
559
|
+
console.log('Mounting:', context.appName);
|
|
864
560
|
await next();
|
|
865
|
-
}, { name: 'my-hook', priority: 10 });
|
|
866
|
-
|
|
867
|
-
// Guard hook (puede cancelar)
|
|
868
|
-
const authGuard = createGuardHook(async (ctx) => {
|
|
869
|
-
return await isUserAuthenticated();
|
|
870
561
|
});
|
|
871
|
-
useHook('beforeMount', authGuard);
|
|
872
562
|
|
|
873
|
-
//
|
|
874
|
-
|
|
875
|
-
await slowOperation();
|
|
876
|
-
}, 5000);
|
|
563
|
+
// Guard — can cancel mount
|
|
564
|
+
useHook('beforeMount', createGuardHook(async () => isAuthenticated()));
|
|
877
565
|
```
|
|
878
566
|
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
| Fase | Descripcion |
|
|
882
|
-
|------|-------------|
|
|
883
|
-
| `beforeInit` | Antes de inicializar framework |
|
|
884
|
-
| `afterInit` | Despues de inicializar |
|
|
885
|
-
| `beforeLoad` | Antes de cargar una app |
|
|
886
|
-
| `afterLoad` | Despues de cargar |
|
|
887
|
-
| `beforeMount` | Antes de montar |
|
|
888
|
-
| `afterMount` | Despues de montar |
|
|
889
|
-
| `beforeUnmount` | Antes de desmontar |
|
|
890
|
-
| `afterUnmount` | Despues de desmontar |
|
|
567
|
+
Phases: `beforeInit` → `afterInit` → `beforeLoad` → `afterLoad` → `beforeMount` → `afterMount` → `beforeUnmount` → `afterUnmount`
|
|
891
568
|
|
|
892
569
|
---
|
|
893
570
|
|
|
@@ -896,378 +573,235 @@ const timedHook = createTimedHook(async (ctx) => {
|
|
|
896
573
|
```js
|
|
897
574
|
await wu.init({
|
|
898
575
|
apps: [
|
|
899
|
-
{ name: 'header',
|
|
900
|
-
{ name: '
|
|
901
|
-
{ name: 'footer', url: '/mfe/footer', strategy: 'preload' },
|
|
576
|
+
{ name: 'header', url: '/mfe/header', strategy: 'eager' },
|
|
577
|
+
{ name: 'content', url: '/mfe/content', strategy: 'lazy' },
|
|
902
578
|
{ name: 'analytics', url: '/mfe/analytics', strategy: 'idle' }
|
|
903
579
|
]
|
|
904
580
|
});
|
|
905
581
|
```
|
|
906
582
|
|
|
907
|
-
|
|
|
908
|
-
|
|
909
|
-
| `lazy` |
|
|
910
|
-
| `eager` |
|
|
911
|
-
| `preload` |
|
|
912
|
-
| `idle` |
|
|
583
|
+
| Strategy | Behavior |
|
|
584
|
+
|----------|----------|
|
|
585
|
+
| `lazy` | Load when mounted (default) |
|
|
586
|
+
| `eager` | Load immediately |
|
|
587
|
+
| `preload` | `<link rel="prefetch">` |
|
|
588
|
+
| `idle` | `requestIdleCallback` |
|
|
589
|
+
| `speculate` | Speculation Rules API with automatic fallback chain |
|
|
913
590
|
|
|
914
591
|
---
|
|
915
592
|
|
|
916
|
-
##
|
|
593
|
+
## Intelligent Prefetch (Speculation Rules API)
|
|
917
594
|
|
|
918
|
-
|
|
919
|
-
import {
|
|
920
|
-
startMeasure,
|
|
921
|
-
endMeasure,
|
|
922
|
-
getPerformanceMetrics,
|
|
923
|
-
generatePerformanceReport
|
|
924
|
-
} from 'wu-framework';
|
|
925
|
-
|
|
926
|
-
// Medicion personalizada
|
|
927
|
-
startMeasure('data-fetch', 'my-app');
|
|
928
|
-
await fetchData();
|
|
929
|
-
const duration = endMeasure('data-fetch', 'my-app');
|
|
930
|
-
|
|
931
|
-
// Reporte completo
|
|
932
|
-
const report = generatePerformanceReport();
|
|
933
|
-
```
|
|
934
|
-
|
|
935
|
-
---
|
|
595
|
+
Wu uses the browser-native [Speculation Rules API](https://developer.chrome.com/docs/web-platform/prerender-pages) (Chrome 121+) for near-instant microfrontend loads, with automatic fallbacks for older browsers.
|
|
936
596
|
|
|
937
|
-
|
|
597
|
+
### Fallback chain
|
|
938
598
|
|
|
939
|
-
```
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
registerErrorHandler({
|
|
943
|
-
name: 'api-error',
|
|
944
|
-
canHandle: (error) => error.message.includes('API'),
|
|
945
|
-
handle: async (error, context) => {
|
|
946
|
-
if (context.retryCount < 3) {
|
|
947
|
-
return { recovered: true, action: 'retry' };
|
|
948
|
-
}
|
|
949
|
-
return { recovered: false, action: 'fallback' };
|
|
950
|
-
}
|
|
951
|
-
});
|
|
952
|
-
|
|
953
|
-
configureErrorBoundary({
|
|
954
|
-
maxRetries: 3,
|
|
955
|
-
retryDelay: 1000,
|
|
956
|
-
showErrorUI: true
|
|
957
|
-
});
|
|
599
|
+
```
|
|
600
|
+
Speculation Rules API → <link rel="modulepreload"> → <link rel="prefetch">
|
|
601
|
+
(Chrome 121+) (modern browsers) (all browsers)
|
|
958
602
|
```
|
|
959
603
|
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
## API Simplificada (wu.app)
|
|
604
|
+
### Usage
|
|
963
605
|
|
|
964
606
|
```js
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
const header = wu.app('header', {
|
|
968
|
-
url: 'http://localhost:3001',
|
|
969
|
-
container: '#header-container'
|
|
970
|
-
});
|
|
971
|
-
|
|
972
|
-
await header.mount();
|
|
973
|
-
console.log(header.isMounted); // true
|
|
607
|
+
// Prefetch specific apps
|
|
608
|
+
wu.prefetch(['sidebar', 'analytics']);
|
|
974
609
|
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
await header.reload(); // Limpia cache
|
|
610
|
+
// Prefetch all registered apps
|
|
611
|
+
wu.prefetchAll();
|
|
978
612
|
|
|
979
|
-
|
|
980
|
-
|
|
613
|
+
// With options
|
|
614
|
+
wu.prefetch(['sidebar'], {
|
|
615
|
+
trigger: 'hover', // 'immediate' | 'hover' | 'visible' | 'idle'
|
|
616
|
+
action: 'prefetch', // 'prefetch' | 'prerender'
|
|
617
|
+
eagerness: 'moderate', // 'immediate' | 'eager' | 'moderate' | 'conservative'
|
|
618
|
+
});
|
|
981
619
|
```
|
|
982
620
|
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
## Sistema de Build
|
|
986
|
-
|
|
987
|
-
Wu Framework incluye un sistema de build completo para proteger el codigo.
|
|
988
|
-
|
|
989
|
-
### Comandos Disponibles
|
|
990
|
-
|
|
991
|
-
```bash
|
|
992
|
-
# Instalar dependencias
|
|
993
|
-
npm install
|
|
994
|
-
|
|
995
|
-
# Build completo (todos los formatos)
|
|
996
|
-
npm run build
|
|
621
|
+
### Triggers
|
|
997
622
|
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
623
|
+
| Trigger | Behavior |
|
|
624
|
+
|---------|----------|
|
|
625
|
+
| `immediate` | Prefetch now (default) |
|
|
626
|
+
| `hover` | Prefetch when user hovers over the app's container |
|
|
627
|
+
| `visible` | Prefetch when app's container enters viewport (IntersectionObserver, 200px margin) |
|
|
628
|
+
| `idle` | Prefetch during browser idle time (`requestIdleCallback`) |
|
|
1004
629
|
|
|
1005
|
-
|
|
1006
|
-
npm run build:protected # Bundle -> Minify -> Obfuscate -> Base64
|
|
1007
|
-
```
|
|
1008
|
-
|
|
1009
|
-
### Pipeline de Proteccion
|
|
630
|
+
### As loading strategy
|
|
1010
631
|
|
|
632
|
+
```js
|
|
633
|
+
await wu.init({
|
|
634
|
+
apps: [
|
|
635
|
+
{ name: 'header', url: '/mfe/header', strategy: 'eager' },
|
|
636
|
+
{ name: 'sidebar', url: '/mfe/sidebar', strategy: 'speculate' },
|
|
637
|
+
{ name: 'analytics', url: '/mfe/analytics', strategy: 'idle' },
|
|
638
|
+
]
|
|
639
|
+
});
|
|
1011
640
|
```
|
|
1012
|
-
Source Code
|
|
1013
|
-
|
|
|
1014
|
-
v
|
|
1015
|
-
[1] Bundle (Rollup) -> Empaqueta todos los modulos
|
|
1016
|
-
|
|
|
1017
|
-
v
|
|
1018
|
-
[2] Minify (Terser) -> Reduce ~60% del tamanio
|
|
1019
|
-
|
|
|
1020
|
-
v
|
|
1021
|
-
[3] Obfuscate -> Codigo ilegible
|
|
1022
|
-
|
|
|
1023
|
-
v
|
|
1024
|
-
[4] Base64 Encode -> Capa adicional de proteccion
|
|
1025
|
-
|
|
|
1026
|
-
v
|
|
1027
|
-
Output: wu-framework.protected.js (~250KB)
|
|
1028
|
-
```
|
|
1029
|
-
|
|
1030
|
-
### Archivos Generados
|
|
1031
641
|
|
|
1032
|
-
|
|
1033
|
-
|---------|---------|-----|
|
|
1034
|
-
| `wu-framework.dev.js` | ES Module | Desarrollo |
|
|
1035
|
-
| `wu-framework.min.js` | ES Module | Produccion |
|
|
1036
|
-
| `wu-framework.obf.js` | ES Module | Produccion protegida |
|
|
1037
|
-
| `wu-framework.protected.js` | ES Module | Maxima proteccion |
|
|
1038
|
-
| `wu-framework.umd.js` | UMD | Browsers via CDN |
|
|
1039
|
-
| `wu-framework.cjs.js` | CommonJS | Node.js |
|
|
642
|
+
The `speculate` strategy uses the Speculation Rules API by default with `immediate` trigger. It degrades gracefully — your app works on any browser, it just loads faster on Chrome 121+.
|
|
1040
643
|
|
|
1041
644
|
---
|
|
1042
645
|
|
|
1043
|
-
##
|
|
646
|
+
## Cookie Overrides for QA/Testing
|
|
1044
647
|
|
|
1045
|
-
|
|
648
|
+
Redirect individual microfrontends to different URLs without changing code or deploying. QA sets a cookie, and only **their browser** sees the override — everyone else sees production.
|
|
1046
649
|
|
|
1047
|
-
###
|
|
650
|
+
### How it works
|
|
1048
651
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
652
|
+
```
|
|
653
|
+
1. QA opens DevTools → Application → Cookies
|
|
654
|
+
2. Sets: wu-override:sidebar=http://localhost:5174
|
|
655
|
+
3. Reloads → only "sidebar" loads from localhost
|
|
656
|
+
4. Everyone else sees production sidebar
|
|
657
|
+
```
|
|
1054
658
|
|
|
1055
|
-
|
|
1056
|
-
- Restaura al desmontar
|
|
1057
|
-
- Compatible con navegadores legacy
|
|
659
|
+
### Programmatic API
|
|
1058
660
|
|
|
1059
661
|
```js
|
|
1060
|
-
//
|
|
1061
|
-
|
|
1062
|
-
console.log(stats.strategy); // 'proxy' o 'snapshot'
|
|
1063
|
-
```
|
|
662
|
+
// Set an override (persists as cookie for 24h)
|
|
663
|
+
wu.override('sidebar', 'http://localhost:5174');
|
|
1064
664
|
|
|
1065
|
-
|
|
665
|
+
// Check active overrides
|
|
666
|
+
wu.getOverrides();
|
|
667
|
+
// → { sidebar: 'http://localhost:5174' }
|
|
1066
668
|
|
|
1067
|
-
|
|
669
|
+
// Remove one
|
|
670
|
+
wu.removeOverride('sidebar');
|
|
1068
671
|
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
```
|
|
1072
|
-
https://cdn.mycompany.com/
|
|
1073
|
-
├── mfe/
|
|
1074
|
-
│ ├── header/
|
|
1075
|
-
│ │ ├── wu.json
|
|
1076
|
-
│ │ ├── index.js
|
|
1077
|
-
│ │ └── assets/
|
|
1078
|
-
│ ├── sidebar/
|
|
1079
|
-
│ │ ├── wu.json
|
|
1080
|
-
│ │ ├── index.js
|
|
1081
|
-
│ │ └── assets/
|
|
1082
|
-
│ └── content/
|
|
1083
|
-
│ ├── wu.json
|
|
1084
|
-
│ ├── index.js
|
|
1085
|
-
│ └── assets/
|
|
672
|
+
// Clear all
|
|
673
|
+
wu.clearOverrides();
|
|
1086
674
|
```
|
|
1087
675
|
|
|
1088
|
-
###
|
|
676
|
+
### Security model (3 layers)
|
|
1089
677
|
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
content: 'http://localhost:3003'
|
|
1096
|
-
},
|
|
1097
|
-
staging: {
|
|
1098
|
-
header: 'https://staging-cdn.mycompany.com/mfe/header',
|
|
1099
|
-
sidebar: 'https://staging-cdn.mycompany.com/mfe/sidebar',
|
|
1100
|
-
content: 'https://staging-cdn.mycompany.com/mfe/content'
|
|
1101
|
-
},
|
|
1102
|
-
production: {
|
|
1103
|
-
header: 'https://cdn.mycompany.com/mfe/header',
|
|
1104
|
-
sidebar: 'https://cdn.mycompany.com/mfe/sidebar',
|
|
1105
|
-
content: 'https://cdn.mycompany.com/mfe/content'
|
|
1106
|
-
}
|
|
1107
|
-
};
|
|
678
|
+
| Layer | Protection |
|
|
679
|
+
|-------|-----------|
|
|
680
|
+
| **Environment gate** | Disabled in production by default. Auto-enabled only on `localhost` / `127.0.0.1`. Must explicitly opt-in for production. |
|
|
681
|
+
| **Domain allowlist** | Only overrides to whitelisted domains are accepted. Supports wildcards: `*.company.com`. |
|
|
682
|
+
| **Visual indicator** | Fixed yellow banner shows active overrides. Prevents silent phishing. Click to hide, double-click to clear. |
|
|
1108
683
|
|
|
1109
|
-
|
|
1110
|
-
const urls = config[env];
|
|
684
|
+
### Enable in production (for QA teams)
|
|
1111
685
|
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
]
|
|
686
|
+
```js
|
|
687
|
+
wu.overrides.configure({
|
|
688
|
+
enabled: true,
|
|
689
|
+
allowedDomains: ['*.mycompany.com', 'localhost', '*.vercel.app'],
|
|
690
|
+
showIndicator: true,
|
|
1118
691
|
});
|
|
1119
692
|
```
|
|
1120
693
|
|
|
1121
|
-
###
|
|
694
|
+
### Cookie format
|
|
1122
695
|
|
|
1123
|
-
```nginx
|
|
1124
|
-
location /mfe/ {
|
|
1125
|
-
add_header Access-Control-Allow-Origin *;
|
|
1126
|
-
add_header Access-Control-Allow-Methods "GET, OPTIONS";
|
|
1127
|
-
add_header Access-Control-Allow-Headers "Content-Type";
|
|
1128
|
-
}
|
|
1129
696
|
```
|
|
697
|
+
wu-override:<appName>=<url>
|
|
1130
698
|
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
{
|
|
1135
|
-
"name": "header",
|
|
1136
|
-
"version": "1.2.0",
|
|
1137
|
-
"entry": "index.js"
|
|
1138
|
-
}
|
|
699
|
+
# Examples:
|
|
700
|
+
wu-override:sidebar=http://localhost:5174
|
|
701
|
+
wu-override:header=https://staging.mycompany.com/header
|
|
1139
702
|
```
|
|
1140
703
|
|
|
1141
|
-
|
|
1142
|
-
await wu.init({
|
|
1143
|
-
apps: [
|
|
1144
|
-
{ name: 'header', url: 'https://cdn.mycompany.com/mfe/header/v1.2.0' },
|
|
1145
|
-
{ name: 'sidebar', url: 'https://cdn.mycompany.com/mfe/sidebar/v2.0.0' }
|
|
1146
|
-
]
|
|
1147
|
-
});
|
|
1148
|
-
```
|
|
704
|
+
No proxy. No server changes. Pure client-side URL substitution at init time.
|
|
1149
705
|
|
|
1150
706
|
---
|
|
1151
707
|
|
|
1152
|
-
##
|
|
708
|
+
## Build & Test
|
|
1153
709
|
|
|
1154
|
-
```
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
│ │ ┌───────────┐ │ │ ┌───────────┐ │ │ ┌─────────┐ │ │
|
|
1161
|
-
│ │ │ Header │ │ │ │ Sidebar │ │ │ │ Content │ │ │
|
|
1162
|
-
│ │ │ (React) │ │ │ │ (Vue) │ │ │ │(Angular)│ │ │
|
|
1163
|
-
│ │ └───────────┘ │ │ └───────────┘ │ │ └─────────┘ │ │
|
|
1164
|
-
│ │ CSS Isolated │ │ CSS Isolated │ │ CSS Isolated│ │
|
|
1165
|
-
│ │ JS Sandboxed │ │ JS Sandboxed │ │ JS Sandboxed│ │
|
|
1166
|
-
│ └─────────────────┘ └─────────────────┘ └─────────────┘ │
|
|
1167
|
-
├─────────────────────────────────────────────────────────────┤
|
|
1168
|
-
│ WU FRAMEWORK CORE │
|
|
1169
|
-
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────────────┐ │
|
|
1170
|
-
│ │ EventBus │ │ Store │ │ Plugins │ │ Sandbox Pool │ │
|
|
1171
|
-
│ └──────────┘ └──────────┘ └──────────┘ └────────────────┘ │
|
|
1172
|
-
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────────────┐ │
|
|
1173
|
-
│ │ Loader │ │ Manifest │ │ Hooks │ │ Style Bridge │ │
|
|
1174
|
-
│ └──────────┘ └──────────┘ └──────────┘ └────────────────┘ │
|
|
1175
|
-
└─────────────────────────────────────────────────────────────┘
|
|
710
|
+
```bash
|
|
711
|
+
npm run build # ESM + CJS + UMD + Dev
|
|
712
|
+
npm run build:prod # Production (minified)
|
|
713
|
+
npm run build:dev # Development (unminified)
|
|
714
|
+
npm run test # 277 tests (Vitest)
|
|
715
|
+
npm run test:coverage # Coverage report
|
|
1176
716
|
```
|
|
1177
717
|
|
|
1178
|
-
|
|
718
|
+
| Output | Format | Use |
|
|
719
|
+
|--------|--------|-----|
|
|
720
|
+
| `wu-framework.esm.js` | ES Module | Bundler imports |
|
|
721
|
+
| `wu-framework.cjs.js` | CommonJS | Node.js require |
|
|
722
|
+
| `wu-framework.umd.js` | UMD | CDN / script tag |
|
|
723
|
+
| `wu-framework.dev.js` | ES Module | Development |
|
|
1179
724
|
|
|
1180
|
-
|
|
725
|
+
---
|
|
1181
726
|
|
|
1182
|
-
|
|
727
|
+
## Architecture
|
|
1183
728
|
|
|
1184
|
-
```
|
|
1185
|
-
|
|
729
|
+
```
|
|
730
|
+
┌───────────────────────────────────────────────────────────────────────┐
|
|
731
|
+
│ SHELL (Astro) │
|
|
732
|
+
│ Sidebar + Section Containers + wu.init() + wu.mount() │
|
|
733
|
+
├──────────┬──────────┬──────────┬──────────┬──────────┬────────────────┤
|
|
734
|
+
│ Shadow │ Shadow │ Shadow │ Shadow │ Shadow │ Shadow ... │
|
|
735
|
+
│ ┌──────┐ │ ┌──────┐ │ ┌──────┐ │ ┌──────┐ │ ┌──────┐ │ ┌──────┐ │
|
|
736
|
+
│ │Topbar│ │ │Dashbd│ │ │Orders│ │ │Produc│ │ │Custo.│ │ │Analyt│ │
|
|
737
|
+
│ │Preact│ │ │Svelte│ │ │React │ │ │Vue 3 │ │ │Solid │ │ │ Lit │ │
|
|
738
|
+
│ └──────┘ │ └──────┘ │ └──────┘ │ └──────┘ │ └──────┘ │ └──────┘ │
|
|
739
|
+
├──────────┴──────────┴──────────┴──────────┴──────────┴────────────────┤
|
|
740
|
+
│ WU FRAMEWORK CORE │
|
|
741
|
+
│ Sandbox (module/strict/eval) · EventBus (wildcards, replay) │
|
|
742
|
+
│ Store (dot-paths, batch) · StyleBridge (shared/isolated/fully-iso) │
|
|
743
|
+
│ Loader · Hooks · Plugins · Cache · Prefetch · Overrides │
|
|
744
|
+
└───────────────────────────────────────────────────────────────────────┘
|
|
1186
745
|
```
|
|
1187
746
|
|
|
1188
|
-
|
|
747
|
+
Each micro-app runs inside its own Shadow DOM with configurable CSS isolation and a JS sandbox that tracks and auto-cleans all side effects on unmount.
|
|
1189
748
|
|
|
1190
|
-
|
|
1191
|
-
import { emit, on, once, off, replayEvents, getEventBusStats } from 'wu-framework';
|
|
1192
|
-
```
|
|
749
|
+
---
|
|
1193
750
|
|
|
1194
|
-
|
|
751
|
+
## API Reference
|
|
1195
752
|
|
|
1196
753
|
```js
|
|
1197
|
-
|
|
1198
|
-
|
|
754
|
+
// Core
|
|
755
|
+
import { wu, init, mount, unmount, destroy, app, define } from 'wu-framework';
|
|
1199
756
|
|
|
1200
|
-
|
|
757
|
+
// Events
|
|
758
|
+
import { emit, on, once, off } from 'wu-framework';
|
|
1201
759
|
|
|
1202
|
-
|
|
1203
|
-
import {
|
|
1204
|
-
```
|
|
760
|
+
// State
|
|
761
|
+
import { getState, setState, onStateChange } from 'wu-framework';
|
|
1205
762
|
|
|
1206
|
-
|
|
763
|
+
// Plugins & Hooks
|
|
764
|
+
import { usePlugin, createPlugin, useHook } from 'wu-framework';
|
|
765
|
+
import { createGuardHook, createTimedHook, createTransformHook } from 'wu-framework';
|
|
1207
766
|
|
|
1208
|
-
|
|
767
|
+
// Performance
|
|
1209
768
|
import { startMeasure, endMeasure, generatePerformanceReport } from 'wu-framework';
|
|
1210
|
-
import { registerErrorHandler, configureErrorBoundary } from 'wu-framework';
|
|
1211
|
-
```
|
|
1212
769
|
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
```js
|
|
1216
|
-
import { configureStyleSharing, reinjectStyles, getStyleStats } from 'wu-framework';
|
|
1217
|
-
```
|
|
770
|
+
// Prefetch (Speculation Rules API)
|
|
771
|
+
import { prefetch, prefetchAll } from 'wu-framework';
|
|
1218
772
|
|
|
1219
|
-
|
|
773
|
+
// Overrides (QA/Testing)
|
|
774
|
+
import { override, removeOverride, getOverrides, clearOverrides } from 'wu-framework';
|
|
1220
775
|
|
|
1221
|
-
|
|
1222
|
-
import { wuReact }
|
|
1223
|
-
import { wuVue }
|
|
776
|
+
// Adapters
|
|
777
|
+
import { wuReact } from 'wu-framework/adapters/react';
|
|
778
|
+
import { wuVue } from 'wu-framework/adapters/vue';
|
|
1224
779
|
import { wuAngular } from 'wu-framework/adapters/angular';
|
|
1225
|
-
import { wuSvelte }
|
|
1226
|
-
import { wuPreact }
|
|
1227
|
-
import { wuSolid }
|
|
1228
|
-
import { wuLit }
|
|
780
|
+
import { wuSvelte } from 'wu-framework/adapters/svelte';
|
|
781
|
+
import { wuPreact } from 'wu-framework/adapters/preact';
|
|
782
|
+
import { wuSolid } from 'wu-framework/adapters/solid';
|
|
783
|
+
import { wuLit } from 'wu-framework/adapters/lit';
|
|
1229
784
|
import { wuVanilla } from 'wu-framework/adapters/vanilla';
|
|
1230
|
-
```
|
|
1231
|
-
|
|
1232
|
-
### Utilities
|
|
1233
|
-
|
|
1234
|
-
```js
|
|
1235
|
-
import { presets, dev, events } from 'wu-framework';
|
|
1236
785
|
|
|
1237
|
-
//
|
|
1238
|
-
|
|
1239
|
-
|
|
786
|
+
// Prefetch
|
|
787
|
+
wu.prefetch(['sidebar', 'analytics']);
|
|
788
|
+
wu.prefetch(['sidebar'], { trigger: 'hover' });
|
|
789
|
+
wu.prefetchAll();
|
|
1240
790
|
|
|
1241
|
-
//
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
791
|
+
// Overrides (QA)
|
|
792
|
+
wu.override('sidebar', 'http://localhost:5174');
|
|
793
|
+
wu.removeOverride('sidebar');
|
|
794
|
+
wu.getOverrides();
|
|
795
|
+
wu.clearOverrides();
|
|
796
|
+
wu.overrides.configure({ enabled: true, allowedDomains: ['*.company.com'] });
|
|
1245
797
|
|
|
1246
|
-
//
|
|
798
|
+
// Log control
|
|
1247
799
|
wu.silence();
|
|
1248
800
|
wu.verbose();
|
|
1249
801
|
```
|
|
1250
802
|
|
|
1251
803
|
---
|
|
1252
804
|
|
|
1253
|
-
## Contributing
|
|
1254
|
-
|
|
1255
|
-
Las contribuciones son bienvenidas!
|
|
1256
|
-
|
|
1257
|
-
1. **Fork** el repositorio
|
|
1258
|
-
2. **Crear** branch (`git checkout -b feature/amazing-feature`)
|
|
1259
|
-
3. **Commit** tus cambios (`git commit -m 'Add amazing feature'`)
|
|
1260
|
-
4. **Push** al branch (`git push origin feature/amazing-feature`)
|
|
1261
|
-
5. **Abrir** un Pull Request
|
|
1262
|
-
|
|
1263
|
-
---
|
|
1264
|
-
|
|
1265
805
|
## License
|
|
1266
806
|
|
|
1267
|
-
MIT
|
|
1268
|
-
|
|
1269
|
-
---
|
|
1270
|
-
|
|
1271
|
-
**Wu Framework - Universal Microfrontends Made Simple**
|
|
1272
|
-
|
|
1273
|
-
*Zero dependencies - 8 Frameworks - Shadow DOM - Proxy Sandbox - Self-Healing - Build Protection*
|
|
807
|
+
MIT
|