wu-framework 1.1.7 โ 1.1.9
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/LICENSE +19 -1
- package/README.md +257 -1122
- package/dist/wu-framework.cjs.js +3 -1
- package/dist/wu-framework.cjs.js.map +1 -0
- package/dist/wu-framework.dev.js +9867 -3183
- 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 +96 -72
- 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 +694 -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-agent.js +546 -0
- package/src/ai/wu-ai-browser-primitives.js +354 -0
- package/src/ai/wu-ai-browser.js +380 -0
- package/src/ai/wu-ai-context.js +332 -0
- package/src/ai/wu-ai-conversation.js +613 -0
- package/src/ai/wu-ai-orchestrate.js +1021 -0
- package/src/ai/wu-ai-permissions.js +381 -0
- package/src/ai/wu-ai-provider.js +700 -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 +804 -0
- package/src/core/wu-app.js +50 -8
- package/src/core/wu-cache.js +2 -3
- package/src/core/wu-core.js +648 -681
- 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 +431 -0
- package/src/core/wu-overrides.js +510 -0
- package/src/core/wu-plugin.js +4 -1
- 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 +23 -23
- package/src/index.js +162 -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/src/core/wu-core.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Motor principal
|
|
2
|
+
* WU-FRAMEWORK: UNIVERSAL MICROFRONTENDS
|
|
3
|
+
* Motor principal agnostico - Funciona con cualquier framework
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { WuLoader } from './wu-loader.js';
|
|
@@ -16,8 +16,11 @@ import { WuPluginSystem } from './wu-plugin.js';
|
|
|
16
16
|
import { WuLoadingStrategy } from './wu-strategies.js';
|
|
17
17
|
import { WuErrorBoundary } from './wu-error-boundary.js';
|
|
18
18
|
import { WuLifecycleHooks } from './wu-hooks.js';
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
19
|
+
import { WuHtmlParser } from './wu-html-parser.js';
|
|
20
|
+
import { WuScriptExecutor } from './wu-script-executor.js';
|
|
21
|
+
import { WuIframeSandbox } from './wu-iframe-sandbox.js';
|
|
22
|
+
import { WuPrefetch } from './wu-prefetch.js';
|
|
23
|
+
import { WuOverrides } from './wu-overrides.js';
|
|
21
24
|
|
|
22
25
|
export class WuCore {
|
|
23
26
|
constructor(options = {}) {
|
|
@@ -26,6 +29,7 @@ export class WuCore {
|
|
|
26
29
|
this.definitions = new Map(); // Definiciones de lifecycle
|
|
27
30
|
this.manifests = new Map(); // Manifiestos cargados
|
|
28
31
|
this.mounted = new Map(); // Apps montadas
|
|
32
|
+
this.hidden = new Map(); // Keep-alive hidden apps
|
|
29
33
|
|
|
30
34
|
// Componentes core
|
|
31
35
|
this.loader = new WuLoader();
|
|
@@ -33,451 +37,84 @@ export class WuCore {
|
|
|
33
37
|
this.manifest = new WuManifest();
|
|
34
38
|
this.store = store;
|
|
35
39
|
|
|
36
|
-
//
|
|
40
|
+
// Strict sandbox support: HTML entry + script execution in proxy
|
|
41
|
+
this.htmlParser = new WuHtmlParser();
|
|
42
|
+
this.scriptExecutor = new WuScriptExecutor();
|
|
43
|
+
|
|
44
|
+
// Sistemas esenciales
|
|
37
45
|
this.cache = new WuCache({ storage: 'localStorage', maxSize: 100 }); // 100MB cache
|
|
38
46
|
this.eventBus = new WuEventBus();
|
|
39
47
|
this.performance = new WuPerformance();
|
|
40
48
|
|
|
41
|
-
//
|
|
49
|
+
// Advanced systems
|
|
42
50
|
this.pluginSystem = new WuPluginSystem(this);
|
|
43
51
|
this.strategies = new WuLoadingStrategy(this);
|
|
44
52
|
this.errorBoundary = new WuErrorBoundary(this);
|
|
45
53
|
this.hooks = new WuLifecycleHooks(this);
|
|
46
|
-
this.
|
|
47
|
-
this.
|
|
48
|
-
|
|
49
|
-
// ๐ HEALTH MONITORING CONFIG
|
|
50
|
-
this.healthConfig = {
|
|
51
|
-
enabled: options.healthMonitoring !== false, // Default: enabled
|
|
52
|
-
autoHeal: options.autoHeal !== false, // Default: enabled
|
|
53
|
-
agingThreshold: options.agingThreshold || Infinity, // Default: never refresh
|
|
54
|
-
checkInterval: options.healthCheckInterval || 60000 // Default: 60s
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
// ๐ HEALTH MONITORING STATE
|
|
58
|
-
this.healthState = {
|
|
59
|
-
monitor: null,
|
|
60
|
-
healingInProgress: new Set(),
|
|
61
|
-
systemStable: true,
|
|
62
|
-
lastHealthCheck: Date.now(),
|
|
63
|
-
mutationObserver: null,
|
|
64
|
-
mutationCheckTimeout: null
|
|
65
|
-
};
|
|
54
|
+
this.prefetcher = new WuPrefetch(this);
|
|
55
|
+
this.overrides = new WuOverrides();
|
|
66
56
|
|
|
67
57
|
// Estado
|
|
68
58
|
this.isInitialized = false;
|
|
69
59
|
|
|
70
|
-
|
|
71
|
-
if (this.healthConfig.enabled) {
|
|
72
|
-
this.initializeHealthMonitoring();
|
|
73
|
-
logger.wuInfo('๐ Wu Framework initialized - Universal Microfrontends with Self-Healing');
|
|
74
|
-
} else {
|
|
75
|
-
logger.wuInfo('๐ Wu Framework initialized - Universal Microfrontends (Health Monitoring Disabled)');
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* ๐ HEALTH MONITORING INITIALIZATION
|
|
81
|
-
*/
|
|
82
|
-
initializeHealthMonitoring() {
|
|
83
|
-
// ๐ซ Optimized health monitor with MutationObserver + reduced polling
|
|
84
|
-
this.healthState.monitor = setInterval(() => {
|
|
85
|
-
this.performHealthCheck();
|
|
86
|
-
}, this.healthConfig.checkInterval);
|
|
87
|
-
|
|
88
|
-
// ๐ Smart MutationObserver for real-time DOM changes
|
|
89
|
-
this.initializeMutationObserver();
|
|
90
|
-
|
|
91
|
-
// ๐ก๏ธ Global error recovery (ONLY if autoHeal enabled)
|
|
92
|
-
if (this.healthConfig.autoHeal) {
|
|
93
|
-
window.addEventListener('error', (event) => {
|
|
94
|
-
this.handleErrorEvent(event);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
// ๐ Unhandled promise rejection recovery
|
|
98
|
-
window.addEventListener('unhandledrejection', (event) => {
|
|
99
|
-
this.handleRejection(event);
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
logger.wuInfo(`๐ Health monitoring initialized - Interval: ${this.healthConfig.checkInterval}ms, AutoHeal: ${this.healthConfig.autoHeal}`);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* ๐ SMART MUTATION OBSERVER: Observa SOLO los contenedores de apps montadas
|
|
108
|
-
* FIX: Ya no observa todo document.body (memory leak)
|
|
109
|
-
*/
|
|
110
|
-
initializeMutationObserver() {
|
|
111
|
-
if (!window.MutationObserver) return;
|
|
112
|
-
|
|
113
|
-
// Map para trackear observers por contenedor (evita memory leak)
|
|
114
|
-
this.healthState.containerObservers = new Map();
|
|
115
|
-
|
|
116
|
-
// Callback compartido para todos los observers
|
|
117
|
-
this.healthState.mutationCallback = (mutations, observer) => {
|
|
118
|
-
let affectedAppName = null;
|
|
119
|
-
|
|
120
|
-
for (const mutation of mutations) {
|
|
121
|
-
if (mutation.type === 'childList' && mutation.removedNodes.length > 0) {
|
|
122
|
-
// Encontrar quรฉ app fue afectada
|
|
123
|
-
for (const [appName, mounted] of this.mounted) {
|
|
124
|
-
const container = mounted.hostContainer || mounted.container;
|
|
125
|
-
if (mutation.target === container ||
|
|
126
|
-
mutation.target.contains?.(container)) {
|
|
127
|
-
affectedAppName = appName;
|
|
128
|
-
break;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
if (affectedAppName) break;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (affectedAppName) {
|
|
136
|
-
clearTimeout(this.healthState.mutationCheckTimeout);
|
|
137
|
-
this.healthState.mutationCheckTimeout = setTimeout(() => {
|
|
138
|
-
this.performHealthCheck();
|
|
139
|
-
}, 1000);
|
|
140
|
-
}
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
logger.wuDebug('๐ MutationObserver system initialized (lazy per-container)');
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* ๐ Observar contenedor especรญfico cuando se monta una app
|
|
148
|
-
*/
|
|
149
|
-
observeContainer(appName, container) {
|
|
150
|
-
if (!this.healthState.containerObservers || !container) return;
|
|
151
|
-
|
|
152
|
-
// No observar si ya existe
|
|
153
|
-
if (this.healthState.containerObservers.has(appName)) return;
|
|
154
|
-
|
|
155
|
-
const observer = new MutationObserver(this.healthState.mutationCallback);
|
|
156
|
-
|
|
157
|
-
// Observar solo el contenedor padre directo (no subtree profundo)
|
|
158
|
-
const parentToObserve = container.parentElement || container;
|
|
159
|
-
observer.observe(parentToObserve, {
|
|
160
|
-
childList: true,
|
|
161
|
-
subtree: false // โ
Solo hijos directos, no todo el รกrbol
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
this.healthState.containerObservers.set(appName, observer);
|
|
165
|
-
logger.wuDebug(`๐ Observing container for ${appName}`);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* ๐ Dejar de observar contenedor cuando se desmonta
|
|
170
|
-
*/
|
|
171
|
-
unobserveContainer(appName) {
|
|
172
|
-
if (!this.healthState.containerObservers) return;
|
|
173
|
-
|
|
174
|
-
const observer = this.healthState.containerObservers.get(appName);
|
|
175
|
-
if (observer) {
|
|
176
|
-
observer.disconnect();
|
|
177
|
-
this.healthState.containerObservers.delete(appName);
|
|
178
|
-
logger.wuDebug(`๐ Stopped observing container for ${appName}`);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* ๐ HEALTH CHECK: Continuous app monitoring
|
|
184
|
-
*/
|
|
185
|
-
async performHealthCheck() {
|
|
186
|
-
try {
|
|
187
|
-
const now = Date.now();
|
|
188
|
-
const healthIssues = [];
|
|
189
|
-
|
|
190
|
-
// Check mounted apps vitality
|
|
191
|
-
for (const [appName, mountedApp] of this.mounted) {
|
|
192
|
-
const age = now - mountedApp.timestamp;
|
|
193
|
-
const container = mountedApp.container;
|
|
194
|
-
|
|
195
|
-
// ๐ก๏ธ ENHANCED CHECK: Verify container exists and is properly connected
|
|
196
|
-
// IMPORTANT: Check multiple conditions to avoid false positives
|
|
197
|
-
let isOrphaned = false;
|
|
198
|
-
|
|
199
|
-
try {
|
|
200
|
-
// Check 1: Direct DOM connection
|
|
201
|
-
const containerInDOM = document.contains(container);
|
|
202
|
-
|
|
203
|
-
// Check 2: Parent element connection (for shadow DOM)
|
|
204
|
-
const parentInDOM = container.parentElement && document.contains(container.parentElement);
|
|
205
|
-
|
|
206
|
-
// Check 3: Shadow root connection
|
|
207
|
-
const shadowHost = mountedApp.sandbox?.shadowRoot?.host;
|
|
208
|
-
const shadowHostInDOM = shadowHost && document.contains(shadowHost);
|
|
209
|
-
|
|
210
|
-
// Check 4: Container still has valid properties (not detached)
|
|
211
|
-
const hasValidParent = container.parentElement !== null || container.parentNode !== null;
|
|
212
|
-
|
|
213
|
-
// Only mark as orphaned if ALL checks fail
|
|
214
|
-
if (!containerInDOM && !parentInDOM && !shadowHostInDOM && !hasValidParent) {
|
|
215
|
-
isOrphaned = true;
|
|
216
|
-
}
|
|
217
|
-
} catch (error) {
|
|
218
|
-
// If checking fails, assume NOT orphaned (conservative approach)
|
|
219
|
-
console.warn(`[Wu] โ ๏ธ Container check failed for ${appName}:`, error);
|
|
220
|
-
isOrphaned = false;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if (isOrphaned) {
|
|
224
|
-
healthIssues.push({
|
|
225
|
-
type: 'orphaned_container',
|
|
226
|
-
appName,
|
|
227
|
-
severity: 'high'
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Check app state - ONLY if it's truly unstable, not 'refreshed'
|
|
232
|
-
// 'refreshed' is a normal state after aging refresh
|
|
233
|
-
if (mountedApp.state && mountedApp.state !== 'stable' && mountedApp.state !== 'refreshed') {
|
|
234
|
-
healthIssues.push({
|
|
235
|
-
type: 'unstable_state',
|
|
236
|
-
appName,
|
|
237
|
-
severity: 'medium'
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// ๐ฐ๏ธ Check for long-running apps that might need refresh
|
|
242
|
-
// ONLY if agingThreshold is configured (default: Infinity = never)
|
|
243
|
-
if (age > this.healthConfig.agingThreshold) {
|
|
244
|
-
healthIssues.push({
|
|
245
|
-
type: 'aging_app',
|
|
246
|
-
appName,
|
|
247
|
-
severity: 'low',
|
|
248
|
-
age: age
|
|
249
|
-
});
|
|
250
|
-
console.log(`[Wu] โฐ App ${appName} has been running for ${(age / 1000 / 60).toFixed(1)} minutes`);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Process health issues
|
|
255
|
-
if (healthIssues.length > 0) {
|
|
256
|
-
await this.processHealthIssues(healthIssues);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
this.healthState.lastHealthCheck = now;
|
|
260
|
-
|
|
261
|
-
} catch (error) {
|
|
262
|
-
console.warn('[Wu] โ ๏ธ Health check failed:', error);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* ๐ ๏ธ HEALTH ISSUE PROCESSOR
|
|
268
|
-
*/
|
|
269
|
-
async processHealthIssues(issues) {
|
|
270
|
-
for (const issue of issues) {
|
|
271
|
-
if (this.healthState.healingInProgress.has(issue.appName)) {
|
|
272
|
-
continue; // Already healing
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
console.log(`[Wu] ๐ฉบ Health issue detected:`, issue);
|
|
276
|
-
|
|
277
|
-
switch (issue.type) {
|
|
278
|
-
case 'orphaned_container':
|
|
279
|
-
await this.healOrphanedContainer(issue.appName);
|
|
280
|
-
break;
|
|
281
|
-
case 'unstable_state':
|
|
282
|
-
await this.stabilizeAppState(issue.appName);
|
|
283
|
-
break;
|
|
284
|
-
case 'aging_app':
|
|
285
|
-
await this.refreshAgingApp(issue.appName);
|
|
286
|
-
break;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* ๐ ORPHANED CONTAINER HEALING
|
|
293
|
-
*/
|
|
294
|
-
async healOrphanedContainer(appName) {
|
|
295
|
-
this.healthState.healingInProgress.add(appName);
|
|
296
|
-
|
|
297
|
-
try {
|
|
298
|
-
console.log(`[Wu] ๐ Healing orphaned container for ${appName}`);
|
|
299
|
-
|
|
300
|
-
// Clean up orphaned state
|
|
301
|
-
await this.unmount(appName);
|
|
302
|
-
|
|
303
|
-
// ๐ Find a suitable container to remount
|
|
304
|
-
const suitableContainers = [
|
|
305
|
-
document.querySelector(`[data-wu-app="${appName}"]`),
|
|
306
|
-
document.querySelector(`#${appName}-container`),
|
|
307
|
-
document.querySelector(`.${appName}-container`),
|
|
308
|
-
document.querySelector(`[id*="${appName}"]`)
|
|
309
|
-
].filter(Boolean);
|
|
310
|
-
|
|
311
|
-
if (suitableContainers.length > 0) {
|
|
312
|
-
const container = suitableContainers[0];
|
|
313
|
-
const containerSelector = container.id ? `#${container.id}` : `.${container.className.split(' ')[0]}`;
|
|
314
|
-
|
|
315
|
-
// ๐ฏ Only attempt re-mount if selector is valid
|
|
316
|
-
if (containerSelector && containerSelector !== '#' && containerSelector !== '.') {
|
|
317
|
-
await this.mount(appName, containerSelector);
|
|
318
|
-
console.log(`[Wu] โจ Successfully healed orphaned ${appName} in ${containerSelector}`);
|
|
319
|
-
} else {
|
|
320
|
-
console.warn(`[Wu] โ ๏ธ Could not determine valid selector for ${appName}`);
|
|
321
|
-
}
|
|
322
|
-
} else {
|
|
323
|
-
console.warn(`[Wu] โ ๏ธ No suitable container found for healing ${appName}`);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
} catch (error) {
|
|
327
|
-
console.warn(`[Wu] โ ๏ธ Failed to heal orphaned ${appName}:`, error);
|
|
328
|
-
} finally {
|
|
329
|
-
this.healthState.healingInProgress.delete(appName);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* โ๏ธ APP STATE STABILIZATION
|
|
335
|
-
*/
|
|
336
|
-
async stabilizeAppState(appName) {
|
|
337
|
-
this.healthState.healingInProgress.add(appName);
|
|
338
|
-
|
|
339
|
-
try {
|
|
340
|
-
console.log(`[Wu] โ๏ธ Stabilizing app state for ${appName}`);
|
|
341
|
-
|
|
342
|
-
const mounted = this.mounted.get(appName);
|
|
343
|
-
if (mounted) {
|
|
344
|
-
mounted.state = 'stable';
|
|
345
|
-
// โ ๏ธ DO NOT RESET TIMESTAMP - preserves app age for aging checks
|
|
346
|
-
console.log(`[Wu] โจ App state stabilized for ${appName}`);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
} catch (error) {
|
|
350
|
-
console.warn(`[Wu] โ ๏ธ Failed to stabilize ${appName}:`, error);
|
|
351
|
-
} finally {
|
|
352
|
-
this.healthState.healingInProgress.delete(appName);
|
|
353
|
-
}
|
|
60
|
+
logger.wuInfo('Wu Framework initialized - Universal Microfrontends');
|
|
354
61
|
}
|
|
355
62
|
|
|
356
63
|
/**
|
|
357
|
-
*
|
|
358
|
-
*
|
|
359
|
-
*/
|
|
360
|
-
async refreshAgingApp(appName) {
|
|
361
|
-
// Safety check: Don't refresh if aging is disabled (Infinity)
|
|
362
|
-
if (this.healthConfig.agingThreshold === Infinity) {
|
|
363
|
-
console.warn(`[Wu] โ ๏ธ refreshAgingApp called but agingThreshold is Infinity - skipping`);
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
this.healthState.healingInProgress.add(appName);
|
|
368
|
-
|
|
369
|
-
try {
|
|
370
|
-
console.log(`[Wu] ๐ฐ๏ธ Refreshing aging app for ${appName}`);
|
|
371
|
-
|
|
372
|
-
const mounted = this.mounted.get(appName);
|
|
373
|
-
if (mounted) {
|
|
374
|
-
// Gentle refresh without full remount
|
|
375
|
-
// Reset timestamp ONLY when explicitly configured aging refresh
|
|
376
|
-
mounted.timestamp = Date.now();
|
|
377
|
-
mounted.state = 'refreshed';
|
|
378
|
-
|
|
379
|
-
// Trigger a soft refresh of the app if it supports it
|
|
380
|
-
if (mounted.lifecycle.refresh) {
|
|
381
|
-
await mounted.lifecycle.refresh(mounted.container);
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
console.log(`[Wu] โจ App refreshed for ${appName}`);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
} catch (error) {
|
|
388
|
-
console.warn(`[Wu] โ ๏ธ Failed to refresh ${appName}:`, error);
|
|
389
|
-
} finally {
|
|
390
|
-
this.healthState.healingInProgress.delete(appName);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* ๐ฅ ERROR EVENT HANDLER
|
|
396
|
-
*/
|
|
397
|
-
handleErrorEvent(event) {
|
|
398
|
-
console.log('[Wu] ๐ฅ Error event detected:', event.error);
|
|
399
|
-
|
|
400
|
-
// Check if error is related to any mounted apps
|
|
401
|
-
for (const [appName, mounted] of this.mounted) {
|
|
402
|
-
if (event.filename && event.filename.includes(mounted.app.url)) {
|
|
403
|
-
console.log(`[Wu] ๐ฏ Error traced to ${appName}, initiating recovery...`);
|
|
404
|
-
|
|
405
|
-
// Schedule healing
|
|
406
|
-
setTimeout(() => {
|
|
407
|
-
this.healOrphanedContainer(appName);
|
|
408
|
-
}, 1000);
|
|
409
|
-
|
|
410
|
-
break;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
/**
|
|
416
|
-
* ๐ซ REJECTION HANDLER
|
|
417
|
-
*/
|
|
418
|
-
handleRejection(event) {
|
|
419
|
-
console.log('[Wu] ๐ซ Promise rejection detected:', event.reason);
|
|
420
|
-
|
|
421
|
-
// Prevent default unhandled rejection
|
|
422
|
-
event.preventDefault();
|
|
423
|
-
|
|
424
|
-
// Mark system as unstable temporarily
|
|
425
|
-
this.healthState.systemStable = false;
|
|
426
|
-
|
|
427
|
-
// Schedule stability restoration
|
|
428
|
-
setTimeout(() => {
|
|
429
|
-
this.healthState.systemStable = true;
|
|
430
|
-
console.log('[Wu] โจ System stability restored');
|
|
431
|
-
}, 5000);
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
/**
|
|
435
|
-
* Inicializar wu-framework con configuraciรณn de apps
|
|
436
|
-
* @param {Object} config - Configuraciรณn { apps: [{name, url}, ...] }
|
|
64
|
+
* Inicializar wu-framework con configuracion de apps
|
|
65
|
+
* @param {Object} config - Configuracion { apps: [{name, url}, ...] }
|
|
437
66
|
*/
|
|
438
67
|
async init(config) {
|
|
439
68
|
if (this.isInitialized) {
|
|
440
|
-
|
|
69
|
+
logger.wuWarn('Framework already initialized');
|
|
441
70
|
return;
|
|
442
71
|
}
|
|
443
72
|
|
|
444
|
-
|
|
73
|
+
// Global sandbox mode: 'module' (default) or 'strict'
|
|
74
|
+
this._sandboxMode = config.sandbox || 'module';
|
|
75
|
+
|
|
76
|
+
logger.wuDebug(`Initializing (sandbox: ${this._sandboxMode}) with apps:`, config.apps?.map(app => app.name));
|
|
445
77
|
|
|
446
78
|
try {
|
|
447
|
-
//
|
|
79
|
+
// Execute beforeInit hooks
|
|
448
80
|
const beforeInitResult = await this.hooks.execute('beforeInit', { config });
|
|
449
81
|
if (beforeInitResult.cancelled) {
|
|
450
|
-
|
|
82
|
+
logger.wuWarn('Initialization cancelled by beforeInit hook');
|
|
451
83
|
return;
|
|
452
84
|
}
|
|
453
85
|
|
|
454
|
-
//
|
|
86
|
+
// Call plugin beforeInit hooks
|
|
455
87
|
await this.pluginSystem.callHook('beforeInit', { config });
|
|
456
88
|
|
|
457
|
-
//
|
|
458
|
-
|
|
89
|
+
// Configure and apply cookie overrides (QA/testing: wu-override:<app>=<url>)
|
|
90
|
+
if (config.overrides) {
|
|
91
|
+
this.overrides.configure(config.overrides);
|
|
92
|
+
}
|
|
93
|
+
const apps = config.apps || [];
|
|
94
|
+
this.overrides.refresh();
|
|
95
|
+
this.overrides.applyToApps(apps);
|
|
459
96
|
|
|
460
97
|
// Registrar todas las apps
|
|
461
|
-
for (const appConfig of
|
|
98
|
+
for (const appConfig of apps) {
|
|
462
99
|
await this.registerApp(appConfig);
|
|
463
100
|
}
|
|
464
101
|
|
|
465
|
-
//
|
|
102
|
+
// Preload apps with eager/preload strategies
|
|
466
103
|
await this.strategies.preload(config.apps || []);
|
|
467
104
|
|
|
468
105
|
this.isInitialized = true;
|
|
469
106
|
|
|
470
|
-
//
|
|
107
|
+
// Execute afterInit hooks
|
|
471
108
|
await this.hooks.execute('afterInit', { config });
|
|
472
109
|
|
|
473
|
-
//
|
|
110
|
+
// Call plugin afterInit hooks
|
|
474
111
|
await this.pluginSystem.callHook('afterInit', { config });
|
|
475
112
|
|
|
476
|
-
logger.wuInfo('
|
|
113
|
+
logger.wuInfo('Framework initialized successfully');
|
|
477
114
|
} catch (error) {
|
|
478
|
-
|
|
115
|
+
logger.wuError('Initialization failed:', error);
|
|
479
116
|
|
|
480
|
-
//
|
|
117
|
+
// Call plugin error hooks
|
|
481
118
|
await this.pluginSystem.callHook('onError', { phase: 'init', error });
|
|
482
119
|
|
|
483
120
|
throw error;
|
|
@@ -485,30 +122,29 @@ export class WuCore {
|
|
|
485
122
|
}
|
|
486
123
|
|
|
487
124
|
/**
|
|
488
|
-
* Registrar una
|
|
489
|
-
* @param {Object} appConfig - { name, url }
|
|
125
|
+
* Registrar una aplicacion
|
|
126
|
+
* @param {Object} appConfig - { name, url, keepAlive, sandbox, container, ... }
|
|
490
127
|
*/
|
|
491
128
|
async registerApp(appConfig) {
|
|
492
129
|
const { name, url } = appConfig;
|
|
493
130
|
|
|
494
131
|
try {
|
|
495
|
-
logger.wuDebug(
|
|
132
|
+
logger.wuDebug(`Registering app: ${name} from ${url}`);
|
|
496
133
|
|
|
497
134
|
// Cargar manifest
|
|
498
135
|
const manifestData = await this.manifest.load(url);
|
|
499
136
|
this.manifests.set(name, manifestData);
|
|
500
137
|
|
|
501
|
-
// Registrar la app
|
|
138
|
+
// Registrar la app โ preserve all config fields (keepAlive, sandbox, container, etc.)
|
|
502
139
|
this.apps.set(name, {
|
|
503
|
-
|
|
504
|
-
url,
|
|
140
|
+
...appConfig,
|
|
505
141
|
manifest: manifestData,
|
|
506
142
|
status: 'registered'
|
|
507
143
|
});
|
|
508
144
|
|
|
509
|
-
logger.wuDebug(
|
|
145
|
+
logger.wuDebug(`App ${name} registered successfully`);
|
|
510
146
|
} catch (error) {
|
|
511
|
-
|
|
147
|
+
logger.wuError(`Failed to register app ${name}:`, error);
|
|
512
148
|
throw error;
|
|
513
149
|
}
|
|
514
150
|
}
|
|
@@ -525,79 +161,86 @@ export class WuCore {
|
|
|
525
161
|
|
|
526
162
|
this.definitions.set(appName, lifecycle);
|
|
527
163
|
|
|
528
|
-
//
|
|
529
|
-
this.registry.markAsRegistered(appName);
|
|
530
|
-
|
|
531
|
-
// ๐ก Dispatch custom event for external listeners
|
|
164
|
+
// Dispatch custom event for external listeners
|
|
532
165
|
const event = new CustomEvent('wu:app:ready', {
|
|
533
166
|
detail: { appName, timestamp: Date.now() }
|
|
534
167
|
});
|
|
535
168
|
window.dispatchEvent(event);
|
|
536
169
|
|
|
537
|
-
logger.wuDebug(
|
|
170
|
+
logger.wuDebug(`Lifecycle defined for: ${appName}`);
|
|
538
171
|
}
|
|
539
172
|
|
|
540
173
|
/**
|
|
541
|
-
*
|
|
174
|
+
* Mount app with multi-retry mounting and recovery.
|
|
175
|
+
* If the app is in keep-alive (hidden) state, shows it instantly.
|
|
176
|
+
*
|
|
542
177
|
* @param {string} appName - Nombre de la app
|
|
543
178
|
* @param {string} containerSelector - Selector del contenedor
|
|
544
179
|
*/
|
|
545
180
|
async mount(appName, containerSelector) {
|
|
181
|
+
// Check if app is in keep-alive (hidden) state
|
|
182
|
+
const hiddenEntry = this.hidden.get(appName);
|
|
183
|
+
if (hiddenEntry) {
|
|
184
|
+
if (hiddenEntry.containerSelector === containerSelector) {
|
|
185
|
+
// Same container โ instant show (no reload)
|
|
186
|
+
return await this.show(appName);
|
|
187
|
+
}
|
|
188
|
+
// Different container โ destroy hidden state, remount normally
|
|
189
|
+
await this._destroyHidden(appName);
|
|
190
|
+
}
|
|
191
|
+
|
|
546
192
|
return await this.mountWithRecovery(appName, containerSelector, 0);
|
|
547
193
|
}
|
|
548
194
|
|
|
549
195
|
/**
|
|
550
|
-
*
|
|
196
|
+
* Mount with recovery: self-healing app mounting
|
|
551
197
|
*/
|
|
552
198
|
async mountWithRecovery(appName, containerSelector, attempt = 0) {
|
|
553
199
|
const maxAttempts = 3;
|
|
554
200
|
|
|
555
201
|
try {
|
|
556
|
-
//
|
|
202
|
+
// Start performance measurement
|
|
557
203
|
this.performance.startMeasure('mount', appName);
|
|
558
204
|
|
|
559
|
-
logger.wuDebug(
|
|
205
|
+
logger.wuDebug(`Mounting ${appName} in ${containerSelector} (attempt ${attempt + 1})`);
|
|
560
206
|
|
|
561
|
-
//
|
|
207
|
+
// Execute beforeLoad hooks
|
|
562
208
|
const beforeLoadResult = await this.hooks.execute('beforeLoad', { appName, containerSelector, attempt });
|
|
563
209
|
if (beforeLoadResult.cancelled) {
|
|
564
|
-
|
|
210
|
+
logger.wuWarn('Mount cancelled by beforeLoad hook');
|
|
565
211
|
return;
|
|
566
212
|
}
|
|
567
213
|
|
|
568
|
-
//
|
|
214
|
+
// Call plugin beforeMount hooks
|
|
569
215
|
const pluginBeforeMount = await this.pluginSystem.callHook('beforeMount', { appName, containerSelector });
|
|
570
216
|
if (pluginBeforeMount === false) {
|
|
571
|
-
|
|
217
|
+
logger.wuWarn('Mount cancelled by plugin beforeMount hook');
|
|
572
218
|
return;
|
|
573
219
|
}
|
|
574
220
|
|
|
575
|
-
//
|
|
221
|
+
// Verify app is registered
|
|
576
222
|
const app = this.apps.get(appName);
|
|
577
223
|
if (!app) {
|
|
578
224
|
throw new Error(`App ${appName} not registered. Call wu.init() first.`);
|
|
579
225
|
}
|
|
580
226
|
|
|
581
|
-
//
|
|
227
|
+
// Container reality check
|
|
582
228
|
const container = document.querySelector(containerSelector);
|
|
583
229
|
if (!container) {
|
|
584
230
|
throw new Error(`Container not found: ${containerSelector}`);
|
|
585
231
|
}
|
|
586
232
|
|
|
587
|
-
//
|
|
588
|
-
const poolSandbox = this.sandboxPool.acquire(appName);
|
|
589
|
-
|
|
590
|
-
// ๐ก๏ธ Quantum sandbox creation - pasar manifest con styleMode y URL de la app
|
|
233
|
+
// Create sandbox - pasar manifest con styleMode y URL de la app
|
|
591
234
|
const sandbox = this.sandbox.create(appName, container, {
|
|
592
235
|
manifest: app.manifest,
|
|
593
236
|
styleMode: app.manifest?.styleMode,
|
|
594
237
|
appUrl: app.url // Pasar URL de la app para filtrar estilos de apps fully-isolated
|
|
595
238
|
});
|
|
596
239
|
|
|
597
|
-
//
|
|
240
|
+
// Execute afterLoad hooks
|
|
598
241
|
await this.hooks.execute('afterLoad', { appName, containerSelector, sandbox });
|
|
599
242
|
|
|
600
|
-
//
|
|
243
|
+
// Resolve lifecycle definition
|
|
601
244
|
let lifecycle = this.definitions.get(appName);
|
|
602
245
|
if (!lifecycle) {
|
|
603
246
|
// Load remote app
|
|
@@ -609,56 +252,53 @@ export class WuCore {
|
|
|
609
252
|
}
|
|
610
253
|
}
|
|
611
254
|
|
|
612
|
-
//
|
|
255
|
+
// Execute beforeMount hooks
|
|
613
256
|
const beforeMountResult = await this.hooks.execute('beforeMount', { appName, containerSelector, sandbox, lifecycle });
|
|
614
257
|
if (beforeMountResult.cancelled) {
|
|
615
|
-
|
|
258
|
+
logger.wuWarn('Mount cancelled by beforeMount hook');
|
|
616
259
|
return;
|
|
617
260
|
}
|
|
618
261
|
|
|
619
|
-
//
|
|
262
|
+
// Wait for styles to be ready before mounting
|
|
620
263
|
if (sandbox.stylesReady) {
|
|
621
|
-
|
|
264
|
+
logger.wuDebug(`Waiting for styles to be ready for ${appName}...`);
|
|
622
265
|
await sandbox.stylesReady;
|
|
623
|
-
|
|
266
|
+
logger.wuDebug(`Styles ready for ${appName}`);
|
|
624
267
|
}
|
|
625
268
|
|
|
626
|
-
//
|
|
269
|
+
// Execute mount lifecycle
|
|
627
270
|
await lifecycle.mount(sandbox.container);
|
|
628
271
|
|
|
629
|
-
//
|
|
272
|
+
// Register mounted app
|
|
630
273
|
this.mounted.set(appName, {
|
|
631
274
|
app,
|
|
632
275
|
sandbox,
|
|
633
|
-
poolSandbox,
|
|
634
276
|
lifecycle,
|
|
635
277
|
container: sandbox.container,
|
|
636
278
|
hostContainer: container,
|
|
279
|
+
containerSelector,
|
|
637
280
|
timestamp: Date.now(),
|
|
638
281
|
state: 'stable'
|
|
639
282
|
});
|
|
640
283
|
|
|
641
|
-
//
|
|
642
|
-
this.observeContainer(appName, container);
|
|
643
|
-
|
|
644
|
-
// โก End performance measurement
|
|
284
|
+
// End performance measurement
|
|
645
285
|
const mountTime = this.performance.endMeasure('mount', appName);
|
|
646
286
|
|
|
647
|
-
//
|
|
287
|
+
// Execute afterMount hooks
|
|
648
288
|
await this.hooks.execute('afterMount', { appName, containerSelector, sandbox, mountTime });
|
|
649
289
|
|
|
650
|
-
//
|
|
290
|
+
// Call plugin afterMount hooks
|
|
651
291
|
await this.pluginSystem.callHook('afterMount', { appName, containerSelector, mountTime });
|
|
652
292
|
|
|
653
|
-
//
|
|
293
|
+
// Emit mount event
|
|
654
294
|
this.eventBus.emit('app:mounted', { appName, mountTime, attempt }, { appName });
|
|
655
295
|
|
|
656
|
-
logger.wuInfo(
|
|
296
|
+
logger.wuInfo(`${appName} mounted successfully in ${mountTime.toFixed(2)}ms`);
|
|
657
297
|
|
|
658
298
|
} catch (error) {
|
|
659
|
-
|
|
299
|
+
logger.wuError(`Mount attempt ${attempt + 1} failed for ${appName}:`, error);
|
|
660
300
|
|
|
661
|
-
//
|
|
301
|
+
// Use error boundary for intelligent error handling
|
|
662
302
|
const errorResult = await this.errorBoundary.handle(error, {
|
|
663
303
|
appName,
|
|
664
304
|
containerSelector,
|
|
@@ -666,54 +306,63 @@ export class WuCore {
|
|
|
666
306
|
container: containerSelector
|
|
667
307
|
});
|
|
668
308
|
|
|
669
|
-
// Si el error boundary
|
|
309
|
+
// Si el error boundary recupero el error, no necesitamos reintentar
|
|
670
310
|
if (errorResult.recovered) {
|
|
671
|
-
|
|
311
|
+
logger.wuDebug('Error recovered by error boundary');
|
|
672
312
|
return;
|
|
673
313
|
}
|
|
674
314
|
|
|
675
|
-
//
|
|
315
|
+
// Recovery protocol
|
|
676
316
|
if (attempt < maxAttempts - 1 && errorResult.action === 'retry') {
|
|
677
|
-
|
|
317
|
+
logger.wuDebug('Initiating recovery protocol...');
|
|
678
318
|
|
|
679
|
-
//
|
|
319
|
+
// Clean app state
|
|
680
320
|
await this.appStateCleanup(appName, containerSelector);
|
|
681
321
|
|
|
682
|
-
//
|
|
322
|
+
// Temporal stabilization
|
|
683
323
|
await new Promise(resolve => setTimeout(resolve, 1000 * (attempt + 1)));
|
|
684
324
|
|
|
685
|
-
//
|
|
325
|
+
// Recursive mounting with recovery
|
|
686
326
|
return await this.mountWithRecovery(appName, containerSelector, attempt + 1);
|
|
687
327
|
}
|
|
688
328
|
|
|
689
|
-
//
|
|
329
|
+
// Call plugin error hooks
|
|
690
330
|
await this.pluginSystem.callHook('onError', { phase: 'mount', error, appName });
|
|
691
331
|
|
|
692
|
-
//
|
|
332
|
+
// Final mount failure - error boundary already handled fallback UI
|
|
693
333
|
throw error;
|
|
694
334
|
}
|
|
695
335
|
}
|
|
696
336
|
|
|
697
337
|
/**
|
|
698
|
-
*
|
|
338
|
+
* App state cleanup: Enhanced container cleanup with framework protection
|
|
699
339
|
*/
|
|
700
340
|
async appStateCleanup(appName, containerSelector) {
|
|
701
341
|
try {
|
|
702
|
-
|
|
342
|
+
logger.wuDebug(`Starting app state cleanup for ${appName}...`);
|
|
343
|
+
|
|
344
|
+
// Clear hidden (keep-alive) state if present
|
|
345
|
+
if (this.hidden.has(appName)) {
|
|
346
|
+
try {
|
|
347
|
+
await this._destroyHidden(appName);
|
|
348
|
+
} catch (hiddenError) {
|
|
349
|
+
logger.wuWarn('Hidden app cleanup failed:', hiddenError);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
703
352
|
|
|
704
|
-
//
|
|
353
|
+
// Clear any existing mounted state safely
|
|
705
354
|
if (this.mounted.has(appName)) {
|
|
706
355
|
try {
|
|
707
|
-
await this.unmount(appName);
|
|
356
|
+
await this.unmount(appName, { force: true });
|
|
708
357
|
} catch (unmountError) {
|
|
709
|
-
|
|
358
|
+
logger.wuWarn('Unmount failed during cleanup:', unmountError);
|
|
710
359
|
}
|
|
711
360
|
}
|
|
712
361
|
|
|
713
|
-
//
|
|
362
|
+
// Enhanced container cleanup with Vue safety measures
|
|
714
363
|
const container = document.querySelector(containerSelector);
|
|
715
364
|
if (container) {
|
|
716
|
-
//
|
|
365
|
+
// Protect Vue's reactivity system
|
|
717
366
|
if (container.shadowRoot) {
|
|
718
367
|
try {
|
|
719
368
|
// Clear shadow root content safely
|
|
@@ -722,43 +371,43 @@ export class WuCore {
|
|
|
722
371
|
try {
|
|
723
372
|
child.remove();
|
|
724
373
|
} catch (removeError) {
|
|
725
|
-
|
|
374
|
+
logger.wuWarn('Failed to remove shadow child:', removeError);
|
|
726
375
|
}
|
|
727
376
|
});
|
|
728
377
|
} catch (shadowError) {
|
|
729
|
-
|
|
378
|
+
logger.wuWarn('Shadow root cleanup failed:', shadowError);
|
|
730
379
|
}
|
|
731
380
|
}
|
|
732
381
|
|
|
733
|
-
//
|
|
382
|
+
// Clear any direct children if no shadow root
|
|
734
383
|
if (!container.shadowRoot && container.children.length > 0) {
|
|
735
384
|
try {
|
|
736
385
|
container.innerHTML = '';
|
|
737
386
|
} catch (htmlError) {
|
|
738
|
-
|
|
387
|
+
logger.wuWarn('Container innerHTML cleanup failed:', htmlError);
|
|
739
388
|
}
|
|
740
389
|
}
|
|
741
390
|
|
|
742
|
-
//
|
|
391
|
+
// Reset container attributes
|
|
743
392
|
container.removeAttribute('data-wu-app');
|
|
744
393
|
container.removeAttribute('data-quantum-state');
|
|
745
394
|
container.removeAttribute('wu-debug');
|
|
746
395
|
}
|
|
747
396
|
|
|
748
|
-
//
|
|
397
|
+
// Reset definition state
|
|
749
398
|
this.definitions.delete(appName);
|
|
750
399
|
|
|
751
|
-
//
|
|
400
|
+
// Clear sandbox registry
|
|
752
401
|
if (this.sandbox && this.sandbox.sandboxes) {
|
|
753
402
|
this.sandbox.sandboxes.delete(appName);
|
|
754
403
|
}
|
|
755
404
|
|
|
756
|
-
|
|
405
|
+
logger.wuDebug(`App state cleaned successfully for ${appName}`);
|
|
757
406
|
|
|
758
407
|
} catch (cleanupError) {
|
|
759
|
-
|
|
408
|
+
logger.wuWarn(`App cleanup partial failure for ${appName}:`, cleanupError);
|
|
760
409
|
|
|
761
|
-
//
|
|
410
|
+
// Emergency cleanup - force clear everything
|
|
762
411
|
try {
|
|
763
412
|
const container = document.querySelector(containerSelector);
|
|
764
413
|
if (container) {
|
|
@@ -770,163 +419,239 @@ export class WuCore {
|
|
|
770
419
|
}, 100);
|
|
771
420
|
}
|
|
772
421
|
} catch (emergencyError) {
|
|
773
|
-
|
|
422
|
+
logger.wuError('Emergency cleanup failed:', emergencyError);
|
|
774
423
|
}
|
|
775
424
|
}
|
|
776
425
|
}
|
|
777
426
|
|
|
778
427
|
/**
|
|
779
|
-
*
|
|
428
|
+
* Remote app loader: Load app in the configured sandbox mode.
|
|
429
|
+
*
|
|
430
|
+
* Three modes:
|
|
431
|
+
*
|
|
432
|
+
* - module (default): ES6 import() + patchWindow for side-effect tracking.
|
|
433
|
+
* Works with Vite, HMR, ES modules. App code runs in global scope.
|
|
434
|
+
* Proxy is a cleanup tracker, not an isolation boundary.
|
|
435
|
+
*
|
|
436
|
+
* - strict: Hidden iframe + real import(). True JS isolation.
|
|
437
|
+
* App code runs in iframe's window (separate global context).
|
|
438
|
+
* Document operations proxied to Shadow DOM.
|
|
439
|
+
* Preserves: tree shaking, source maps, HMR.
|
|
440
|
+
* Falls back to eval mode if import() fails (CORS, etc.)
|
|
441
|
+
*
|
|
442
|
+
* - eval: Fetch HTML โ parse scripts โ execute with(proxy).
|
|
443
|
+
* Maximum JS isolation via with(proxy) statement.
|
|
444
|
+
* Requires bundled apps (UMD/IIFE), not ES modules.
|
|
445
|
+
* No tree shaking, no source maps, no HMR.
|
|
446
|
+
*
|
|
447
|
+
* Set per-app: { name: 'app', url: '...', sandbox: 'strict' }
|
|
448
|
+
* Or globally: wu.init({ sandbox: 'strict', apps: [...] })
|
|
780
449
|
*/
|
|
781
|
-
async
|
|
450
|
+
async loadAndMountRemoteApp(app, sandbox) {
|
|
451
|
+
const mode = app.sandbox || this._sandboxMode || 'module';
|
|
452
|
+
|
|
453
|
+
if (mode === 'strict') {
|
|
454
|
+
await this._loadStrict(app, sandbox);
|
|
455
|
+
} else if (mode === 'eval') {
|
|
456
|
+
await this._loadEval(app, sandbox);
|
|
457
|
+
} else {
|
|
458
|
+
await this._loadModule(app, sandbox);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* MODULE MODE: import() + patchWindow (default).
|
|
464
|
+
* Side effects tracked during load, cleaned on unmount.
|
|
465
|
+
* App code runs in global scope.
|
|
466
|
+
*/
|
|
467
|
+
async _loadModule(app, sandbox) {
|
|
468
|
+
const moduleUrl = await this.resolveModulePath(app);
|
|
469
|
+
logger.wuDebug(`[module] Loading ES module: ${moduleUrl}`);
|
|
470
|
+
|
|
471
|
+
const jsSandbox = sandbox.jsSandbox;
|
|
472
|
+
if (jsSandbox?.patchWindow) {
|
|
473
|
+
jsSandbox.patchWindow();
|
|
474
|
+
}
|
|
475
|
+
|
|
782
476
|
try {
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
Object.assign(errorContainer.style, {
|
|
795
|
-
padding: '2rem',
|
|
796
|
-
borderRadius: '12px',
|
|
797
|
-
background: 'linear-gradient(135deg, #1a1a2e, #16213e)',
|
|
798
|
-
border: '2px solid #ff6b6b',
|
|
799
|
-
color: '#fff',
|
|
800
|
-
fontFamily: '"Courier New", monospace',
|
|
801
|
-
textAlign: 'center',
|
|
802
|
-
boxShadow: '0 0 20px rgba(255, 107, 107, 0.3)'
|
|
803
|
-
});
|
|
477
|
+
await this.moduleLoader(moduleUrl, app.name);
|
|
478
|
+
logger.wuDebug(`[module] ES module loaded: ${app.name}`);
|
|
479
|
+
} catch (error) {
|
|
480
|
+
logger.wuError(`[module] Failed to load ${moduleUrl}:`, error);
|
|
481
|
+
throw error;
|
|
482
|
+
} finally {
|
|
483
|
+
if (jsSandbox?.unpatchWindow) {
|
|
484
|
+
jsSandbox.unpatchWindow();
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
804
488
|
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
489
|
+
/**
|
|
490
|
+
* STRICT MODE: Hidden iframe + real import().
|
|
491
|
+
*
|
|
492
|
+
* The iframe provides a separate window context. import() inside the iframe
|
|
493
|
+
* is a real ES module import โ tree shaking, source maps, and HMR all work.
|
|
494
|
+
*
|
|
495
|
+
* Pipeline:
|
|
496
|
+
* 1. Create hidden iframe with <base href="appUrl">
|
|
497
|
+
* 2. Patch iframe's document โ DOM operations go to Shadow DOM
|
|
498
|
+
* 3. import() the app module inside iframe
|
|
499
|
+
* 4. Wait for wu.define() registration
|
|
500
|
+
*
|
|
501
|
+
* If import() fails (CORS, network, etc.), falls back to eval mode
|
|
502
|
+
* with a console warning explaining why.
|
|
503
|
+
*/
|
|
504
|
+
async _loadStrict(app, sandbox) {
|
|
505
|
+
logger.wuDebug(`[strict] Loading ${app.name} via iframe sandbox`);
|
|
812
506
|
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
margin: '0 0 1rem 0'
|
|
818
|
-
});
|
|
507
|
+
// Create and activate iframe sandbox
|
|
508
|
+
const iframeSandbox = new WuIframeSandbox(app.name);
|
|
509
|
+
iframeSandbox.activate(app.url, sandbox.container, sandbox.shadowRoot);
|
|
510
|
+
sandbox.iframeSandbox = iframeSandbox;
|
|
819
511
|
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
512
|
+
try {
|
|
513
|
+
// Resolve module path (same logic as module mode)
|
|
514
|
+
const moduleUrl = await this.resolveModulePath(app);
|
|
515
|
+
logger.wuDebug(`[strict] Importing module in iframe: ${moduleUrl}`);
|
|
516
|
+
|
|
517
|
+
// Import module inside iframe โ real import()!
|
|
518
|
+
await iframeSandbox.importModule(moduleUrl);
|
|
519
|
+
logger.wuDebug(`[strict] Module imported for ${app.name}`);
|
|
520
|
+
|
|
521
|
+
} catch (importError) {
|
|
522
|
+
// import() failed โ likely CORS or module error.
|
|
523
|
+
// Fall back to eval mode (fetch + parse + with(proxy)).
|
|
524
|
+
logger.wuWarn(
|
|
525
|
+
`[strict] iframe import failed for ${app.name}: ${importError.message}\n` +
|
|
526
|
+
`Falling back to eval mode (fetch + parse + execute with proxy).\n` +
|
|
527
|
+
`To fix: ensure the app's dev server sets Access-Control-Allow-Origin: * headers,\n` +
|
|
528
|
+
`or use sandbox: 'eval' explicitly for UMD/IIFE bundles.`
|
|
529
|
+
);
|
|
530
|
+
|
|
531
|
+
// Destroy failed iframe
|
|
532
|
+
iframeSandbox.destroy();
|
|
533
|
+
sandbox.iframeSandbox = null;
|
|
534
|
+
|
|
535
|
+
// Fallback to eval mode
|
|
536
|
+
await this._loadEval(app, sandbox);
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
826
539
|
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
margin: '1rem 0',
|
|
830
|
-
textAlign: 'left'
|
|
831
|
-
});
|
|
540
|
+
// Wait for wu.define()
|
|
541
|
+
await this._waitForDefine(app.name, 'strict');
|
|
832
542
|
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
Object.assign(summary.style, {
|
|
836
|
-
cursor: 'pointer',
|
|
837
|
-
color: '#4ecdc4'
|
|
838
|
-
});
|
|
543
|
+
logger.wuDebug(`[strict] ${app.name} loaded and registered via iframe`);
|
|
544
|
+
}
|
|
839
545
|
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
546
|
+
/**
|
|
547
|
+
* EVAL MODE: Fetch HTML โ parse โ execute scripts inside proxy.
|
|
548
|
+
*
|
|
549
|
+
* Maximum JS isolation via with(proxy) statement โ all unqualified
|
|
550
|
+
* identifiers (setTimeout, document, fetch) go through proxy traps.
|
|
551
|
+
*
|
|
552
|
+
* Requires bundled apps (UMD/IIFE). ES modules cannot be eval'd.
|
|
553
|
+
* No tree shaking, no source maps, no HMR.
|
|
554
|
+
*
|
|
555
|
+
* Pipeline:
|
|
556
|
+
* 1. Fetch HTML from app URL
|
|
557
|
+
* 2. Parse: extract scripts (inline + external), styles, clean DOM
|
|
558
|
+
* 3. Inject DOM + styles into Shadow DOM
|
|
559
|
+
* 4. Execute all scripts inside the proxy via WuScriptExecutor
|
|
560
|
+
* 5. Wait for wu.define()
|
|
561
|
+
*/
|
|
562
|
+
async _loadEval(app, sandbox) {
|
|
563
|
+
logger.wuDebug(`[eval] Loading ${app.name} from ${app.url}`);
|
|
850
564
|
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
Object.assign(button.style, {
|
|
854
|
-
background: 'linear-gradient(45deg, #4ecdc4, #44a08d)',
|
|
855
|
-
border: 'none',
|
|
856
|
-
padding: '0.8rem 1.5rem',
|
|
857
|
-
borderRadius: '6px',
|
|
858
|
-
color: 'white',
|
|
859
|
-
cursor: 'pointer',
|
|
860
|
-
fontWeight: 'bold',
|
|
861
|
-
transition: 'transform 0.2s'
|
|
862
|
-
});
|
|
565
|
+
const jsSandbox = sandbox.jsSandbox;
|
|
566
|
+
const proxy = jsSandbox.getProxy();
|
|
863
567
|
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
button.addEventListener('mouseleave', () => button.style.transform = 'scale(1)');
|
|
568
|
+
if (!proxy) {
|
|
569
|
+
throw new Error(`[eval] No active proxy for ${app.name}. Sandbox must be activated first.`);
|
|
570
|
+
}
|
|
868
571
|
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
details.appendChild(pre);
|
|
572
|
+
// 1. Fetch and parse HTML
|
|
573
|
+
const parsed = await this.htmlParser.fetchAndParse(app.url, app.name);
|
|
872
574
|
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
errorContainer.appendChild(button);
|
|
575
|
+
// 2. Inject clean DOM into container
|
|
576
|
+
if (parsed.dom) {
|
|
577
|
+
sandbox.container.innerHTML = parsed.dom;
|
|
578
|
+
}
|
|
878
579
|
|
|
879
|
-
|
|
580
|
+
// 3. Inject styles into shadow root
|
|
581
|
+
const styleTarget = sandbox.shadowRoot || sandbox.container;
|
|
880
582
|
|
|
881
|
-
|
|
882
|
-
|
|
583
|
+
for (const cssText of parsed.styles.inline) {
|
|
584
|
+
const style = document.createElement('style');
|
|
585
|
+
style.textContent = cssText;
|
|
586
|
+
styleTarget.appendChild(style);
|
|
883
587
|
}
|
|
884
|
-
}
|
|
885
588
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
// ๐ SMART PATH RESOLUTION: Multi-path URL construction with validation
|
|
893
|
-
const moduleUrl = await this.resolveModulePath(app);
|
|
589
|
+
for (const href of parsed.styles.external) {
|
|
590
|
+
const link = document.createElement('link');
|
|
591
|
+
link.rel = 'stylesheet';
|
|
592
|
+
link.href = href;
|
|
593
|
+
styleTarget.appendChild(link);
|
|
594
|
+
}
|
|
894
595
|
|
|
895
|
-
|
|
596
|
+
// 4. Build and execute scripts inside the proxy
|
|
597
|
+
const scripts = [];
|
|
598
|
+
for (const content of parsed.scripts.inline) {
|
|
599
|
+
scripts.push({ content });
|
|
600
|
+
}
|
|
601
|
+
for (const src of parsed.scripts.external) {
|
|
602
|
+
scripts.push({ src });
|
|
603
|
+
}
|
|
896
604
|
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
await this.moduleLoader(moduleUrl, app.name);
|
|
605
|
+
await this.scriptExecutor.executeAll(scripts, app.name, proxy);
|
|
606
|
+
logger.wuDebug(`[eval] Scripts executed for ${app.name}`);
|
|
900
607
|
|
|
901
|
-
|
|
608
|
+
// 5. Wait for wu.define()
|
|
609
|
+
await this._waitForDefine(app.name, 'eval');
|
|
902
610
|
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
|
|
611
|
+
logger.wuDebug(`[eval] ${app.name} loaded and registered`);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* Wait for an app to call wu.define() with a timeout.
|
|
616
|
+
* Shared by strict and eval modes.
|
|
617
|
+
*/
|
|
618
|
+
async _waitForDefine(appName, mode) {
|
|
619
|
+
const maxWaitTime = 10000;
|
|
620
|
+
const checkInterval = 50;
|
|
621
|
+
const startTime = Date.now();
|
|
622
|
+
|
|
623
|
+
while (!this.definitions.has(appName)) {
|
|
624
|
+
if (Date.now() - startTime >= maxWaitTime) {
|
|
625
|
+
throw new Error(
|
|
626
|
+
`[${mode}] App '${appName}' loaded but wu.define() was not called within ${maxWaitTime}ms.\n` +
|
|
627
|
+
`Make sure your app calls: window.wu.define('${appName}', { mount, unmount })`
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
|
906
631
|
}
|
|
907
632
|
}
|
|
908
633
|
|
|
909
634
|
/**
|
|
910
|
-
*
|
|
635
|
+
* Module path resolver: Intelligent URL construction with fallback
|
|
911
636
|
* Intelligently resolves module paths with real-time validation
|
|
912
637
|
*/
|
|
913
638
|
async resolveModulePath(app) {
|
|
914
639
|
let entryFile = app.manifest?.entry || 'main.js';
|
|
915
640
|
const baseUrl = app.url.replace(/\/$/, ''); // Remove trailing slash
|
|
916
641
|
|
|
917
|
-
//
|
|
642
|
+
// Normalize path: Remove duplicated directories
|
|
918
643
|
// If entry already starts with 'src/', 'dist/', etc., use it as-is
|
|
919
644
|
const hasFolderPrefix = /^(src|dist|public|build|assets|lib|es)\//.test(entryFile);
|
|
920
645
|
|
|
921
646
|
if (hasFolderPrefix) {
|
|
922
|
-
|
|
647
|
+
logger.wuDebug(`Entry already has folder prefix: ${entryFile}`);
|
|
923
648
|
// Entry already has folder, just use baseUrl + entryFile
|
|
924
649
|
const directPath = `${baseUrl}/${entryFile}`;
|
|
925
|
-
|
|
650
|
+
logger.wuDebug(`Using direct path: ${directPath}`);
|
|
926
651
|
return directPath;
|
|
927
652
|
}
|
|
928
653
|
|
|
929
|
-
//
|
|
654
|
+
// Multi-path candidates (in order of preference)
|
|
930
655
|
const pathCandidates = [
|
|
931
656
|
`${baseUrl}/src/${entryFile}`, // Standard structure
|
|
932
657
|
`${baseUrl}/${entryFile}`, // Root level
|
|
@@ -938,47 +663,47 @@ export class WuCore {
|
|
|
938
663
|
`${baseUrl}/es/${entryFile}` // ES modules folder
|
|
939
664
|
];
|
|
940
665
|
|
|
941
|
-
|
|
666
|
+
logger.wuDebug(`Attempting path resolution for ${app.name}...`);
|
|
942
667
|
|
|
943
|
-
//
|
|
668
|
+
// Smart path discovery: Try each candidate with validation
|
|
944
669
|
for (let i = 0; i < pathCandidates.length; i++) {
|
|
945
670
|
const candidate = pathCandidates[i];
|
|
946
671
|
|
|
947
672
|
try {
|
|
948
|
-
|
|
673
|
+
logger.wuDebug(`Testing path candidate ${i + 1}/${pathCandidates.length}: ${candidate}`);
|
|
949
674
|
|
|
950
|
-
//
|
|
675
|
+
// Path validation with enhanced verification
|
|
951
676
|
const isValid = await this.validatePath(candidate);
|
|
952
677
|
|
|
953
678
|
if (isValid) {
|
|
954
|
-
|
|
679
|
+
logger.wuDebug(`Path resolved successfully: ${candidate}`);
|
|
955
680
|
return candidate;
|
|
956
681
|
} else {
|
|
957
|
-
|
|
682
|
+
logger.wuDebug(`Path candidate ${i + 1} failed validation: ${candidate}`);
|
|
958
683
|
}
|
|
959
684
|
|
|
960
685
|
} catch (error) {
|
|
961
|
-
|
|
686
|
+
logger.wuDebug(`Path candidate ${i + 1} threw error: ${candidate} - ${error.message}`);
|
|
962
687
|
continue;
|
|
963
688
|
}
|
|
964
689
|
}
|
|
965
690
|
|
|
966
|
-
//
|
|
691
|
+
// Fallback: If all candidates fail, use the first one and let the error bubble up
|
|
967
692
|
const fallbackPath = pathCandidates[0];
|
|
968
|
-
|
|
693
|
+
logger.wuWarn(`All path candidates failed, using fallback: ${fallbackPath}`);
|
|
969
694
|
return fallbackPath;
|
|
970
695
|
}
|
|
971
696
|
|
|
972
697
|
/**
|
|
973
|
-
*
|
|
698
|
+
* Path validator: Smart existence verification with module testing
|
|
974
699
|
* Validates if a path exists and can be loaded as an ES module
|
|
975
700
|
*/
|
|
976
701
|
async validatePath(url) {
|
|
977
702
|
try {
|
|
978
|
-
//
|
|
979
|
-
|
|
703
|
+
// Enhanced validation: Try actual module import for reliable verification
|
|
704
|
+
logger.wuDebug(`Testing path: ${url}`);
|
|
980
705
|
|
|
981
|
-
//
|
|
706
|
+
// First, try a GET request to check if file exists and is accessible
|
|
982
707
|
const response = await fetch(url, {
|
|
983
708
|
method: 'GET',
|
|
984
709
|
cache: 'no-cache',
|
|
@@ -986,11 +711,11 @@ export class WuCore {
|
|
|
986
711
|
});
|
|
987
712
|
|
|
988
713
|
if (!response.ok) {
|
|
989
|
-
|
|
714
|
+
logger.wuDebug(`Path validation failed - HTTP ${response.status}: ${url}`);
|
|
990
715
|
return false;
|
|
991
716
|
}
|
|
992
717
|
|
|
993
|
-
//
|
|
718
|
+
// Check content type and file extension
|
|
994
719
|
const contentType = response.headers.get('content-type') || '';
|
|
995
720
|
const isJavaScript =
|
|
996
721
|
contentType.includes('javascript') ||
|
|
@@ -1000,14 +725,14 @@ export class WuCore {
|
|
|
1000
725
|
url.endsWith('.mjs');
|
|
1001
726
|
|
|
1002
727
|
if (!isJavaScript) {
|
|
1003
|
-
|
|
728
|
+
logger.wuDebug(`Path validation failed - Invalid content type '${contentType}': ${url}`);
|
|
1004
729
|
return false;
|
|
1005
730
|
}
|
|
1006
731
|
|
|
1007
|
-
//
|
|
732
|
+
// Final verification: Check if content looks like a valid module
|
|
1008
733
|
const content = await response.text();
|
|
1009
734
|
|
|
1010
|
-
//
|
|
735
|
+
// Detect HTML fallback: Check if server returned HTML instead of JS
|
|
1011
736
|
// Only check if content STARTS with HTML markers (trimmed), not if it contains them anywhere
|
|
1012
737
|
// This avoids false positives for Angular/React bundles that contain template strings
|
|
1013
738
|
const trimmedContent = content.trim().toLowerCase();
|
|
@@ -1019,11 +744,11 @@ export class WuCore {
|
|
|
1019
744
|
trimmedContent.startsWith('<!-');
|
|
1020
745
|
|
|
1021
746
|
if (isHtmlFallback) {
|
|
1022
|
-
|
|
747
|
+
logger.wuDebug(`Path validation failed - Server returned HTML fallback page: ${url}`);
|
|
1023
748
|
return false;
|
|
1024
749
|
}
|
|
1025
750
|
|
|
1026
|
-
//
|
|
751
|
+
// Check for valid JavaScript module content
|
|
1027
752
|
const hasModuleContent =
|
|
1028
753
|
content.includes('export') ||
|
|
1029
754
|
content.includes('import') ||
|
|
@@ -1033,44 +758,44 @@ export class WuCore {
|
|
|
1033
758
|
(content.includes('function') && content.length > 10);
|
|
1034
759
|
|
|
1035
760
|
if (!hasModuleContent) {
|
|
1036
|
-
|
|
1037
|
-
|
|
761
|
+
logger.wuDebug(`Path validation failed - No valid module content: ${url}`);
|
|
762
|
+
logger.wuDebug(`Content preview: ${content.substring(0, 100)}...`);
|
|
1038
763
|
return false;
|
|
1039
764
|
}
|
|
1040
765
|
|
|
1041
|
-
|
|
766
|
+
logger.wuDebug(`Path validation successful: ${url} (${content.length} chars)`);
|
|
1042
767
|
return true;
|
|
1043
768
|
|
|
1044
769
|
} catch (error) {
|
|
1045
|
-
//
|
|
1046
|
-
|
|
770
|
+
// Network, timeout, or parsing error means path is invalid
|
|
771
|
+
logger.wuDebug(`Path validation failed for ${url}: ${error.message}`);
|
|
1047
772
|
return false;
|
|
1048
773
|
}
|
|
1049
774
|
}
|
|
1050
775
|
|
|
1051
776
|
/**
|
|
1052
|
-
*
|
|
777
|
+
* Module loader: Advanced registration patterns
|
|
1053
778
|
* Handles asynchronous registration with timing synchronization
|
|
1054
|
-
*
|
|
779
|
+
* Verifica que definitions tenga el lifecycle despues de cargar
|
|
1055
780
|
*/
|
|
1056
781
|
async moduleLoader(moduleUrl, appName) {
|
|
1057
|
-
//
|
|
782
|
+
// Check if already registered
|
|
1058
783
|
if (this.definitions.has(appName)) {
|
|
1059
|
-
|
|
784
|
+
logger.wuDebug(`App ${appName} already registered`);
|
|
1060
785
|
return;
|
|
1061
786
|
}
|
|
1062
787
|
|
|
1063
|
-
|
|
788
|
+
logger.wuDebug(`Using event-based registration for ${appName}`);
|
|
1064
789
|
|
|
1065
|
-
//
|
|
790
|
+
// Load module first
|
|
1066
791
|
try {
|
|
1067
792
|
await import(/* @vite-ignore */ moduleUrl);
|
|
1068
793
|
} catch (loadError) {
|
|
1069
|
-
|
|
794
|
+
logger.wuError(`Failed to import module ${moduleUrl}:`, loadError);
|
|
1070
795
|
throw loadError;
|
|
1071
796
|
}
|
|
1072
797
|
|
|
1073
|
-
//
|
|
798
|
+
// Wait for wu.define() to be called with real verification
|
|
1074
799
|
const maxWaitTime = 10000; // 10 segundos
|
|
1075
800
|
const checkInterval = 50; // Verificar cada 50ms
|
|
1076
801
|
const startTime = Date.now();
|
|
@@ -1092,34 +817,62 @@ export class WuCore {
|
|
|
1092
817
|
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
|
1093
818
|
}
|
|
1094
819
|
|
|
1095
|
-
|
|
820
|
+
logger.wuDebug(`App ${appName} loaded and registered (verified in definitions)`);
|
|
1096
821
|
}
|
|
1097
822
|
|
|
1098
823
|
/**
|
|
1099
|
-
* Desmontar una
|
|
824
|
+
* Desmontar una aplicacion.
|
|
825
|
+
*
|
|
826
|
+
* With keepAlive, the app is hidden instead of destroyed.
|
|
827
|
+
* All DOM, JS state, timers, and iframe are preserved.
|
|
828
|
+
* Re-mounting shows the app instantly.
|
|
829
|
+
*
|
|
830
|
+
* keepAlive is resolved from (in priority order):
|
|
831
|
+
* 1. options.keepAlive (per-call override)
|
|
832
|
+
* 2. app config keepAlive (set via wu.app() or registerApp)
|
|
833
|
+
* 3. false (default: destroy)
|
|
834
|
+
*
|
|
835
|
+
* Use options.force = true to destroy even if keepAlive is set.
|
|
836
|
+
*
|
|
1100
837
|
* @param {string} appName - Nombre de la app
|
|
838
|
+
* @param {Object} [options] - Unmount options
|
|
839
|
+
* @param {boolean} [options.keepAlive] - Preserve state for instant re-mount
|
|
840
|
+
* @param {boolean} [options.force] - Force destroy even if keepAlive
|
|
1101
841
|
*/
|
|
1102
|
-
async unmount(appName) {
|
|
842
|
+
async unmount(appName, options = {}) {
|
|
1103
843
|
try {
|
|
1104
|
-
|
|
844
|
+
logger.wuDebug(`Unmounting ${appName}`);
|
|
1105
845
|
|
|
1106
846
|
const mounted = this.mounted.get(appName);
|
|
1107
847
|
if (!mounted) {
|
|
1108
|
-
|
|
848
|
+
// Check if it's hidden (keep-alive) โ force destroy if requested
|
|
849
|
+
if (options.force && this.hidden.has(appName)) {
|
|
850
|
+
return await this._destroyHidden(appName);
|
|
851
|
+
}
|
|
852
|
+
logger.wuWarn(`App ${appName} not mounted`);
|
|
1109
853
|
return;
|
|
1110
854
|
}
|
|
1111
855
|
|
|
1112
|
-
//
|
|
856
|
+
// Resolve keepAlive: per-call > per-app config > default false
|
|
857
|
+
const keepAlive = options.force
|
|
858
|
+
? false
|
|
859
|
+
: (options.keepAlive ?? mounted.app?.keepAlive ?? false);
|
|
860
|
+
|
|
861
|
+
if (keepAlive) {
|
|
862
|
+
return await this.hide(appName);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// Execute beforeUnmount hooks
|
|
1113
866
|
const beforeUnmountResult = await this.hooks.execute('beforeUnmount', { appName, mounted });
|
|
1114
867
|
if (beforeUnmountResult.cancelled) {
|
|
1115
|
-
|
|
868
|
+
logger.wuWarn('Unmount cancelled by beforeUnmount hook');
|
|
1116
869
|
return;
|
|
1117
870
|
}
|
|
1118
871
|
|
|
1119
|
-
//
|
|
872
|
+
// Call plugin beforeUnmount hooks
|
|
1120
873
|
const pluginBeforeUnmount = await this.pluginSystem.callHook('beforeUnmount', { appName });
|
|
1121
874
|
if (pluginBeforeUnmount === false) {
|
|
1122
|
-
|
|
875
|
+
logger.wuWarn('Unmount cancelled by plugin beforeUnmount hook');
|
|
1123
876
|
return;
|
|
1124
877
|
}
|
|
1125
878
|
|
|
@@ -1128,42 +881,197 @@ export class WuCore {
|
|
|
1128
881
|
await mounted.lifecycle.unmount(mounted.container);
|
|
1129
882
|
}
|
|
1130
883
|
|
|
1131
|
-
//
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
if (mounted.poolSandbox) {
|
|
1136
|
-
this.sandboxPool.release(appName);
|
|
884
|
+
// Destroy iframe sandbox if present (strict mode)
|
|
885
|
+
if (mounted.sandbox.iframeSandbox) {
|
|
886
|
+
mounted.sandbox.iframeSandbox.destroy();
|
|
887
|
+
mounted.sandbox.iframeSandbox = null;
|
|
1137
888
|
}
|
|
1138
889
|
|
|
1139
|
-
//
|
|
1140
|
-
this.
|
|
890
|
+
// Limpiar sandbox
|
|
891
|
+
this.sandbox.cleanup(mounted.sandbox);
|
|
1141
892
|
|
|
1142
893
|
// Remover del registro de montadas
|
|
1143
894
|
this.mounted.delete(appName);
|
|
1144
895
|
|
|
1145
|
-
//
|
|
896
|
+
// Execute afterUnmount hooks
|
|
1146
897
|
await this.hooks.execute('afterUnmount', { appName });
|
|
1147
898
|
|
|
1148
|
-
//
|
|
899
|
+
// Call plugin afterUnmount hooks
|
|
1149
900
|
await this.pluginSystem.callHook('afterUnmount', { appName });
|
|
1150
901
|
|
|
1151
|
-
//
|
|
902
|
+
// Emit unmount event
|
|
1152
903
|
this.eventBus.emit('app:unmounted', { appName }, { appName });
|
|
1153
904
|
|
|
1154
|
-
|
|
905
|
+
logger.wuDebug(`${appName} unmounted successfully`);
|
|
1155
906
|
} catch (error) {
|
|
1156
|
-
|
|
907
|
+
logger.wuError(`Failed to unmount ${appName}:`, error);
|
|
1157
908
|
|
|
1158
|
-
//
|
|
909
|
+
// Call plugin error hooks
|
|
1159
910
|
await this.pluginSystem.callHook('onError', { phase: 'unmount', error, appName });
|
|
1160
911
|
|
|
1161
|
-
//
|
|
912
|
+
// Emit error event
|
|
1162
913
|
this.eventBus.emit('app:error', { appName, error: error.message }, { appName });
|
|
1163
914
|
throw error;
|
|
1164
915
|
}
|
|
1165
916
|
}
|
|
1166
917
|
|
|
918
|
+
/**
|
|
919
|
+
* Hide a mounted app (keep-alive).
|
|
920
|
+
*
|
|
921
|
+
* Preserves all state: DOM in Shadow DOM, JS in iframe, timers, listeners.
|
|
922
|
+
* The app's optional `deactivate()` lifecycle hook is called.
|
|
923
|
+
* Re-show with `show()` or `mount()` with the same container.
|
|
924
|
+
*
|
|
925
|
+
* @param {string} appName - App to hide
|
|
926
|
+
*/
|
|
927
|
+
async hide(appName) {
|
|
928
|
+
const mounted = this.mounted.get(appName);
|
|
929
|
+
if (!mounted) {
|
|
930
|
+
logger.wuWarn(`Cannot hide ${appName}: not mounted`);
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
logger.wuDebug(`Hiding ${appName} (keep-alive)`);
|
|
935
|
+
|
|
936
|
+
// Call optional deactivate lifecycle hook
|
|
937
|
+
if (mounted.lifecycle?.deactivate) {
|
|
938
|
+
try {
|
|
939
|
+
await mounted.lifecycle.deactivate(mounted.container);
|
|
940
|
+
} catch (err) {
|
|
941
|
+
logger.wuWarn(`deactivate() failed for ${appName}:`, err);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// Execute beforeUnmount hooks (so plugins know)
|
|
946
|
+
await this.hooks.execute('beforeUnmount', { appName, mounted, keepAlive: true });
|
|
947
|
+
await this.pluginSystem.callHook('beforeUnmount', { appName, keepAlive: true });
|
|
948
|
+
|
|
949
|
+
// Hide the host container โ all Shadow DOM content stays intact
|
|
950
|
+
mounted.hostContainer.style.display = 'none';
|
|
951
|
+
mounted.state = 'hidden';
|
|
952
|
+
mounted.hiddenAt = Date.now();
|
|
953
|
+
|
|
954
|
+
// Move from mounted โ hidden
|
|
955
|
+
this.hidden.set(appName, mounted);
|
|
956
|
+
this.mounted.delete(appName);
|
|
957
|
+
|
|
958
|
+
// Execute afterUnmount hooks
|
|
959
|
+
await this.hooks.execute('afterUnmount', { appName, keepAlive: true });
|
|
960
|
+
await this.pluginSystem.callHook('afterUnmount', { appName, keepAlive: true });
|
|
961
|
+
|
|
962
|
+
// Emit event
|
|
963
|
+
this.eventBus.emit('app:hidden', { appName }, { appName });
|
|
964
|
+
|
|
965
|
+
logger.wuInfo(`${appName} hidden (keep-alive) โ state preserved`);
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/**
|
|
969
|
+
* Show a hidden (keep-alive) app.
|
|
970
|
+
*
|
|
971
|
+
* Restores visibility instantly โ no reload, no remount.
|
|
972
|
+
* The app's optional `activate()` lifecycle hook is called.
|
|
973
|
+
*
|
|
974
|
+
* @param {string} appName - App to show
|
|
975
|
+
*/
|
|
976
|
+
async show(appName) {
|
|
977
|
+
const hidden = this.hidden.get(appName);
|
|
978
|
+
if (!hidden) {
|
|
979
|
+
logger.wuWarn(`Cannot show ${appName}: not in keep-alive state`);
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
this.performance.startMeasure('show', appName);
|
|
984
|
+
logger.wuDebug(`Showing ${appName} from keep-alive`);
|
|
985
|
+
|
|
986
|
+
// Execute beforeMount hooks
|
|
987
|
+
await this.hooks.execute('beforeMount', {
|
|
988
|
+
appName,
|
|
989
|
+
containerSelector: hidden.containerSelector,
|
|
990
|
+
sandbox: hidden.sandbox,
|
|
991
|
+
lifecycle: hidden.lifecycle,
|
|
992
|
+
keepAlive: true
|
|
993
|
+
});
|
|
994
|
+
await this.pluginSystem.callHook('beforeMount', {
|
|
995
|
+
appName,
|
|
996
|
+
containerSelector: hidden.containerSelector,
|
|
997
|
+
keepAlive: true
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
// Show the host container
|
|
1001
|
+
hidden.hostContainer.style.display = '';
|
|
1002
|
+
hidden.state = 'stable';
|
|
1003
|
+
delete hidden.hiddenAt;
|
|
1004
|
+
|
|
1005
|
+
// Move from hidden โ mounted
|
|
1006
|
+
this.mounted.set(appName, hidden);
|
|
1007
|
+
this.hidden.delete(appName);
|
|
1008
|
+
|
|
1009
|
+
// Call optional activate lifecycle hook
|
|
1010
|
+
if (hidden.lifecycle?.activate) {
|
|
1011
|
+
try {
|
|
1012
|
+
await hidden.lifecycle.activate(hidden.container);
|
|
1013
|
+
} catch (err) {
|
|
1014
|
+
logger.wuWarn(`activate() failed for ${appName}:`, err);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
const showTime = this.performance.endMeasure('show', appName);
|
|
1019
|
+
|
|
1020
|
+
// Execute afterMount hooks
|
|
1021
|
+
await this.hooks.execute('afterMount', {
|
|
1022
|
+
appName,
|
|
1023
|
+
containerSelector: hidden.containerSelector,
|
|
1024
|
+
sandbox: hidden.sandbox,
|
|
1025
|
+
mountTime: showTime,
|
|
1026
|
+
keepAlive: true
|
|
1027
|
+
});
|
|
1028
|
+
await this.pluginSystem.callHook('afterMount', {
|
|
1029
|
+
appName,
|
|
1030
|
+
containerSelector: hidden.containerSelector,
|
|
1031
|
+
mountTime: showTime,
|
|
1032
|
+
keepAlive: true
|
|
1033
|
+
});
|
|
1034
|
+
|
|
1035
|
+
// Emit event
|
|
1036
|
+
this.eventBus.emit('app:shown', { appName, showTime }, { appName });
|
|
1037
|
+
|
|
1038
|
+
logger.wuInfo(`${appName} shown from keep-alive in ${showTime.toFixed(2)}ms`);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
/**
|
|
1042
|
+
* Force-destroy a hidden (keep-alive) app.
|
|
1043
|
+
* Runs full cleanup: lifecycle unmount, iframe destroy, sandbox cleanup.
|
|
1044
|
+
*
|
|
1045
|
+
* @param {string} appName
|
|
1046
|
+
* @private
|
|
1047
|
+
*/
|
|
1048
|
+
async _destroyHidden(appName) {
|
|
1049
|
+
const hidden = this.hidden.get(appName);
|
|
1050
|
+
if (!hidden) return;
|
|
1051
|
+
|
|
1052
|
+
logger.wuDebug(`Force-destroying hidden app: ${appName}`);
|
|
1053
|
+
|
|
1054
|
+
// Show first (so unmount sees the container)
|
|
1055
|
+
hidden.hostContainer.style.display = '';
|
|
1056
|
+
hidden.state = 'stable';
|
|
1057
|
+
|
|
1058
|
+
// Move back to mounted temporarily
|
|
1059
|
+
this.mounted.set(appName, hidden);
|
|
1060
|
+
this.hidden.delete(appName);
|
|
1061
|
+
|
|
1062
|
+
// Now do a full unmount
|
|
1063
|
+
await this.unmount(appName, { force: true });
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
/**
|
|
1067
|
+
* Check if an app is in keep-alive (hidden) state.
|
|
1068
|
+
* @param {string} appName
|
|
1069
|
+
* @returns {boolean}
|
|
1070
|
+
*/
|
|
1071
|
+
isHidden(appName) {
|
|
1072
|
+
return this.hidden.has(appName);
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1167
1075
|
/**
|
|
1168
1076
|
* Cargar componente compartido (para imports/exports)
|
|
1169
1077
|
* @param {string} componentPath - Ruta del componente (ej: "shared.Button")
|
|
@@ -1192,7 +1100,7 @@ export class WuCore {
|
|
|
1192
1100
|
}
|
|
1193
1101
|
|
|
1194
1102
|
/**
|
|
1195
|
-
* Obtener
|
|
1103
|
+
* Obtener informacion de una app
|
|
1196
1104
|
* @param {string} appName - Nombre de la app
|
|
1197
1105
|
*/
|
|
1198
1106
|
getAppInfo(appName) {
|
|
@@ -1205,19 +1113,20 @@ export class WuCore {
|
|
|
1205
1113
|
}
|
|
1206
1114
|
|
|
1207
1115
|
/**
|
|
1208
|
-
* Obtener
|
|
1116
|
+
* Obtener estadisticas del framework
|
|
1209
1117
|
*/
|
|
1210
1118
|
getStats() {
|
|
1211
1119
|
return {
|
|
1212
1120
|
registered: this.apps.size,
|
|
1213
1121
|
defined: this.definitions.size,
|
|
1214
1122
|
mounted: this.mounted.size,
|
|
1123
|
+
hidden: this.hidden.size,
|
|
1215
1124
|
apps: Array.from(this.apps.keys())
|
|
1216
1125
|
};
|
|
1217
1126
|
}
|
|
1218
1127
|
|
|
1219
1128
|
/**
|
|
1220
|
-
*
|
|
1129
|
+
* Store methods: Convenience methods for state management
|
|
1221
1130
|
*/
|
|
1222
1131
|
|
|
1223
1132
|
/**
|
|
@@ -1274,7 +1183,79 @@ export class WuCore {
|
|
|
1274
1183
|
}
|
|
1275
1184
|
|
|
1276
1185
|
/**
|
|
1277
|
-
*
|
|
1186
|
+
* Set a URL override for an app (QA/testing).
|
|
1187
|
+
* Sets a cookie so the override persists across page reloads.
|
|
1188
|
+
* Only affects the current browser โ no one else sees it.
|
|
1189
|
+
*
|
|
1190
|
+
* @param {string} appName - App to override
|
|
1191
|
+
* @param {string} url - Override URL (e.g., 'http://localhost:5173')
|
|
1192
|
+
* @param {Object} [options]
|
|
1193
|
+
* @param {number} [options.maxAge=86400] - Cookie lifetime in seconds (default: 24h)
|
|
1194
|
+
*
|
|
1195
|
+
* @example
|
|
1196
|
+
* wu.override('cart', 'http://localhost:5173');
|
|
1197
|
+
* wu.override('header', 'https://preview-abc123.vercel.app');
|
|
1198
|
+
*/
|
|
1199
|
+
override(appName, url, options) {
|
|
1200
|
+
this.overrides.set(appName, url, options);
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
/**
|
|
1204
|
+
* Remove URL override for an app.
|
|
1205
|
+
* @param {string} appName
|
|
1206
|
+
*/
|
|
1207
|
+
removeOverride(appName) {
|
|
1208
|
+
this.overrides.remove(appName);
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
/**
|
|
1212
|
+
* Get all active overrides.
|
|
1213
|
+
* @returns {Object} { appName: url, ... }
|
|
1214
|
+
*/
|
|
1215
|
+
getOverrides() {
|
|
1216
|
+
return this.overrides.getAll();
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
/**
|
|
1220
|
+
* Remove all overrides.
|
|
1221
|
+
*/
|
|
1222
|
+
clearOverrides() {
|
|
1223
|
+
this.overrides.clearAll();
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
/**
|
|
1227
|
+
* Prefetch one or more apps before they're needed.
|
|
1228
|
+
*
|
|
1229
|
+
* Uses Speculation Rules API (Chrome 121+), falls back to
|
|
1230
|
+
* <link rel="modulepreload"> or <link rel="prefetch">.
|
|
1231
|
+
*
|
|
1232
|
+
* @param {string|string[]} appNames - App name(s) to prefetch
|
|
1233
|
+
* @param {Object} [options]
|
|
1234
|
+
* @param {'immediate'|'hover'|'visible'|'idle'} [options.on='immediate'] - When to trigger
|
|
1235
|
+
* @param {string|Element} [options.target] - Element for hover/visible triggers
|
|
1236
|
+
* @param {'conservative'|'moderate'|'eager'} [options.eagerness='moderate'] - Speculation eagerness
|
|
1237
|
+
* @returns {Promise<void>|Function} Promise or cleanup function
|
|
1238
|
+
*
|
|
1239
|
+
* @example
|
|
1240
|
+
* wu.prefetch('cart');
|
|
1241
|
+
* wu.prefetch('cart', { on: 'hover', target: '#cart-link' });
|
|
1242
|
+
* wu.prefetch('cart', { on: 'visible', target: '#cart-section' });
|
|
1243
|
+
* wu.prefetch(['profile', 'settings'], { on: 'idle' });
|
|
1244
|
+
*/
|
|
1245
|
+
prefetch(appNames, options) {
|
|
1246
|
+
return this.prefetcher.prefetch(appNames, options);
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
/**
|
|
1250
|
+
* Prefetch all registered but not-yet-mounted apps.
|
|
1251
|
+
* @param {Object} [options] - Same options as prefetch()
|
|
1252
|
+
*/
|
|
1253
|
+
prefetchAll(options) {
|
|
1254
|
+
return this.prefetcher.prefetchAll(options);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
/**
|
|
1258
|
+
* Create WuApp instance for declarative usage
|
|
1278
1259
|
* @param {string} name - App name
|
|
1279
1260
|
* @param {Object} config - Configuration { url, container, autoInit }
|
|
1280
1261
|
* @returns {WuApp} WuApp instance
|
|
@@ -1287,71 +1268,57 @@ export class WuCore {
|
|
|
1287
1268
|
* Limpiar todo el framework
|
|
1288
1269
|
*/
|
|
1289
1270
|
async destroy() {
|
|
1290
|
-
|
|
1271
|
+
logger.wuDebug('Destroying framework...');
|
|
1291
1272
|
|
|
1292
1273
|
try {
|
|
1293
|
-
//
|
|
1274
|
+
// Execute beforeDestroy hooks
|
|
1294
1275
|
await this.hooks.execute('beforeDestroy', {});
|
|
1295
1276
|
|
|
1296
|
-
//
|
|
1277
|
+
// Call plugin onDestroy hooks
|
|
1297
1278
|
await this.pluginSystem.callHook('onDestroy', {});
|
|
1298
1279
|
|
|
1299
|
-
//
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
this.healthState.monitor = null;
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
// Limpiar todos los MutationObservers por contenedor
|
|
1306
|
-
if (this.healthState.containerObservers) {
|
|
1307
|
-
for (const [appName, observer] of this.healthState.containerObservers) {
|
|
1308
|
-
observer.disconnect();
|
|
1309
|
-
}
|
|
1310
|
-
this.healthState.containerObservers.clear();
|
|
1311
|
-
}
|
|
1312
|
-
|
|
1313
|
-
// Limpiar timeouts pendientes
|
|
1314
|
-
if (this.healthState.mutationCheckTimeout) {
|
|
1315
|
-
clearTimeout(this.healthState.mutationCheckTimeout);
|
|
1280
|
+
// Force-destroy all hidden (keep-alive) apps first
|
|
1281
|
+
for (const appName of [...this.hidden.keys()]) {
|
|
1282
|
+
await this._destroyHidden(appName);
|
|
1316
1283
|
}
|
|
1317
1284
|
|
|
1318
1285
|
// Desmontar todas las apps
|
|
1319
|
-
for (const appName of this.mounted.keys()) {
|
|
1320
|
-
await this.unmount(appName);
|
|
1286
|
+
for (const appName of [...this.mounted.keys()]) {
|
|
1287
|
+
await this.unmount(appName, { force: true });
|
|
1321
1288
|
}
|
|
1322
1289
|
|
|
1323
|
-
//
|
|
1290
|
+
// Limpiar sistemas esenciales
|
|
1324
1291
|
this.cache.clear();
|
|
1325
1292
|
this.eventBus.removeAll();
|
|
1326
1293
|
this.eventBus.clearHistory();
|
|
1327
1294
|
this.performance.clearMetrics();
|
|
1328
1295
|
|
|
1329
|
-
//
|
|
1296
|
+
// Limpiar advanced systems
|
|
1330
1297
|
this.pluginSystem.cleanup();
|
|
1331
1298
|
this.strategies.cleanup();
|
|
1332
1299
|
this.errorBoundary.cleanup();
|
|
1333
1300
|
this.hooks.cleanup();
|
|
1334
|
-
this.
|
|
1335
|
-
this.registry.cleanup();
|
|
1301
|
+
this.prefetcher.cleanup();
|
|
1336
1302
|
|
|
1337
1303
|
// Limpiar registros
|
|
1338
1304
|
this.apps.clear();
|
|
1339
1305
|
this.definitions.clear();
|
|
1340
1306
|
this.manifests.clear();
|
|
1341
1307
|
this.mounted.clear();
|
|
1308
|
+
this.hidden.clear();
|
|
1342
1309
|
|
|
1343
1310
|
// Limpiar store
|
|
1344
1311
|
this.store.clear();
|
|
1345
1312
|
|
|
1346
1313
|
this.isInitialized = false;
|
|
1347
1314
|
|
|
1348
|
-
//
|
|
1315
|
+
// Execute afterDestroy hooks
|
|
1349
1316
|
await this.hooks.execute('afterDestroy', {});
|
|
1350
1317
|
|
|
1351
|
-
|
|
1318
|
+
logger.wuDebug('Framework destroyed');
|
|
1352
1319
|
} catch (error) {
|
|
1353
|
-
|
|
1320
|
+
logger.wuError('Error during destroy:', error);
|
|
1354
1321
|
throw error;
|
|
1355
1322
|
}
|
|
1356
1323
|
}
|
|
1357
|
-
}
|
|
1324
|
+
}
|