wu-framework 1.1.6 → 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.
Files changed (90) hide show
  1. package/README.md +511 -977
  2. package/dist/wu-framework.cjs.js +3 -1
  3. package/dist/wu-framework.cjs.js.map +1 -0
  4. package/dist/wu-framework.dev.js +7533 -2761
  5. package/dist/wu-framework.dev.js.map +1 -1
  6. package/dist/wu-framework.esm.js +3 -0
  7. package/dist/wu-framework.esm.js.map +1 -0
  8. package/dist/wu-framework.umd.js +3 -1
  9. package/dist/wu-framework.umd.js.map +1 -0
  10. package/integrations/astro/README.md +127 -0
  11. package/integrations/astro/WuApp.astro +63 -0
  12. package/integrations/astro/WuShell.astro +39 -0
  13. package/integrations/astro/index.js +68 -0
  14. package/integrations/astro/package.json +38 -0
  15. package/integrations/astro/types.d.ts +53 -0
  16. package/package.json +94 -74
  17. package/src/adapters/angular/ai.js +30 -0
  18. package/src/adapters/angular/index.d.ts +154 -0
  19. package/src/adapters/angular/index.js +932 -0
  20. package/src/adapters/angular.d.ts +3 -154
  21. package/src/adapters/angular.js +3 -813
  22. package/src/adapters/index.js +35 -24
  23. package/src/adapters/lit/ai.js +20 -0
  24. package/src/adapters/lit/index.d.ts +120 -0
  25. package/src/adapters/lit/index.js +721 -0
  26. package/src/adapters/lit.d.ts +3 -120
  27. package/src/adapters/lit.js +3 -726
  28. package/src/adapters/preact/ai.js +33 -0
  29. package/src/adapters/preact/index.d.ts +108 -0
  30. package/src/adapters/preact/index.js +661 -0
  31. package/src/adapters/preact.d.ts +3 -108
  32. package/src/adapters/preact.js +3 -665
  33. package/src/adapters/react/ai.js +135 -0
  34. package/src/adapters/react/index.d.ts +246 -0
  35. package/src/adapters/react/index.js +689 -0
  36. package/src/adapters/react.d.ts +3 -212
  37. package/src/adapters/react.js +3 -513
  38. package/src/adapters/shared.js +64 -0
  39. package/src/adapters/solid/ai.js +32 -0
  40. package/src/adapters/solid/index.d.ts +101 -0
  41. package/src/adapters/solid/index.js +586 -0
  42. package/src/adapters/solid.d.ts +3 -101
  43. package/src/adapters/solid.js +3 -591
  44. package/src/adapters/svelte/ai.js +31 -0
  45. package/src/adapters/svelte/index.d.ts +166 -0
  46. package/src/adapters/svelte/index.js +798 -0
  47. package/src/adapters/svelte.d.ts +3 -166
  48. package/src/adapters/svelte.js +3 -803
  49. package/src/adapters/vanilla/ai.js +30 -0
  50. package/src/adapters/vanilla/index.d.ts +179 -0
  51. package/src/adapters/vanilla/index.js +785 -0
  52. package/src/adapters/vanilla.d.ts +3 -179
  53. package/src/adapters/vanilla.js +3 -791
  54. package/src/adapters/vue/ai.js +52 -0
  55. package/src/adapters/vue/index.d.ts +299 -0
  56. package/src/adapters/vue/index.js +608 -0
  57. package/src/adapters/vue.d.ts +3 -299
  58. package/src/adapters/vue.js +3 -611
  59. package/src/ai/wu-ai-actions.js +261 -0
  60. package/src/ai/wu-ai-browser.js +663 -0
  61. package/src/ai/wu-ai-context.js +332 -0
  62. package/src/ai/wu-ai-conversation.js +554 -0
  63. package/src/ai/wu-ai-permissions.js +381 -0
  64. package/src/ai/wu-ai-provider.js +605 -0
  65. package/src/ai/wu-ai-schema.js +225 -0
  66. package/src/ai/wu-ai-triggers.js +396 -0
  67. package/src/ai/wu-ai.js +474 -0
  68. package/src/core/wu-app.js +50 -8
  69. package/src/core/wu-cache.js +1 -1
  70. package/src/core/wu-core.js +645 -677
  71. package/src/core/wu-html-parser.js +121 -211
  72. package/src/core/wu-iframe-sandbox.js +328 -0
  73. package/src/core/wu-mcp-bridge.js +647 -0
  74. package/src/core/wu-overrides.js +510 -0
  75. package/src/core/wu-prefetch.js +414 -0
  76. package/src/core/wu-proxy-sandbox.js +398 -75
  77. package/src/core/wu-sandbox.js +86 -268
  78. package/src/core/wu-script-executor.js +79 -182
  79. package/src/core/wu-snapshot-sandbox.js +149 -106
  80. package/src/core/wu-strategies.js +13 -0
  81. package/src/core/wu-style-bridge.js +0 -2
  82. package/src/index.js +139 -665
  83. package/dist/wu-framework.hex.js +0 -23
  84. package/dist/wu-framework.min.js +0 -1
  85. package/dist/wu-framework.obf.js +0 -1
  86. package/scripts/build-protected.js +0 -366
  87. package/scripts/build.js +0 -212
  88. package/scripts/rollup-plugin-hex.js +0 -143
  89. package/src/core/wu-registry.js +0 -60
  90. package/src/core/wu-sandbox-pool.js +0 -390
