wu-framework 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +559 -0
- package/package.json +84 -0
- package/src/api/wu-simple.js +316 -0
- package/src/core/wu-app.js +192 -0
- package/src/core/wu-cache.js +374 -0
- package/src/core/wu-core.js +1296 -0
- package/src/core/wu-error-boundary.js +380 -0
- package/src/core/wu-event-bus.js +257 -0
- package/src/core/wu-hooks.js +348 -0
- package/src/core/wu-html-parser.js +280 -0
- package/src/core/wu-loader.js +271 -0
- package/src/core/wu-logger.js +119 -0
- package/src/core/wu-manifest.js +366 -0
- package/src/core/wu-performance.js +226 -0
- package/src/core/wu-plugin.js +213 -0
- package/src/core/wu-proxy-sandbox.js +153 -0
- package/src/core/wu-registry.js +130 -0
- package/src/core/wu-sandbox-pool.js +390 -0
- package/src/core/wu-sandbox.js +720 -0
- package/src/core/wu-script-executor.js +216 -0
- package/src/core/wu-snapshot-sandbox.js +184 -0
- package/src/core/wu-store.js +297 -0
- package/src/core/wu-strategies.js +241 -0
- package/src/core/wu-style-bridge.js +357 -0
- package/src/index.js +690 -0
- package/src/utils/dependency-resolver.js +326 -0
|
@@ -0,0 +1,1296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🚀 WU-FRAMEWORK: UNIVERSAL MICROFRONTENDS
|
|
3
|
+
* Motor principal agnóstico - 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 { WuSandboxPool } from './wu-sandbox-pool.js';
|
|
20
|
+
import { WuRegistry } from './wu-registry.js';
|
|
21
|
+
|
|
22
|
+
export class WuCore {
|
|
23
|
+
constructor(options = {}) {
|
|
24
|
+
// Registros principales
|
|
25
|
+
this.apps = new Map(); // Apps registradas
|
|
26
|
+
this.definitions = new Map(); // Definiciones de lifecycle
|
|
27
|
+
this.manifests = new Map(); // Manifiestos cargados
|
|
28
|
+
this.mounted = new Map(); // Apps montadas
|
|
29
|
+
|
|
30
|
+
// Componentes core
|
|
31
|
+
this.loader = new WuLoader();
|
|
32
|
+
this.sandbox = new WuSandbox();
|
|
33
|
+
this.manifest = new WuManifest();
|
|
34
|
+
this.store = store;
|
|
35
|
+
|
|
36
|
+
// 🚀 SISTEMAS ESENCIALES
|
|
37
|
+
this.cache = new WuCache({ storage: 'localStorage', maxSize: 100 }); // 100MB cache
|
|
38
|
+
this.eventBus = new WuEventBus();
|
|
39
|
+
this.performance = new WuPerformance();
|
|
40
|
+
|
|
41
|
+
// 🎯 ADVANCED SYSTEMS
|
|
42
|
+
this.pluginSystem = new WuPluginSystem(this);
|
|
43
|
+
this.strategies = new WuLoadingStrategy(this);
|
|
44
|
+
this.errorBoundary = new WuErrorBoundary(this);
|
|
45
|
+
this.hooks = new WuLifecycleHooks(this);
|
|
46
|
+
this.sandboxPool = new WuSandboxPool(this);
|
|
47
|
+
this.registry = new WuRegistry();
|
|
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
|
+
};
|
|
66
|
+
|
|
67
|
+
// Estado
|
|
68
|
+
this.isInitialized = false;
|
|
69
|
+
|
|
70
|
+
// 🚀 Initialize self-healing system (ONLY if enabled)
|
|
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: Real-time DOM monitoring for efficiency
|
|
108
|
+
*/
|
|
109
|
+
initializeMutationObserver() {
|
|
110
|
+
if (!window.MutationObserver) return; // Fallback for older browsers
|
|
111
|
+
|
|
112
|
+
this.healthState.mutationObserver = new MutationObserver((mutations) => {
|
|
113
|
+
let shouldCheckHealth = false;
|
|
114
|
+
|
|
115
|
+
for (const mutation of mutations) {
|
|
116
|
+
// Only check health if it affects our mounted apps
|
|
117
|
+
if (mutation.type === 'childList') {
|
|
118
|
+
const removedNodes = Array.from(mutation.removedNodes);
|
|
119
|
+
const affectedApps = removedNodes.some(node => {
|
|
120
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
121
|
+
// Check if removed node contains any of our mounted apps
|
|
122
|
+
for (const [appName, mounted] of this.mounted) {
|
|
123
|
+
if (node.contains && node.contains(mounted.container)) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
if (node === mounted.container) {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return false;
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
if (affectedApps) {
|
|
135
|
+
shouldCheckHealth = true;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Debounced health check only when necessary
|
|
142
|
+
if (shouldCheckHealth) {
|
|
143
|
+
clearTimeout(this.healthState.mutationCheckTimeout);
|
|
144
|
+
this.healthState.mutationCheckTimeout = setTimeout(() => {
|
|
145
|
+
this.performHealthCheck();
|
|
146
|
+
}, 1000); // Wait 1 second to batch multiple mutations
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Observe the entire document but only care about child changes
|
|
151
|
+
this.healthState.mutationObserver.observe(document.body, {
|
|
152
|
+
childList: true,
|
|
153
|
+
subtree: true
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
logger.wuDebug('🔍 MutationObserver initialized for smart DOM monitoring');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* 💊 HEALTH CHECK: Continuous app monitoring
|
|
161
|
+
*/
|
|
162
|
+
async performHealthCheck() {
|
|
163
|
+
try {
|
|
164
|
+
const now = Date.now();
|
|
165
|
+
const healthIssues = [];
|
|
166
|
+
|
|
167
|
+
// Check mounted apps vitality
|
|
168
|
+
for (const [appName, mountedApp] of this.mounted) {
|
|
169
|
+
const age = now - mountedApp.timestamp;
|
|
170
|
+
const container = mountedApp.container;
|
|
171
|
+
|
|
172
|
+
// 🛡️ ENHANCED CHECK: Verify container exists and is properly connected
|
|
173
|
+
// IMPORTANT: Check multiple conditions to avoid false positives
|
|
174
|
+
let isOrphaned = false;
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
// Check 1: Direct DOM connection
|
|
178
|
+
const containerInDOM = document.contains(container);
|
|
179
|
+
|
|
180
|
+
// Check 2: Parent element connection (for shadow DOM)
|
|
181
|
+
const parentInDOM = container.parentElement && document.contains(container.parentElement);
|
|
182
|
+
|
|
183
|
+
// Check 3: Shadow root connection
|
|
184
|
+
const shadowHost = mountedApp.sandbox?.shadowRoot?.host;
|
|
185
|
+
const shadowHostInDOM = shadowHost && document.contains(shadowHost);
|
|
186
|
+
|
|
187
|
+
// Check 4: Container still has valid properties (not detached)
|
|
188
|
+
const hasValidParent = container.parentElement !== null || container.parentNode !== null;
|
|
189
|
+
|
|
190
|
+
// Only mark as orphaned if ALL checks fail
|
|
191
|
+
if (!containerInDOM && !parentInDOM && !shadowHostInDOM && !hasValidParent) {
|
|
192
|
+
isOrphaned = true;
|
|
193
|
+
}
|
|
194
|
+
} catch (error) {
|
|
195
|
+
// If checking fails, assume NOT orphaned (conservative approach)
|
|
196
|
+
console.warn(`[Wu] ⚠️ Container check failed for ${appName}:`, error);
|
|
197
|
+
isOrphaned = false;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (isOrphaned) {
|
|
201
|
+
healthIssues.push({
|
|
202
|
+
type: 'orphaned_container',
|
|
203
|
+
appName,
|
|
204
|
+
severity: 'high'
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Check app state - ONLY if it's truly unstable, not 'refreshed'
|
|
209
|
+
// 'refreshed' is a normal state after aging refresh
|
|
210
|
+
if (mountedApp.state && mountedApp.state !== 'stable' && mountedApp.state !== 'refreshed') {
|
|
211
|
+
healthIssues.push({
|
|
212
|
+
type: 'unstable_state',
|
|
213
|
+
appName,
|
|
214
|
+
severity: 'medium'
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// 🕰️ Check for long-running apps that might need refresh
|
|
219
|
+
// ONLY if agingThreshold is configured (default: Infinity = never)
|
|
220
|
+
if (age > this.healthConfig.agingThreshold) {
|
|
221
|
+
healthIssues.push({
|
|
222
|
+
type: 'aging_app',
|
|
223
|
+
appName,
|
|
224
|
+
severity: 'low',
|
|
225
|
+
age: age
|
|
226
|
+
});
|
|
227
|
+
console.log(`[Wu] ⏰ App ${appName} has been running for ${(age / 1000 / 60).toFixed(1)} minutes`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Process health issues
|
|
232
|
+
if (healthIssues.length > 0) {
|
|
233
|
+
await this.processHealthIssues(healthIssues);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
this.healthState.lastHealthCheck = now;
|
|
237
|
+
|
|
238
|
+
} catch (error) {
|
|
239
|
+
console.warn('[Wu] ⚠️ Health check failed:', error);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* 🛠️ HEALTH ISSUE PROCESSOR
|
|
245
|
+
*/
|
|
246
|
+
async processHealthIssues(issues) {
|
|
247
|
+
for (const issue of issues) {
|
|
248
|
+
if (this.healthState.healingInProgress.has(issue.appName)) {
|
|
249
|
+
continue; // Already healing
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
console.log(`[Wu] 🩺 Health issue detected:`, issue);
|
|
253
|
+
|
|
254
|
+
switch (issue.type) {
|
|
255
|
+
case 'orphaned_container':
|
|
256
|
+
await this.healOrphanedContainer(issue.appName);
|
|
257
|
+
break;
|
|
258
|
+
case 'unstable_state':
|
|
259
|
+
await this.stabilizeAppState(issue.appName);
|
|
260
|
+
break;
|
|
261
|
+
case 'aging_app':
|
|
262
|
+
await this.refreshAgingApp(issue.appName);
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* 🔄 ORPHANED CONTAINER HEALING
|
|
270
|
+
*/
|
|
271
|
+
async healOrphanedContainer(appName) {
|
|
272
|
+
this.healthState.healingInProgress.add(appName);
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
console.log(`[Wu] 🔄 Healing orphaned container for ${appName}`);
|
|
276
|
+
|
|
277
|
+
// Clean up orphaned state
|
|
278
|
+
await this.unmount(appName);
|
|
279
|
+
|
|
280
|
+
// 🔍 Find a suitable container to remount
|
|
281
|
+
const suitableContainers = [
|
|
282
|
+
document.querySelector(`[data-wu-app="${appName}"]`),
|
|
283
|
+
document.querySelector(`#${appName}-container`),
|
|
284
|
+
document.querySelector(`.${appName}-container`),
|
|
285
|
+
document.querySelector(`[id*="${appName}"]`)
|
|
286
|
+
].filter(Boolean);
|
|
287
|
+
|
|
288
|
+
if (suitableContainers.length > 0) {
|
|
289
|
+
const container = suitableContainers[0];
|
|
290
|
+
const containerSelector = container.id ? `#${container.id}` : `.${container.className.split(' ')[0]}`;
|
|
291
|
+
|
|
292
|
+
// 🎯 Only attempt re-mount if selector is valid
|
|
293
|
+
if (containerSelector && containerSelector !== '#' && containerSelector !== '.') {
|
|
294
|
+
await this.mount(appName, containerSelector);
|
|
295
|
+
console.log(`[Wu] ✨ Successfully healed orphaned ${appName} in ${containerSelector}`);
|
|
296
|
+
} else {
|
|
297
|
+
console.warn(`[Wu] ⚠️ Could not determine valid selector for ${appName}`);
|
|
298
|
+
}
|
|
299
|
+
} else {
|
|
300
|
+
console.warn(`[Wu] ⚠️ No suitable container found for healing ${appName}`);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
} catch (error) {
|
|
304
|
+
console.warn(`[Wu] ⚠️ Failed to heal orphaned ${appName}:`, error);
|
|
305
|
+
} finally {
|
|
306
|
+
this.healthState.healingInProgress.delete(appName);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* ⚖️ APP STATE STABILIZATION
|
|
312
|
+
*/
|
|
313
|
+
async stabilizeAppState(appName) {
|
|
314
|
+
this.healthState.healingInProgress.add(appName);
|
|
315
|
+
|
|
316
|
+
try {
|
|
317
|
+
console.log(`[Wu] ⚖️ Stabilizing app state for ${appName}`);
|
|
318
|
+
|
|
319
|
+
const mounted = this.mounted.get(appName);
|
|
320
|
+
if (mounted) {
|
|
321
|
+
mounted.state = 'stable';
|
|
322
|
+
// ⚠️ DO NOT RESET TIMESTAMP - preserves app age for aging checks
|
|
323
|
+
console.log(`[Wu] ✨ App state stabilized for ${appName}`);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
} catch (error) {
|
|
327
|
+
console.warn(`[Wu] ⚠️ Failed to stabilize ${appName}:`, error);
|
|
328
|
+
} finally {
|
|
329
|
+
this.healthState.healingInProgress.delete(appName);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* 🕰️ AGING APP REFRESH
|
|
335
|
+
* NOTE: This should ONLY trigger if agingThreshold is explicitly configured
|
|
336
|
+
*/
|
|
337
|
+
async refreshAgingApp(appName) {
|
|
338
|
+
// Safety check: Don't refresh if aging is disabled (Infinity)
|
|
339
|
+
if (this.healthConfig.agingThreshold === Infinity) {
|
|
340
|
+
console.warn(`[Wu] ⚠️ refreshAgingApp called but agingThreshold is Infinity - skipping`);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
this.healthState.healingInProgress.add(appName);
|
|
345
|
+
|
|
346
|
+
try {
|
|
347
|
+
console.log(`[Wu] 🕰️ Refreshing aging app for ${appName}`);
|
|
348
|
+
|
|
349
|
+
const mounted = this.mounted.get(appName);
|
|
350
|
+
if (mounted) {
|
|
351
|
+
// Gentle refresh without full remount
|
|
352
|
+
// Reset timestamp ONLY when explicitly configured aging refresh
|
|
353
|
+
mounted.timestamp = Date.now();
|
|
354
|
+
mounted.state = 'refreshed';
|
|
355
|
+
|
|
356
|
+
// Trigger a soft refresh of the app if it supports it
|
|
357
|
+
if (mounted.lifecycle.refresh) {
|
|
358
|
+
await mounted.lifecycle.refresh(mounted.container);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
console.log(`[Wu] ✨ App refreshed for ${appName}`);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
} catch (error) {
|
|
365
|
+
console.warn(`[Wu] ⚠️ Failed to refresh ${appName}:`, error);
|
|
366
|
+
} finally {
|
|
367
|
+
this.healthState.healingInProgress.delete(appName);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* 💥 ERROR EVENT HANDLER
|
|
373
|
+
*/
|
|
374
|
+
handleErrorEvent(event) {
|
|
375
|
+
console.log('[Wu] 💥 Error event detected:', event.error);
|
|
376
|
+
|
|
377
|
+
// Check if error is related to any mounted apps
|
|
378
|
+
for (const [appName, mounted] of this.mounted) {
|
|
379
|
+
if (event.filename && event.filename.includes(mounted.app.url)) {
|
|
380
|
+
console.log(`[Wu] 🎯 Error traced to ${appName}, initiating recovery...`);
|
|
381
|
+
|
|
382
|
+
// Schedule healing
|
|
383
|
+
setTimeout(() => {
|
|
384
|
+
this.healOrphanedContainer(appName);
|
|
385
|
+
}, 1000);
|
|
386
|
+
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* 🚫 REJECTION HANDLER
|
|
394
|
+
*/
|
|
395
|
+
handleRejection(event) {
|
|
396
|
+
console.log('[Wu] 🚫 Promise rejection detected:', event.reason);
|
|
397
|
+
|
|
398
|
+
// Prevent default unhandled rejection
|
|
399
|
+
event.preventDefault();
|
|
400
|
+
|
|
401
|
+
// Mark system as unstable temporarily
|
|
402
|
+
this.healthState.systemStable = false;
|
|
403
|
+
|
|
404
|
+
// Schedule stability restoration
|
|
405
|
+
setTimeout(() => {
|
|
406
|
+
this.healthState.systemStable = true;
|
|
407
|
+
console.log('[Wu] ✨ System stability restored');
|
|
408
|
+
}, 5000);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Inicializar wu-framework con configuración de apps
|
|
413
|
+
* @param {Object} config - Configuración { apps: [{name, url}, ...] }
|
|
414
|
+
*/
|
|
415
|
+
async init(config) {
|
|
416
|
+
if (this.isInitialized) {
|
|
417
|
+
console.warn('[Wu] Framework already initialized');
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
logger.wuDebug('🔧 Initializing with apps:', config.apps?.map(app => app.name));
|
|
422
|
+
|
|
423
|
+
try {
|
|
424
|
+
// 🪝 Execute beforeInit hooks
|
|
425
|
+
const beforeInitResult = await this.hooks.execute('beforeInit', { config });
|
|
426
|
+
if (beforeInitResult.cancelled) {
|
|
427
|
+
console.warn('[Wu] Initialization cancelled by beforeInit hook');
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// 🔌 Call plugin beforeInit hooks
|
|
432
|
+
await this.pluginSystem.callHook('beforeInit', { config });
|
|
433
|
+
|
|
434
|
+
// 🏊 Initialize sandbox pool
|
|
435
|
+
await this.sandboxPool.init();
|
|
436
|
+
|
|
437
|
+
// Registrar todas las apps
|
|
438
|
+
for (const appConfig of config.apps || []) {
|
|
439
|
+
await this.registerApp(appConfig);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// 🎯 Preload apps with eager/preload strategies
|
|
443
|
+
await this.strategies.preload(config.apps || []);
|
|
444
|
+
|
|
445
|
+
this.isInitialized = true;
|
|
446
|
+
|
|
447
|
+
// 🪝 Execute afterInit hooks
|
|
448
|
+
await this.hooks.execute('afterInit', { config });
|
|
449
|
+
|
|
450
|
+
// 🔌 Call plugin afterInit hooks
|
|
451
|
+
await this.pluginSystem.callHook('afterInit', { config });
|
|
452
|
+
|
|
453
|
+
logger.wuInfo('✅ Framework initialized successfully');
|
|
454
|
+
} catch (error) {
|
|
455
|
+
console.error('[Wu] ❌ Initialization failed:', error);
|
|
456
|
+
|
|
457
|
+
// 🔌 Call plugin error hooks
|
|
458
|
+
await this.pluginSystem.callHook('onError', { phase: 'init', error });
|
|
459
|
+
|
|
460
|
+
throw error;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Registrar una aplicación
|
|
466
|
+
* @param {Object} appConfig - { name, url }
|
|
467
|
+
*/
|
|
468
|
+
async registerApp(appConfig) {
|
|
469
|
+
const { name, url } = appConfig;
|
|
470
|
+
|
|
471
|
+
try {
|
|
472
|
+
logger.wuDebug(`📦 Registering app: ${name} from ${url}`);
|
|
473
|
+
|
|
474
|
+
// Cargar manifest
|
|
475
|
+
const manifestData = await this.manifest.load(url);
|
|
476
|
+
this.manifests.set(name, manifestData);
|
|
477
|
+
|
|
478
|
+
// Registrar la app
|
|
479
|
+
this.apps.set(name, {
|
|
480
|
+
name,
|
|
481
|
+
url,
|
|
482
|
+
manifest: manifestData,
|
|
483
|
+
status: 'registered'
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
logger.wuDebug(`✅ App ${name} registered successfully`);
|
|
487
|
+
} catch (error) {
|
|
488
|
+
console.error(`[Wu] ❌ Failed to register app ${name}:`, error);
|
|
489
|
+
throw error;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Definir lifecycle de una micro-app
|
|
495
|
+
* @param {string} appName - Nombre de la app
|
|
496
|
+
* @param {Object} lifecycle - { mount, unmount }
|
|
497
|
+
*/
|
|
498
|
+
define(appName, lifecycle) {
|
|
499
|
+
if (!lifecycle.mount) {
|
|
500
|
+
throw new Error(`[Wu] Mount function required for app: ${appName}`);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
this.definitions.set(appName, lifecycle);
|
|
504
|
+
|
|
505
|
+
// 🔔 Notify registry that app is ready
|
|
506
|
+
this.registry.markAsRegistered(appName);
|
|
507
|
+
|
|
508
|
+
// 📡 Dispatch custom event for external listeners
|
|
509
|
+
const event = new CustomEvent('wu:app:ready', {
|
|
510
|
+
detail: { appName, timestamp: Date.now() }
|
|
511
|
+
});
|
|
512
|
+
window.dispatchEvent(event);
|
|
513
|
+
|
|
514
|
+
logger.wuDebug(`📋 Lifecycle defined for: ${appName}`);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* 🚀 MOUNT APP: Multi-retry app mounting with self-healing
|
|
519
|
+
* @param {string} appName - Nombre de la app
|
|
520
|
+
* @param {string} containerSelector - Selector del contenedor
|
|
521
|
+
*/
|
|
522
|
+
async mount(appName, containerSelector) {
|
|
523
|
+
return await this.mountWithRecovery(appName, containerSelector, 0);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* 🔄 MOUNT WITH RECOVERY: Self-healing app mounting
|
|
528
|
+
*/
|
|
529
|
+
async mountWithRecovery(appName, containerSelector, attempt = 0) {
|
|
530
|
+
const maxAttempts = 3;
|
|
531
|
+
|
|
532
|
+
try {
|
|
533
|
+
// ⚡ Start performance measurement
|
|
534
|
+
this.performance.startMeasure('mount', appName);
|
|
535
|
+
|
|
536
|
+
logger.wuDebug(`🔗 Mounting ${appName} in ${containerSelector} (attempt ${attempt + 1})`);
|
|
537
|
+
|
|
538
|
+
// 🪝 Execute beforeLoad hooks
|
|
539
|
+
const beforeLoadResult = await this.hooks.execute('beforeLoad', { appName, containerSelector, attempt });
|
|
540
|
+
if (beforeLoadResult.cancelled) {
|
|
541
|
+
console.warn('[Wu] Mount cancelled by beforeLoad hook');
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// 🔌 Call plugin beforeMount hooks
|
|
546
|
+
const pluginBeforeMount = await this.pluginSystem.callHook('beforeMount', { appName, containerSelector });
|
|
547
|
+
if (pluginBeforeMount === false) {
|
|
548
|
+
console.warn('[Wu] Mount cancelled by plugin beforeMount hook');
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// 🔮 Quantum state verification
|
|
553
|
+
const app = this.apps.get(appName);
|
|
554
|
+
if (!app) {
|
|
555
|
+
throw new Error(`App ${appName} not registered. Call wu.init() first.`);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// 🌟 Container reality check
|
|
559
|
+
const container = document.querySelector(containerSelector);
|
|
560
|
+
if (!container) {
|
|
561
|
+
throw new Error(`Container not found: ${containerSelector}`);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// 🏊 Acquire sandbox from pool (if configured)
|
|
565
|
+
const poolSandbox = this.sandboxPool.acquire(appName);
|
|
566
|
+
|
|
567
|
+
// 🛡️ Quantum sandbox creation
|
|
568
|
+
const sandbox = this.sandbox.create(appName, container);
|
|
569
|
+
|
|
570
|
+
// 🪝 Execute afterLoad hooks
|
|
571
|
+
await this.hooks.execute('afterLoad', { appName, containerSelector, sandbox });
|
|
572
|
+
|
|
573
|
+
// 🚀 Transcendent lifecycle resolution
|
|
574
|
+
let lifecycle = this.definitions.get(appName);
|
|
575
|
+
if (!lifecycle) {
|
|
576
|
+
// Load remote app
|
|
577
|
+
await this.loadAndMountRemoteApp(app, sandbox);
|
|
578
|
+
lifecycle = this.definitions.get(appName);
|
|
579
|
+
|
|
580
|
+
if (!lifecycle) {
|
|
581
|
+
throw new Error(`App ${appName} did not register with wu.define()`);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// 🪝 Execute beforeMount hooks
|
|
586
|
+
const beforeMountResult = await this.hooks.execute('beforeMount', { appName, containerSelector, sandbox, lifecycle });
|
|
587
|
+
if (beforeMountResult.cancelled) {
|
|
588
|
+
console.warn('[Wu] Mount cancelled by beforeMount hook');
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// 🎨 Wait for styles to be ready before mounting
|
|
593
|
+
if (sandbox.stylesReady) {
|
|
594
|
+
console.log(`[Wu] ⏳ Waiting for styles to be ready for ${appName}...`);
|
|
595
|
+
await sandbox.stylesReady;
|
|
596
|
+
console.log(`[Wu] ✅ Styles ready for ${appName}`);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// ⚡ Quantum mounting execution
|
|
600
|
+
await lifecycle.mount(sandbox.container);
|
|
601
|
+
|
|
602
|
+
// 🌌 Registration in mounted dimension
|
|
603
|
+
this.mounted.set(appName, {
|
|
604
|
+
app,
|
|
605
|
+
sandbox,
|
|
606
|
+
poolSandbox,
|
|
607
|
+
lifecycle,
|
|
608
|
+
container: sandbox.container,
|
|
609
|
+
timestamp: Date.now(),
|
|
610
|
+
state: 'stable'
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
// ⚡ End performance measurement
|
|
614
|
+
const mountTime = this.performance.endMeasure('mount', appName);
|
|
615
|
+
|
|
616
|
+
// 🪝 Execute afterMount hooks
|
|
617
|
+
await this.hooks.execute('afterMount', { appName, containerSelector, sandbox, mountTime });
|
|
618
|
+
|
|
619
|
+
// 🔌 Call plugin afterMount hooks
|
|
620
|
+
await this.pluginSystem.callHook('afterMount', { appName, containerSelector, mountTime });
|
|
621
|
+
|
|
622
|
+
// 📢 Emit mount event
|
|
623
|
+
this.eventBus.emit('app:mounted', { appName, mountTime, attempt }, { appName });
|
|
624
|
+
|
|
625
|
+
logger.wuInfo(`✅ ${appName} mounted successfully in ${mountTime.toFixed(2)}ms`);
|
|
626
|
+
|
|
627
|
+
} catch (error) {
|
|
628
|
+
console.error(`[Wu] ❌ Mount attempt ${attempt + 1} failed for ${appName}:`, error);
|
|
629
|
+
|
|
630
|
+
// 🛡️ Use error boundary for intelligent error handling
|
|
631
|
+
const errorResult = await this.errorBoundary.handle(error, {
|
|
632
|
+
appName,
|
|
633
|
+
containerSelector,
|
|
634
|
+
retryCount: attempt,
|
|
635
|
+
container: containerSelector
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
// Si el error boundary recuperó el error, no necesitamos reintentar
|
|
639
|
+
if (errorResult.recovered) {
|
|
640
|
+
console.log(`[Wu] ✨ Error recovered by error boundary`);
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// 🔄 RECOVERY PROTOCOL
|
|
645
|
+
if (attempt < maxAttempts - 1 && errorResult.action === 'retry') {
|
|
646
|
+
console.log(`[Wu] 🌟 Initiating recovery protocol...`);
|
|
647
|
+
|
|
648
|
+
// 🛠️ Clean app state
|
|
649
|
+
await this.appStateCleanup(appName, containerSelector);
|
|
650
|
+
|
|
651
|
+
// ⏱️ Temporal stabilization
|
|
652
|
+
await new Promise(resolve => setTimeout(resolve, 1000 * (attempt + 1)));
|
|
653
|
+
|
|
654
|
+
// 🚀 Recursive mounting with recovery
|
|
655
|
+
return await this.mountWithRecovery(appName, containerSelector, attempt + 1);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// 🔌 Call plugin error hooks
|
|
659
|
+
await this.pluginSystem.callHook('onError', { phase: 'mount', error, appName });
|
|
660
|
+
|
|
661
|
+
// 💥 Final mount failure - error boundary already handled fallback UI
|
|
662
|
+
throw error;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* 🛠️ APP STATE CLEANUP: Enhanced container cleanup with framework protection
|
|
668
|
+
*/
|
|
669
|
+
async appStateCleanup(appName, containerSelector) {
|
|
670
|
+
try {
|
|
671
|
+
console.log(`[Wu] 🧹 Starting app state cleanup for ${appName}...`);
|
|
672
|
+
|
|
673
|
+
// 🚀 Clear any existing mounted state safely
|
|
674
|
+
if (this.mounted.has(appName)) {
|
|
675
|
+
try {
|
|
676
|
+
await this.unmount(appName);
|
|
677
|
+
} catch (unmountError) {
|
|
678
|
+
console.warn(`[Wu] ⚠️ Unmount failed during cleanup:`, unmountError);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// 🌌 Enhanced container cleanup with Vue safety measures
|
|
683
|
+
const container = document.querySelector(containerSelector);
|
|
684
|
+
if (container) {
|
|
685
|
+
// 🛡️ Protect Vue's reactivity system
|
|
686
|
+
if (container.shadowRoot) {
|
|
687
|
+
try {
|
|
688
|
+
// Clear shadow root content safely
|
|
689
|
+
const shadowChildren = Array.from(container.shadowRoot.children);
|
|
690
|
+
shadowChildren.forEach(child => {
|
|
691
|
+
try {
|
|
692
|
+
child.remove();
|
|
693
|
+
} catch (removeError) {
|
|
694
|
+
console.warn(`[Wu] ⚠️ Failed to remove shadow child:`, removeError);
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
} catch (shadowError) {
|
|
698
|
+
console.warn(`[Wu] ⚠️ Shadow root cleanup failed:`, shadowError);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// 🔮 Clear any direct children if no shadow root
|
|
703
|
+
if (!container.shadowRoot && container.children.length > 0) {
|
|
704
|
+
try {
|
|
705
|
+
container.innerHTML = '';
|
|
706
|
+
} catch (htmlError) {
|
|
707
|
+
console.warn(`[Wu] ⚠️ Container innerHTML cleanup failed:`, htmlError);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// 🌟 Reset container attributes
|
|
712
|
+
container.removeAttribute('data-wu-app');
|
|
713
|
+
container.removeAttribute('data-quantum-state');
|
|
714
|
+
container.removeAttribute('wu-debug');
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// 🎯 Reset definition state
|
|
718
|
+
this.definitions.delete(appName);
|
|
719
|
+
|
|
720
|
+
// 🚀 Clear sandbox registry
|
|
721
|
+
if (this.sandbox && this.sandbox.sandboxes) {
|
|
722
|
+
this.sandbox.sandboxes.delete(appName);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
console.log(`[Wu] ✅ App state cleaned successfully for ${appName}`);
|
|
726
|
+
|
|
727
|
+
} catch (cleanupError) {
|
|
728
|
+
console.warn(`[Wu] ⚠️ App cleanup partial failure for ${appName}:`, cleanupError);
|
|
729
|
+
|
|
730
|
+
// 🌌 Emergency cleanup - force clear everything
|
|
731
|
+
try {
|
|
732
|
+
const container = document.querySelector(containerSelector);
|
|
733
|
+
if (container) {
|
|
734
|
+
container.style.display = 'none';
|
|
735
|
+
setTimeout(() => {
|
|
736
|
+
if (container) {
|
|
737
|
+
container.style.display = '';
|
|
738
|
+
}
|
|
739
|
+
}, 100);
|
|
740
|
+
}
|
|
741
|
+
} catch (emergencyError) {
|
|
742
|
+
console.error(`[Wu] 💥 Emergency cleanup failed:`, emergencyError);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* 💥 ERROR STATE RENDERER: Visual error manifestation
|
|
749
|
+
*/
|
|
750
|
+
async renderErrorState(containerSelector, appName, error) {
|
|
751
|
+
try {
|
|
752
|
+
const container = document.querySelector(containerSelector);
|
|
753
|
+
if (!container) return;
|
|
754
|
+
|
|
755
|
+
// 🛡️ Clear container safely
|
|
756
|
+
container.innerHTML = '';
|
|
757
|
+
|
|
758
|
+
// 🌌 Create quantum error visualization with safe DOM manipulation
|
|
759
|
+
const errorContainer = document.createElement('div');
|
|
760
|
+
errorContainer.className = 'quantum-error-state';
|
|
761
|
+
|
|
762
|
+
// Apply styles programmatically
|
|
763
|
+
Object.assign(errorContainer.style, {
|
|
764
|
+
padding: '2rem',
|
|
765
|
+
borderRadius: '12px',
|
|
766
|
+
background: 'linear-gradient(135deg, #1a1a2e, #16213e)',
|
|
767
|
+
border: '2px solid #ff6b6b',
|
|
768
|
+
color: '#fff',
|
|
769
|
+
fontFamily: '"Courier New", monospace',
|
|
770
|
+
textAlign: 'center',
|
|
771
|
+
boxShadow: '0 0 20px rgba(255, 107, 107, 0.3)'
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
// Create elements safely
|
|
775
|
+
const icon = document.createElement('div');
|
|
776
|
+
icon.textContent = '🌌';
|
|
777
|
+
Object.assign(icon.style, {
|
|
778
|
+
fontSize: '3rem',
|
|
779
|
+
marginBottom: '1rem'
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
const title = document.createElement('h3');
|
|
783
|
+
title.textContent = 'MOUNT ERROR DETECTED';
|
|
784
|
+
Object.assign(title.style, {
|
|
785
|
+
color: '#ff6b6b',
|
|
786
|
+
margin: '0 0 1rem 0'
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
const description = document.createElement('p');
|
|
790
|
+
description.textContent = `App "${appName}" failed to mount in the container`;
|
|
791
|
+
Object.assign(description.style, {
|
|
792
|
+
margin: '0 0 1rem 0',
|
|
793
|
+
opacity: '0.8'
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
const details = document.createElement('details');
|
|
797
|
+
Object.assign(details.style, {
|
|
798
|
+
margin: '1rem 0',
|
|
799
|
+
textAlign: 'left'
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
const summary = document.createElement('summary');
|
|
803
|
+
summary.textContent = '🔍 Debug Info';
|
|
804
|
+
Object.assign(summary.style, {
|
|
805
|
+
cursor: 'pointer',
|
|
806
|
+
color: '#4ecdc4'
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
const pre = document.createElement('pre');
|
|
810
|
+
pre.textContent = error.message; // Safe text content, no HTML injection
|
|
811
|
+
Object.assign(pre.style, {
|
|
812
|
+
background: 'rgba(0,0,0,0.3)',
|
|
813
|
+
padding: '1rem',
|
|
814
|
+
borderRadius: '6px',
|
|
815
|
+
marginTop: '0.5rem',
|
|
816
|
+
overflow: 'auto',
|
|
817
|
+
fontSize: '0.8rem'
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
const button = document.createElement('button');
|
|
821
|
+
button.textContent = '🔄 Reload Page';
|
|
822
|
+
Object.assign(button.style, {
|
|
823
|
+
background: 'linear-gradient(45deg, #4ecdc4, #44a08d)',
|
|
824
|
+
border: 'none',
|
|
825
|
+
padding: '0.8rem 1.5rem',
|
|
826
|
+
borderRadius: '6px',
|
|
827
|
+
color: 'white',
|
|
828
|
+
cursor: 'pointer',
|
|
829
|
+
fontWeight: 'bold',
|
|
830
|
+
transition: 'transform 0.2s'
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
// Add safe event listeners
|
|
834
|
+
button.addEventListener('click', () => window.location.reload());
|
|
835
|
+
button.addEventListener('mouseenter', () => button.style.transform = 'scale(1.05)');
|
|
836
|
+
button.addEventListener('mouseleave', () => button.style.transform = 'scale(1)');
|
|
837
|
+
|
|
838
|
+
// Assemble DOM structure
|
|
839
|
+
details.appendChild(summary);
|
|
840
|
+
details.appendChild(pre);
|
|
841
|
+
|
|
842
|
+
errorContainer.appendChild(icon);
|
|
843
|
+
errorContainer.appendChild(title);
|
|
844
|
+
errorContainer.appendChild(description);
|
|
845
|
+
errorContainer.appendChild(details);
|
|
846
|
+
errorContainer.appendChild(button);
|
|
847
|
+
|
|
848
|
+
container.appendChild(errorContainer);
|
|
849
|
+
|
|
850
|
+
} catch (renderError) {
|
|
851
|
+
console.error(`[Wu] 💥 Failed to render error state:`, renderError);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
/**
|
|
856
|
+
* 📦 REMOTE APP LOADER: Intelligent remote app loading with path resolution
|
|
857
|
+
* @param {Object} app - Información de la app
|
|
858
|
+
* @param {Object} sandbox - Sandbox creado
|
|
859
|
+
*/
|
|
860
|
+
async loadAndMountRemoteApp(app, sandbox) {
|
|
861
|
+
// 🔍 SMART PATH RESOLUTION: Multi-path URL construction with validation
|
|
862
|
+
const moduleUrl = await this.resolveModulePath(app);
|
|
863
|
+
|
|
864
|
+
console.log(`[Wu] 📦 Loading ES module: ${moduleUrl}`);
|
|
865
|
+
|
|
866
|
+
try {
|
|
867
|
+
// 🌟 MODULE LOADING: Multi-path module resolution
|
|
868
|
+
await this.moduleLoader(moduleUrl, app.name);
|
|
869
|
+
|
|
870
|
+
console.log(`[Wu] ✅ ES module loaded: ${app.name}`);
|
|
871
|
+
|
|
872
|
+
} catch (error) {
|
|
873
|
+
console.error(`[Wu] ❌ Failed to load ES module ${moduleUrl}:`, error);
|
|
874
|
+
throw error;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
/**
|
|
879
|
+
* 🔍 MODULE PATH RESOLVER: Intelligent URL construction with fallback
|
|
880
|
+
* Intelligently resolves module paths with real-time validation
|
|
881
|
+
*/
|
|
882
|
+
async resolveModulePath(app) {
|
|
883
|
+
let entryFile = app.manifest?.entry || 'main.js';
|
|
884
|
+
const baseUrl = app.url.replace(/\/$/, ''); // Remove trailing slash
|
|
885
|
+
|
|
886
|
+
// 🔧 NORMALIZE PATH: Remove duplicated directories
|
|
887
|
+
// If entry already starts with 'src/', 'dist/', etc., use it as-is
|
|
888
|
+
const hasFolderPrefix = /^(src|dist|public|build|assets|lib|es)\//.test(entryFile);
|
|
889
|
+
|
|
890
|
+
if (hasFolderPrefix) {
|
|
891
|
+
console.log(`[Wu] 🔧 Entry already has folder prefix: ${entryFile}`);
|
|
892
|
+
// Entry already has folder, just use baseUrl + entryFile
|
|
893
|
+
const directPath = `${baseUrl}/${entryFile}`;
|
|
894
|
+
console.log(`[Wu] 🎯 Using direct path: ${directPath}`);
|
|
895
|
+
return directPath;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// 🔍 Multi-path candidates (in order of preference)
|
|
899
|
+
const pathCandidates = [
|
|
900
|
+
`${baseUrl}/src/${entryFile}`, // Standard structure
|
|
901
|
+
`${baseUrl}/${entryFile}`, // Root level
|
|
902
|
+
`${baseUrl}/dist/${entryFile}`, // Built version
|
|
903
|
+
`${baseUrl}/public/${entryFile}`, // Public folder
|
|
904
|
+
`${baseUrl}/build/${entryFile}`, // Build folder
|
|
905
|
+
`${baseUrl}/assets/${entryFile}`, // Assets folder
|
|
906
|
+
`${baseUrl}/lib/${entryFile}`, // Library folder
|
|
907
|
+
`${baseUrl}/es/${entryFile}` // ES modules folder
|
|
908
|
+
];
|
|
909
|
+
|
|
910
|
+
console.log(`[Wu] 🔍 Attempting path resolution for ${app.name}...`);
|
|
911
|
+
|
|
912
|
+
// 🚀 SMART PATH DISCOVERY: Try each candidate with validation
|
|
913
|
+
for (let i = 0; i < pathCandidates.length; i++) {
|
|
914
|
+
const candidate = pathCandidates[i];
|
|
915
|
+
|
|
916
|
+
try {
|
|
917
|
+
console.log(`[Wu] 🎯 Testing path candidate ${i + 1}/${pathCandidates.length}: ${candidate}`);
|
|
918
|
+
|
|
919
|
+
// 🌟 Path validation with enhanced verification
|
|
920
|
+
const isValid = await this.validatePath(candidate);
|
|
921
|
+
|
|
922
|
+
if (isValid) {
|
|
923
|
+
console.log(`[Wu] ✅ Path resolved successfully: ${candidate}`);
|
|
924
|
+
return candidate;
|
|
925
|
+
} else {
|
|
926
|
+
console.log(`[Wu] ❌ Path candidate ${i + 1} failed validation: ${candidate}`);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
} catch (error) {
|
|
930
|
+
console.log(`[Wu] ⚠️ Path candidate ${i + 1} threw error: ${candidate} - ${error.message}`);
|
|
931
|
+
continue;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// 🌌 FALLBACK: If all candidates fail, use the first one and let the error bubble up
|
|
936
|
+
const fallbackPath = pathCandidates[0];
|
|
937
|
+
console.warn(`[Wu] 🚨 All path candidates failed, using fallback: ${fallbackPath}`);
|
|
938
|
+
return fallbackPath;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/**
|
|
942
|
+
* 🔧 PATH VALIDATOR: Smart existence verification with module testing
|
|
943
|
+
* Validates if a path exists and can be loaded as an ES module
|
|
944
|
+
*/
|
|
945
|
+
async validatePath(url) {
|
|
946
|
+
try {
|
|
947
|
+
// 🌟 ENHANCED VALIDATION: Try actual module import for reliable verification
|
|
948
|
+
console.log(`[Wu] 🔍 Testing path: ${url}`);
|
|
949
|
+
|
|
950
|
+
// 🚀 First, try a GET request to check if file exists and is accessible
|
|
951
|
+
const response = await fetch(url, {
|
|
952
|
+
method: 'GET',
|
|
953
|
+
cache: 'no-cache',
|
|
954
|
+
signal: AbortSignal.timeout(2000) // 2 second timeout
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
if (!response.ok) {
|
|
958
|
+
console.log(`[Wu] ❌ Path validation failed - HTTP ${response.status}: ${url}`);
|
|
959
|
+
return false;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
// 🎯 Check content type and file extension
|
|
963
|
+
const contentType = response.headers.get('content-type') || '';
|
|
964
|
+
const isJavaScript =
|
|
965
|
+
contentType.includes('javascript') ||
|
|
966
|
+
contentType.includes('module') ||
|
|
967
|
+
contentType.includes('text/plain') || // Some servers serve JS as plain text
|
|
968
|
+
url.endsWith('.js') ||
|
|
969
|
+
url.endsWith('.mjs');
|
|
970
|
+
|
|
971
|
+
if (!isJavaScript) {
|
|
972
|
+
console.log(`[Wu] ❌ Path validation failed - Invalid content type '${contentType}': ${url}`);
|
|
973
|
+
return false;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// 🌌 FINAL VERIFICATION: Check if content looks like a valid module
|
|
977
|
+
const content = await response.text();
|
|
978
|
+
|
|
979
|
+
// 🚫 DETECT HTML FALLBACK: Check if server returned HTML instead of JS
|
|
980
|
+
const isHtmlFallback =
|
|
981
|
+
content.includes('<!doctype') ||
|
|
982
|
+
content.includes('<html') ||
|
|
983
|
+
content.includes('<head>') ||
|
|
984
|
+
content.includes('<body>') ||
|
|
985
|
+
(content.includes('<') && content.includes('>') && content.length > 200);
|
|
986
|
+
|
|
987
|
+
if (isHtmlFallback) {
|
|
988
|
+
console.log(`[Wu] ❌ Path validation failed - Server returned HTML fallback page: ${url}`);
|
|
989
|
+
return false;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
// 🎯 Check for valid JavaScript module content
|
|
993
|
+
const hasModuleContent =
|
|
994
|
+
content.includes('export') ||
|
|
995
|
+
content.includes('import') ||
|
|
996
|
+
content.includes('wu.define') ||
|
|
997
|
+
content.includes('module.exports') ||
|
|
998
|
+
content.includes('console.log') ||
|
|
999
|
+
(content.includes('function') && content.length > 10);
|
|
1000
|
+
|
|
1001
|
+
if (!hasModuleContent) {
|
|
1002
|
+
console.log(`[Wu] ❌ Path validation failed - No valid module content: ${url}`);
|
|
1003
|
+
console.log(`[Wu] 🔍 Content preview: ${content.substring(0, 100)}...`);
|
|
1004
|
+
return false;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
console.log(`[Wu] ✅ Path validation successful: ${url} (${content.length} chars)`);
|
|
1008
|
+
return true;
|
|
1009
|
+
|
|
1010
|
+
} catch (error) {
|
|
1011
|
+
// 🚫 Network, timeout, or parsing error means path is invalid
|
|
1012
|
+
console.log(`[Wu] ❌ Path validation failed for ${url}: ${error.message}`);
|
|
1013
|
+
return false;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/**
|
|
1018
|
+
* 📦 MODULE LOADER: Advanced registration patterns
|
|
1019
|
+
* Handles asynchronous registration with timing synchronization
|
|
1020
|
+
*/
|
|
1021
|
+
async moduleLoader(moduleUrl, appName) {
|
|
1022
|
+
// ✅ Check if already registered
|
|
1023
|
+
if (this.definitions.has(appName)) {
|
|
1024
|
+
console.log(`[Wu] ⚡ App ${appName} already registered`);
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
console.log(`[Wu] 📡 Using event-based registration for ${appName}`);
|
|
1029
|
+
|
|
1030
|
+
// 🔔 Use event-based waiting (no polling!)
|
|
1031
|
+
const registrationPromise = this.registry.waitForApp(appName, 10000);
|
|
1032
|
+
|
|
1033
|
+
// 🚀 Load module asynchronously
|
|
1034
|
+
const moduleLoadPromise = import(/* @vite-ignore */ moduleUrl);
|
|
1035
|
+
|
|
1036
|
+
// 🌌 Wait for both operations to complete
|
|
1037
|
+
await Promise.all([moduleLoadPromise, registrationPromise]);
|
|
1038
|
+
|
|
1039
|
+
console.log(`[Wu] ✅ App ${appName} loaded and registered via events`);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
/**
|
|
1043
|
+
* Desmontar una aplicación
|
|
1044
|
+
* @param {string} appName - Nombre de la app
|
|
1045
|
+
*/
|
|
1046
|
+
async unmount(appName) {
|
|
1047
|
+
try {
|
|
1048
|
+
console.log(`[Wu] 🗑️ Unmounting ${appName}`);
|
|
1049
|
+
|
|
1050
|
+
const mounted = this.mounted.get(appName);
|
|
1051
|
+
if (!mounted) {
|
|
1052
|
+
console.warn(`[Wu] App ${appName} not mounted`);
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
// 🪝 Execute beforeUnmount hooks
|
|
1057
|
+
const beforeUnmountResult = await this.hooks.execute('beforeUnmount', { appName, mounted });
|
|
1058
|
+
if (beforeUnmountResult.cancelled) {
|
|
1059
|
+
console.warn('[Wu] Unmount cancelled by beforeUnmount hook');
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// 🔌 Call plugin beforeUnmount hooks
|
|
1064
|
+
const pluginBeforeUnmount = await this.pluginSystem.callHook('beforeUnmount', { appName });
|
|
1065
|
+
if (pluginBeforeUnmount === false) {
|
|
1066
|
+
console.warn('[Wu] Unmount cancelled by plugin beforeUnmount hook');
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// Ejecutar unmount del lifecycle si existe
|
|
1071
|
+
if (mounted.lifecycle?.unmount) {
|
|
1072
|
+
await mounted.lifecycle.unmount(mounted.container);
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
// Limpiar sandbox
|
|
1076
|
+
this.sandbox.cleanup(mounted.sandbox);
|
|
1077
|
+
|
|
1078
|
+
// 🏊 Release sandbox to pool
|
|
1079
|
+
if (mounted.poolSandbox) {
|
|
1080
|
+
this.sandboxPool.release(appName);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// Remover del registro de montadas
|
|
1084
|
+
this.mounted.delete(appName);
|
|
1085
|
+
|
|
1086
|
+
// 🪝 Execute afterUnmount hooks
|
|
1087
|
+
await this.hooks.execute('afterUnmount', { appName });
|
|
1088
|
+
|
|
1089
|
+
// 🔌 Call plugin afterUnmount hooks
|
|
1090
|
+
await this.pluginSystem.callHook('afterUnmount', { appName });
|
|
1091
|
+
|
|
1092
|
+
// 📢 Emit unmount event
|
|
1093
|
+
this.eventBus.emit('app:unmounted', { appName }, { appName });
|
|
1094
|
+
|
|
1095
|
+
console.log(`[Wu] ✅ ${appName} unmounted successfully`);
|
|
1096
|
+
} catch (error) {
|
|
1097
|
+
console.error(`[Wu] ❌ Failed to unmount ${appName}:`, error);
|
|
1098
|
+
|
|
1099
|
+
// 🔌 Call plugin error hooks
|
|
1100
|
+
await this.pluginSystem.callHook('onError', { phase: 'unmount', error, appName });
|
|
1101
|
+
|
|
1102
|
+
// 📢 Emit error event
|
|
1103
|
+
this.eventBus.emit('app:error', { appName, error: error.message }, { appName });
|
|
1104
|
+
throw error;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
/**
|
|
1109
|
+
* Cargar componente compartido (para imports/exports)
|
|
1110
|
+
* @param {string} componentPath - Ruta del componente (ej: "shared.Button")
|
|
1111
|
+
*/
|
|
1112
|
+
async use(componentPath) {
|
|
1113
|
+
const [appName, componentName] = componentPath.split('.');
|
|
1114
|
+
|
|
1115
|
+
if (!appName || !componentName) {
|
|
1116
|
+
throw new Error(`Invalid component path: ${componentPath}. Use format "app.component"`);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
const app = this.apps.get(appName);
|
|
1120
|
+
if (!app) {
|
|
1121
|
+
throw new Error(`App ${appName} not registered`);
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
const manifest = this.manifests.get(appName);
|
|
1125
|
+
const exportPath = manifest?.wu?.exports?.[componentName];
|
|
1126
|
+
|
|
1127
|
+
if (!exportPath) {
|
|
1128
|
+
throw new Error(`Component ${componentName} not exported by ${appName}`);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
// Cargar componente
|
|
1132
|
+
return await this.loader.loadComponent(app.url, exportPath);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
/**
|
|
1136
|
+
* Obtener información de una app
|
|
1137
|
+
* @param {string} appName - Nombre de la app
|
|
1138
|
+
*/
|
|
1139
|
+
getAppInfo(appName) {
|
|
1140
|
+
return {
|
|
1141
|
+
registered: this.apps.get(appName),
|
|
1142
|
+
manifest: this.manifests.get(appName),
|
|
1143
|
+
mounted: this.mounted.get(appName),
|
|
1144
|
+
definition: this.definitions.get(appName)
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
/**
|
|
1149
|
+
* Obtener estadísticas del framework
|
|
1150
|
+
*/
|
|
1151
|
+
getStats() {
|
|
1152
|
+
return {
|
|
1153
|
+
registered: this.apps.size,
|
|
1154
|
+
defined: this.definitions.size,
|
|
1155
|
+
mounted: this.mounted.size,
|
|
1156
|
+
apps: Array.from(this.apps.keys())
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
/**
|
|
1161
|
+
* 🏪 STORE METHODS: Convenience methods for state management
|
|
1162
|
+
*/
|
|
1163
|
+
|
|
1164
|
+
/**
|
|
1165
|
+
* Get value from global store
|
|
1166
|
+
* @param {string} path - Dot notation path
|
|
1167
|
+
* @returns {*} Value at path
|
|
1168
|
+
*/
|
|
1169
|
+
getState(path) {
|
|
1170
|
+
return this.store.get(path);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
/**
|
|
1174
|
+
* Set value in global store
|
|
1175
|
+
* @param {string} path - Dot notation path
|
|
1176
|
+
* @param {*} value - Value to set
|
|
1177
|
+
* @returns {number} Sequence number
|
|
1178
|
+
*/
|
|
1179
|
+
setState(path, value) {
|
|
1180
|
+
return this.store.set(path, value);
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
/**
|
|
1184
|
+
* Subscribe to state changes
|
|
1185
|
+
* @param {string} pattern - Path or pattern
|
|
1186
|
+
* @param {Function} callback - Callback function
|
|
1187
|
+
* @returns {Function} Unsubscribe function
|
|
1188
|
+
*/
|
|
1189
|
+
onStateChange(pattern, callback) {
|
|
1190
|
+
return this.store.on(pattern, callback);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
/**
|
|
1194
|
+
* Batch set multiple state values
|
|
1195
|
+
* @param {Object} updates - Object with path:value pairs
|
|
1196
|
+
* @returns {Array} Sequence numbers
|
|
1197
|
+
*/
|
|
1198
|
+
batchState(updates) {
|
|
1199
|
+
return this.store.batch(updates);
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
/**
|
|
1203
|
+
* Get store metrics
|
|
1204
|
+
* @returns {Object} Performance metrics
|
|
1205
|
+
*/
|
|
1206
|
+
getStoreMetrics() {
|
|
1207
|
+
return this.store.getMetrics();
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
/**
|
|
1211
|
+
* Clear all state
|
|
1212
|
+
*/
|
|
1213
|
+
clearState() {
|
|
1214
|
+
this.store.clear();
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
/**
|
|
1218
|
+
* 🎯 SIMPLIFIED API: Create WuApp instance for declarative usage
|
|
1219
|
+
* @param {string} name - App name
|
|
1220
|
+
* @param {Object} config - Configuration { url, container, autoInit }
|
|
1221
|
+
* @returns {WuApp} WuApp instance
|
|
1222
|
+
*/
|
|
1223
|
+
app(name, config) {
|
|
1224
|
+
return new WuApp(name, config, this);
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
/**
|
|
1228
|
+
* Limpiar todo el framework
|
|
1229
|
+
*/
|
|
1230
|
+
async destroy() {
|
|
1231
|
+
console.log('[Wu] 🧹 Destroying framework...');
|
|
1232
|
+
|
|
1233
|
+
try {
|
|
1234
|
+
// 🪝 Execute beforeDestroy hooks
|
|
1235
|
+
await this.hooks.execute('beforeDestroy', {});
|
|
1236
|
+
|
|
1237
|
+
// 🔌 Call plugin onDestroy hooks
|
|
1238
|
+
await this.pluginSystem.callHook('onDestroy', {});
|
|
1239
|
+
|
|
1240
|
+
// Limpiar health monitor
|
|
1241
|
+
if (this.healthState.monitor) {
|
|
1242
|
+
clearInterval(this.healthState.monitor);
|
|
1243
|
+
this.healthState.monitor = null;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
// Limpiar MutationObserver
|
|
1247
|
+
if (this.healthState.mutationObserver) {
|
|
1248
|
+
this.healthState.mutationObserver.disconnect();
|
|
1249
|
+
this.healthState.mutationObserver = null;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
// Limpiar timeouts pendientes
|
|
1253
|
+
if (this.healthState.mutationCheckTimeout) {
|
|
1254
|
+
clearTimeout(this.healthState.mutationCheckTimeout);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
// Desmontar todas las apps
|
|
1258
|
+
for (const appName of this.mounted.keys()) {
|
|
1259
|
+
await this.unmount(appName);
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
// 🧹 Limpiar sistemas esenciales
|
|
1263
|
+
this.cache.clear();
|
|
1264
|
+
this.eventBus.removeAll();
|
|
1265
|
+
this.eventBus.clearHistory();
|
|
1266
|
+
this.performance.clearMetrics();
|
|
1267
|
+
|
|
1268
|
+
// 🧹 Limpiar advanced systems
|
|
1269
|
+
this.pluginSystem.cleanup();
|
|
1270
|
+
this.strategies.cleanup();
|
|
1271
|
+
this.errorBoundary.cleanup();
|
|
1272
|
+
this.hooks.cleanup();
|
|
1273
|
+
this.sandboxPool.cleanup();
|
|
1274
|
+
this.registry.cleanup();
|
|
1275
|
+
|
|
1276
|
+
// Limpiar registros
|
|
1277
|
+
this.apps.clear();
|
|
1278
|
+
this.definitions.clear();
|
|
1279
|
+
this.manifests.clear();
|
|
1280
|
+
this.mounted.clear();
|
|
1281
|
+
|
|
1282
|
+
// Limpiar store
|
|
1283
|
+
this.store.clear();
|
|
1284
|
+
|
|
1285
|
+
this.isInitialized = false;
|
|
1286
|
+
|
|
1287
|
+
// 🪝 Execute afterDestroy hooks
|
|
1288
|
+
await this.hooks.execute('afterDestroy', {});
|
|
1289
|
+
|
|
1290
|
+
console.log('[Wu] ✅ Framework destroyed');
|
|
1291
|
+
} catch (error) {
|
|
1292
|
+
console.error('[Wu] ❌ Error during destroy:', error);
|
|
1293
|
+
throw error;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
}
|