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