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.
- 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 +94 -74
- 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
|
@@ -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
|
+
}
|