wu-framework 1.0.3 → 1.0.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wu-framework",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "🚀 Universal Microfrontends Framework - Framework agnostic, zero config, Shadow DOM isolation",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -102,7 +102,11 @@ export class WuCache {
102
102
  };
103
103
 
104
104
  // Verificar si necesitamos hacer espacio
105
- this.ensureSpace(entry.size);
105
+ const hasSpace = this.ensureSpace(entry.size);
106
+ if (hasSpace === false) {
107
+ console.warn(`[WuCache] ⚠️ Cannot cache item: ${key} (too large)`);
108
+ return false;
109
+ }
106
110
 
107
111
  // Guardar en memoria
108
112
  this.memoryCache.set(key, entry);
@@ -194,9 +198,28 @@ export class WuCache {
194
198
  ensureSpace(neededSize) {
195
199
  const maxSizeBytes = this.config.maxSize * 1024 * 1024;
196
200
 
201
+ // 🛡️ FIX: Validar que el item no sea más grande que el máximo permitido
202
+ if (neededSize > maxSizeBytes) {
203
+ console.warn(`[WuCache] ⚠️ Item size (${neededSize}) exceeds max cache size (${maxSizeBytes}). Skipping.`);
204
+ return false;
205
+ }
206
+
207
+ // 🛡️ FIX: Límite de iteraciones para evitar loop infinito
208
+ const maxIterations = this.config.maxItems + 10;
209
+ let iterations = 0;
210
+
197
211
  // Verificar si necesitamos limpiar
198
- while (this.stats.size + neededSize > maxSizeBytes ||
199
- this.memoryCache.size >= this.config.maxItems) {
212
+ while ((this.stats.size + neededSize > maxSizeBytes ||
213
+ this.memoryCache.size >= this.config.maxItems) &&
214
+ iterations < maxIterations) {
215
+
216
+ iterations++;
217
+
218
+ // 🛡️ FIX: Si el cache está vacío pero aún no hay espacio, salir
219
+ if (this.memoryCache.size === 0) {
220
+ console.warn('[WuCache] ⚠️ Cache empty but still no space. Breaking loop.');
221
+ break;
222
+ }
200
223
 
201
224
  // Encontrar entrada menos recientemente usada (LRU)
202
225
  let oldestKey = null;
@@ -217,6 +240,13 @@ export class WuCache {
217
240
  break;
218
241
  }
219
242
  }
243
+
244
+ // 🛡️ FIX: Log si alcanzamos el límite de iteraciones
245
+ if (iterations >= maxIterations) {
246
+ console.error(`[WuCache] 🚨 Max eviction iterations reached (${maxIterations}). Possible infinite loop prevented.`);
247
+ }
248
+
249
+ return true;
220
250
  }
221
251
 
222
252
  /**
@@ -104,56 +104,79 @@ export class WuCore {
104
104
  }
105
105
 
106
106
  /**
107
- * 🔍 SMART MUTATION OBSERVER: Real-time DOM monitoring for efficiency
107
+ * 🔍 SMART MUTATION OBSERVER: Observa SOLO los contenedores de apps montadas
108
+ * FIX: Ya no observa todo document.body (memory leak)
108
109
  */
109
110
  initializeMutationObserver() {
110
- if (!window.MutationObserver) return; // Fallback for older browsers
111
+ if (!window.MutationObserver) return;
111
112
 
112
- this.healthState.mutationObserver = new MutationObserver((mutations) => {
113
- let shouldCheckHealth = false;
113
+ // Map para trackear observers por contenedor (evita memory leak)
114
+ this.healthState.containerObservers = new Map();
115
+
116
+ // Callback compartido para todos los observers
117
+ this.healthState.mutationCallback = (mutations, observer) => {
118
+ let affectedAppName = null;
114
119
 
115
120
  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
- }
121
+ if (mutation.type === 'childList' && mutation.removedNodes.length > 0) {
122
+ // Encontrar qué app fue afectada
123
+ for (const [appName, mounted] of this.mounted) {
124
+ const container = mounted.hostContainer || mounted.container;
125
+ if (mutation.target === container ||
126
+ mutation.target.contains?.(container)) {
127
+ affectedAppName = appName;
128
+ break;
130
129
  }
131
- return false;
132
- });
133
-
134
- if (affectedApps) {
135
- shouldCheckHealth = true;
136
- break;
137
130
  }
138
131
  }
132
+ if (affectedAppName) break;
139
133
  }
140
134
 
141
- // Debounced health check only when necessary
142
- if (shouldCheckHealth) {
135
+ if (affectedAppName) {
143
136
  clearTimeout(this.healthState.mutationCheckTimeout);
144
137
  this.healthState.mutationCheckTimeout = setTimeout(() => {
145
138
  this.performHealthCheck();
146
- }, 1000); // Wait 1 second to batch multiple mutations
139
+ }, 1000);
147
140
  }
148
- });
141
+ };
142
+
143
+ logger.wuDebug('🔍 MutationObserver system initialized (lazy per-container)');
144
+ }
145
+
146
+ /**
147
+ * 🔗 Observar contenedor específico cuando se monta una app
148
+ */
149
+ observeContainer(appName, container) {
150
+ if (!this.healthState.containerObservers || !container) return;
151
+
152
+ // No observar si ya existe
153
+ if (this.healthState.containerObservers.has(appName)) return;
149
154
 
150
- // Observe the entire document but only care about child changes
151
- this.healthState.mutationObserver.observe(document.body, {
155
+ const observer = new MutationObserver(this.healthState.mutationCallback);
156
+
157
+ // Observar solo el contenedor padre directo (no subtree profundo)
158
+ const parentToObserve = container.parentElement || container;
159
+ observer.observe(parentToObserve, {
152
160
  childList: true,
153
- subtree: true
161
+ subtree: false // ✅ Solo hijos directos, no todo el árbol
154
162
  });
155
163
 
156
- logger.wuDebug('🔍 MutationObserver initialized for smart DOM monitoring');
164
+ this.healthState.containerObservers.set(appName, observer);
165
+ logger.wuDebug(`🔍 Observing container for ${appName}`);
166
+ }
167
+
168
+ /**
169
+ * 🔓 Dejar de observar contenedor cuando se desmonta
170
+ */
171
+ unobserveContainer(appName) {
172
+ if (!this.healthState.containerObservers) return;
173
+
174
+ const observer = this.healthState.containerObservers.get(appName);
175
+ if (observer) {
176
+ observer.disconnect();
177
+ this.healthState.containerObservers.delete(appName);
178
+ logger.wuDebug(`🔓 Stopped observing container for ${appName}`);
179
+ }
157
180
  }
158
181
 
159
182
  /**
@@ -606,10 +629,14 @@ export class WuCore {
606
629
  poolSandbox,
607
630
  lifecycle,
608
631
  container: sandbox.container,
632
+ hostContainer: container,
609
633
  timestamp: Date.now(),
610
634
  state: 'stable'
611
635
  });
612
636
 
637
+ // 🔍 Observar contenedor para health monitoring (sin memory leak)
638
+ this.observeContainer(appName, container);
639
+
613
640
  // ⚡ End performance measurement
614
641
  const mountTime = this.performance.endMeasure('mount', appName);
615
642
 
@@ -1017,6 +1044,7 @@ export class WuCore {
1017
1044
  /**
1018
1045
  * 📦 MODULE LOADER: Advanced registration patterns
1019
1046
  * Handles asynchronous registration with timing synchronization
1047
+ * FIX: Verifica que definitions tenga el lifecycle después de cargar
1020
1048
  */
1021
1049
  async moduleLoader(moduleUrl, appName) {
1022
1050
  // ✅ Check if already registered
@@ -1027,16 +1055,37 @@ export class WuCore {
1027
1055
 
1028
1056
  console.log(`[Wu] 📡 Using event-based registration for ${appName}`);
1029
1057
 
1030
- // 🔔 Use event-based waiting (no polling!)
1031
- const registrationPromise = this.registry.waitForApp(appName, 10000);
1058
+ // 🚀 Load module first
1059
+ try {
1060
+ await import(/* @vite-ignore */ moduleUrl);
1061
+ } catch (loadError) {
1062
+ console.error(`[Wu] ❌ Failed to import module ${moduleUrl}:`, loadError);
1063
+ throw loadError;
1064
+ }
1032
1065
 
1033
- // 🚀 Load module asynchronously
1034
- const moduleLoadPromise = import(/* @vite-ignore */ moduleUrl);
1066
+ // 🛡️ FIX: Esperar a que wu.define() se ejecute con verificación real
1067
+ const maxWaitTime = 10000; // 10 segundos
1068
+ const checkInterval = 50; // Verificar cada 50ms
1069
+ const startTime = Date.now();
1070
+
1071
+ while (!this.definitions.has(appName)) {
1072
+ const elapsed = Date.now() - startTime;
1073
+
1074
+ if (elapsed >= maxWaitTime) {
1075
+ throw new Error(
1076
+ `App '${appName}' module loaded but wu.define() was not called within ${maxWaitTime}ms.\n\n` +
1077
+ `Make sure your module calls:\n` +
1078
+ ` wu.define('${appName}', { mount, unmount })\n\n` +
1079
+ `Or using window.wu:\n` +
1080
+ ` window.wu.define('${appName}', { mount, unmount })`
1081
+ );
1082
+ }
1035
1083
 
1036
- // 🌌 Wait for both operations to complete
1037
- await Promise.all([moduleLoadPromise, registrationPromise]);
1084
+ // Esperar un poco antes de verificar de nuevo
1085
+ await new Promise(resolve => setTimeout(resolve, checkInterval));
1086
+ }
1038
1087
 
1039
- console.log(`[Wu] ✅ App ${appName} loaded and registered via events`);
1088
+ console.log(`[Wu] ✅ App ${appName} loaded and registered (verified in definitions)`);
1040
1089
  }
1041
1090
 
1042
1091
  /**
@@ -1080,6 +1129,9 @@ export class WuCore {
1080
1129
  this.sandboxPool.release(appName);
1081
1130
  }
1082
1131
 
1132
+ // 🔓 Dejar de observar contenedor (evita memory leak)
1133
+ this.unobserveContainer(appName);
1134
+
1083
1135
  // Remover del registro de montadas
1084
1136
  this.mounted.delete(appName);
1085
1137
 
@@ -1243,10 +1295,12 @@ export class WuCore {
1243
1295
  this.healthState.monitor = null;
1244
1296
  }
1245
1297
 
1246
- // Limpiar MutationObserver
1247
- if (this.healthState.mutationObserver) {
1248
- this.healthState.mutationObserver.disconnect();
1249
- this.healthState.mutationObserver = null;
1298
+ // Limpiar todos los MutationObservers por contenedor
1299
+ if (this.healthState.containerObservers) {
1300
+ for (const [appName, observer] of this.healthState.containerObservers) {
1301
+ observer.disconnect();
1302
+ }
1303
+ this.healthState.containerObservers.clear();
1250
1304
  }
1251
1305
 
1252
1306
  // Limpiar timeouts pendientes
package/src/index.js CHANGED
@@ -103,7 +103,7 @@ if (typeof window !== 'undefined') {
103
103
 
104
104
  // Configurar propiedades si no existen
105
105
  if (!wu.version) {
106
- wu.version = '1.0.3';
106
+ wu.version = '1.0.4';
107
107
  console.log('🚀 Wu Framework loaded - Universal Microfrontends ready');
108
108
  wu.info = {
109
109
  name: 'Wu Framework',