@@ -0,0 +1,414 @@
1
+ /**
2
+ * WU-PREFETCH: Intelligent Prefetching with Speculation Rules API
3
+ *
4
+ * Prefetches microfrontend modules BEFORE they're needed using:
5
+ * 1. Speculation Rules API (Chrome 121+) — browser-native prerender/prefetch
6
+ * 2. <link rel="modulepreload"> — ES module prefetch (all modern browsers)
7
+ * 3. <link rel="prefetch"> — generic fallback
8
+ *
9
+ * Trigger modes:
10
+ * - immediate: prefetch now
11
+ * - hover: prefetch when user hovers a target element
12
+ * - visible: prefetch when target element enters viewport (IntersectionObserver)
13
+ * - idle: prefetch during browser idle time (requestIdleCallback)
14
+ *
15
+ * @example
16
+ * // Prefetch immediately
17
+ * wu.prefetch('cart');
18
+ *
19
+ * // Prefetch when user hovers the cart link
20
+ * wu.prefetch('cart', { on: 'hover', target: '#cart-link' });
21
+ *
22
+ * // Prefetch when the section becomes visible
23
+ * wu.prefetch('cart', { on: 'visible', target: '#cart-section' });
24
+ *
25
+ * // Prefetch during idle time
26
+ * wu.prefetch('cart', { on: 'idle' });
27
+ *
28
+ * // Prefetch multiple apps with eagerness control
29
+ * wu.prefetch(['cart', 'profile'], { eagerness: 'moderate' });
30
+ */
31
+
32
+ import { logger } from './wu-logger.js';
33
+
34
+ export class WuPrefetch {
35
+ constructor(core) {
36
+ this.core = core;
37
+
38
+ // Track what we've already prefetched to avoid duplicates
39
+ this.prefetched = new Set();
40
+
41
+ // Active observers and listeners (for cleanup)
42
+ this._observers = new Map();
43
+ this._listeners = [];
44
+
45
+ // Speculation Rules script element (one per page, updated dynamically)
46
+ this._speculationScript = null;
47
+ this._speculationRules = { prefetch: [], prerender: [] };
48
+
49
+ // Detect browser support
50
+ this.supportsSpeculationRules = this._detectSpeculationRules();
51
+ this.supportsModulePreload = this._detectModulePreload();
52
+
53
+ logger.wuDebug(
54
+ `[WuPrefetch] Initialized — ` +
55
+ `Speculation Rules: ${this.supportsSpeculationRules ? 'yes' : 'no'}, ` +
56
+ `Module Preload: ${this.supportsModulePreload ? 'yes' : 'no'}`
57
+ );
58
+ }
59
+
60
+ // ─── Detection ───────────────────────────────────────────────
61
+
62
+ _detectSpeculationRules() {
63
+ if (typeof HTMLScriptElement === 'undefined') return false;
64
+ return HTMLScriptElement.supports?.('speculationrules') ?? false;
65
+ }
66
+
67
+ _detectModulePreload() {
68
+ if (typeof document === 'undefined') return false;
69
+ const link = document.createElement('link');
70
+ return link.relList?.supports?.('modulepreload') ?? false;
71
+ }
72
+
73
+ // ─── Main API ────────────────────────────────────────────────
74
+
75
+ /**
76
+ * Prefetch one or more apps.
77
+ *
78
+ * @param {string|string[]} appNames - App name(s) to prefetch
79
+ * @param {Object} [options]
80
+ * @param {'immediate'|'hover'|'visible'|'idle'} [options.on='immediate'] - Trigger mode
81
+ * @param {string|Element} [options.target] - CSS selector or element (for hover/visible)
82
+ * @param {'conservative'|'moderate'|'eager'} [options.eagerness='moderate'] - Speculation Rules eagerness
83
+ * @returns {Promise<void>|Function} Promise for immediate, cleanup function for deferred
84
+ */
85
+ async prefetch(appNames, options = {}) {
86
+ const names = Array.isArray(appNames) ? appNames : [appNames];
87
+ const trigger = options.on || 'immediate';
88
+
89
+ switch (trigger) {
90
+ case 'immediate':
91
+ return this._prefetchImmediate(names, options);
92
+
93
+ case 'hover':
94
+ return this._prefetchOnHover(names, options);
95
+
96
+ case 'visible':
97
+ return this._prefetchOnVisible(names, options);
98
+
99
+ case 'idle':
100
+ return this._prefetchOnIdle(names, options);
101
+
102
+ default:
103
+ logger.wuWarn(`[WuPrefetch] Unknown trigger "${trigger}", using immediate`);
104
+ return this._prefetchImmediate(names, options);
105
+ }
106
+ }
107
+
108
+ // ─── Immediate Prefetch ──────────────────────────────────────
109
+
110
+ async _prefetchImmediate(appNames, options) {
111
+ const urls = await this._resolveAppUrls(appNames);
112
+ if (urls.length === 0) return;
113
+
114
+ // Mark all as prefetched by name (prevents duplicate resolution)
115
+ urls.forEach(({ name }) => this.prefetched.add(name));
116
+
117
+ // Strategy 1: Speculation Rules API (Chrome 121+)
118
+ if (this.supportsSpeculationRules) {
119
+ this._addSpeculationRules(urls, options.eagerness || 'moderate');
120
+ return;
121
+ }
122
+
123
+ // Strategy 2: <link rel="modulepreload"> for ES modules
124
+ if (this.supportsModulePreload) {
125
+ urls.forEach(({ url }) => this._injectModulePreload(url));
126
+ return;
127
+ }
128
+
129
+ // Strategy 3: <link rel="prefetch"> fallback
130
+ urls.forEach(({ url }) => this._injectPrefetch(url));
131
+ }
132
+
133
+ // ─── Hover Trigger ───────────────────────────────────────────
134
+
135
+ _prefetchOnHover(appNames, options) {
136
+ const target = this._resolveTarget(options.target);
137
+ if (!target) {
138
+ logger.wuWarn('[WuPrefetch] hover trigger requires a target element or selector');
139
+ return () => {};
140
+ }
141
+
142
+ let done = false;
143
+
144
+ const handler = () => {
145
+ if (done) return;
146
+ done = true;
147
+ this._prefetchImmediate(appNames, options);
148
+ // One-shot: remove after first trigger
149
+ target.removeEventListener('mouseenter', handler);
150
+ target.removeEventListener('focusin', handler);
151
+ };
152
+
153
+ // Mouse hover OR keyboard focus
154
+ target.addEventListener('mouseenter', handler, { passive: true });
155
+ target.addEventListener('focusin', handler, { passive: true });
156
+
157
+ const cleanup = () => {
158
+ target.removeEventListener('mouseenter', handler);
159
+ target.removeEventListener('focusin', handler);
160
+ };
161
+
162
+ this._listeners.push(cleanup);
163
+ return cleanup;
164
+ }
165
+
166
+ // ─── Visibility Trigger (IntersectionObserver) ───────────────
167
+
168
+ _prefetchOnVisible(appNames, options) {
169
+ const target = this._resolveTarget(options.target);
170
+ if (!target) {
171
+ logger.wuWarn('[WuPrefetch] visible trigger requires a target element or selector');
172
+ return () => {};
173
+ }
174
+
175
+ if (typeof IntersectionObserver === 'undefined') {
176
+ // No IntersectionObserver → prefetch immediately
177
+ this._prefetchImmediate(appNames, options);
178
+ return () => {};
179
+ }
180
+
181
+ const observer = new IntersectionObserver(
182
+ (entries) => {
183
+ for (const entry of entries) {
184
+ if (entry.isIntersecting) {
185
+ this._prefetchImmediate(appNames, options);
186
+ observer.disconnect();
187
+ this._observers.delete(target);
188
+ break;
189
+ }
190
+ }
191
+ },
192
+ { rootMargin: '200px' } // Start prefetching 200px before visible
193
+ );
194
+
195
+ observer.observe(target);
196
+ this._observers.set(target, observer);
197
+
198
+ const cleanup = () => {
199
+ observer.disconnect();
200
+ this._observers.delete(target);
201
+ };
202
+
203
+ return cleanup;
204
+ }
205
+
206
+ // ─── Idle Trigger ────────────────────────────────────────────
207
+
208
+ _prefetchOnIdle(appNames, options) {
209
+ const callback = () => this._prefetchImmediate(appNames, options);
210
+
211
+ if ('requestIdleCallback' in window) {
212
+ const id = requestIdleCallback(callback, { timeout: 3000 });
213
+ const cleanup = () => cancelIdleCallback(id);
214
+ this._listeners.push(cleanup);
215
+ return cleanup;
216
+ }
217
+
218
+ // Fallback: setTimeout 2s
219
+ const id = setTimeout(callback, 2000);
220
+ const cleanup = () => clearTimeout(id);
221
+ this._listeners.push(cleanup);
222
+ return cleanup;
223
+ }
224
+
225
+ // ─── Speculation Rules API ───────────────────────────────────
226
+
227
+ _addSpeculationRules(urls, eagerness) {
228
+ const newEntries = urls.filter(({ name }) => !this.prefetched.has(name));
229
+ if (newEntries.length === 0) return;
230
+
231
+ // Mark as prefetched
232
+ newEntries.forEach(({ name }) => this.prefetched.add(name));
233
+
234
+ // Build URL list for speculation rules
235
+ const urlList = newEntries.map(({ url }) => url);
236
+
237
+ // Add prefetch rule
238
+ this._speculationRules.prefetch.push({
239
+ source: 'list',
240
+ urls: urlList,
241
+ eagerness
242
+ });
243
+
244
+ // Inject or update the speculation rules script
245
+ this._updateSpeculationScript();
246
+
247
+ logger.wuDebug(
248
+ `[WuPrefetch] Speculation Rules: prefetch ${newEntries.map(e => e.name).join(', ')} ` +
249
+ `(eagerness: ${eagerness})`
250
+ );
251
+ }
252
+
253
+ _updateSpeculationScript() {
254
+ // Remove existing script (spec requires replacing, not updating)
255
+ if (this._speculationScript) {
256
+ this._speculationScript.remove();
257
+ }
258
+
259
+ const script = document.createElement('script');
260
+ script.type = 'speculationrules';
261
+ script.textContent = JSON.stringify(this._speculationRules);
262
+ document.head.appendChild(script);
263
+
264
+ this._speculationScript = script;
265
+ }
266
+
267
+ // ─── Module Preload ──────────────────────────────────────────
268
+
269
+ _injectModulePreload(url) {
270
+ if (this.prefetched.has(url)) return;
271
+ this.prefetched.add(url);
272
+
273
+ const link = document.createElement('link');
274
+ link.rel = 'modulepreload';
275
+ link.href = url;
276
+ document.head.appendChild(link);
277
+
278
+ logger.wuDebug(`[WuPrefetch] modulepreload: ${url}`);
279
+ }
280
+
281
+ // ─── Generic Prefetch ────────────────────────────────────────
282
+
283
+ _injectPrefetch(url) {
284
+ if (this.prefetched.has(url)) return;
285
+ this.prefetched.add(url);
286
+
287
+ const link = document.createElement('link');
288
+ link.rel = 'prefetch';
289
+ link.href = url;
290
+ link.as = 'script';
291
+ document.head.appendChild(link);
292
+
293
+ logger.wuDebug(`[WuPrefetch] prefetch: ${url}`);
294
+ }
295
+
296
+ // ─── URL Resolution ──────────────────────────────────────────
297
+
298
+ /**
299
+ * Resolve app names to their module URLs.
300
+ * Skips apps that are already mounted, already prefetched, or not registered.
301
+ */
302
+ async _resolveAppUrls(appNames) {
303
+ const results = [];
304
+
305
+ for (const name of appNames) {
306
+ // Skip if already prefetched
307
+ if (this.prefetched.has(name)) {
308
+ logger.wuDebug(`[WuPrefetch] ${name} already prefetched, skipping`);
309
+ continue;
310
+ }
311
+
312
+ // Skip if already mounted (no need to prefetch)
313
+ if (this.core.mounted.has(name)) {
314
+ logger.wuDebug(`[WuPrefetch] ${name} already mounted, skipping`);
315
+ continue;
316
+ }
317
+
318
+ // Skip if already loaded (definition exists)
319
+ if (this.core.definitions.has(name)) {
320
+ logger.wuDebug(`[WuPrefetch] ${name} already defined, skipping`);
321
+ continue;
322
+ }
323
+
324
+ const app = this.core.apps.get(name);
325
+ if (!app) {
326
+ logger.wuWarn(`[WuPrefetch] App "${name}" not registered, cannot prefetch`);
327
+ continue;
328
+ }
329
+
330
+ try {
331
+ const url = await this.core.resolveModulePath(app);
332
+ results.push({ name, url });
333
+ } catch (error) {
334
+ logger.wuWarn(`[WuPrefetch] Failed to resolve URL for "${name}":`, error.message);
335
+ }
336
+ }
337
+
338
+ return results;
339
+ }
340
+
341
+ // ─── Helpers ─────────────────────────────────────────────────
342
+
343
+ _resolveTarget(target) {
344
+ if (!target) return null;
345
+ if (typeof target === 'string') return document.querySelector(target);
346
+ if (target instanceof Element) return target;
347
+ return null;
348
+ }
349
+
350
+ // ─── Prefetch All Registered (utility) ───────────────────────
351
+
352
+ /**
353
+ * Prefetch all registered but not-yet-mounted apps.
354
+ * Useful for aggressive prefetching after initial load.
355
+ *
356
+ * @param {Object} [options] - Same options as prefetch()
357
+ */
358
+ async prefetchAll(options = {}) {
359
+ const unmountedApps = [];
360
+ for (const [name] of this.core.apps) {
361
+ if (!this.core.mounted.has(name) && !this.prefetched.has(name)) {
362
+ unmountedApps.push(name);
363
+ }
364
+ }
365
+
366
+ if (unmountedApps.length === 0) {
367
+ logger.wuDebug('[WuPrefetch] No apps to prefetch');
368
+ return;
369
+ }
370
+
371
+ logger.wuDebug(`[WuPrefetch] Prefetching all: ${unmountedApps.join(', ')}`);
372
+ return this.prefetch(unmountedApps, options);
373
+ }
374
+
375
+ // ─── Stats ───────────────────────────────────────────────────
376
+
377
+ getStats() {
378
+ return {
379
+ prefetched: [...this.prefetched],
380
+ activeObservers: this._observers.size,
381
+ activeListeners: this._listeners.length,
382
+ speculationRulesSupported: this.supportsSpeculationRules,
383
+ modulePreloadSupported: this.supportsModulePreload,
384
+ speculationRules: this._speculationRules
385
+ };
386
+ }
387
+
388
+ // ─── Cleanup ─────────────────────────────────────────────────
389
+
390
+ cleanup() {
391
+ // Disconnect all IntersectionObservers
392
+ for (const [, observer] of this._observers) {
393
+ observer.disconnect();
394
+ }
395
+ this._observers.clear();
396
+
397
+ // Remove all event listeners
398
+ for (const cleanup of this._listeners) {
399
+ cleanup();
400
+ }
401
+ this._listeners = [];
402
+
403
+ // Remove speculation rules script
404
+ if (this._speculationScript) {
405
+ this._speculationScript.remove();
406
+ this._speculationScript = null;
407
+ }
408
+
409
+ this._speculationRules = { prefetch: [], prerender: [] };
410
+ this.prefetched.clear();
411
+
412
+ logger.wuDebug('[WuPrefetch] Cleaned up');
413
+ }
414
+ }