wu-framework 1.1.18 → 1.2.0
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 +58 -9
- package/package.json +11 -8
- package/src/adapters/qwik/index.js +80 -186
package/README.md
CHANGED
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
<a href="https://www.npmjs.com/package/wu-framework"><img src="https://img.shields.io/npm/v/wu-framework.svg?color=
|
|
13
|
-
<a href="https://github.com/LuisPadre25/wu-framework/actions"><img src="https://img.shields.io/github/actions/workflow/status/LuisPadre25/wu-framework/ci.yml?label=tests&color=
|
|
14
|
-
<img src="https://img.shields.io/badge/tests-650%20passed-
|
|
15
|
-
<img src="https://img.shields.io/badge/dependencies-0-
|
|
12
|
+
<a href="https://www.npmjs.com/package/wu-framework"><img src="https://img.shields.io/npm/v/wu-framework.svg?color=6366f1&label=npm" alt="npm version" /></a>
|
|
13
|
+
<a href="https://github.com/LuisPadre25/wu-framework/actions"><img src="https://img.shields.io/github/actions/workflow/status/LuisPadre25/wu-framework/ci.yml?label=tests&color=14b8a6" alt="tests" /></a>
|
|
14
|
+
<img src="https://img.shields.io/badge/tests-650%20passed-14b8a6" alt="650 tests" />
|
|
15
|
+
<img src="https://img.shields.io/badge/dependencies-0-6366f1" alt="zero deps" />
|
|
16
16
|
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License" /></a>
|
|
17
17
|
</p>
|
|
18
18
|
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
---
|
|
27
27
|
|
|
28
|
-
Run **React, Vue, Angular, Svelte, Solid, Preact, Lit, and Vanilla JS** micro-apps side by side in the same page. Each app lives in its own Shadow DOM with full CSS isolation. Apps communicate through a shared event bus and store — no tight coupling, no iframes.
|
|
28
|
+
Run **React, Vue, Angular, Svelte, Solid, Preact, Lit, Qwik, Alpine.js, Stencil, HTMX, Stimulus, and Vanilla JS** micro-apps side by side in the same page. Each app lives in its own Shadow DOM with full CSS isolation. Apps communicate through a shared event bus and store — no tight coupling, no iframes.
|
|
29
29
|
|
|
30
30
|
Add AI to any app with one line. Connect your own LLM (OpenAI, Anthropic, Ollama) and your app gains context-aware tool calling, autonomous agents, and cross-app orchestration. **WebMCP ready** for Chrome 146+.
|
|
31
31
|
|
|
@@ -68,7 +68,7 @@ await wu.ai.send('Add product SKU-42 to the cart');
|
|
|
68
68
|
|
|
69
69
|
| | **Wu Framework** | **single-spa** | **Module Federation** | **iframes** |
|
|
70
70
|
|---|:---:|:---:|:---:|:---:|
|
|
71
|
-
| Framework adapters | **
|
|
71
|
+
| Framework adapters | **13** | 4 | 1* | Any |
|
|
72
72
|
| Shadow DOM isolation | Yes | No | No | Yes (heavy) |
|
|
73
73
|
| Shared event bus | Built-in | Manual | Manual | postMessage |
|
|
74
74
|
| Shared store | Built-in | Manual | Manual | No |
|
|
@@ -87,7 +87,7 @@ await wu.ai.send('Add product SKU-42 to the cart');
|
|
|
87
87
|
|
|
88
88
|
### Core
|
|
89
89
|
|
|
90
|
-
- **
|
|
90
|
+
- **13 Framework Adapters** — React, Vue, Angular, Svelte, Solid, Preact, Lit, Qwik, Alpine.js, Stencil, HTMX, Stimulus, Vanilla
|
|
91
91
|
- **Shadow DOM Isolation** — CSS and DOM fully sandboxed per app
|
|
92
92
|
- **3 Sandbox Strategies** — Shadow DOM, Proxy, iframe — choose per app
|
|
93
93
|
- **3 CSS Isolation Modes** — `shared`, `isolated`, `fully-isolated` per app
|
|
@@ -138,6 +138,26 @@ wuAngular.registerStandalone('settings', AppComponent, { createApplication, crea
|
|
|
138
138
|
import { wuSvelte } from 'wu-framework/adapters/svelte';
|
|
139
139
|
wuSvelte.registerSvelte5('dashboard', App);
|
|
140
140
|
|
|
141
|
+
// Qwik
|
|
142
|
+
import { wuQwik } from 'wu-framework/adapters/qwik';
|
|
143
|
+
wuQwik.register('widget', QwikApp);
|
|
144
|
+
|
|
145
|
+
// Alpine.js
|
|
146
|
+
import { wuAlpine } from 'wu-framework/adapters/alpine';
|
|
147
|
+
wuAlpine.register('search', initFn);
|
|
148
|
+
|
|
149
|
+
// Stencil (Web Components)
|
|
150
|
+
import { wuStencil } from 'wu-framework/adapters/stencil';
|
|
151
|
+
wuStencil.register('badge', 'my-badge');
|
|
152
|
+
|
|
153
|
+
// HTMX
|
|
154
|
+
import { wuHtmx } from 'wu-framework/adapters/htmx';
|
|
155
|
+
wuHtmx.register('feed', { template: '<div hx-get="/api/feed">...</div>' });
|
|
156
|
+
|
|
157
|
+
// Stimulus
|
|
158
|
+
import { wuStimulus } from 'wu-framework/adapters/stimulus';
|
|
159
|
+
wuStimulus.register('form', { controllers: { hello: HelloController }, template: '...' });
|
|
160
|
+
|
|
141
161
|
// Same pattern for Solid, Preact, Lit, Vanilla
|
|
142
162
|
```
|
|
143
163
|
|
|
@@ -324,7 +344,7 @@ QA sets a cookie → only **their browser** sees the override. Everyone else see
|
|
|
324
344
|
| Source files | 79 |
|
|
325
345
|
| Lines of code | 23,442 |
|
|
326
346
|
| Test cases | **650** |
|
|
327
|
-
| Framework adapters |
|
|
347
|
+
| Framework adapters | 13 |
|
|
328
348
|
| AI modules | 12 |
|
|
329
349
|
| Core modules | 23 |
|
|
330
350
|
| Runtime dependencies | **0** |
|
|
@@ -429,12 +449,41 @@ Something is coming. It learned from every LLM call you ever made. It doesn't ne
|
|
|
429
449
|
|
|
430
450
|
---
|
|
431
451
|
|
|
452
|
+
## Wu CLI
|
|
453
|
+
|
|
454
|
+
The official CLI for wu-framework. One binary, one port, all your micro-apps.
|
|
455
|
+
|
|
456
|
+
```bash
|
|
457
|
+
npm install -g @wu-framework/cli
|
|
458
|
+
|
|
459
|
+
wu create my-project # Interactive scaffolding (13 frameworks)
|
|
460
|
+
cd my-project
|
|
461
|
+
wu dev # Native Zig dev server on one port
|
|
462
|
+
wu build # Production build (parallel Vite)
|
|
463
|
+
wu serve # Serve production build
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
Features:
|
|
467
|
+
- Native SIMD HTTP server written in Zig (16 bytes/cycle parsing)
|
|
468
|
+
- Three-tier compilation: Native Zig JSX (0-2ms) -> Compiler Daemon (10-50ms) -> Node fallback
|
|
469
|
+
- Two-level cache with 73-138x speedup on warm restart
|
|
470
|
+
- Theme-aware production shell with dark/light toggle
|
|
471
|
+
- 13 framework app templates with CSS variable theming
|
|
472
|
+
|
|
473
|
+
See [@wu-framework/cli](https://github.com/LuisPadre25/wu-cli) for full documentation.
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
432
477
|
## Contributing
|
|
433
478
|
|
|
434
479
|
Contributions welcome. Please open an issue first to discuss what you'd like to change.
|
|
435
480
|
|
|
436
481
|
## License
|
|
437
482
|
|
|
438
|
-
[MIT](./LICENSE)
|
|
483
|
+
[MIT](./LICENSE) -- Free for personal and commercial use.
|
|
439
484
|
|
|
440
485
|
See [LICENSE-COMMERCIAL.md](./LICENSE-COMMERCIAL.md) for optional enterprise support and consulting.
|
|
486
|
+
|
|
487
|
+
---
|
|
488
|
+
|
|
489
|
+
*2026 Wu Framework*
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wu-framework",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Universal Microfrontends Framework -
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "Universal Microfrontends Framework - 13 frameworks, zero config, Shadow DOM isolation",
|
|
5
5
|
"main": "dist/wu-framework.cjs.js",
|
|
6
6
|
"module": "src/index.js",
|
|
7
7
|
"browser": "dist/wu-framework.umd.js",
|
|
@@ -149,18 +149,21 @@
|
|
|
149
149
|
},
|
|
150
150
|
"peerDependencies": {
|
|
151
151
|
"@angular/core": ">=14.0.0",
|
|
152
|
+
"@builder.io/qwik": ">=1.0.0",
|
|
153
|
+
"@hotwired/stimulus": ">=3.0.0",
|
|
154
|
+
"@stencil/core": ">=4.0.0",
|
|
155
|
+
"alpinejs": ">=3.0.0",
|
|
156
|
+
"htmx.org": ">=2.0.0",
|
|
152
157
|
"lit": ">=2.0.0",
|
|
153
158
|
"preact": ">=10.0.0",
|
|
154
159
|
"react": ">=16.8.0",
|
|
155
160
|
"react-dom": ">=16.8.0",
|
|
156
161
|
"solid-js": ">=1.0.0",
|
|
157
162
|
"svelte": ">=3.0.0",
|
|
158
|
-
"vue": ">=3.0.0"
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
"
|
|
162
|
-
"htmx.org": ">=2.0.0",
|
|
163
|
-
"@hotwired/stimulus": ">=3.0.0"
|
|
163
|
+
"vue": ">=3.0.0"
|
|
164
|
+
},
|
|
165
|
+
"overrides": {
|
|
166
|
+
"serialize-javascript": "^7.0.3"
|
|
164
167
|
},
|
|
165
168
|
"peerDependenciesMeta": {
|
|
166
169
|
"react": {
|
|
@@ -1,214 +1,108 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* WU-FRAMEWORK QWIK ADAPTER
|
|
3
3
|
*
|
|
4
|
-
* Integrates Qwik components into Wu Framework's
|
|
5
|
-
* orchestration
|
|
6
|
-
* serializes state into HTML and lazily hydrates on interaction --
|
|
7
|
-
* makes it a natural fit for microfrontend boundaries. The adapter
|
|
8
|
-
* leverages dynamic import of @builder.io/qwik to keep the shell
|
|
9
|
-
* bundle zero-cost until a Qwik island actually mounts.
|
|
4
|
+
* Integrates Qwik components (component$) into Wu Framework's
|
|
5
|
+
* microfrontend orchestration via @builder.io/qwik render API.
|
|
10
6
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
7
|
+
* Shadow DOM compatibility:
|
|
8
|
+
* Qwik uses QRL-based event delegation — the qwikloader script
|
|
9
|
+
* attaches capture-phase listeners on `document` to intercept events
|
|
10
|
+
* and resolve QRL attributes (on:click, on:input, etc.) on elements.
|
|
14
11
|
*
|
|
15
|
-
*
|
|
12
|
+
* Inside Shadow DOM, event.target is retargeted to the shadow host,
|
|
13
|
+
* so qwikloader can't find QRL attributes on the actual elements.
|
|
14
|
+
* We fix this by wrapping qwikloader's document listeners with a
|
|
15
|
+
* Proxy that returns event.composedPath()[0] as the real target.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
* // With props and lifecycle hooks
|
|
19
|
-
* wuQwik.register('dashboard', DashboardComponent, {
|
|
20
|
-
* props: { userId: 42 },
|
|
21
|
-
* onMount: (container) => console.log('Qwik island mounted'),
|
|
22
|
-
* onUnmount: (container) => console.log('Qwik island destroyed')
|
|
23
|
-
* });
|
|
17
|
+
* See: https://github.com/QwikDev/qwik-evolution/issues/283
|
|
24
18
|
*/
|
|
25
19
|
|
|
26
|
-
|
|
27
|
-
const adapterState = {
|
|
28
|
-
apps: new Map()
|
|
29
|
-
};
|
|
20
|
+
let _patched = false;
|
|
30
21
|
|
|
31
22
|
/**
|
|
32
|
-
*
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (typeof window === 'undefined') return null;
|
|
36
|
-
|
|
37
|
-
return window.wu
|
|
38
|
-
|| window.parent?.wu
|
|
39
|
-
|| window.top?.wu
|
|
40
|
-
|| null;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Espera a que Wu Framework este disponible
|
|
45
|
-
*/
|
|
46
|
-
function waitForWu(timeout = 5000) {
|
|
47
|
-
return new Promise((resolve, reject) => {
|
|
48
|
-
const wu = getWuInstance();
|
|
49
|
-
if (wu) {
|
|
50
|
-
resolve(wu);
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const startTime = Date.now();
|
|
55
|
-
|
|
56
|
-
const handleWuReady = () => {
|
|
57
|
-
cleanup();
|
|
58
|
-
resolve(getWuInstance());
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
window.addEventListener('wu:ready', handleWuReady);
|
|
62
|
-
window.addEventListener('wu:app:ready', handleWuReady);
|
|
63
|
-
|
|
64
|
-
const checkInterval = setInterval(() => {
|
|
65
|
-
const wu = getWuInstance();
|
|
66
|
-
if (wu) {
|
|
67
|
-
cleanup();
|
|
68
|
-
resolve(wu);
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (Date.now() - startTime > timeout) {
|
|
73
|
-
cleanup();
|
|
74
|
-
reject(new Error(`Wu Framework not found after ${timeout}ms`));
|
|
75
|
-
}
|
|
76
|
-
}, 200);
|
|
77
|
-
|
|
78
|
-
function cleanup() {
|
|
79
|
-
clearInterval(checkInterval);
|
|
80
|
-
window.removeEventListener('wu:ready', handleWuReady);
|
|
81
|
-
window.removeEventListener('wu:app:ready', handleWuReady);
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Registra un componente Qwik como microfrontend
|
|
23
|
+
* Patches document.addEventListener so capture-phase handlers (used by
|
|
24
|
+
* qwikloader) receive the real event target from inside Shadow DOM via
|
|
25
|
+
* composedPath()[0], instead of the retargeted shadow host.
|
|
88
26
|
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
* @param {Object} [options] - Opciones adicionales
|
|
92
|
-
* @param {Object} [options.props={}] - Props a pasar al componente
|
|
93
|
-
* @param {Function} [options.onMount] - Callback despues de montar
|
|
94
|
-
* @param {Function} [options.onUnmount] - Callback antes de desmontar
|
|
95
|
-
* @param {boolean} [options.standalone=true] - Permitir ejecucion standalone
|
|
96
|
-
* @param {string} [options.standaloneContainer='#app'] - Selector para modo standalone
|
|
97
|
-
* @returns {Promise<boolean>} true si el registro fue exitoso
|
|
27
|
+
* Only activates when there's actual Shadow DOM retargeting — events
|
|
28
|
+
* in the light DOM pass through with zero overhead (no Proxy created).
|
|
98
29
|
*/
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
unmountApp();
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
try {
|
|
126
|
-
container.innerHTML = '';
|
|
127
|
-
|
|
128
|
-
// Dynamic import keeps Qwik out of the critical path
|
|
129
|
-
import('@builder.io/qwik').then(({ render, jsx }) => {
|
|
130
|
-
render(container, jsx(Component, props));
|
|
131
|
-
|
|
132
|
-
adapterState.apps.set(appName, {
|
|
133
|
-
container,
|
|
134
|
-
Component,
|
|
135
|
-
props
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
console.log(`[WuQwik] ${appName} mounted successfully`);
|
|
139
|
-
|
|
140
|
-
if (onMount) {
|
|
141
|
-
onMount(container);
|
|
30
|
+
function patchDocumentListenersForShadowDOM() {
|
|
31
|
+
if (_patched) return;
|
|
32
|
+
_patched = true;
|
|
33
|
+
|
|
34
|
+
const origAdd = document.addEventListener;
|
|
35
|
+
|
|
36
|
+
document.addEventListener = function (type, handler, options) {
|
|
37
|
+
const isCapture = options === true || (options && options.capture);
|
|
38
|
+
|
|
39
|
+
if (isCapture && typeof handler === 'function') {
|
|
40
|
+
const wrapped = function (event) {
|
|
41
|
+
const path = event.composedPath();
|
|
42
|
+
// Only proxy when Shadow DOM retargeting occurred
|
|
43
|
+
if (path.length && path[0] !== event.target && path[0] instanceof Element) {
|
|
44
|
+
const realTarget = path[0];
|
|
45
|
+
const proxy = new Proxy(event, {
|
|
46
|
+
get(obj, prop) {
|
|
47
|
+
if (prop === 'target') return realTarget;
|
|
48
|
+
const val = Reflect.get(obj, prop);
|
|
49
|
+
return typeof val === 'function' ? val.bind(obj) : val;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
return handler.call(this, proxy);
|
|
142
53
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
});
|
|
147
|
-
} catch (error) {
|
|
148
|
-
console.error(`[WuQwik] Mount error for ${appName}:`, error);
|
|
149
|
-
throw error;
|
|
54
|
+
return handler.call(this, event);
|
|
55
|
+
};
|
|
56
|
+
return origAdd.call(this, type, wrapped, options);
|
|
150
57
|
}
|
|
58
|
+
|
|
59
|
+
return origAdd.call(this, type, handler, options);
|
|
151
60
|
};
|
|
61
|
+
}
|
|
152
62
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const
|
|
63
|
+
export const wuQwik = {
|
|
64
|
+
async register(appName, Component, options = {}) {
|
|
65
|
+
const wu = window.wu || window.parent?.wu;
|
|
66
|
+
if (!wu) return;
|
|
156
67
|
|
|
157
|
-
|
|
158
|
-
try {
|
|
159
|
-
if (onUnmount) {
|
|
160
|
-
onUnmount(appData.container);
|
|
161
|
-
}
|
|
68
|
+
const qwik = await import('@builder.io/qwik');
|
|
162
69
|
|
|
163
|
-
|
|
164
|
-
|
|
70
|
+
// Patch document listeners BEFORE injecting qwikloader so its
|
|
71
|
+
// capture-phase handlers are wrapped with Shadow DOM awareness.
|
|
72
|
+
// Must stay active permanently — qwikloader registers event types
|
|
73
|
+
// lazily as new on: attributes appear in the DOM.
|
|
74
|
+
patchDocumentListenersForShadowDOM();
|
|
165
75
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
76
|
+
// Inject qwikloader once per document (event delegation for QRLs)
|
|
77
|
+
if (!document.__wu_qwikloader) {
|
|
78
|
+
document.__wu_qwikloader = true;
|
|
79
|
+
try {
|
|
80
|
+
const { QWIK_LOADER } = await import('@builder.io/qwik/loader');
|
|
81
|
+
const s = document.createElement('script');
|
|
82
|
+
s.textContent = QWIK_LOADER;
|
|
83
|
+
document.head.appendChild(s);
|
|
84
|
+
} catch (e) {
|
|
85
|
+
console.warn('[WuQwik] qwikloader not available:', e.message);
|
|
169
86
|
}
|
|
170
87
|
}
|
|
171
88
|
|
|
172
|
-
if (container) {
|
|
173
|
-
container.innerHTML = '';
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
// Register with Wu Framework or fall back to standalone
|
|
178
|
-
try {
|
|
179
|
-
const wu = await waitForWu(3000);
|
|
180
|
-
|
|
181
89
|
wu.define(appName, {
|
|
182
|
-
mount
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if (containerElement) {
|
|
196
|
-
console.log(`[WuQwik] Running ${appName} in standalone mode`);
|
|
197
|
-
mountApp(containerElement);
|
|
198
|
-
return true;
|
|
90
|
+
async mount(container) {
|
|
91
|
+
container.innerHTML = '';
|
|
92
|
+
try {
|
|
93
|
+
const vnode = qwik.jsx(Component, options.props || {});
|
|
94
|
+
await qwik.render(container, vnode);
|
|
95
|
+
} catch (e) {
|
|
96
|
+
console.error('[WuQwik] render error:', e);
|
|
97
|
+
container.innerHTML = '<pre style="color:#f66;padding:1rem">' + e.message + '</pre>';
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
unmount(container) {
|
|
101
|
+
container.innerHTML = '';
|
|
199
102
|
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return false;
|
|
103
|
+
});
|
|
203
104
|
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Public API
|
|
207
|
-
export const wuQwik = {
|
|
208
|
-
register,
|
|
209
|
-
getWuInstance,
|
|
210
|
-
waitForWu
|
|
211
105
|
};
|
|
212
106
|
|
|
213
|
-
export { register
|
|
107
|
+
export const { register } = wuQwik;
|
|
214
108
|
export default wuQwik;
|