versacompiler 2.4.1 → 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 (52) hide show
  1. package/README.md +722 -722
  2. package/dist/compiler/compile-worker-pool.js +108 -0
  3. package/dist/compiler/compile-worker-thread.cjs +72 -0
  4. package/dist/compiler/compile.js +177 -18
  5. package/dist/compiler/error-reporter.js +12 -0
  6. package/dist/compiler/integrity-validator.js +13 -1
  7. package/dist/compiler/linter.js +12 -0
  8. package/dist/compiler/minify.js +12 -0
  9. package/dist/compiler/minifyTemplate.js +12 -0
  10. package/dist/compiler/module-resolution-optimizer.js +35 -20
  11. package/dist/compiler/parser.js +12 -0
  12. package/dist/compiler/performance-monitor.js +73 -61
  13. package/dist/compiler/pipeline/build-pipeline.js +139 -0
  14. package/dist/compiler/pipeline/core-plugins.js +230 -0
  15. package/dist/compiler/pipeline/module-graph.js +75 -0
  16. package/dist/compiler/pipeline/plugin-driver.js +99 -0
  17. package/dist/compiler/pipeline/types.js +14 -0
  18. package/dist/compiler/tailwindcss.js +12 -0
  19. package/dist/compiler/transform-optimizer.js +12 -0
  20. package/dist/compiler/transformTStoJS.js +38 -5
  21. package/dist/compiler/transforms.js +234 -16
  22. package/dist/compiler/typescript-compiler.js +12 -0
  23. package/dist/compiler/typescript-error-parser.js +12 -0
  24. package/dist/compiler/typescript-manager.js +15 -1
  25. package/dist/compiler/typescript-sync-validator.js +45 -31
  26. package/dist/compiler/typescript-worker-pool.js +12 -0
  27. package/dist/compiler/typescript-worker-thread.cjs +482 -475
  28. package/dist/compiler/typescript-worker.js +12 -0
  29. package/dist/compiler/vuejs.js +73 -47
  30. package/dist/config.js +14 -0
  31. package/dist/hrm/VueHRM.js +484 -359
  32. package/dist/hrm/errorScreen.js +95 -83
  33. package/dist/hrm/getInstanciaVue.js +325 -313
  34. package/dist/hrm/initHRM.js +736 -586
  35. package/dist/hrm/versaHMR.js +317 -0
  36. package/dist/main.js +23 -3
  37. package/dist/servicios/browserSync.js +127 -6
  38. package/dist/servicios/file-watcher.js +139 -8
  39. package/dist/servicios/logger.js +12 -0
  40. package/dist/servicios/readConfig.js +141 -54
  41. package/dist/servicios/versacompile.config.types.js +14 -0
  42. package/dist/utils/excluded-modules.js +12 -0
  43. package/dist/utils/module-resolver.js +86 -40
  44. package/dist/utils/promptUser.js +12 -0
  45. package/dist/utils/proxyValidator.js +12 -0
  46. package/dist/utils/resolve-bin.js +12 -0
  47. package/dist/utils/utils.js +12 -0
  48. package/dist/utils/vue-types-setup.js +260 -248
  49. package/dist/wrappers/eslint-node.js +15 -1
  50. package/dist/wrappers/oxlint-node.js +15 -1
  51. package/dist/wrappers/tailwind-node.js +12 -0
  52. package/package.json +74 -54
