versacompiler 1.0.4 → 1.0.5

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.
@@ -126,6 +126,7 @@ export async function reloadComponent(
126
126
  ) {
127
127
  try {
128
128
  const baseUrl = window.location.href;
129
+ // console.log(relativePath);
129
130
  const newBaseUrl = new URL(baseUrl);
130
131
  const urlOrigin = `${newBaseUrl.origin}/${relativePath}`;
131
132
  const module = await import(`${urlOrigin}?t=${Date.now()}`);
@@ -193,13 +194,109 @@ export function debounce(func, waitFor) {
193
194
  return debounced;
194
195
  }
195
196
 
196
- export async function reloadJS(_relativePath) {
197
- location.reload();
197
+ export async function reloadJS(pathWithTimestamp) {
198
+ // Extraer la ruta base sin el timestamp
199
+ const pathParts = pathWithTimestamp.split('?');
200
+ const basePath = pathParts[0];
201
+
202
+ const contenidoArchivo = await fetch(pathWithTimestamp).then(res =>
203
+ res.text(),
204
+ );
205
+
206
+ // Verificar la marca para recarga completa
207
+ if (contenidoArchivo.startsWith('//versaHRM-reloadFILE')) {
208
+ console.log(
209
+ `[HMR] Marca //versaHRM-reloadFILE detectada en ${basePath}. Recargando página completa.`,
210
+ );
211
+ window.location.reload();
212
+ return; // Detener procesamiento adicional para este archivo
213
+ }
214
+
215
+ /* La lógica anterior para createApp/mount ya no es necesaria aquí, la marca la cubre.
216
+ if (
217
+ contenidoArchivo.includes('createApp') ||
218
+ contenidoArchivo.includes('.mount')
219
+ ) {
220
+ window.location.reload();
221
+ return;
222
+ }
223
+ */
224
+
225
+ // Verificar si tenemos una función de recarga registrada para este módulo
226
+ if (
227
+ window.__VERSA_HMR &&
228
+ window.__VERSA_HMR.modules &&
229
+ window.__VERSA_HMR.modules[basePath]
230
+ ) {
231
+ console.log(
232
+ `[HMR] Usando sistema HMR interno para recargar: ${basePath}`,
233
+ );
234
+ try {
235
+ const result = await window.__VERSA_HMR.modules[basePath]();
236
+ return result;
237
+ } catch (error) {
238
+ console.error(
239
+ `[HMR] Error al recargar el módulo usando sistema HMR interno:`,
240
+ error,
241
+ );
242
+ // Si falla el sistema interno, intentamos el enfoque tradicional
243
+ }
244
+ }
245
+
246
+ // Si no hay una función específica, usar la lógica existente
247
+ try {
248
+ console.log(`[HMR] Intentando re-importar JS: ${pathWithTimestamp}`);
249
+ // La URL ya está completa y lista para usar.
250
+ // El `import()` dinámico usa la URL base del script actual si la ruta es relativa,
251
+ // o la URL tal cual si es absoluta (comenzando con / o http/https).
252
+ // Como pathWithTimestamp comienza con '/', se resolverá desde la raíz del host.
253
+ const newModule = await import(pathWithTimestamp);
254
+ console.log(
255
+ `[HMR] Módulo JS ${pathWithTimestamp} re-importado exitosamente.`,
256
+ );
257
+
258
+ // Lógica de ejemplo: si el módulo exporta una función 'onHotUpdate' o 'init', llamarla.
259
+ // Esto es una convención que tus módulos JS tendrían que seguir.
260
+ if (newModule && typeof newModule.onHotUpdate === 'function') {
261
+ console.log(
262
+ `[HMR] Llamando a onHotUpdate() para el módulo ${pathWithTimestamp}`,
263
+ );
264
+ newModule.onHotUpdate();
265
+ } else if (newModule && typeof newModule.init === 'function') {
266
+ // Alternativamente, una función 'init' si es más genérico
267
+ console.log(
268
+ `[HMR] Llamando a init() para el módulo ${pathWithTimestamp}`,
269
+ );
270
+ newModule.init();
271
+ } else if (newModule && typeof newModule.main === 'function') {
272
+ // O una función 'main'
273
+ console.log(
274
+ `[HMR] Llamando a main() para el módulo ${pathWithTimestamp}`,
275
+ );
276
+ newModule.main();
277
+ }
278
+ // Si no hay una función específica, la simple re-importación podría ser suficiente
279
+ // si el módulo se auto-ejecuta (ej. añade event listeners, modifica el DOM globalmente).
280
+ // ¡CUIDADO con efectos secundarios duplicados en este caso!
281
+
282
+ return null; // Indicar éxito
283
+ } catch (error) {
284
+ console.error(
285
+ `[HMR] Error al re-importar el módulo JS ${pathWithTimestamp}:`,
286
+ error,
287
+ );
288
+ // Aquí podrías decidir si mostrar un error en el overlay o, como último recurso, recargar.
289
+ // Por ahora, solo retornamos false para que el llamador (vueLoader.js) decida.
290
+ return {
291
+ msg: `Error al re-importar el módulo JS ${pathWithTimestamp}:`,
292
+ error,
293
+ }; // Indicar fallo
294
+ }
198
295
  }
199
296
 
200
297
  export function socketReload(app) {
201
298
  if (window.___browserSync___?.socket) {
202
- const socket = window.___browserSync___.socket;
299
+ // const socket = window.___browserSync___.socket;
203
300
  // Configura el observer para actualizar el árbol de componentes en cada mutación relevante
204
301
  if (app && app._container) {
205
302
  currentComponentTree = buildComponentTree(app._instance);
@@ -209,27 +306,27 @@ export function socketReload(app) {
209
306
  }
210
307
  });
211
308
  }
212
- socket.on('vue:update', data => {
213
- console.log('pasa');
214
- if (document.querySelector('#versa-hmr-error-overlay')) {
215
- window.location.reload();
216
- return;
217
- }
309
+ // socket.on('vue:update', data => {
310
+ // console.log('pasa');
311
+ // if (document.querySelector('#versa-hmr-error-overlay')) {
312
+ // window.location.reload();
313
+ // return;
314
+ // }
218
315
 
219
- const { component, relativePath, extension, type, timestamp } =
220
- data;
221
- if (extension === 'vue') {
222
- reloadComponent(
223
- app,
224
- component,
225
- `/${relativePath}`,
226
- type,
227
- extension,
228
- );
229
- } else {
230
- reloadJS(`/${relativePath}?t=${timestamp}`);
231
- }
232
- });
316
+ // const { component, relativePath, extension, type, timestamp } =
317
+ // data;
318
+ // if (extension === 'vue') {
319
+ // reloadComponent(
320
+ // app,
321
+ // component,
322
+ // `/${relativePath}`,
323
+ // type,
324
+ // extension,
325
+ // );
326
+ // } else {
327
+ // reloadJS(`/${relativePath}?t=${timestamp}`);
328
+ // }
329
+ // });
233
330
  } else {
234
331
  setTimeout(() => {
235
332
  window.location.reload();
package/dist/index.js CHANGED
@@ -25,6 +25,7 @@ import { linter } from './services/linter.js';
25
25
  import { minifyJS } from './services/minify.js';
26
26
  import { preCompileTS } from './services/typescript.js';
27
27
  import { preCompileVue } from './services/vuejs.js';
28
+ import { transformModuleWithAcorn } from './utils/transformWithAcorn.js';
28
29
 
29
30
  import { addImportEndJs, mapRuta, showTimingForHumans } from './utils/utils.js';
30
31
 
@@ -340,6 +341,10 @@ const estandarizaData = async data => {
340
341
  data = await replaceAliasImportsAsync(data);
341
342
  data = await addImportEndJs(data);
342
343
 
344
+ if (!isProd) {
345
+ data = await transformModuleWithAcorn(data);
346
+ }
347
+
343
348
  return data;
344
349
  };
345
350
 
@@ -348,26 +353,38 @@ const estandarizaData = async data => {
348
353
  * @param {string} source - La ruta del archivo fuente.
349
354
  * @param {string} destination - La ruta del archivo de destino.
350
355
  *
351
- * @returns {Promise<void>} - Una promesa que se resuelve después de la compilación.
356
+ * @returns {Promise<Object>} - Un objeto con información sobre la compilación.
352
357
  */
353
358
  const compileJS = async (source, destination) => {
354
359
  try {
355
- const startTime = Date.now(); // optener la hora actual
360
+ const startTime = Date.now();
361
+ const originalExtension = source.split('.').pop();
362
+ const baseName = path.basename(destination, path.extname(destination));
363
+ const finalFileName = baseName.endsWith('.js')
364
+ ? baseName.slice(0, -3)
365
+ : baseName;
356
366
 
357
- const filename = path.basename(source);
358
- await log(chalk.blue(`🪄 :start compilation`));
367
+ await log(chalk.blue(`🪄 :Iniciando compilación de ${source}`));
359
368
 
360
369
  let data = await readFile(source, 'utf-8');
361
- if (!data) {
362
- await error(chalk.yellow('⚠️ :Archivo vacío\n'));
363
- return;
370
+ if (!data || data.trim().length === 0) {
371
+ await error(
372
+ chalk.yellow(
373
+ `⚠️ :Archivo fuente ${source} está vacío. No se procesará.`,
374
+ ),
375
+ );
376
+ return {
377
+ contentWasWritten: false,
378
+ extension: originalExtension,
379
+ normalizedPath: destination,
380
+ fileName: finalFileName,
381
+ };
364
382
  }
365
383
 
366
- const extension = source.split('.').pop();
367
384
  let resultVue = null;
368
- if (extension === 'vue') {
385
+ if (originalExtension === 'vue') {
369
386
  vueFiles++;
370
- await log(chalk.green(`💚 :Pre Compile VUE`));
387
+ await log(chalk.green(`💚 :Precompilando VUE: ${source}`));
371
388
  resultVue = await preCompileVue(data, source, isProd);
372
389
  data = resultVue.data;
373
390
  if (resultVue.error !== null) {
@@ -379,20 +396,25 @@ const compileJS = async (source, destination) => {
379
396
  });
380
397
  await error(
381
398
  chalk.red(
382
- `🚩 :Error durante la compilación Vue :${resultVue.error}\n`,
399
+ `🚩 :Error durante la compilación Vue para ${source} :${resultVue.error}\n`,
383
400
  ),
384
401
  );
385
- return;
402
+ return {
403
+ contentWasWritten: false,
404
+ extension: originalExtension,
405
+ normalizedPath: destination,
406
+ fileName: finalFileName,
407
+ };
386
408
  }
387
409
  destination = destination.replace('.vue', '.js');
388
410
  }
389
411
 
390
- if (extension === 'ts' || resultVue?.lang === 'ts') {
412
+ if (originalExtension === 'ts' || resultVue?.lang === 'ts') {
391
413
  tsFiles++;
392
- await log(chalk.blue(`🔄️ :Pre Compilando TS`));
414
+ await log(chalk.blue(`🔄️ :Precompilando TS: ${source}`));
393
415
  const Resultdata = await preCompileTS(
394
416
  data,
395
- filename,
417
+ path.basename(source),
396
418
  PATH_CONFIG_FILE,
397
419
  );
398
420
  if (Resultdata.error !== null) {
@@ -404,10 +426,15 @@ const compileJS = async (source, destination) => {
404
426
  });
405
427
  await error(
406
428
  chalk.red(
407
- `🚩 :Error durante la compilación TS: ${Resultdata.error}\n`,
429
+ `🚩 :Error durante la compilación TS para ${source}: ${Resultdata.error}\n`,
408
430
  ),
409
431
  );
410
- return;
432
+ return {
433
+ contentWasWritten: false,
434
+ extension: originalExtension,
435
+ normalizedPath: destination,
436
+ fileName: finalFileName,
437
+ };
411
438
  }
412
439
  destination = destination.replace('.ts', '.js');
413
440
  data = Resultdata.data;
@@ -415,9 +442,11 @@ const compileJS = async (source, destination) => {
415
442
 
416
443
  data = await estandarizaData(data);
417
444
 
418
- // await writeFile(`${destination}-temp.js`, data, 'utf-8');
445
+ // const destinationDir = path.dirname(destination);
446
+ // await mkdir(destinationDir, { recursive: true });
447
+ // await writeFile(destination, data, 'utf-8');
419
448
 
420
- await log(chalk.green(`🔍 :Validando Sintaxis`));
449
+ await log(chalk.green(`🔍 :Validando Sintaxis para ${source}`));
421
450
  const resultAcorn = await checkSintaxysAcorn(data);
422
451
  if (resultAcorn.error !== null) {
423
452
  errorFiles++;
@@ -426,30 +455,44 @@ const compileJS = async (source, destination) => {
426
455
  error: resultAcorn.error.message,
427
456
  proceso: 'Validación Sintaxis',
428
457
  });
429
- return;
458
+ await error(
459
+ chalk.red(
460
+ `🚩 :Error de sintaxis Acorn para ${source}: ${resultAcorn.error.message}\n`,
461
+ ),
462
+ );
463
+ return {
464
+ contentWasWritten: false,
465
+ extension: originalExtension,
466
+ normalizedPath: destination,
467
+ fileName: finalFileName,
468
+ };
430
469
  }
431
470
  acornFiles++;
432
471
 
433
472
  let result = null;
434
473
  if (isProd) {
435
- await log(chalk.blue(`🤖 :minifying`));
436
- result = await minifyJS(data, filename, isProd);
474
+ await log(chalk.blue(`🤖 :Minificando ${source}`));
475
+ result = await minifyJS(data, path.basename(source), isProd);
437
476
  } else {
438
477
  result = { code: data };
439
478
  }
440
- await log(chalk.green(`📝 :Escribiendo ${destination}`));
479
+ await log(chalk.green(`📝 :Intentando escribir ${destination}`));
441
480
 
442
- if (result.code.length === 0) {
481
+ if (!result.code || result.code.trim().length === 0) {
443
482
  await error(
444
483
  chalk.yellow(
445
- '⚠️ :Warning al compilar JS: El archivo está vacío\n',
484
+ `⚠️ :Advertencia al compilar JS para ${source}: El archivo resultante está vacío. No se escribirá en disco.\n`,
446
485
  ),
447
486
  );
448
- await unlink(destination);
487
+ return {
488
+ contentWasWritten: false,
489
+ extension: originalExtension,
490
+ normalizedPath: destination,
491
+ fileName: finalFileName,
492
+ };
449
493
  } else {
450
494
  if (!isProd) {
451
495
  result.code = result.code.replaceAll('*/export', '*/\nexport');
452
- result.code = result.code.replaceAll('*/export', '*/\nexport');
453
496
  }
454
497
  const destinationDir = path.dirname(destination);
455
498
  await mkdir(destinationDir, { recursive: true });
@@ -458,24 +501,128 @@ const compileJS = async (source, destination) => {
458
501
  const endTime = Date.now();
459
502
  const elapsedTime = showTimingForHumans(endTime - startTime);
460
503
  await log(
461
- chalk.gray(`✅ :Compilación exitosa (${elapsedTime}) \n`),
504
+ chalk.gray(
505
+ `✅ :Compilación exitosa para ${finalFileName} (${elapsedTime}) \n`,
506
+ ),
462
507
  );
463
508
  successfulFiles++;
509
+ return {
510
+ contentWasWritten: true,
511
+ extension: originalExtension,
512
+ normalizedPath: destination,
513
+ fileName: finalFileName,
514
+ };
464
515
  }
465
516
  } catch (errora) {
466
517
  errorFiles++;
518
+ const ext = source.split('.').pop() || 'unknown';
519
+ const fName = path.basename(source, path.extname(source));
467
520
  errorList.push({
468
521
  file: source,
469
522
  error: errora.message,
470
- proceso: 'Compilación JS',
523
+ proceso: 'Compilación JS (Catch General)',
471
524
  });
472
525
  await error(
473
- chalk.red(`🚩 :Error durante la compilación JS: ${errora}\n`),
474
- errora,
526
+ chalk.red(
527
+ `🚩 :Error catastrófico durante la compilación JS para ${source}: ${errora.message}\n`,
528
+ ),
529
+ errora.stack,
530
+ );
531
+ return {
532
+ contentWasWritten: false,
533
+ extension: ext,
534
+ normalizedPath: destination || source,
535
+ fileName: fName,
536
+ };
537
+ }
538
+ };
539
+
540
+ /**
541
+ * Compila un archivo dado su ruta.
542
+ * @param {string} path - La ruta del archivo a compilar.
543
+ * @returns {Promise<Object>} - Un objeto con información sobre la compilación.
544
+ */
545
+ const compile = async filePath => {
546
+ if (!filePath || typeof filePath !== 'string') {
547
+ console.error(
548
+ chalk.red('⚠️ :Ruta inválida proporcionada a compile():', filePath),
475
549
  );
550
+ return {
551
+ contentWasWritten: false,
552
+ extension: null,
553
+ normalizedPath: filePath,
554
+ fileName: null,
555
+ };
556
+ }
557
+ if (filePath.includes('.d.ts')) {
558
+ return {
559
+ contentWasWritten: false,
560
+ extension: 'd.ts',
561
+ normalizedPath: filePath,
562
+ fileName: path.basename(filePath),
563
+ };
564
+ }
565
+
566
+ const normalizedPathSource = path.normalize(filePath).replace(/\\/g, '/');
567
+ const sourceForDist = normalizedPathSource.startsWith('./')
568
+ ? normalizedPathSource
569
+ : `./${normalizedPathSource}`;
570
+ const outputPath = sourceForDist.replace(PATH_SOURCE, PATH_DIST);
571
+ const finalOutputJsPath = outputPath.replace(/\.(vue|ts)$/, '.js');
572
+
573
+ console.log(
574
+ chalk.green(`🔜 :Fuente para compilar: ${normalizedPathSource}`),
575
+ );
576
+ console.log(chalk.green(`🔚 :Destino potencial: ${finalOutputJsPath}`));
577
+
578
+ if (outputPath) {
579
+ return await compileJS(normalizedPathSource, finalOutputJsPath);
580
+ } else {
581
+ const ext = normalizedPathSource.split('.').pop() || null;
582
+ const fName = path.basename(
583
+ normalizedPathSource,
584
+ path.extname(normalizedPathSource),
585
+ );
586
+ await log(
587
+ chalk.yellow(
588
+ `⚠️ :Tipo de archivo no reconocido o ruta de salida no determinada para: ${normalizedPathSource}, extensión: ${ext}`,
589
+ ),
590
+ );
591
+ return {
592
+ contentWasWritten: false,
593
+ extension: ext,
594
+ normalizedPath: finalOutputJsPath,
595
+ fileName: fName,
596
+ };
476
597
  }
477
598
  };
478
599
 
600
+ /**
601
+ * Emite cambios a través de BrowserSync.
602
+ * @param {Object} bs - Instancia de BrowserSync.
603
+ * @param {string} extension - Extensión del archivo.
604
+ * @param {string} normalizedPath - Ruta normalizada del archivo.
605
+ * @param {string} fileName - Nombre del archivo.
606
+ * @param {string} type - Tipo de cambio (add, change, delete).
607
+ */
608
+ const emitirCambios = async (bs, extension, normalizedPath, fileName, type) => {
609
+ const serverRelativePath = path
610
+ .normalize(normalizedPath)
611
+ .replace(/^\\|^\//, '')
612
+ .replace(/\\/g, '/');
613
+
614
+ bs.sockets.emit('vue:update', {
615
+ component: fileName,
616
+ timestamp: Date.now(),
617
+ relativePath: serverRelativePath,
618
+ extension,
619
+ type,
620
+ });
621
+ console.log(
622
+ `📡 : Emitiendo evento 'vue:update' para ${fileName} (${type}) -> ${serverRelativePath} \n`,
623
+ );
624
+ };
625
+
479
626
  async function generateTailwindCSS(_filePath = null) {
480
627
  if (!tailwindcss) {
481
628
  return;
@@ -496,42 +643,6 @@ async function generateTailwindCSS(_filePath = null) {
496
643
  });
497
644
  }
498
645
 
499
- /**
500
- * Compila un archivo dado su ruta.
501
- * @param {string} path - La ruta del archivo a compilar.
502
- */
503
- const compile = async filePath => {
504
- if (!filePath || typeof filePath !== 'string') {
505
- console.error(chalk.red('⚠️ :Ruta inválida:', filePath));
506
- return;
507
- }
508
- if (filePath.includes('.d.ts')) {
509
- return;
510
- }
511
- const normalizedPath = path.normalize(filePath).replace(/\\/g, '/'); // Normalizar la ruta para que use barras inclinadas hacia adelante
512
- const filePathForReplate = `./${normalizedPath}`;
513
- const outputPath = filePathForReplate.replace(PATH_SOURCE, PATH_DIST);
514
- const outFileJs = outputPath.replace('.ts', '.js').replace('.vue', '.js');
515
-
516
- console.log(chalk.green(`🔜 :Source ${filePathForReplate}`));
517
- console.log(chalk.green(`🔚 :destination ${outFileJs}`));
518
-
519
- const extension = normalizedPath.split('.').pop();
520
- //sólo el filename sin extesion
521
- const fileName = path
522
- .basename(normalizedPath)
523
- .replace('.vue', '')
524
- .replace('.ts', '')
525
- .replace('.js', '');
526
-
527
- if (outputPath) {
528
- await compileJS(normalizedPath, outputPath);
529
- } else {
530
- await log(chalk.yellow(`⚠️ :Tipo no reconocido: ${extension}`));
531
- }
532
- return { extension, normalizedPath: path.normalize(outFileJs), fileName };
533
- };
534
-
535
646
  /**
536
647
  * Compila todos los archivos en los directorios de origen.
537
648
  */
@@ -616,17 +727,6 @@ const compileAll = async () => {
616
727
  }
617
728
  };
618
729
 
619
- const emitirCambios = async (bs, extension, normalizedPath, fileName, type) => {
620
- bs.sockets.emit('vue:update', {
621
- component: fileName,
622
- timestamp: Date.now(),
623
- relativePath: normalizedPath,
624
- extension,
625
- type,
626
- });
627
- console.log(`📡 : Emitiendo evento 'vue:update' para ${fileName} \n`);
628
- };
629
-
630
730
  /**
631
731
  * Inicializa el proceso de compilación y observación de archivos.
632
732
  */
@@ -652,19 +752,47 @@ const initChokidar = async () => {
652
752
  // Evento cuando se añade un archivo
653
753
  watcher.on('add', async filePath => {
654
754
  await generateTailwindCSS(filePath);
655
- const { extension, normalizedPath, fileName } = await compile(
755
+ const result = await compile(
656
756
  path.normalize(filePath).replace(/\\/g, '/'),
657
757
  );
658
- emitirCambios(bs, extension, normalizedPath, fileName, 'add');
758
+ if (result && result.contentWasWritten) {
759
+ emitirCambios(
760
+ bs,
761
+ result.extension,
762
+ result.normalizedPath,
763
+ result.fileName,
764
+ 'add',
765
+ );
766
+ } else {
767
+ console.log(
768
+ chalk.yellow(
769
+ `[HMR] No se emite evento para archivo nuevo no escrito o vacío: ${filePath}. Razón: contentWasWritten es false o resultado inválido.`,
770
+ ),
771
+ );
772
+ }
659
773
  });
660
774
 
661
775
  // Evento cuando se modifica un archivo
662
776
  watcher.on('change', async filePath => {
663
777
  await generateTailwindCSS(filePath);
664
- const { extension, normalizedPath, fileName } = await compile(
778
+ const result = await compile(
665
779
  path.normalize(filePath).replace(/\\/g, '/'),
666
780
  );
667
- emitirCambios(bs, extension, normalizedPath, fileName, 'change');
781
+ if (result && result.contentWasWritten) {
782
+ emitirCambios(
783
+ bs,
784
+ result.extension,
785
+ result.normalizedPath,
786
+ result.fileName,
787
+ 'change',
788
+ );
789
+ } else {
790
+ console.log(
791
+ chalk.yellow(
792
+ `[HMR] No se emite evento para archivo modificado a vacío, con errores, o no escrito: ${filePath}. Razón: contentWasWritten es false o resultado inválido.`,
793
+ ),
794
+ );
795
+ }
668
796
  });
669
797
 
670
798
  // Evento cuando se elimina un archivo
@@ -749,6 +877,15 @@ const initChokidar = async () => {
749
877
  res.setHeader('Access-Control-Allow-Credentials', 'true');
750
878
  res.setHeader('Access-Control-Max-Age', '3600');
751
879
 
880
+ if (req.url.endsWith('.js')) {
881
+ res.setHeader(
882
+ 'Cache-Control',
883
+ 'no-cache, no-store, must-revalidate',
884
+ );
885
+ res.setHeader('Pragma', 'no-cache');
886
+ res.setHeader('Expires', '0');
887
+ }
888
+
752
889
  //para redigir a la ubicación correcta
753
890
  if (req.url === '/__versa/vueLoader.js') {
754
891
  // Busca vueLoader.js en la carpeta de salida configurada
@@ -1,5 +1,4 @@
1
- let socketReload,
2
- getInstancia,
1
+ let getInstancia,
3
2
  getVueInstance,
4
3
  showErrorOverlay,
5
4
  hideErrorOverlay,
@@ -12,7 +11,6 @@ let socketReload,
12
11
  // Importa todas las dependencias HMR necesarias aquí
13
12
  // CORREGIR RUTA DE IMPORTACIÓN PARA devMode.js
14
13
  const devModeModule = await import('./hrm/devMode.js');
15
- socketReload = devModeModule.socketReload;
16
14
  reloadComponent = devModeModule.reloadComponent;
17
15
  reloadJS = devModeModule.reloadJS;
18
16
 
@@ -161,20 +159,20 @@ const initSocket = async (retries = 0) => {
161
159
  '[HMR] hideErrorOverlay no es una función al momento de la conexión',
162
160
  );
163
161
  }
164
- const vueAppInstance = await waitForVueInstance();
165
- if (vueAppInstance && vueAppInstance._instance) {
166
- if (typeof socketReload === 'function') {
167
- socketReload(vueAppInstance);
168
- } else {
169
- console.warn(
170
- '[HMR] socketReload no es una función al momento de la conexión',
171
- );
172
- }
173
- } else {
174
- console.error(
175
- '❌ Versa HMR: Instancia de Vue no encontrada después de la conexión del socket',
176
- );
177
- }
162
+ await waitForVueInstance();
163
+ // if (vueAppInstance && vueAppInstance._instance) {
164
+ // if (typeof socketReload === 'function') {
165
+ // socketReload(vueAppInstance);
166
+ // } else {
167
+ // console.warn(
168
+ // '[HMR] socketReload no es una función al momento de la conexión',
169
+ // );
170
+ // }
171
+ // } else {
172
+ // console.error(
173
+ // '❌ Versa HMR: Instancia de Vue no encontrada después de la conexión del socket',
174
+ // );
175
+ // }
178
176
  console.log('✔️ Versa HMR: Socket conectado');
179
177
  });
180
178
 
@@ -238,8 +236,9 @@ const initSocket = async (retries = 0) => {
238
236
  }
239
237
 
240
238
  try {
239
+ let result;
241
240
  if (extension === 'vue') {
242
- const result = await reloadComponent(
241
+ result = await reloadComponent(
243
242
  appInstance,
244
243
  component,
245
244
  `${relativePath}`,
@@ -251,7 +250,10 @@ const initSocket = async (retries = 0) => {
251
250
  }
252
251
  } else {
253
252
  // Asumiendo que reloadJS existe
254
- await reloadJS(`/${relativePath}?t=${timestamp}`);
253
+ result = await reloadJS(`/${relativePath}?t=${timestamp}`);
254
+ if (result && result.msg) {
255
+ throw new Error(result.msg);
256
+ }
255
257
  }
256
258
  } catch (hmrError) {
257
259
  const errorMsg = `HMR falló para ${relativePath}`;
@@ -0,0 +1,316 @@
1
+ import * as acorn from 'acorn';
2
+
3
+ function generateHMRBlock(imports) {
4
+ let moduleRegistrationBlocks = [];
5
+
6
+ imports.default.forEach(imp => {
7
+ moduleRegistrationBlocks.push(
8
+ `window.__VERSA_HMR.modules['${imp.filePath}'] = async () => {\n` +
9
+ ` try {\n` +
10
+ ` ${imp.varName} = (await importWithTimestamp('${imp.filePath}')).default;\n` +
11
+ ` console.log('[HMR] Módulo ${imp.filePath} (default) recargado');\n` +
12
+ ` return true;\n` +
13
+ ` } catch (e) {\n` +
14
+ ` console.error('[HMR] Error recargando ${imp.filePath}', e);\n` +
15
+ ` return false;\n` +
16
+ ` }\n` +
17
+ ` };`,
18
+ );
19
+ });
20
+
21
+ imports.namespace.forEach(imp => {
22
+ moduleRegistrationBlocks.push(
23
+ `window.__VERSA_HMR.modules['${imp.filePath}'] = async () => {\n` +
24
+ ` try {\n` +
25
+ ` ${imp.varName} = await importWithTimestamp('${imp.filePath}');\n` +
26
+ ` console.log('[HMR] Módulo ${imp.filePath} (namespace) recargado');\n` +
27
+ ` return true;\n` +
28
+ ` } catch (e) {\n` +
29
+ ` console.error('[HMR] Error recargando ${imp.filePath}', e);\n` +
30
+ ` return false;\n` +
31
+ ` }\n` +
32
+ ` };`,
33
+ );
34
+ });
35
+
36
+ imports.named.forEach(imp => {
37
+ moduleRegistrationBlocks.push(
38
+ `window.__VERSA_HMR.modules['${imp.filePath}'] = async () => {\n` +
39
+ ` try {\n` +
40
+ ` ({ ${imp.namedExports} } = await importWithTimestamp('${imp.filePath}'));\n` +
41
+ ` console.log('[HMR] Módulo ${imp.filePath} (named) recargado');\n` +
42
+ ` return true;\n` +
43
+ ` } catch (e) {\n` +
44
+ ` console.error('[HMR] Error recargando ${imp.filePath}', e);\n` +
45
+ ` return false;\n` +
46
+ ` }\n` +
47
+ ` };`,
48
+ );
49
+ });
50
+
51
+ if (moduleRegistrationBlocks.length === 0) return '';
52
+
53
+ // Se incluye la definición de importWithTimestamp y la inicialización de __VERSA_HMR
54
+ // solo si hay módulos que registrar.
55
+ return `
56
+ const importWithTimestamp = (path) => import(path);
57
+ window.__VERSA_HMR = window.__VERSA_HMR || {};
58
+ window.__VERSA_HMR.modules = window.__VERSA_HMR.modules || {};
59
+ ${moduleRegistrationBlocks.join('\n ')}`;
60
+ }
61
+
62
+ // Función auxiliar para obtener nombres de _resolveComponent
63
+ const getResolvedComponents = codeString => {
64
+ const resolved = new Set();
65
+ const resolveComponentRegex =
66
+ /_resolveComponent\s*\(\s*["']([^"']+)["']\s*\)/g;
67
+ let match;
68
+ while ((match = resolveComponentRegex.exec(codeString)) !== null) {
69
+ resolved.add(match[1]);
70
+ }
71
+ return resolved;
72
+ };
73
+
74
+ /**
75
+ * Transforma código fuente JS/TS usando Acorn AST.
76
+ * Reconstruye el código separando imports, exports, funciones y bloques ejecutables.
77
+ * - Convierte imports estáticos en dinámicos
78
+ * - Preserva exports y estructura
79
+ * - Inserta bloque HMR solo si hay defineComponent y imports dinámicos
80
+ *
81
+ * @param {string} code - El código fuente a analizar.
82
+ * @returns {string} - El nuevo código transformado.
83
+ *
84
+ * Ejemplo de uso:
85
+ * const nuevoCodigo = transformModuleWithAcorn(codigoFuente);
86
+ */
87
+ export function transformModuleWithAcorn(code) {
88
+ const ast = acorn.parse(code, {
89
+ sourceType: 'module',
90
+ ecmaVersion: 'latest',
91
+ locations: true,
92
+ onComment: [],
93
+ });
94
+
95
+ const getCode = node => code.slice(node.start, node.end);
96
+ const resolvedComponents = getResolvedComponents(code);
97
+ const isFileAComponent = code.includes('defineComponent'); // Detectar si el archivo es un componente Vue
98
+ const isVueInitializationFile =
99
+ code.includes('mount') || code.includes('createApp'); // Detectar si es un archivo de inicialización de Vue
100
+
101
+ // Heurística para isExternalImport (debe estar definida antes de usarse en isCoreDefinitionFile)
102
+ const isExternalImport = src => {
103
+ if (src.startsWith('vue') || src.startsWith('react')) {
104
+ return true;
105
+ }
106
+ if (!src.endsWith('.js')) {
107
+ return true;
108
+ }
109
+ return false;
110
+ };
111
+
112
+ // Nueva detección generalizada para archivos de definición central
113
+ let isCoreDefinitionFile = false;
114
+ try {
115
+ if (!isFileAComponent && !isVueInitializationFile) {
116
+ let importExportNodeCount = 0;
117
+ let hasLocalJsImport = false;
118
+ let totalTopLevelNodes = 0;
119
+
120
+ if (ast.body && ast.body.length > 0) {
121
+ totalTopLevelNodes = ast.body.length;
122
+ for (const node of ast.body) {
123
+ if (node.type === 'ImportDeclaration') {
124
+ importExportNodeCount++;
125
+ const sourceValue = node.source.value;
126
+ if (
127
+ sourceValue.endsWith('.js') &&
128
+ !isExternalImport(sourceValue)
129
+ ) {
130
+ hasLocalJsImport = true;
131
+ }
132
+ } else if (
133
+ node.type === 'ExportNamedDeclaration' ||
134
+ node.type === 'ExportDefaultDeclaration'
135
+ ) {
136
+ importExportNodeCount++;
137
+ }
138
+ }
139
+
140
+ if (hasLocalJsImport && totalTopLevelNodes >= 2) {
141
+ // Mínimo 2 nodos para aplicar ratio
142
+ const ratio = importExportNodeCount / totalTopLevelNodes;
143
+ if (ratio >= 0.7) {
144
+ // Umbral ajustable (70%)
145
+ isCoreDefinitionFile = true;
146
+ }
147
+ }
148
+ }
149
+ }
150
+ } catch (e) {
151
+ console.error(
152
+ 'Error during isCoreDefinitionFile (ratio-based) check:',
153
+ e,
154
+ );
155
+ }
156
+
157
+ let nonDynamicImportStatements = [];
158
+ let hmrImports = { default: [], namespace: [], named: [] };
159
+ let importVarDecls = new Set();
160
+
161
+ // Primera pasada: clasificar imports
162
+ ast.body.forEach(node => {
163
+ if (node.type === 'ImportDeclaration') {
164
+ const sourceValue = node.source.value;
165
+ const importedLocalNames = node.specifiers.map(
166
+ spec => spec.local.name,
167
+ );
168
+
169
+ let isResolvedCompImport = false;
170
+ for (const localName of importedLocalNames) {
171
+ if (resolvedComponents.has(localName)) {
172
+ isResolvedCompImport = true;
173
+ break;
174
+ }
175
+ }
176
+
177
+ // Condición actualizada para mantener el import estático
178
+ if (
179
+ isFileAComponent ||
180
+ isVueInitializationFile ||
181
+ isCoreDefinitionFile || // Usando la nueva heurística de ratio
182
+ isExternalImport(sourceValue) ||
183
+ isResolvedCompImport
184
+ ) {
185
+ nonDynamicImportStatements.push(getCode(node));
186
+ } else {
187
+ // .js local, no es componente resuelto, y el archivo no cumple ninguna de las condiciones de exclusión -> transformar
188
+ node.specifiers.forEach(spec => {
189
+ const localName = spec.local.name;
190
+ importVarDecls.add(`let ${localName};`); // Declarar variable
191
+
192
+ if (spec.type === 'ImportDefaultSpecifier') {
193
+ hmrImports.default.push({
194
+ varName: localName,
195
+ filePath: sourceValue,
196
+ });
197
+ } else if (spec.type === 'ImportNamespaceSpecifier') {
198
+ hmrImports.namespace.push({
199
+ varName: localName,
200
+ filePath: sourceValue,
201
+ });
202
+ } else if (spec.type === 'ImportSpecifier') {
203
+ let namedEntry = hmrImports.named.find(
204
+ e => e.filePath === sourceValue,
205
+ );
206
+ if (!namedEntry) {
207
+ namedEntry = {
208
+ filePath: sourceValue,
209
+ namedExports: [],
210
+ variables: [],
211
+ };
212
+ hmrImports.named.push(namedEntry);
213
+ }
214
+ const exportString =
215
+ spec.imported.name === localName
216
+ ? localName
217
+ : `${spec.imported.name} as ${localName}`;
218
+ if (!namedEntry.namedExports.includes(exportString)) {
219
+ namedEntry.namedExports.push(exportString);
220
+ }
221
+ if (!namedEntry.variables.includes(localName)) {
222
+ namedEntry.variables.push(localName);
223
+ }
224
+ }
225
+ });
226
+ }
227
+ }
228
+ });
229
+
230
+ // Construir dynamicImportInitialLoadLines a partir de hmrImports consolidados
231
+ let dynamicImportInitialLoadLines = [];
232
+ hmrImports.default.forEach(imp => {
233
+ dynamicImportInitialLoadLines.push(
234
+ `${imp.varName} = (await import('${imp.filePath}')).default;`,
235
+ );
236
+ });
237
+ hmrImports.namespace.forEach(imp => {
238
+ dynamicImportInitialLoadLines.push(
239
+ `${imp.varName} = await import('${imp.filePath}');`,
240
+ );
241
+ });
242
+ hmrImports.named.forEach(imp => {
243
+ if (imp.namedExports.length > 0) {
244
+ dynamicImportInitialLoadLines.push(
245
+ `({ ${imp.namedExports.join(', ')} } = await import('${imp.filePath}'));`,
246
+ );
247
+ }
248
+ });
249
+
250
+ let exportStatements = [];
251
+ ast.body.forEach(node => {
252
+ if (
253
+ node.type === 'ExportNamedDeclaration' ||
254
+ node.type === 'ExportDefaultDeclaration'
255
+ ) {
256
+ exportStatements.push(getCode(node));
257
+ }
258
+ });
259
+
260
+ const bodyNodes = ast.body.filter(
261
+ n =>
262
+ n.type !== 'ImportDeclaration' &&
263
+ n.type !== 'ExportNamedDeclaration' &&
264
+ n.type !== 'ExportDefaultDeclaration',
265
+ );
266
+ const bodyNodesCode = bodyNodes.map(getCode).join('\n');
267
+
268
+ let result = nonDynamicImportStatements.join('\n');
269
+ if (importVarDecls.size > 0) {
270
+ result += '\n' + Array.from(importVarDecls).join('\n') + '\n';
271
+ }
272
+
273
+ const hasDynamicImports =
274
+ hmrImports.default.length > 0 ||
275
+ hmrImports.namespace.length > 0 ||
276
+ hmrImports.named.some(e => e.namedExports.length > 0);
277
+
278
+ if (hasDynamicImports) {
279
+ let hmrBlock = hasDynamicImports ? generateHMRBlock(hmrImports) : '';
280
+ const indentedHMRBlock = hmrBlock
281
+ .split('\n')
282
+ .map(line => ' ' + line)
283
+ .join('\n')
284
+ .trimStart();
285
+
286
+ // Indentar todo el bodyNodesCode si se mueve al IIFE
287
+ const indentedBodyNodesCode = bodyNodesCode
288
+ .split('\n')
289
+ .map(line => ' ' + line)
290
+ .join('\n')
291
+ .trimStart();
292
+
293
+ result += `\n(async () => {\n ${dynamicImportInitialLoadLines.join('\n ')}`;
294
+ if (indentedHMRBlock) result += `\n${indentedHMRBlock}`;
295
+ if (indentedBodyNodesCode) result += `\n ${indentedBodyNodesCode}`;
296
+ result += `\n})();\n`;
297
+ } else {
298
+ // Si no hay imports dinámicos, añadir bodyNodesCode directamente
299
+ result += '\n' + bodyNodesCode + '\n';
300
+ }
301
+
302
+ result += '\n' + exportStatements.join('\n');
303
+
304
+ let finalOutput = result.trim(); // Limpiar espacios extra del ensamblaje
305
+
306
+ const shouldBeMarkedForReload =
307
+ isFileAComponent || isVueInitializationFile || isCoreDefinitionFile;
308
+
309
+ if (shouldBeMarkedForReload) {
310
+ // Asegurarse de no duplicar la marca
311
+ if (!finalOutput.startsWith('//versaHRM-reloadFILE')) {
312
+ finalOutput = '//versaHRM-reloadFILE\n' + finalOutput;
313
+ }
314
+ }
315
+ return finalOutput;
316
+ }
@@ -1,48 +1,48 @@
1
- import path from 'node:path';
2
- /**
3
- * Converts a 24-hour time string to a 12-hour time string with AM/PM.
4
- *
5
- * @param {number} timing - The value of the timing en miliseconds.
6
- * @returns {string} the timing in ms, seconds, minutes or hours.
7
- */
8
- export const showTimingForHumans = timing => {
9
- if (timing < 1000) {
10
- return `${timing} ms`;
11
- } else if (timing < 60000) {
12
- return `${timing / 1000} s`;
13
- } else if (timing < 3600000) {
14
- return `${timing / 60000} min`;
15
- } else {
16
- return `${timing / 3600000} h`;
17
- }
18
- };
19
-
20
- /**
21
- * Mapea una ruta de origen a una ruta de destino en el directorio de distribución.
22
- * @param {string} ruta - La ruta de origen.
23
- * @returns {Promise<string>} - La ruta mapeada en el directorio de distribución.
24
- */
25
- export const mapRuta = async (ruta, PATH_DIST, PATH_SOURCE) =>
26
- path.join(PATH_DIST, path.relative(PATH_SOURCE, ruta));
27
-
28
- /**
29
- * Agrega la extensión .js a las importaciones en la cadena de datos proporcionada.
30
- * @param {string} data - La cadena de entrada que contiene el código JavaScript.
31
- * @returns {Promise<string>} - Una promesa que se resuelve con la cadena modificada con las importaciones actualizadas.
32
- */
33
- export const addImportEndJs = async data => {
34
- const importRegExp =
35
- /(?:import\s+.*?from\s+['"](.*?)['"]|import\(['"](.*?)['"]\))/g; // Manejar importaciones estáticas y dinámicas
36
-
37
- return data.replace(importRegExp, (match, ruta1, ruta2) => {
38
- const ruta = ruta1 || ruta2; // Usar la ruta capturada, ya sea estática o dinámica
39
- if (ruta.endsWith('.vue') || ruta.endsWith('.ts')) {
40
- const fullPath = ruta.replace(/\.(vue|ts)$/, '.js');
41
- return match.replace(ruta, fullPath);
42
- } else if (!ruta.match(/\/.*\.(js|mjs|css)$/) && ruta.includes('/')) {
43
- return match.replace(ruta, `${ruta}.js`);
44
- }
45
-
46
- return match; // Devolver el match original si no se cumple ninguna condición
47
- });
48
- };
1
+ import path from 'node:path';
2
+ /**
3
+ * Converts a 24-hour time string to a 12-hour time string with AM/PM.
4
+ *
5
+ * @param {number} timing - The value of the timing en miliseconds.
6
+ * @returns {string} the timing in ms, seconds, minutes or hours.
7
+ */
8
+ export const showTimingForHumans = timing => {
9
+ if (timing < 1000) {
10
+ return `${timing} ms`;
11
+ } else if (timing < 60000) {
12
+ return `${timing / 1000} s`;
13
+ } else if (timing < 3600000) {
14
+ return `${timing / 60000} min`;
15
+ } else {
16
+ return `${timing / 3600000} h`;
17
+ }
18
+ };
19
+
20
+ /**
21
+ * Mapea una ruta de origen a una ruta de destino en el directorio de distribución.
22
+ * @param {string} ruta - La ruta de origen.
23
+ * @returns {Promise<string>} - La ruta mapeada en el directorio de distribución.
24
+ */
25
+ export const mapRuta = async (ruta, PATH_DIST, PATH_SOURCE) =>
26
+ path.join(PATH_DIST, path.relative(PATH_SOURCE, ruta));
27
+
28
+ /**
29
+ * Agrega la extensión .js a las importaciones en la cadena de datos proporcionada.
30
+ * @param {string} data - La cadena de entrada que contiene el código JavaScript.
31
+ * @returns {Promise<string>} - Una promesa que se resuelve con la cadena modificada con las importaciones actualizadas.
32
+ */
33
+ export const addImportEndJs = async data => {
34
+ const importRegExp =
35
+ /(?:import\s+.*?from\s+['"](.*?)['"]|import\(['"](.*?)['"]\))/g; // Manejar importaciones estáticas y dinámicas
36
+
37
+ return data.replace(importRegExp, (match, ruta1, ruta2) => {
38
+ const ruta = ruta1 || ruta2; // Usar la ruta capturada, ya sea estática o dinámica
39
+ if (ruta.endsWith('.vue') || ruta.endsWith('.ts')) {
40
+ const fullPath = ruta.replace(/\.(vue|ts)$/, '.js');
41
+ return match.replace(ruta, fullPath);
42
+ } else if (!ruta.match(/\/.*\.(js|mjs|css)$/) && ruta.includes('/')) {
43
+ return match.replace(ruta, `${ruta}.js`);
44
+ }
45
+
46
+ return match; // Devolver el match original si no se cumple ninguna condición
47
+ });
48
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "versacompiler",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Una herramienta para compilar y minificar archivos .vue, .js y .ts para proyectos de Vue 3 con soporte para TypeScript.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -19,7 +19,8 @@
19
19
  "dev": "node --watch dist/index.js",
20
20
  "compile": "node dist/index.js",
21
21
  "compile-dev": "node dist/index.js --all",
22
- "compile-prod": "node dist/index.js --all --prod"
22
+ "compile-prod": "node dist/index.js --all --prod",
23
+ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
23
24
  },
24
25
  "keywords": [
25
26
  "vue",
@@ -37,9 +38,9 @@
37
38
  "chalk": "5.4.1",
38
39
  "chokidar": "^4.0.3",
39
40
  "get-port": "^7.1.0",
40
- "oxc-minify": "^0.69.0",
41
+ "oxc-minify": "^0.70.0",
41
42
  "typescript": "^5.8.3",
42
- "vue": "3.5.13"
43
+ "vue": "3.5.14"
43
44
  },
44
45
  "devDependencies": {
45
46
  "@tailwindcss/cli": "^4.1.6",
@@ -47,6 +48,7 @@
47
48
  "code-tag": "^1.2.0",
48
49
  "oxlint": "^0.16.10",
49
50
  "prettier": "3.5.3",
50
- "tailwindcss": "^4.1.6"
51
+ "tailwindcss": "^4.1.6",
52
+ "jest": "^29.7.0"
51
53
  }
52
54
  }