versacompiler 2.5.0 → 2.6.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.
Files changed (49) hide show
  1. package/dist/compiler/compile-worker-pool.js +12 -0
  2. package/dist/compiler/compile.js +97 -17
  3. package/dist/compiler/error-reporter.js +12 -0
  4. package/dist/compiler/integrity-validator.js +12 -0
  5. package/dist/compiler/linter.js +12 -0
  6. package/dist/compiler/minify.js +12 -0
  7. package/dist/compiler/minifyTemplate.js +12 -0
  8. package/dist/compiler/module-resolution-optimizer.js +13 -1
  9. package/dist/compiler/parser.js +12 -0
  10. package/dist/compiler/performance-monitor.js +12 -0
  11. package/dist/compiler/pipeline/build-pipeline.js +12 -0
  12. package/dist/compiler/pipeline/core-plugins.js +12 -0
  13. package/dist/compiler/pipeline/module-graph.js +12 -0
  14. package/dist/compiler/pipeline/plugin-driver.js +12 -0
  15. package/dist/compiler/pipeline/types.js +12 -0
  16. package/dist/compiler/tailwindcss.js +12 -0
  17. package/dist/compiler/transform-optimizer.js +12 -0
  18. package/dist/compiler/transformTStoJS.js +38 -5
  19. package/dist/compiler/transforms.js +12 -0
  20. package/dist/compiler/typescript-compiler.js +12 -0
  21. package/dist/compiler/typescript-error-parser.js +12 -0
  22. package/dist/compiler/typescript-manager.js +12 -0
  23. package/dist/compiler/typescript-sync-validator.js +12 -0
  24. package/dist/compiler/typescript-worker-pool.js +12 -0
  25. package/dist/compiler/typescript-worker.js +12 -0
  26. package/dist/compiler/vuejs.js +41 -15
  27. package/dist/config.js +12 -0
  28. package/dist/hrm/VueHRM.js +132 -7
  29. package/dist/hrm/errorScreen.js +12 -0
  30. package/dist/hrm/getInstanciaVue.js +12 -0
  31. package/dist/hrm/initHRM.js +117 -9
  32. package/dist/hrm/versaHMR.js +317 -0
  33. package/dist/main.js +21 -2
  34. package/dist/servicios/browserSync.js +119 -4
  35. package/dist/servicios/file-watcher.js +104 -15
  36. package/dist/servicios/logger.js +12 -0
  37. package/dist/servicios/readConfig.js +13 -1
  38. package/dist/servicios/versacompile.config.types.js +12 -0
  39. package/dist/utils/excluded-modules.js +12 -0
  40. package/dist/utils/module-resolver.js +12 -0
  41. package/dist/utils/promptUser.js +12 -0
  42. package/dist/utils/proxyValidator.js +12 -0
  43. package/dist/utils/resolve-bin.js +12 -0
  44. package/dist/utils/utils.js +12 -0
  45. package/dist/utils/vue-types-setup.js +14 -2
  46. package/dist/wrappers/eslint-node.js +12 -0
  47. package/dist/wrappers/oxlint-node.js +12 -0
  48. package/dist/wrappers/tailwind-node.js +12 -0
  49. package/package.json +4 -3
@@ -1,3 +1,15 @@
1
+ /* VersaCompiler HMR shim [dev] */
2
+ if (typeof window !== 'undefined' && window.__versaHMR) {
3
+ (() => {
4
+ const _id = new URL(import.meta.url).pathname;
5
+ import.meta.hot = {
6
+ accept(cb) { window.__versaHMR.accept(_id, typeof cb === 'function' ? cb : () => {}); },
7
+ invalidate() { window.__versaHMR._invalidate?.(_id); },
8
+ dispose(cb) { window.__versaHMR._onDispose?.(_id, cb); },
9
+ get data() { return window.__versaHMR._getHotData?.(_id) ?? {}; },
10
+ };
11
+ })();
12
+ }
1
13
  import { createHash } from 'node:crypto';
2
14
  import path from 'node:path';
3
15
  import * as vCompiler from 'vue/compiler-sfc';
