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