@@ -1,586 +1,736 @@
1
- /**
2
- * @fileoverview Inicialización del sistema Hot Module Replacement (HMR) para VersaCompiler
3
- * Este archivo maneja la conexión con BrowserSync y configura los listeners para HMR de Vue
4
- */
5
-
6
- /**
7
- * @typedef {Object} ComponentInfo
8
- * @property {string} normalizedPath - Ruta normalizada del componente
9
- * @property {string} nameFile - Nombre del archivo del componente
10
- */
11
-
12
- import { hideErrorOverlay, showErrorOverlay } from './errorScreen.js';
13
- import { obtenerInstanciaVue } from './getInstanciaVue.js';
14
- import { reloadComponent } from './VueHRM.js';
15
-
16
- /**
17
- * Analiza el contenido de un módulo para extraer sus imports/exports
18
- * @param {string} content - Contenido del archivo JavaScript/TypeScript
19
- * @returns {{imports: string[], exports: string[], hasHMRAccept: boolean}}
20
- */
21
- function analyzeModuleContent(content) {
22
- const imports = [];
23
- const exports = [];
24
- let hasHMRAccept = false;
25
-
26
- // Detectar imports (ESM y CommonJS)
27
- const importMatches = content.matchAll(
28
- /import\s+(?:{[^}]*}|[^\s]+|\*\s+as\s+\w+)\s+from\s+['"]([^'"]+)['"]/g,
29
- );
30
- const requireMatches = content.matchAll(/require\(['"]([^'"]+)['"]\)/g);
31
-
32
- for (const match of importMatches) {
33
- if (match[1]) imports.push(match[1]);
34
- }
35
- for (const match of requireMatches) {
36
- if (match[1]) imports.push(match[1]);
37
- }
38
-
39
- // Detectar exports
40
- const exportMatches = content.matchAll(
41
- /export\s+(?:default|const|let|var|function|class|{[^}]*})\s+(\w+)/g,
42
- );
43
- for (const match of exportMatches) {
44
- if (match[1]) exports.push(match[1]);
45
- }
46
-
47
- // Detectar si el módulo acepta HMR (import.meta.hot.accept)
48
- hasHMRAccept = /import\.meta\.hot\.accept/.test(content);
49
-
50
- return { imports, exports, hasHMRAccept };
51
- }
52
-
53
- /**
54
- * Detecta automáticamente la estrategia de HMR para un archivo modificado
55
- * @param {string} filePath - Ruta del archivo modificado
56
- * @param {string|null} content - Contenido del archivo (si está disponible)
57
- * @returns {Promise<{strategy: 'self-accept'|'propagate'|'full-reload', boundary?: string}>}
58
- */
59
- async function detectHMRStrategy(filePath, content = null) {
60
- try {
61
- // Si no tenemos el contenido, intentar leerlo
62
- if (!content && typeof fetch !== 'undefined') {
63
- try {
64
- const response = await fetch(filePath);
65
- content = await response.text();
66
- } catch (error) {
67
- console.warn(
68
- '⚠️ No se pudo obtener contenido del módulo:',
69
- error,
70
- );
71
- return { strategy: 'full-reload' };
72
- }
73
- }
74
-
75
- if (!content) {
76
- return { strategy: 'full-reload' };
77
- }
78
-
79
- const analysis = analyzeModuleContent(content);
80
-
81
- // Estrategia 1: Self-accept (el módulo declara que puede reemplazarse a sí mismo)
82
- if (analysis.hasHMRAccept) {
83
- console.log(' Módulo con self-accept HMR:', filePath);
84
- return { strategy: 'self-accept', boundary: filePath };
85
- }
86
-
87
- // Estrategia 2: Propagate (buscar el primer importador que acepte HMR)
88
- // En el browser no tenemos acceso al módulo graph del servidor,
89
- // así que usamos heurísticas basadas en el contenido
90
-
91
- // Si solo exporta funciones/constantes simples, probablemente sea seguro recargar
92
- const hasOnlySimpleExports =
93
- analysis.exports.length > 0 &&
94
- !content.includes('class ') &&
95
- !content.includes('new ');
96
-
97
- if (hasOnlySimpleExports) {
98
- console.log(
99
- '✨ Módulo con exports simples, propagando HMR:',
100
- filePath,
101
- );
102
- return { strategy: 'propagate', boundary: filePath };
103
- }
104
-
105
- // Estrategia 3: Full reload (no es seguro hacer HMR)
106
- console.log('⚠️ Módulo requiere recarga completa:', filePath);
107
- return { strategy: 'full-reload' };
108
- } catch (error) {
109
- console.error(' Error detectando estrategia HMR:', error);
110
- return { strategy: 'full-reload' };
111
- }
112
- }
113
-
114
- /**
115
- * Envía un error del cliente al servidor para debugging
116
- * @param {string} type - Tipo de error (hmr, reload, vue, etc)
117
- * @param {Error|string} error - El error a reportar
118
- * @param {Object} [context] - Contexto adicional del error
119
- */
120
- function reportErrorToServer(type, error, context = {}) {
121
- try {
122
- // Verificar que estamos en un navegador
123
- if (typeof window === 'undefined') {
124
- return;
125
- }
126
-
127
- if (!window.___browserSync___ || !window.___browserSync___.socket) {
128
- console.warn(
129
- '⚠️ BrowserSync socket no disponible para reportar error',
130
- );
131
- return;
132
- }
133
-
134
- const errorData = {
135
- type,
136
- timestamp: new Date().toISOString(),
137
- userAgent: navigator.userAgent,
138
- url: window.location.href,
139
- context,
140
- error: {
141
- message: error?.message || String(error),
142
- stack: error?.stack || '',
143
- name: error?.name || 'Error',
144
- },
145
- };
146
-
147
- // Enviar error al servidor
148
- window.___browserSync___.socket.emit('client:error', errorData);
149
-
150
- console.error(`📤 Error reportado al servidor [${type}]:`, errorData);
151
- } catch (err) {
152
- console.error('❌ Error al reportar error al servidor:', err);
153
- }
154
- }
155
-
156
- /**
157
- * Maneja el hot reload de librerías sin recarga completa de página
158
- * @param {Object} data - Datos del evento HRMHelper
159
- * @param {string} data.libraryName - Nombre de la librería a actualizar
160
- * @param {string} data.libraryPath - Ruta de la nueva librería
161
- * @param {string} [data.globalName] - Nombre global de la librería (ej: 'Vue', 'React')
162
- * @param {Function} [importFn] - Función import para cargar módulos (inyectable para tests)
163
- * @returns {Promise<boolean>} True si el hot reload fue exitoso
164
- */
165
- export async function handleLibraryHotReload(
166
- data,
167
- importFn = url => import(url),
168
- ) {
169
- const { libraryName, libraryPath, globalName } = data;
170
-
171
- if (!libraryName || !libraryPath) {
172
- console.error(
173
- '❌ HRMHelper: Datos incompletos para hot reload de librería',
174
- );
175
- return false;
176
- }
177
-
178
- // 2. Determinar el nombre global de la librería
179
- const targetGlobalName = globalName || libraryName;
180
-
181
- // 3. Backup de la versión anterior (antes de cargar la nueva)
182
- let oldLibraryVersion;
183
-
184
- try {
185
- console.log(`🔄 Iniciando hot reload de librería: ${libraryName}`);
186
-
187
- oldLibraryVersion = window[targetGlobalName];
188
-
189
- // 4. Cargar la nueva versión de la librería
190
- const timestamp = Date.now();
191
- const moduleUrl = `${libraryPath}?t=${timestamp}`;
192
-
193
- console.log(`📦 Cargando nueva versión desde: ${moduleUrl}`);
194
- const module = await importFn(moduleUrl);
195
-
196
- if (!module.default && !module[libraryName]) {
197
- console.error(
198
- '❌ HRMHelper: La nueva versión no tiene export válido',
199
- );
200
- return false;
201
- }
202
-
203
- const newLibraryVersion = module.default || module[libraryName];
204
-
205
- // 3. Reemplazar la librería en el contexto global
206
- console.log(`🔄 Reemplazando ${targetGlobalName} en contexto global`);
207
- window[targetGlobalName] = newLibraryVersion;
208
-
209
- // 4. Limpiar caches si existen
210
- if (
211
- typeof newLibraryVersion === 'object' &&
212
- newLibraryVersion.clearCache
213
- ) {
214
- try {
215
- newLibraryVersion.clearCache();
216
- } catch {
217
- // Ignorar errores de clearCache, no es crítico
218
- }
219
- }
220
-
221
- // 5. Re-inicializar aplicación si es necesario
222
- if (targetGlobalName === 'Vue' || libraryName.includes('vue')) {
223
- console.log(
224
- '🔄 Librería Vue actualizada, se recomienda recarga completa',
225
- );
226
- // Para Vue, es más seguro hacer recarga completa
227
- window.location.reload();
228
- return true;
229
- }
230
-
231
- // 6. Intentar limpiar caches si existen
232
- try {
233
- // Limpiar cualquier cache que pueda existir
234
- if (
235
- typeof window !== 'undefined' &&
236
- window.performance &&
237
- window.performance.clearResourceTimings
238
- ) {
239
- window.performance.clearResourceTimings();
240
- }
241
- } catch {
242
- // Ignorar errores de limpieza de cache
243
- }
244
-
245
- console.log(`✅ Hot reload exitoso de librería: ${libraryName}`);
246
- return true;
247
- } catch (error) {
248
- console.error(
249
- `❌ Error en hot reload de librería ${libraryName}:`,
250
- error,
251
- );
252
-
253
- // Reportar error al servidor
254
- reportErrorToServer('library-hotreload', error, {
255
- libraryName,
256
- libraryPath,
257
- globalName,
258
- hadOldVersion: oldLibraryVersion !== undefined,
259
- });
260
-
261
- // Intentar rollback si es posible
262
- if (targetGlobalName && oldLibraryVersion !== undefined) {
263
- console.log('🔄 Intentando rollback de librería...');
264
- window[targetGlobalName] = oldLibraryVersion;
265
- }
266
-
267
- return false;
268
- }
269
- }
270
-
271
- // Variable para controlar si ya se está inicializando
272
- let isInitializing = false;
273
- let initializationTimeout = null;
274
-
275
- /**
276
- * Inicializa la conexión socket con BrowserSync y configura los listeners para HMR
277
- * @param {number} [retries=0] - Número de reintentos realizados
278
- * @returns {Promise<void>} Promise que se resuelve cuando la conexión está configurada
279
- */
280
- async function initSocket(retries = 0) {
281
- // Evitar inicializaciones concurrentes
282
- if (isInitializing && retries > 0) {
283
- console.log(
284
- '⏳ Versa HMR: Ya hay una inicialización en curso, saltando...',
285
- );
286
- return;
287
- }
288
-
289
- isInitializing = true;
290
-
291
- const maxRetries = 10;
292
- const retryDelay = Math.min(2000 * (retries + 1), 10000); // Backoff exponencial hasta 10s
293
-
294
- // Verificar si BrowserSync está disponible y tiene socket
295
- if (window.___browserSync___ && window.___browserSync___.socket) {
296
- const socket = window.___browserSync___.socket;
297
- let connected = socket.connected; // Verificar estado inicial de conexión
298
-
299
- // Limpiar listeners previos para evitar duplicados
300
- socket.off('connect');
301
- socket.off('disconnect');
302
- socket.off('reloadFull');
303
- socket.off('HRMVue');
304
- socket.off('HRMHelper');
305
- socket.off('error');
306
-
307
- // Limpiar timeout previo si existe
308
- if (initializationTimeout) {
309
- clearTimeout(initializationTimeout);
310
- initializationTimeout = null;
311
- }
312
-
313
- // Configurar listener para eventos de conexión
314
- socket.on('connect', async () => {
315
- connected = true;
316
- isInitializing = false;
317
- hideErrorOverlay();
318
- console.log('✔️ Versa HMR: Socket conectado');
319
- });
320
-
321
- // Configurar listener para eventos de desconexión
322
- socket.on('disconnect', () => {
323
- connected = false;
324
- isInitializing = false;
325
- console.log('❌ Versa HMR: Socket desconectado, reintentando...');
326
- // Lógica de reintentos para desconexión
327
- initializationTimeout = setTimeout(() => {
328
- if (!socket.connected && retries < maxRetries) {
329
- initSocket(retries + 1);
330
- } else if (!socket.connected) {
331
- console.error(
332
- `❌ Versa HMR: Socket no conectado después de ${maxRetries} reintentos tras desconexión.`,
333
- );
334
- showErrorOverlay(
335
- 'HMR Desconectado',
336
- 'No se pudo reconectar a BrowserSync después de múltiples reintentos.',
337
- );
338
- }
339
- }, retryDelay);
340
- });
341
-
342
- // Configurar listener para recarga completa
343
- socket.on('reloadFull', () => window.location.reload()); // Obtener la instancia de Vue con toda la lógica integrada
344
- let vueInstance = await obtenerInstanciaVue();
345
-
346
- // Configurar listener para HMR de componentes Vue
347
- socket.on('HRMVue', async (/** @type {ComponentInfo} */ data) => {
348
- try {
349
- hideErrorOverlay();
350
- vueInstance = window.__VUE_APP__ || vueInstance;
351
- if (vueInstance) {
352
- console.log('🔥 Preparando HMR para Vue...');
353
- await reloadComponent(vueInstance, data);
354
- } else {
355
- console.log('🔄 Usando método fallback:', vueInstance);
356
- }
357
- } catch (error) {
358
- console.error(' Error en HMR de Vue:', error);
359
- reportErrorToServer('vue-hmr', error, {
360
- component: data?.nameFile,
361
- path: data?.normalizedPath,
362
- });
363
- showErrorOverlay(
364
- 'Error en HMR de Vue',
365
- error.message || String(error),
366
- );
367
- }
368
- });
369
-
370
- // Configurar listener para datos auxiliares de HMR
371
- socket.on('HRMHelper', async data => {
372
- console.log('🔄 HRMHelper recibido:', data);
373
- console.log('📋 Archivo modificado:', data.filePath);
374
-
375
- // Sistema inteligente de detección automática (como Vite/esbuild)
376
- if (data.filePath && !data.libraryName && !data.libraryPath) {
377
- console.log('🔍 Analizando estrategia HMR automática...');
378
-
379
- // Detectar la estrategia de HMR apropiada
380
- const hmrStrategy = await detectHMRStrategy(
381
- data.filePath,
382
- data.content,
383
- );
384
-
385
- console.log('📊 Estrategia detectada:', hmrStrategy.strategy);
386
-
387
- // Ejecutar la estrategia apropiada
388
- switch (hmrStrategy.strategy) {
389
- case 'self-accept':
390
- // El módulo puede reemplazarse a sí mismo
391
- console.log(' Aplicando self-accept HMR');
392
- try {
393
- // Reimportar el módulo con cache busting
394
- const timestamp = Date.now();
395
- await import(`${data.filePath}?t=${timestamp}`);
396
- console.log('✅ Módulo recargado exitosamente');
397
- return;
398
- } catch (error) {
399
- console.error(
400
- 'Error en self-accept HMR:',
401
- error,
402
- );
403
- reportErrorToServer(
404
- 'hmr-self-accept-failed',
405
- error instanceof Error
406
- ? error
407
- : new Error(String(error)),
408
- data,
409
- );
410
- }
411
- break;
412
-
413
- case 'propagate':
414
- // Propagar la actualización a los importadores
415
- console.log(
416
- '🔄 Propagando actualización a importadores',
417
- );
418
- try {
419
- // Invalidar el módulo en el cache del navegador
420
- // y dejar que los importadores se actualicen
421
- const timestamp = Date.now();
422
- await import(`${data.filePath}?t=${timestamp}`);
423
- console.log(
424
- '✅ Actualización propagada exitosamente',
425
- );
426
- return;
427
- } catch (error) {
428
- console.error(
429
- '❌ Error propagando actualización:',
430
- error,
431
- );
432
- reportErrorToServer(
433
- 'hmr-propagate-failed',
434
- error instanceof Error
435
- ? error
436
- : new Error(String(error)),
437
- data,
438
- );
439
- }
440
- break;
441
-
442
- case 'full-reload':
443
- default:
444
- // Recarga completa necesaria
445
- console.log(
446
- '🔄 Recarga completa requerida - módulo no soporta HMR',
447
- );
448
- reportErrorToServer(
449
- 'hmr-full-reload',
450
- new Error(
451
- 'Módulo requiere recarga completa (no self-accept)',
452
- ),
453
- {
454
- ...data,
455
- strategy: hmrStrategy.strategy,
456
- hint: 'Considera agregar import.meta.hot.accept() al módulo para HMR sin recarga',
457
- },
458
- );
459
- window.location.reload();
460
- return;
461
- }
462
- }
463
-
464
- try {
465
- // Intentar hacer hot reload de librería sin recarga completa
466
- const success = await handleLibraryHotReload(data);
467
- if (!success) {
468
- console.warn(
469
- '⚠️ Hot reload de librería falló, haciendo recarga completa',
470
- );
471
- reportErrorToServer(
472
- 'hmr-helper-failed',
473
- new Error('Hot reload returned false'),
474
- data,
475
- );
476
- window.location.reload();
477
- }
478
- } catch (error) {
479
- console.error('❌ Error en HRMHelper:', error);
480
- reportErrorToServer(
481
- 'hmr-helper-exception',
482
- error instanceof Error ? error : new Error(String(error)),
483
- data,
484
- );
485
- window.location.reload();
486
- }
487
- });
488
-
489
- // Configurar listener para errores de socket
490
- socket.on('error', err => {
491
- console.error('❌ Versa HMR: Error en el socket:', err);
492
- showErrorOverlay(
493
- 'Error de Socket',
494
- 'Se produjo un error en la conexión de BrowserSync.',
495
- );
496
- }); // Watchdog para verificar conexión inicial si el socket existe pero no está conectado
497
- if (!connected) {
498
- console.log(
499
- `Versa HMR: Objeto socket encontrado, intentando conexión (Intento ${
500
- retries + 1
501
- }/${maxRetries})`,
502
- );
503
- initializationTimeout = setTimeout(() => {
504
- if (!socket.connected && retries <= maxRetries) {
505
- console.warn(
506
- 'Versa HMR: Sin conexión de socket después del tiempo de espera inicial, reintentando initSocket...',
507
- );
508
- initSocket(retries + 1);
509
- } else if (!socket.connected) {
510
- isInitializing = false;
511
- console.error(
512
- `❌ Versa HMR: Socket aún no conectado después de ${maxRetries} intentos iniciales.`,
513
- );
514
- showErrorOverlay(
515
- 'Falló HMR de BrowserSync',
516
- 'No se pudo conectar al socket de BrowserSync después de intentos iniciales.',
517
- );
518
- }
519
- }, 5000); // Timeout de 5s para el watchdog inicial
520
- } else {
521
- isInitializing = false;
522
- }
523
- } else {
524
- // BrowserSync no está disponible, intentar reinicializar
525
- console.warn(
526
- `[HMR] Socket de BrowserSync no encontrado o BrowserSync no completamente inicializado. Reintentando initSocket... (${
527
- retries + 1
528
- }/${maxRetries})`,
529
- );
530
- if (retries < maxRetries) {
531
- initializationTimeout = setTimeout(
532
- () => initSocket(retries + 1),
533
- retryDelay,
534
- );
535
- } else {
536
- isInitializing = false;
537
- console.error(
538
- `❌ Versa HMR: Socket de BrowserSync no encontrado después de ${maxRetries} reintentos.`,
539
- );
540
- showErrorOverlay(
541
- 'Falló HMR de BrowserSync',
542
- 'Socket o cliente de BrowserSync no encontrado después de múltiples reintentos.',
543
- );
544
- }
545
- }
546
- }
547
-
548
- // Solo ejecutar en ambiente de navegador (no en tests de Node.js)
549
- if (
550
- typeof window !== 'undefined' &&
551
- typeof window.addEventListener === 'function'
552
- ) {
553
- // Capturar errores globales no manejados
554
- window.addEventListener('error', event => {
555
- if (
556
- event.filename &&
557
- (event.filename.includes('/hrm/') || event.filename.includes('HRM'))
558
- ) {
559
- reportErrorToServer(
560
- 'uncaught-error',
561
- event.error || new Error(event.message),
562
- {
563
- filename: event.filename,
564
- lineno: event.lineno,
565
- colno: event.colno,
566
- },
567
- );
568
- }
569
- });
570
-
571
- // Capturar promesas rechazadas no manejadas
572
- window.addEventListener('unhandledrejection', event => {
573
- const error = event.reason;
574
- if (
575
- error &&
576
- (error.stack?.includes('/hrm/') || error.message?.includes('HRM'))
577
- ) {
578
- reportErrorToServer('unhandled-rejection', error, {
579
- promise: String(event.promise),
580
- });
581
- }
582
- });
583
-
584
- // Inicializar el sistema HMR al cargar el script
585
- initSocket();
586
- }
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
+ }
13
+ /**
14
+ * @fileoverview Inicialización del sistema Hot Module Replacement (HMR) para VersaCompiler
15
+ * Este archivo maneja la conexión con BrowserSync y configura los listeners para HMR de Vue
16
+ */
17
+
18
+ /**
19
+ * @typedef {Object} ComponentInfo
20
+ * @property {string} normalizedPath - Ruta normalizada del componente
21
+ * @property {string} nameFile - Nombre del archivo del componente
22
+ */
23
+
24
+ import { hideErrorOverlay, showErrorOverlay } from './errorScreen.js';
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';
29
+ import { reloadComponent } from './VueHRM.js';
30
+
31
+ /**
32
+ * Analiza el contenido de un módulo para extraer sus imports/exports
33
+ * @param {string} content - Contenido del archivo JavaScript/TypeScript
34
+ * @returns {{imports: string[], exports: string[], hasHMRAccept: boolean}}
35
+ */
36
+ function analyzeModuleContent(content) {
37
+ const imports = [];
38
+ const exports = [];
39
+ let hasHMRAccept = false;
40
+
41
+ // Detectar imports (ESM y CommonJS)
42
+ const importMatches = content.matchAll(
43
+ /import\s+(?:{[^}]*}|[^\s]+|\*\s+as\s+\w+)\s+from\s+['"]([^'"]+)['"]/g,
44
+ );
45
+ const requireMatches = content.matchAll(/require\(['"]([^'"]+)['"]\)/g);
46
+
47
+ for (const match of importMatches) {
48
+ if (match[1]) imports.push(match[1]);
49
+ }
50
+ for (const match of requireMatches) {
51
+ if (match[1]) imports.push(match[1]);
52
+ }
53
+
54
+ // Detectar exports
55
+ const exportMatches = content.matchAll(
56
+ /export\s+(?:default|const|let|var|function|class|{[^}]*})\s+(\w+)/g,
57
+ );
58
+ for (const match of exportMatches) {
59
+ if (match[1]) exports.push(match[1]);
60
+ }
61
+
62
+ // Detectar si el módulo acepta HMR (import.meta.hot.accept)
63
+ hasHMRAccept = /import\.meta\.hot\.accept/.test(content);
64
+
65
+ return { imports, exports, hasHMRAccept };
66
+ }
67
+
68
+ /**
69
+ * Detecta automáticamente la estrategia de HMR para un archivo modificado
70
+ * @param {string} filePath - Ruta del archivo modificado
71
+ * @param {string|null} content - Contenido del archivo (si está disponible)
72
+ * @returns {Promise<{strategy: 'self-accept'|'propagate'|'full-reload', boundary?: string}>}
73
+ */
74
+ async function detectHMRStrategy(filePath, content = null) {
75
+ try {
76
+ // Si no tenemos el contenido, intentar leerlo
77
+ if (!content && typeof fetch !== 'undefined') {
78
+ try {
79
+ const response = await fetch(filePath);
80
+ content = await response.text();
81
+ } catch (error) {
82
+ console.warn(
83
+ '⚠️ No se pudo obtener contenido del módulo:',
84
+ error,
85
+ );
86
+ return { strategy: 'full-reload' };
87
+ }
88
+ }
89
+
90
+ if (!content) {
91
+ return { strategy: 'full-reload' };
92
+ }
93
+
94
+ const analysis = analyzeModuleContent(content);
95
+
96
+ // Estrategia 1: Self-accept (el módulo declara que puede reemplazarse a sí mismo)
97
+ if (analysis.hasHMRAccept) {
98
+ console.log('✨ Módulo con self-accept HMR:', filePath);
99
+ return { strategy: 'self-accept', boundary: filePath };
100
+ }
101
+
102
+ // Estrategia 2: Propagate (buscar el primer importador que acepte HMR)
103
+ // En el browser no tenemos acceso al módulo graph del servidor,
104
+ // así que usamos heurísticas basadas en el contenido
105
+
106
+ // Si solo exporta funciones/constantes simples, probablemente sea seguro recargar
107
+ const hasOnlySimpleExports =
108
+ analysis.exports.length > 0 &&
109
+ !content.includes('class ') &&
110
+ !content.includes('new ');
111
+
112
+ if (hasOnlySimpleExports) {
113
+ console.log(
114
+ '✨ Módulo con exports simples, propagando HMR:',
115
+ filePath,
116
+ );
117
+ return { strategy: 'propagate', boundary: filePath };
118
+ }
119
+
120
+ // Estrategia 3: Full reload (no es seguro hacer HMR)
121
+ console.log('⚠️ Módulo requiere recarga completa:', filePath);
122
+ return { strategy: 'full-reload' };
123
+ } catch (error) {
124
+ console.error('❌ Error detectando estrategia HMR:', error);
125
+ return { strategy: 'full-reload' };
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Envía un error del cliente al servidor para debugging
131
+ * @param {string} type - Tipo de error (hmr, reload, vue, etc)
132
+ * @param {Error|string} error - El error a reportar
133
+ * @param {Object} [context] - Contexto adicional del error
134
+ */
135
+ function reportErrorToServer(type, error, context = {}) {
136
+ try {
137
+ // Verificar que estamos en un navegador
138
+ if (typeof window === 'undefined') {
139
+ return;
140
+ }
141
+
142
+ if (!window.___browserSync___ || !window.___browserSync___.socket) {
143
+ console.warn(
144
+ '⚠️ BrowserSync socket no disponible para reportar error',
145
+ );
146
+ return;
147
+ }
148
+
149
+ const errorData = {
150
+ type,
151
+ timestamp: new Date().toISOString(),
152
+ userAgent: navigator.userAgent,
153
+ url: window.location.href,
154
+ context,
155
+ error: {
156
+ message: error?.message || String(error),
157
+ stack: error?.stack || '',
158
+ name: error?.name || 'Error',
159
+ },
160
+ };
161
+
162
+ // Enviar error al servidor
163
+ window.___browserSync___.socket.emit('client:error', errorData);
164
+
165
+ console.error(`📤 Error reportado al servidor [${type}]:`, errorData);
166
+ } catch (err) {
167
+ console.error('❌ Error al reportar error al servidor:', err);
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Maneja el hot reload de librerías sin recarga completa de página
173
+ * @param {Object} data - Datos del evento HRMHelper
174
+ * @param {string} data.libraryName - Nombre de la librería a actualizar
175
+ * @param {string} data.libraryPath - Ruta de la nueva librería
176
+ * @param {string} [data.globalName] - Nombre global de la librería (ej: 'Vue', 'React')
177
+ * @param {Function} [importFn] - Función import para cargar módulos (inyectable para tests)
178
+ * @returns {Promise<boolean>} True si el hot reload fue exitoso
179
+ */
180
+ export async function handleLibraryHotReload(
181
+ data,
182
+ importFn = url => import(url),
183
+ ) {
184
+ const { libraryName, libraryPath, globalName } = data;
185
+
186
+ if (!libraryName || !libraryPath) {
187
+ console.error(
188
+ '❌ HRMHelper: Datos incompletos para hot reload de librería',
189
+ );
190
+ return false;
191
+ }
192
+
193
+ // 2. Determinar el nombre global de la librería
194
+ const targetGlobalName = globalName || libraryName;
195
+
196
+ // 3. Backup de la versión anterior (antes de cargar la nueva)
197
+ let oldLibraryVersion;
198
+
199
+ try {
200
+ console.log(`🔄 Iniciando hot reload de librería: ${libraryName}`);
201
+
202
+ oldLibraryVersion = window[targetGlobalName];
203
+
204
+ // 4. Cargar la nueva versión de la librería
205
+ const timestamp = Date.now();
206
+ const moduleUrl = `${libraryPath}?t=${timestamp}`;
207
+
208
+ console.log(`📦 Cargando nueva versión desde: ${moduleUrl}`);
209
+ const module = await importFn(moduleUrl);
210
+
211
+ if (!module.default && !module[libraryName]) {
212
+ console.error(
213
+ '❌ HRMHelper: La nueva versión no tiene export válido',
214
+ );
215
+ return false;
216
+ }
217
+
218
+ const newLibraryVersion = module.default || module[libraryName];
219
+
220
+ // 3. Reemplazar la librería en el contexto global
221
+ console.log(`🔄 Reemplazando ${targetGlobalName} en contexto global`);
222
+ window[targetGlobalName] = newLibraryVersion;
223
+
224
+ // 4. Limpiar caches si existen
225
+ if (
226
+ typeof newLibraryVersion === 'object' &&
227
+ newLibraryVersion.clearCache
228
+ ) {
229
+ try {
230
+ newLibraryVersion.clearCache();
231
+ } catch {
232
+ // Ignorar errores de clearCache, no es crítico
233
+ }
234
+ }
235
+
236
+ // 5. Re-inicializar aplicación si es necesario
237
+ if (targetGlobalName === 'Vue' || libraryName.includes('vue')) {
238
+ console.log(
239
+ '🔄 Librería Vue actualizada, se recomienda recarga completa',
240
+ );
241
+ // Para Vue, es más seguro hacer recarga completa
242
+ window.location.reload();
243
+ return true;
244
+ }
245
+
246
+ // 6. Intentar limpiar caches si existen
247
+ try {
248
+ // Limpiar cualquier cache que pueda existir
249
+ if (
250
+ typeof window !== 'undefined' &&
251
+ window.performance &&
252
+ window.performance.clearResourceTimings
253
+ ) {
254
+ window.performance.clearResourceTimings();
255
+ }
256
+ } catch {
257
+ // Ignorar errores de limpieza de cache
258
+ }
259
+
260
+ console.log(`✅ Hot reload exitoso de librería: ${libraryName}`);
261
+ return true;
262
+ } catch (error) {
263
+ console.error(
264
+ `❌ Error en hot reload de librería ${libraryName}:`,
265
+ error,
266
+ );
267
+
268
+ // Reportar error al servidor
269
+ reportErrorToServer('library-hotreload', error, {
270
+ libraryName,
271
+ libraryPath,
272
+ globalName,
273
+ hadOldVersion: oldLibraryVersion !== undefined,
274
+ });
275
+
276
+ // Intentar rollback si es posible
277
+ if (targetGlobalName && oldLibraryVersion !== undefined) {
278
+ console.log('🔄 Intentando rollback de librería...');
279
+ window[targetGlobalName] = oldLibraryVersion;
280
+ }
281
+
282
+ return false;
283
+ }
284
+ }
285
+
286
+ // Variable para controlar si ya se está inicializando
287
+ let isInitializing = false;
288
+ let initializationTimeout = null;
289
+
290
+ /**
291
+ * Inicializa la conexión socket con BrowserSync y configura los listeners para HMR
292
+ * @param {number} [retries=0] - Número de reintentos realizados
293
+ * @returns {Promise<void>} Promise que se resuelve cuando la conexión está configurada
294
+ */
295
+ async function initSocket(retries = 0) {
296
+ // Evitar inicializaciones concurrentes
297
+ if (isInitializing && retries > 0) {
298
+ console.log(
299
+ '⏳ Versa HMR: Ya hay una inicialización en curso, saltando...',
300
+ );
301
+ return;
302
+ }
303
+
304
+ isInitializing = true;
305
+
306
+ const maxRetries = 10;
307
+ const retryDelay = Math.min(2000 * (retries + 1), 10000); // Backoff exponencial hasta 10s
308
+
309
+ // Verificar si BrowserSync está disponible y tiene socket
310
+ if (window.___browserSync___ && window.___browserSync___.socket) {
311
+ const socket = window.___browserSync___.socket;
312
+ let connected = socket.connected; // Verificar estado inicial de conexión
313
+
314
+ // Limpiar listeners previos para evitar duplicados
315
+ socket.off('connect');
316
+ socket.off('disconnect');
317
+ socket.off('reloadFull');
318
+ socket.off('HRMVue');
319
+ socket.off('HRMHelper');
320
+ socket.off('error');
321
+
322
+ // Limpiar timeout previo si existe
323
+ if (initializationTimeout) {
324
+ clearTimeout(initializationTimeout);
325
+ initializationTimeout = null;
326
+ }
327
+
328
+ // Configurar listener para eventos de conexión
329
+ socket.on('connect', async () => {
330
+ connected = true;
331
+ isInitializing = false;
332
+ hideErrorOverlay();
333
+ console.log('✔️ Versa HMR: Socket conectado');
334
+ });
335
+
336
+ // Configurar listener para eventos de desconexión
337
+ socket.on('disconnect', () => {
338
+ connected = false;
339
+ isInitializing = false;
340
+ console.log('❌ Versa HMR: Socket desconectado, reintentando...');
341
+ // Lógica de reintentos para desconexión
342
+ initializationTimeout = setTimeout(() => {
343
+ if (!socket.connected && retries < maxRetries) {
344
+ initSocket(retries + 1);
345
+ } else if (!socket.connected) {
346
+ console.error(
347
+ `❌ Versa HMR: Socket no conectado después de ${maxRetries} reintentos tras desconexión.`,
348
+ );
349
+ showErrorOverlay(
350
+ 'HMR Desconectado',
351
+ 'No se pudo reconectar a BrowserSync después de múltiples reintentos.',
352
+ );
353
+ }
354
+ }, retryDelay);
355
+ });
356
+
357
+ // Configurar listener para recarga completa
358
+ socket.on('reloadFull', () => window.location.reload()); // Obtener la instancia de Vue con toda la lógica integrada
359
+ let vueInstance = await obtenerInstanciaVue();
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
+
382
+ // Configurar listener para HMR de componentes Vue
383
+ socket.on('HRMVue', async (/** @type {ComponentInfo} */ data) => {
384
+ try {
385
+ hideErrorOverlay();
386
+ vueInstance = window.__VUE_APP__ || vueInstance;
387
+ if (vueInstance) {
388
+ console.log('🔥 Preparando HMR para Vue...');
389
+ await reloadComponent(vueInstance, data);
390
+ } else {
391
+ console.log('🔄 Usando método fallback:', vueInstance);
392
+ }
393
+ } catch (error) {
394
+ console.error('❌ Error en HMR de Vue:', error);
395
+ reportErrorToServer('vue-hmr', error, {
396
+ component: data?.nameFile,
397
+ path: data?.normalizedPath,
398
+ });
399
+ showErrorOverlay(
400
+ 'Error en HMR de Vue',
401
+ error.message || String(error),
402
+ );
403
+ }
404
+ });
405
+
406
+ // Configurar listener para datos auxiliares de HMR
407
+ socket.on('HRMHelper', async data => {
408
+ console.log('🔄 HRMHelper recibido:', data);
409
+ console.log('📋 Archivo modificado:', data.filePath);
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
+
415
+ if (data?.strategy) {
416
+ switch (data.strategy) {
417
+ case 'self-accept': {
418
+ // El módulo acepta su propio reemplazo (import.meta.hot.accept)
419
+ try {
420
+ const timestamp = Date.now();
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}`);
436
+ return;
437
+ } catch (error) {
438
+ reportErrorToServer(
439
+ 'hmr-self-accept-failed',
440
+ error instanceof Error
441
+ ? error
442
+ : new Error(String(error)),
443
+ data,
444
+ );
445
+ }
446
+ break;
447
+ }
448
+ case 'propagate': {
449
+ // Re-importar el módulo con cache-bust y notificar al registry
450
+ try {
451
+ const timestamp = Date.now();
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
+ );
491
+ return;
492
+ } catch (error) {
493
+ reportErrorToServer(
494
+ 'hmr-propagate-failed',
495
+ error instanceof Error
496
+ ? error
497
+ : new Error(String(error)),
498
+ data,
499
+ );
500
+ // Fallback a full-reload en caso de error
501
+ window.location.reload();
502
+ return;
503
+ }
504
+ }
505
+ case 'full-reload':
506
+ default: {
507
+ window.location.reload();
508
+ return;
509
+ }
510
+ }
511
+ }
512
+
513
+ // Sistema inteligente de detección automática (como Vite/esbuild)
514
+ if (data.filePath && !data.libraryName && !data.libraryPath) {
515
+ console.log('🔍 Analizando estrategia HMR automática...');
516
+
517
+ // Detectar la estrategia de HMR apropiada
518
+ const hmrStrategy = await detectHMRStrategy(
519
+ data.filePath,
520
+ data.content,
521
+ );
522
+
523
+ console.log('📊 Estrategia detectada:', hmrStrategy.strategy);
524
+
525
+ // Ejecutar la estrategia apropiada
526
+ switch (hmrStrategy.strategy) {
527
+ case 'self-accept':
528
+ // El módulo puede reemplazarse a sí mismo
529
+ console.log('✨ Aplicando self-accept HMR');
530
+ try {
531
+ // Reimportar el módulo con cache busting
532
+ const timestamp = Date.now();
533
+ await import(`${data.filePath}?t=${timestamp}`);
534
+ console.log('✅ Módulo recargado exitosamente');
535
+ return;
536
+ } catch (error) {
537
+ console.error(
538
+ '❌ Error en self-accept HMR:',
539
+ error,
540
+ );
541
+ reportErrorToServer(
542
+ 'hmr-self-accept-failed',
543
+ error instanceof Error
544
+ ? error
545
+ : new Error(String(error)),
546
+ data,
547
+ );
548
+ }
549
+ break;
550
+
551
+ case 'propagate':
552
+ // Propagar la actualización: re-importar y notificar al registry
553
+ console.log(
554
+ '🔄 Propagando actualización a importadores',
555
+ );
556
+ try {
557
+ const timestamp = Date.now();
558
+ const newModule = await import(
559
+ `${data.filePath}?t=${timestamp}`
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
+ }
576
+ return;
577
+ } catch (error) {
578
+ console.error(
579
+ '❌ Error propagando actualización:',
580
+ error,
581
+ );
582
+ reportErrorToServer(
583
+ 'hmr-propagate-failed',
584
+ error instanceof Error
585
+ ? error
586
+ : new Error(String(error)),
587
+ data,
588
+ );
589
+ }
590
+ break;
591
+
592
+ case 'full-reload':
593
+ default:
594
+ // Recarga completa necesaria
595
+ console.log(
596
+ '🔄 Recarga completa requerida - módulo no soporta HMR',
597
+ );
598
+ reportErrorToServer(
599
+ 'hmr-full-reload',
600
+ new Error(
601
+ 'Módulo requiere recarga completa (no self-accept)',
602
+ ),
603
+ {
604
+ ...data,
605
+ strategy: hmrStrategy.strategy,
606
+ hint: 'Considera agregar import.meta.hot.accept() al módulo para HMR sin recarga',
607
+ },
608
+ );
609
+ window.location.reload();
610
+ return;
611
+ }
612
+ }
613
+
614
+ try {
615
+ // Intentar hacer hot reload de librería sin recarga completa
616
+ const success = await handleLibraryHotReload(data);
617
+ if (!success) {
618
+ console.warn(
619
+ '⚠️ Hot reload de librería falló, haciendo recarga completa',
620
+ );
621
+ reportErrorToServer(
622
+ 'hmr-helper-failed',
623
+ new Error('Hot reload returned false'),
624
+ data,
625
+ );
626
+ window.location.reload();
627
+ }
628
+ } catch (error) {
629
+ console.error('❌ Error en HRMHelper:', error);
630
+ reportErrorToServer(
631
+ 'hmr-helper-exception',
632
+ error instanceof Error ? error : new Error(String(error)),
633
+ data,
634
+ );
635
+ window.location.reload();
636
+ }
637
+ });
638
+
639
+ // Configurar listener para errores de socket
640
+ socket.on('error', err => {
641
+ console.error('❌ Versa HMR: Error en el socket:', err);
642
+ showErrorOverlay(
643
+ 'Error de Socket',
644
+ 'Se produjo un error en la conexión de BrowserSync.',
645
+ );
646
+ }); // Watchdog para verificar conexión inicial si el socket existe pero no está conectado
647
+ if (!connected) {
648
+ console.log(
649
+ `Versa HMR: Objeto socket encontrado, intentando conexión (Intento ${
650
+ retries + 1
651
+ }/${maxRetries})`,
652
+ );
653
+ initializationTimeout = setTimeout(() => {
654
+ if (!socket.connected && retries <= maxRetries) {
655
+ console.warn(
656
+ 'Versa HMR: Sin conexión de socket después del tiempo de espera inicial, reintentando initSocket...',
657
+ );
658
+ initSocket(retries + 1);
659
+ } else if (!socket.connected) {
660
+ isInitializing = false;
661
+ console.error(
662
+ `❌ Versa HMR: Socket aún no conectado después de ${maxRetries} intentos iniciales.`,
663
+ );
664
+ showErrorOverlay(
665
+ 'Falló HMR de BrowserSync',
666
+ 'No se pudo conectar al socket de BrowserSync después de intentos iniciales.',
667
+ );
668
+ }
669
+ }, 5000); // Timeout de 5s para el watchdog inicial
670
+ } else {
671
+ isInitializing = false;
672
+ }
673
+ } else {
674
+ // BrowserSync no está disponible, intentar reinicializar
675
+ console.warn(
676
+ `[HMR] Socket de BrowserSync no encontrado o BrowserSync no completamente inicializado. Reintentando initSocket... (${
677
+ retries + 1
678
+ }/${maxRetries})`,
679
+ );
680
+ if (retries < maxRetries) {
681
+ initializationTimeout = setTimeout(
682
+ () => initSocket(retries + 1),
683
+ retryDelay,
684
+ );
685
+ } else {
686
+ isInitializing = false;
687
+ console.error(
688
+ `❌ Versa HMR: Socket de BrowserSync no encontrado después de ${maxRetries} reintentos.`,
689
+ );
690
+ showErrorOverlay(
691
+ 'Falló HMR de BrowserSync',
692
+ 'Socket o cliente de BrowserSync no encontrado después de múltiples reintentos.',
693
+ );
694
+ }
695
+ }
696
+ }
697
+
698
+ // Solo ejecutar en ambiente de navegador (no en tests de Node.js)
699
+ if (
700
+ typeof window !== 'undefined' &&
701
+ typeof window.addEventListener === 'function'
702
+ ) {
703
+ // Capturar errores globales no manejados
704
+ window.addEventListener('error', event => {
705
+ if (
706
+ event.filename &&
707
+ (event.filename.includes('/hrm/') || event.filename.includes('HRM'))
708
+ ) {
709
+ reportErrorToServer(
710
+ 'uncaught-error',
711
+ event.error || new Error(event.message),
712
+ {
713
+ filename: event.filename,
714
+ lineno: event.lineno,
715
+ colno: event.colno,
716
+ },
717
+ );
718
+ }
719
+ });
720
+
721
+ // Capturar promesas rechazadas no manejadas
722
+ window.addEventListener('unhandledrejection', event => {
723
+ const error = event.reason;
724
+ if (
725
+ error &&
726
+ (error.stack?.includes('/hrm/') || error.message?.includes('HRM'))
727
+ ) {
728
+ reportErrorToServer('unhandled-rejection', error, {
729
+ promise: String(event.promise),
730
+ });
731
+ }
732
+ });
733
+
734
+ // Inicializar el sistema HMR al cargar el script
735
+ initSocket();
736
+ }