@@ -63,19 +75,30 @@ class VueHMRInjectionCache {
63
75
  else {
64
76
  injectedData = originalData.replace(/(<script.*?>)/, `$1${varContent}`);
65
77
  }
66
- // Inyectar :key en el template
67
- injectedData = injectedData.replace(/(<template[^>]*>[\s\S]*?)(<(\w+)([^>]*?))(\/?>)/, (match, p1, p2, p3, p4, p5) => {
68
- if (p4.includes(':key=') || p4.includes('key=')) {
69
- return match;
70
- }
71
- const isSelfClosing = p5 === '/>';
72
- if (isSelfClosing) {
73
- return `${p1}<${p3}${p4} :key="versaComponentKey" />`;
74
- }
75
- else {
76
- return `${p1}<${p3}${p4} :key="versaComponentKey">`;
78
+ // Inyectar :key en el primer elemento hijo del template.
79
+ // Dos pasos para evitar backtracking catastrófico (ReDoS) que ocurría con
80
+ // el pattern combinado /(<template[^>]*>[\s\S]*?)(<(\w+)([^>]*?))(\/?>)/
81
+ const templateTagMatch = /(<template[^>]*>)/.exec(injectedData);
82
+ if (templateTagMatch) {
83
+ const templateEnd = templateTagMatch.index + templateTagMatch[0].length;
84
+ const afterTemplate = injectedData.slice(templateEnd);
85
+ // Buscar el primer tag de elemento (ignora espacios/comentarios) justo después del <template>
86
+ const firstChildMatch = /^(\s*)(<(\w+)([^>]*?)(\/?>))/.exec(afterTemplate);
87
+ if (firstChildMatch) {
88
+ const [, whitespace = '', fullTag = '', tagName, attrs = '', closing,] = firstChildMatch;
89
+ if (!attrs.includes(':key=') && !attrs.includes('key=')) {
90
+ const isSelfClosing = closing === '/>';
91
+ const newTag = isSelfClosing
92
+ ? `<${tagName}${attrs} :key="versaComponentKey" />`
93
+ : `<${tagName}${attrs} :key="versaComponentKey">`;
94
+ injectedData =
95
+ injectedData.slice(0, templateEnd) +
96
+ whitespace +
97
+ newTag +
98
+ afterTemplate.slice(whitespace.length + fullTag.length);
99
+ }
77
100
  }
78
- });
101
+ }
79
102
  // Cachear resultado
80
103
  this.cache.set(cacheKey, {
81
104
  contentHash,
@@ -167,8 +190,6 @@ export const preCompileVue = async (data, source, isProd = false) => {
167
190
  scriptInfo: undefined,
168
191
  };
169
192
  }
170
- // Guardar el código original antes de inyectar HMR
171
- const originalData = data;
172
193
  if (!isProd) {
173
194
  const { injectedData } = hmrInjectionCache.getOrGenerateHMRInjection(data, fileName);
174
195
  data = injectedData;
@@ -312,10 +333,14 @@ export const preCompileVue = async (data, source, isProd = false) => {
312
333
  filename: `${fileName}.vue`,
313
334
  });
314
335
  });
336
+ // data-versa-hmr-component permite a VueHRM.js eliminar style tags
337
+ // de ciclos HMR anteriores para evitar acumulación de estilos duplicados.
338
+ const sanitizedForAttr = fileName.replace(/[^a-zA-Z0-9_-]/g, '');
315
339
  const insertStyles = compiledStyles.length
316
340
  ? `(function(){
317
341
  let styleTag = document.createElement('style');
318
342
  styleTag.setAttribute('data-v-${id}', '');
343
+ styleTag.setAttribute('data-versa-hmr-component', '${sanitizedForAttr}');
319
344
  styleTag.innerHTML = \`${compiledStyles.map((s) => s.code).join('\n')}\`;
320
345
  document.head.appendChild(styleTag);
321
346
  })();`
@@ -380,6 +405,8 @@ export const preCompileVue = async (data, source, isProd = false) => {
380
405
  export default ${componentName}; `;
381
406
  output = `${output}\n${finishComponent}`;
382
407
  // 🚀 OPTIMIZACIÓN CRÍTICA: Evitar crear scriptInfo si no hay script
408
+ // originalData NO se incluye en scriptInfo para evitar retener la cadena completa
409
+ // del .vue en memoria. Se pasa por separado como sourceCode en parseTypeScriptErrors.
383
410
  const result = {
384
411
  lang: finalCompiledScript.lang,
385
412
  error: null,
@@ -391,7 +418,6 @@ export const preCompileVue = async (data, source, isProd = false) => {
391
418
  startLine: (descriptor.script || descriptor.scriptSetup).loc?.start
392
419
  .line || 1,
393
420
  content: (descriptor.script || descriptor.scriptSetup).content,
394
- originalData: originalData, // String directa, no closure
395
421
  };
396
422
  }
397
423
  return result;
package/dist/config.js CHANGED
@@ -1,2 +1,14 @@
1
+ /* VersaCompiler HMR shim [dev] */
2
+ if (typeof window !== 'undefined' && window.__versaHMR) {
3
+ (() => {
4
+ const _id = new URL(import.meta.url).pathname;
5
+ import.meta.hot = {
6
+ accept(cb) { window.__versaHMR.accept(_id, typeof cb === 'function' ? cb : () => {}); },
7
+ invalidate() { window.__versaHMR._invalidate?.(_id); },
8
+ dispose(cb) { window.__versaHMR._onDispose?.(_id, cb); },
9
+ get data() { return window.__versaHMR._getHotData?.(_id) ?? {}; },
10
+ };
11
+ })();
12
+ }
1
13
  export { defineConfig, } from './servicios/versacompile.config.types';
2
14
  //# sourceMappingURL=config.js.map
@@ -1,3 +1,15 @@
1
+ /* VersaCompiler HMR shim [dev] */
2
+ if (typeof window !== 'undefined' && window.__versaHMR) {
3
+ (() => {
4
+ const _id = new URL(import.meta.url).pathname;
5
+ import.meta.hot = {
6
+ accept(cb) { window.__versaHMR.accept(_id, typeof cb === 'function' ? cb : () => {}); },
7
+ invalidate() { window.__versaHMR._invalidate?.(_id); },
8
+ dispose(cb) { window.__versaHMR._onDispose?.(_id, cb); },
9
+ get data() { return window.__versaHMR._getHotData?.(_id) ?? {}; },
10
+ };
11
+ })();
12
+ }
1
13
  /**
2
14
  * @typedef {Object} TreeNode
3
15
  * @property {string} name - Nombre del componente
@@ -222,7 +234,80 @@ function tryForceUpdate(instance) {
222
234
  }
223
235
 
224
236
  /**
225
- * Intenta actualizar un componente en el camino del árbol
237
+ * Limpia los caches internos de Vue para una definición de componente.
238
+ * Necesario para que Vue detecte el cambio de props/emits/options.
239
+ * @param {Object} appContext - Contexto de la app Vue (instance.appContext)
240
+ * @param {Object} componentDef - Definición del componente
241
+ */
242
+ function clearVueCaches(appContext, componentDef) {
243
+ if (!appContext || !componentDef) return;
244
+ try {
245
+ appContext.propsCache?.delete(componentDef);
246
+ appContext.emitsCache?.delete(componentDef);
247
+ appContext.optionsCache?.delete(componentDef);
248
+ } catch {
249
+ // Los caches pueden no existir en todas las versiones de Vue
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Actualiza una instancia de componente Vue en-place con la nueva definición.
255
+ * Muta el objeto `instance.type` directamente para que TODAS las referencias
256
+ * a la definición (incluidas las capturadas en closures de render functions
257
+ * de componentes padre que hacen import estático) vean la nueva versión.
258
+ *
259
+ * @param {Object} instance - Instancia Vue del componente a actualizar
260
+ * @param {Object} newComponentDef - Nueva definición del componente
261
+ * @returns {boolean} true si la actualización fue exitosa
262
+ */
263
+ function updateInstanceInPlace(instance, newComponentDef) {
264
+ if (!instance || !newComponentDef) return false;
265
+
266
+ const oldDef = instance.type;
267
+ if (!oldDef || typeof oldDef !== 'object') return false;
268
+
269
+ // 1. Mutar la definición existente en-place.
270
+ // Object.assign copia propiedades enumerables; copiamos render/setup
271
+ // explícitamente porque pueden no ser enumerables en algunos builds.
272
+ Object.assign(oldDef, newComponentDef);
273
+ if (newComponentDef.render) oldDef.render = newComponentDef.render;
274
+ if (newComponentDef.setup) oldDef.setup = newComponentDef.setup;
275
+ if (newComponentDef.ssrRender) oldDef.ssrRender = newComponentDef.ssrRender;
276
+
277
+ // 2. Actualizar instance.render directamente.
278
+ // Vue almacena la referencia a la render function en instance.render durante
279
+ // el mount (handleSetupResult), y es ESA la que llama en cada patch.
280
+ // Cambiar solo instance.type.render NO es suficiente — hay que actualizar
281
+ // también instance.render para que el próximo update use la nueva template.
282
+ if (newComponentDef.render && typeof instance.render !== 'undefined') {
283
+ instance.render = newComponentDef.render;
284
+ }
285
+
286
+ // 3. Limpiar caches internos de Vue para que re-evalúe props/emits/options.
287
+ clearVueCaches(instance.appContext, oldDef);
288
+
289
+ // 4. Forzar actualización de ESTA instancia directamente (no solo del padre).
290
+ // Incrementar versaComponentKey para triggear el :key del template.
291
+ if (instance.ctx?._.setupState?.versaComponentKey !== undefined) {
292
+ instance.ctx._.setupState.versaComponentKey++;
293
+ }
294
+ if (typeof instance.update === 'function') {
295
+ instance.update();
296
+ return true;
297
+ }
298
+ if (instance.proxy && typeof instance.proxy.$forceUpdate === 'function') {
299
+ instance.proxy.$forceUpdate();
300
+ return true;
301
+ }
302
+
303
+ return false;
304
+ }
305
+
306
+ /**
307
+ * Intenta actualizar un componente en el camino del árbol.
308
+ * Ahora usa mutación in-place de la definición + limpieza de caches Vue,
309
+ * en lugar de reemplazar la referencia en el mapa de components del padre.
310
+ *
226
311
  * @param {TreeNode[]} path - Camino de nodos desde el componente hasta la raíz
227
312
  * @param {Object} newComponent - Nuevo componente a usar
228
313
  * @param {string} componentName - Nombre del componente
@@ -235,7 +320,25 @@ function tryUpdateComponentPath(path, newComponent, componentName, App) {
235
320
  return false;
236
321
  }
237
322
 
238
- // Recorrer el path desde el padre hacia la raíz (saltando el primer elemento que es el propio componente)
323
+ // path[0] es el nodo del propio componente a actualizar.
324
+ // Intentar actualización directa en la instancia del componente.
325
+ const targetNode = path[0];
326
+ if (targetNode?.instancia) {
327
+ const updated = updateInstanceInPlace(
328
+ targetNode.instancia,
329
+ newComponent,
330
+ );
331
+ if (updated) {
332
+ // También forzar actualización del padre para que el vdom se reconcilie.
333
+ const parentNode = path[1];
334
+ if (parentNode?.instancia && !parentNode.isRoot) {
335
+ tryForceUpdate(parentNode.instancia);
336
+ }
337
+ return true;
338
+ }
339
+ }
340
+
341
+ // Fallback: recorrer hacia el padre si el nodo propio no es accesible.
239
342
  for (let i = 1; i < path.length; i++) {
240
343
  const parent = path[i];
241
344
 
@@ -244,19 +347,29 @@ function tryUpdateComponentPath(path, newComponent, componentName, App) {
244
347
  return true;
245
348
  }
246
349
 
247
- if (!parent || !parent.instancia) {
350
+ if (!parent?.instancia) {
248
351
  console.error('❌ Nodo padre no válido en el camino:', parent);
249
- continue; // Continúa con el siguiente padre en lugar de fallar
352
+ continue;
250
353
  }
251
354
 
252
- // Actualizar la instancia del componente
253
355
  const componentsDefinition =
254
356
  parent.instancia?.type?.components || parent.instancia?.components;
255
357
 
256
358
  if (componentsDefinition && componentsDefinition[componentName]) {
257
- componentsDefinition[componentName] = newComponent;
359
+ // Mutar la definición existente en el mapa del padre también,
360
+ // para que nuevas instancias del componente creadas después se usen
361
+ // con la definición actualizada.
362
+ const existingDef = componentsDefinition[componentName];
363
+ if (existingDef && typeof existingDef === 'object') {
364
+ Object.assign(existingDef, newComponent);
365
+ if (newComponent.render)
366
+ existingDef.render = newComponent.render;
367
+ if (newComponent.setup) existingDef.setup = newComponent.setup;
368
+ clearVueCaches(parent.instancia.appContext, existingDef);
369
+ } else {
370
+ componentsDefinition[componentName] = newComponent;
371
+ }
258
372
 
259
- // Forzar actualización de la instancia padre
260
373
  return (
261
374
  tryForceUpdate(parent.instancia) ||
262
375
  tryForceUpdate(parent.instancia.proxy)
@@ -297,6 +410,18 @@ export async function reloadComponent(App, Component) {
297
410
  const timestamp = Date.now();
298
411
  const moduleUrl = `${urlOrigin}?t=${timestamp}`;
299
412
 
413
+ // Eliminar style tags del ciclo HMR anterior para este componente
414
+ // para evitar acumulación de estilos duplicados en el documento.
415
+ const componentName_clean = componentName.replace(
416
+ /[^a-zA-Z0-9_-]/g,
417
+ '',
418
+ );
419
+ document
420
+ .querySelectorAll(
421
+ `[data-versa-hmr-component="${componentName_clean}"]`,
422
+ )
423
+ .forEach(el => el.remove());
424
+
300
425
  const module = await import(moduleUrl);
301
426
 
302
427
  if (!module.default) {
@@ -1,3 +1,15 @@
1
+ /* VersaCompiler HMR shim [dev] */
2
+ if (typeof window !== 'undefined' && window.__versaHMR) {
3
+ (() => {
4
+ const _id = new URL(import.meta.url).pathname;
5
+ import.meta.hot = {
6
+ accept(cb) { window.__versaHMR.accept(_id, typeof cb === 'function' ? cb : () => {}); },
7
+ invalidate() { window.__versaHMR._invalidate?.(_id); },
8
+ dispose(cb) { window.__versaHMR._onDispose?.(_id, cb); },
9
+ get data() { return window.__versaHMR._getHotData?.(_id) ?? {}; },
10
+ };
11
+ })();
12
+ }
1
13
  /**
2
14
  * Variable global que mantiene la referencia al overlay de error actual
3
15
  * @type {HTMLElement|null}
@@ -1,3 +1,15 @@
1
+ /* VersaCompiler HMR shim [dev] */
2
+ if (typeof window !== 'undefined' && window.__versaHMR) {
3
+ (() => {
4
+ const _id = new URL(import.meta.url).pathname;
5
+ import.meta.hot = {
6
+ accept(cb) { window.__versaHMR.accept(_id, typeof cb === 'function' ? cb : () => {}); },
7
+ invalidate() { window.__versaHMR._invalidate?.(_id); },
8
+ dispose(cb) { window.__versaHMR._onDispose?.(_id, cb); },
9
+ get data() { return window.__versaHMR._getHotData?.(_id) ?? {}; },
10
+ };
11
+ })();
12
+ }
1
13
  /**
2
14
  * Script para obtener la instancia de Vue usando solo JavaScript
3
15
  * Compatible con Vue 2 y Vue 3
@@ -1,3 +1,15 @@
1
+ /* VersaCompiler HMR shim [dev] */
2
+ if (typeof window !== 'undefined' && window.__versaHMR) {
3
+ (() => {
4
+ const _id = new URL(import.meta.url).pathname;
5
+ import.meta.hot = {
6
+ accept(cb) { window.__versaHMR.accept(_id, typeof cb === 'function' ? cb : () => {}); },
7
+ invalidate() { window.__versaHMR._invalidate?.(_id); },
8
+ dispose(cb) { window.__versaHMR._onDispose?.(_id, cb); },
9
+ get data() { return window.__versaHMR._getHotData?.(_id) ?? {}; },
10
+ };
11
+ })();
12
+ }
1
13
  /**
2
14
  * @fileoverview Inicialización del sistema Hot Module Replacement (HMR) para VersaCompiler
3
15
  * Este archivo maneja la conexión con BrowserSync y configura los listeners para HMR de Vue
@@ -11,6 +23,9 @@
11
23
 
12
24
  import { hideErrorOverlay, showErrorOverlay } from './errorScreen.js';
13
25
  import { obtenerInstanciaVue } from './getInstanciaVue.js';
26
+
27
+ // oxlint-disable-next-line import/no-unassigned-import -- side-effect: inicializa window.__versaHMR
28
+ import './versaHMR.js';
14
29
  import { reloadComponent } from './VueHRM.js';
15
30
 
16
31
  /**
@@ -343,6 +358,27 @@ async function initSocket(retries = 0) {
343
358
  socket.on('reloadFull', () => window.location.reload()); // Obtener la instancia de Vue con toda la lógica integrada
344
359
  let vueInstance = await obtenerInstanciaVue();
345
360
 
361
+ // Exponer función de recarga Vue por path para que los shims de módulos
362
+ // puedan disparar una actualización de instancia Vue cuando una dependencia
363
+ // (sampleFile.js, etc.) se actualiza vía HMR propagation.
364
+ if (window.__versaHMR) {
365
+ window.__versaHMR._reloadVueByPath = async path => {
366
+ try {
367
+ vueInstance = window.__VUE_APP__ || vueInstance;
368
+ // normalizedPath sin slash inicial (reloadComponent usa origin + '/' + path)
369
+ const normalizedPath = path.replace(/^\//, '');
370
+ const nameFile =
371
+ path.split('/').pop()?.replace('.js', '') ?? '';
372
+ await reloadComponent(vueInstance, {
373
+ normalizedPath,
374
+ nameFile,
375
+ });
376
+ } catch (err) {
377
+ console.error('❌ _reloadVueByPath failed:', err);
378
+ }
379
+ };
380
+ }
381
+
346
382
  // Configurar listener para HMR de componentes Vue
347
383
  socket.on('HRMVue', async (/** @type {ComponentInfo} */ data) => {
348
384
  try {
@@ -372,12 +408,31 @@ async function initSocket(retries = 0) {
372
408
  console.log('🔄 HRMHelper recibido:', data);
373
409
  console.log('📋 Archivo modificado:', data.filePath);
374
410
 
411
+ // moduleId: identificador canónico del módulo en el registry
412
+ // El servidor lo envía como el path del output (ej: /public/utils/math.js)
413
+ const moduleId = data.moduleId || data.filePath;
414
+
375
415
  if (data?.strategy) {
376
416
  switch (data.strategy) {
377
417
  case 'self-accept': {
418
+ // El módulo acepta su propio reemplazo (import.meta.hot.accept)
378
419
  try {
379
420
  const timestamp = Date.now();
380
- await import(`${data.filePath}?t=${timestamp}`);
421
+ // Usar moduleId (path absoluto con /) en lugar de data.filePath (relativo)
422
+ // data.filePath relativo resuelve mal desde /__versa/initHRM.js
423
+ const newModule = await import(
424
+ `${moduleId}?t=${timestamp}`
425
+ );
426
+ // También notificar al registry por si hay consumers adicionales
427
+ const registry = window.__versaHMR;
428
+ if (registry && registry.hasObservers(moduleId)) {
429
+ const exportedValue =
430
+ newModule.default !== undefined
431
+ ? newModule.default
432
+ : newModule;
433
+ registry.notifyUpdate(moduleId, exportedValue);
434
+ }
435
+ console.log(`✅ Self-accept HMR: ${moduleId}`);
381
436
  return;
382
437
  } catch (error) {
383
438
  reportErrorToServer(
@@ -391,9 +446,48 @@ async function initSocket(retries = 0) {
391
446
  break;
392
447
  }
393
448
  case 'propagate': {
449
+ // Re-importar el módulo con cache-bust y notificar al registry
394
450
  try {
395
451
  const timestamp = Date.now();
396
- await import(`${data.filePath}?t=${timestamp}`);
452
+ // Usar moduleId (path absoluto con /) en lugar de data.filePath (relativo)
453
+ // data.filePath relativo resuelve mal desde /__versa/initHRM.js
454
+ const newModule = await import(
455
+ `${moduleId}?t=${timestamp}`
456
+ );
457
+
458
+ // Extraer el valor exportado (default o namespace)
459
+ const exportedValue =
460
+ newModule.default !== undefined
461
+ ? newModule.default
462
+ : newModule;
463
+
464
+ // Notificar al registry de VersaHMR si hay observers registrados
465
+ const registry = window.__versaHMR;
466
+ if (registry && registry.hasObservers(moduleId)) {
467
+ const notified = registry.notifyUpdate(
468
+ moduleId,
469
+ exportedValue,
470
+ );
471
+ if (notified) {
472
+ console.log(
473
+ `✅ HMR sin recarga: ${moduleId} actualizado via registry`,
474
+ );
475
+ return;
476
+ }
477
+ // Si los callbacks fallaron, hacer full-reload como safety net
478
+ console.warn(
479
+ `⚠️ Callbacks del registry fallaron para ${moduleId}, haciendo full-reload`,
480
+ );
481
+ window.location.reload();
482
+ return;
483
+ }
484
+
485
+ // Sin observers en registry → la re-importación ya actualizó
486
+ // el módulo en el scope de ES modules del browser.
487
+ // Los consumers que hicieron import dinámico obtendrán la nueva versión.
488
+ console.log(
489
+ `✅ HMR propagado: ${moduleId} (sin observers en registry, re-import completado)`,
490
+ );
397
491
  return;
398
492
  } catch (error) {
399
493
  reportErrorToServer(
@@ -403,8 +497,10 @@ async function initSocket(retries = 0) {
403
497
  : new Error(String(error)),
404
498
  data,
405
499
  );
500
+ // Fallback a full-reload en caso de error
501
+ window.location.reload();
502
+ return;
406
503
  }
407
- break;
408
504
  }
409
505
  case 'full-reload':
410
506
  default: {
@@ -453,18 +549,30 @@ async function initSocket(retries = 0) {
453
549
  break;
454
550
 
455
551
  case 'propagate':
456
- // Propagar la actualización a los importadores
552
+ // Propagar la actualización: re-importar y notificar al registry
457
553
  console.log(
458
554
  '🔄 Propagando actualización a importadores',
459
555
  );
460
556
  try {
461
- // Invalidar el módulo en el cache del navegador
462
- // y dejar que los importadores se actualicen
463
557
  const timestamp = Date.now();
464
- await import(`${data.filePath}?t=${timestamp}`);
465
- console.log(
466
- '✅ Actualización propagada exitosamente',
558
+ const newModule = await import(
559
+ `${data.filePath}?t=${timestamp}`
467
560
  );
561
+ const exportedValue =
562
+ newModule.default !== undefined
563
+ ? newModule.default
564
+ : newModule;
565
+ const registry = window.__versaHMR;
566
+ if (registry && registry.hasObservers(moduleId)) {
567
+ registry.notifyUpdate(moduleId, exportedValue);
568
+ console.log(
569
+ '✅ Actualización propagada via registry',
570
+ );
571
+ } else {
572
+ console.log(
573
+ '✅ Re-import completado (sin observers en registry)',
574
+ );
575
+ }
468
576
  return;
469
577
  } catch (error) {
470
578
  console.error(