versacompiler 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,249 @@
1
+ let currentComponentTree = null;
2
+
3
+ function findNodeByInstance(tree, instance) {
4
+ if (tree.name === instance) return tree;
5
+ for (const child of tree.children) {
6
+ const found = findNodeByInstance(child, instance);
7
+ if (found) return found;
8
+ }
9
+ return null;
10
+ }
11
+
12
+ function getPathToRoot(node) {
13
+ const path = [];
14
+ while (node) {
15
+ path.push(node);
16
+ node = node.parent;
17
+ }
18
+ return path; // Ordenado desde hijo hasta raíz
19
+ }
20
+
21
+ // Función auxiliar para encontrar componentes recursivamente dentro de un VNode genérico
22
+ function recursivelyFindComponentsInVNode(vnode, parentTreeNode) {
23
+ if (!vnode || typeof vnode !== 'object') {
24
+ return;
25
+ }
26
+
27
+ if (vnode.component) {
28
+ const childComponentInstance = vnode.component;
29
+
30
+ let componentName = 'Anonymous';
31
+ if (childComponentInstance.type) {
32
+ if (childComponentInstance.type.name) {
33
+ componentName = childComponentInstance.type.name;
34
+ } else if (childComponentInstance.type.__name) {
35
+ componentName = childComponentInstance.type.__name;
36
+ } else if (typeof childComponentInstance.type === 'function') {
37
+ const funcName = childComponentInstance.type.name;
38
+ if (funcName && funcName !== 'Anonymous function') {
39
+ componentName = funcName;
40
+ }
41
+ // Heurísticas para componentes comunes de Vue
42
+ const typeStr = childComponentInstance.type.toString();
43
+ if (typeStr.includes('BaseTransition')) {
44
+ componentName = 'Transition';
45
+ } else if (typeStr.includes('KeepAlive')) {
46
+ componentName = 'KeepAlive';
47
+ } else if (typeStr.includes('Suspense')) {
48
+ componentName = 'Suspense';
49
+ }
50
+ }
51
+ }
52
+
53
+ const childTreeNode = {
54
+ name: componentName,
55
+ instancia: childComponentInstance,
56
+ children: [],
57
+ parent: parentTreeNode,
58
+ isRoot: false,
59
+ };
60
+ parentTreeNode.children.push(childTreeNode);
61
+ traverseComponentInstance(childComponentInstance, childTreeNode);
62
+ } else {
63
+ const childrenToExplore = vnode.children || vnode.dynamicChildren;
64
+ if (Array.isArray(childrenToExplore)) {
65
+ childrenToExplore.forEach(childVNode => {
66
+ recursivelyFindComponentsInVNode(childVNode, parentTreeNode);
67
+ });
68
+ }
69
+ }
70
+ }
71
+
72
+ // Función principal de recorrido, ahora llamada traverseComponentInstance
73
+ function traverseComponentInstance(componentInstance, currentTreeNode) {
74
+ const subTreeVNode = componentInstance.subTree;
75
+
76
+ if (!subTreeVNode) {
77
+ return;
78
+ }
79
+
80
+ recursivelyFindComponentsInVNode(subTreeVNode, currentTreeNode);
81
+ }
82
+
83
+ export const buildComponentTree = componentRootInstance => {
84
+ const tree = {
85
+ name:
86
+ componentRootInstance.type?.name ||
87
+ componentRootInstance.type?.__name ||
88
+ 'Anonymous',
89
+ instancia: componentRootInstance,
90
+ children: [],
91
+ parent: null,
92
+ isRoot: true,
93
+ };
94
+ traverseComponentInstance(componentRootInstance, tree);
95
+ return tree;
96
+ };
97
+
98
+ // Nueva función auxiliar para intentar forzar la actualización de una instancia
99
+ function tryForceUpdate(instance) {
100
+ if (!instance) {
101
+ return false;
102
+ }
103
+ if (instance.proxy && typeof instance.proxy.$forceUpdate === 'function') {
104
+ instance.proxy.$forceUpdate();
105
+ instance.update();
106
+ // buscar una varible en el componente que se llame versaComponentKey y sumarle 1
107
+ instance.ctx._.setupState.versaComponentKey++;
108
+ return true;
109
+ }
110
+ if (typeof instance.update === 'function') {
111
+ if (instance.ctx._.setupState.versaComponentKey) {
112
+ instance.ctx._.setupState.versaComponentKey++;
113
+ }
114
+ instance.update();
115
+ return true;
116
+ }
117
+ return false;
118
+ }
119
+
120
+ export async function reloadComponent(
121
+ app,
122
+ componentName,
123
+ relativePath,
124
+ _extension,
125
+ _type,
126
+ ) {
127
+ try {
128
+ const baseUrl = window.location.href;
129
+ const newBaseUrl = new URL(baseUrl);
130
+ const urlOrigin = `${newBaseUrl.origin}/${relativePath}`;
131
+ const module = await import(`${urlOrigin}?t=${Date.now()}`);
132
+ currentComponentTree = buildComponentTree(app._instance);
133
+
134
+ const targetNode = findNodeByInstance(
135
+ currentComponentTree,
136
+ componentName,
137
+ );
138
+ const path = getPathToRoot(targetNode);
139
+ for (const instanciaParent of path) {
140
+ if (
141
+ instanciaParent.isRoot ||
142
+ instanciaParent.name === 'KeepAlive'
143
+ ) {
144
+ window.location.reload();
145
+ return;
146
+ }
147
+ if (instanciaParent.name !== componentName) {
148
+ if (
149
+ instanciaParent.name !== 'BaseTransition' &&
150
+ instanciaParent.name !== 'Transition' &&
151
+ instanciaParent.name !== 'Suspense'
152
+ ) {
153
+ const componentsDefinition =
154
+ instanciaParent.instancia?.type?.components ||
155
+ instanciaParent.instancia?.components;
156
+
157
+ if (
158
+ componentsDefinition &&
159
+ componentsDefinition[componentName]
160
+ ) {
161
+ componentsDefinition[componentName] = module.default;
162
+ if (tryForceUpdate(instanciaParent.instancia)) {
163
+ console.log(
164
+ `✔️ Versa HMR: Component updated successfully`,
165
+ );
166
+ return;
167
+ }
168
+ }
169
+ }
170
+ }
171
+ }
172
+ return null;
173
+ } catch (error) {
174
+ console.log(error.stack);
175
+ return {
176
+ msg: `Error al recargar ${componentName}: ${error}`,
177
+ error,
178
+ };
179
+ }
180
+ }
181
+
182
+ // Función Debounce
183
+ export function debounce(func, waitFor) {
184
+ let timeout = null;
185
+
186
+ const debounced = (...args) => {
187
+ if (timeout) {
188
+ clearTimeout(timeout);
189
+ }
190
+ timeout = setTimeout(() => func(...args), waitFor);
191
+ };
192
+
193
+ return debounced;
194
+ }
195
+
196
+ export async function reloadJS(_relativePath) {
197
+ location.reload();
198
+ }
199
+
200
+ export function socketReload(app) {
201
+ if (window.___browserSync___?.socket) {
202
+ const socket = window.___browserSync___.socket;
203
+ // Configura el observer para actualizar el árbol de componentes en cada mutación relevante
204
+ if (app && app._container) {
205
+ currentComponentTree = buildComponentTree(app._instance);
206
+ initializeMutationObserver(app._container, () => {
207
+ if (app._instance) {
208
+ currentComponentTree = buildComponentTree(app._instance);
209
+ }
210
+ });
211
+ }
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
+ }
218
+
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
+ });
233
+ } else {
234
+ setTimeout(() => {
235
+ window.location.reload();
236
+ }, 5000);
237
+ }
238
+ }
239
+
240
+ function initializeMutationObserver(targetNode, callback, options) {
241
+ const observerInstance = new MutationObserver(callback);
242
+ const defaultOptions = {
243
+ childList: true,
244
+ subtree: true,
245
+ attributes: false,
246
+ };
247
+ observerInstance.observe(targetNode, { ...defaultOptions, ...options });
248
+ return observerInstance;
249
+ }
@@ -0,0 +1,61 @@
1
+ let errorOverlay;
2
+ export function hideErrorOverlay() {
3
+ const existingOverlay = document.getElementById('versa-hmr-error-overlay');
4
+ if (existingOverlay) {
5
+ existingOverlay.remove();
6
+ }
7
+ errorOverlay = null;
8
+ }
9
+ export function showErrorOverlay(errorMessage, errorDetails = '') {
10
+ hideErrorOverlay(); // Ensure no duplicate overlays
11
+
12
+ errorOverlay = document.createElement('div');
13
+ errorOverlay.id = 'versa-hmr-error-overlay';
14
+ errorOverlay.style.position = 'fixed';
15
+ errorOverlay.style.top = '0';
16
+ errorOverlay.style.left = '0';
17
+ errorOverlay.style.width = '100vw';
18
+ errorOverlay.style.height = '100vh';
19
+ errorOverlay.style.backgroundColor = 'rgba(0, 0, 0, 1.85)';
20
+ errorOverlay.style.color = '#ff8080';
21
+ errorOverlay.style.zIndex = '999999';
22
+ errorOverlay.style.display = 'flex';
23
+ errorOverlay.style.flexDirection = 'column';
24
+ errorOverlay.style.alignItems = 'center';
25
+ errorOverlay.style.justifyContent = 'center';
26
+ errorOverlay.style.fontFamily = 'monospace';
27
+ errorOverlay.style.fontSize = '16px';
28
+ errorOverlay.style.padding = '20px';
29
+ errorOverlay.style.boxSizing = 'border-box';
30
+ errorOverlay.style.textAlign = 'left';
31
+ errorOverlay.style.overflow = 'auto';
32
+
33
+ const title = document.createElement('h2');
34
+ title.textContent = 'Versa HMR Error';
35
+ title.style.color = '#ff4d4d';
36
+ title.style.fontSize = '24px';
37
+ title.style.marginBottom = '20px';
38
+
39
+ const messageDiv = document.createElement('div');
40
+ messageDiv.textContent = errorMessage;
41
+ messageDiv.style.marginBottom = '15px';
42
+ messageDiv.style.whiteSpace = 'pre-wrap';
43
+
44
+ const detailsPre = document.createElement('pre');
45
+ detailsPre.textContent = errorDetails;
46
+ detailsPre.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
47
+ detailsPre.style.padding = '10px';
48
+ detailsPre.style.borderRadius = '5px';
49
+ detailsPre.style.maxHeight = '50vh';
50
+ detailsPre.style.overflow = 'auto';
51
+ detailsPre.style.width = '100%';
52
+ detailsPre.style.maxWidth = '800px';
53
+
54
+ errorOverlay.appendChild(title);
55
+ errorOverlay.appendChild(messageDiv);
56
+ if (errorDetails) {
57
+ errorOverlay.appendChild(detailsPre);
58
+ }
59
+
60
+ document.body.appendChild(errorOverlay);
61
+ }
@@ -0,0 +1,35 @@
1
+ // Store for the Vue app instance
2
+ let vueAppInstance = null;
3
+
4
+ // Exponer la instancia en window para acceso global
5
+ if (typeof window !== 'undefined') {
6
+ window.__VUE_APP_INSTANCE__ = {
7
+ set: function(instance) {
8
+ vueAppInstance = instance;
9
+ console.log('Vue app instance stored successfully in window.__VUE_APP_INSTANCE__');
10
+ return instance;
11
+ },
12
+ get: function() {
13
+ return vueAppInstance;
14
+ }
15
+ };
16
+ }
17
+
18
+ export default {
19
+ methods: {
20
+ // Set the Vue app instance
21
+ set(instance) {
22
+ vueAppInstance = instance;
23
+ // También lo guardamos en window para acceso global
24
+ if (typeof window !== 'undefined') {
25
+ window.__VUE_APP_INSTANCE__.set(instance);
26
+ }
27
+ console.log('Vue app instance stored successfully');
28
+ return instance;
29
+ },
30
+ // Get the Vue app instance
31
+ get() {
32
+ return vueAppInstance;
33
+ },
34
+ },
35
+ };
@@ -0,0 +1,57 @@
1
+ // setupHMR.js - Helper to set up Hot Module Reloading for Vue
2
+ import instanciaVue from './instanciaVue.js';
3
+
4
+ /**
5
+ * Set up Hot Module Reloading for a Vue application
6
+ * @param {Object} app - The Vue application instance
7
+ * @returns {Object} - The same Vue application instance
8
+ */
9
+ export function setupHMR(app) {
10
+ // Store the Vue app instance in our instanciaVue module
11
+ instanciaVue.methods.set(app);
12
+
13
+ // También lo guardamos directamente en window para mayor accesibilidad
14
+ if (typeof window !== 'undefined') {
15
+ window.__VUE_APP__ = app;
16
+ console.log('Vue app instance stored directly in window.__VUE_APP__');
17
+ }
18
+
19
+ console.log('HMR setup complete - Vue instance stored for hot reloading');
20
+
21
+ return app;
22
+ }
23
+
24
+ /**
25
+ * Helper function to wrap your Vue app creation with HMR support
26
+ * @param {Function} createAppFn - Function that creates and returns your Vue app
27
+ * @returns {Object} - The Vue application instance with HMR support
28
+ */
29
+ export function createAppWithHMR(createAppFn) {
30
+ const app = createAppFn();
31
+ return setupHMR(app);
32
+ }
33
+
34
+ /**
35
+ * Función para obtener la instancia de Vue desde cualquier parte de la aplicación
36
+ * @returns {Object|null} - La instancia de Vue o null si no está disponible
37
+ */
38
+ export function getVueInstance() {
39
+ // Intentar obtener la instancia desde diferentes fuentes
40
+ if (typeof window !== 'undefined') {
41
+ // Primero intentar desde window.__VUE_APP__
42
+ if (window.__VUE_APP__) {
43
+ return window.__VUE_APP__;
44
+ }
45
+
46
+ // Luego intentar desde window.__VUE_APP_INSTANCE__
47
+ if (
48
+ window.__VUE_APP_INSTANCE__ &&
49
+ typeof window.__VUE_APP_INSTANCE__.get === 'function'
50
+ ) {
51
+ return window.__VUE_APP_INSTANCE__.get();
52
+ }
53
+ }
54
+
55
+ // Finalmente intentar desde instanciaVue
56
+ return instanciaVue.methods.get();
57
+ }
package/dist/index.js CHANGED
@@ -15,6 +15,10 @@ import {
15
15
  writeFile,
16
16
  } from 'node:fs/promises';
17
17
  import path from 'node:path';
18
+ import { fileURLToPath } from 'url';
19
+
20
+ const __filename = fileURLToPath(import.meta.url);
21
+ const __dirname = path.dirname(__filename);
18
22
 
19
23
  import { checkSintaxysAcorn } from './services/acorn.js';
20
24
  import { linter } from './services/linter.js';
@@ -161,9 +165,19 @@ const deleteFile = async ruta => {
161
165
  )
162
166
  ).toString();
163
167
  try {
164
- log(chalk.yellow(`🗑️ :Eliminando ${newPath}`));
168
+ log(chalk.yellow(`🗑️ :Intentando eliminar ${newPath}`));
169
+
170
+ const stats = await stat(newPath).catch(() => null);
171
+
172
+ if (!stats) {
173
+ log(
174
+ chalk.yellow(
175
+ `⚠️ :El archivo o directorio no existe: ${newPath}`,
176
+ ),
177
+ );
178
+ return { extension: null, normalizedPath: null, fileName: null };
179
+ }
165
180
 
166
- const stats = await stat(newPath);
167
181
  if (stats.isDirectory()) {
168
182
  await rmdir(newPath, { recursive: true });
169
183
  } else if (stats.isFile()) {
@@ -186,9 +200,10 @@ const deleteFile = async ruta => {
186
200
  } catch (errora) {
187
201
  error(
188
202
  chalk.red(
189
- `🚩 :Error al eliminar el archivo/directorio ${newPath}: ${errora}\n`,
203
+ `🚩 :Error al eliminar el archivo/directorio ${newPath}: ${errora.message}\n`,
190
204
  ),
191
205
  );
206
+ return { extension: null, normalizedPath: null, fileName: null }; // Asegurar que se devuelve un objeto en caso de otros errores
192
207
  }
193
208
  };
194
209
 
@@ -697,13 +712,20 @@ const initChokidar = async () => {
697
712
  port: uiPort, // Puerto aleatorio para la interfaz de usuario
698
713
  },
699
714
  socket: {
700
- domain: `localhost:${port}`, // Mismo puerto que arriba
715
+ // domain: `localhost:${port}`, // Dominio para la conexión de socket
701
716
  path: '/browser-sync/socket.io', // Ruta correcta para socket.io
702
717
  },
703
718
  snippetOptions: {
704
719
  rule: {
705
720
  match: /<\/body>/i,
706
- fn: (snippet, match) => `${snippet}${match}`,
721
+ fn: (snippet, match) => {
722
+ console.log(
723
+ '🟢 Inyectando snippet de BrowserSync y vueLoader.js en el HTML',
724
+ );
725
+ return `${snippet}${match}
726
+ <script type="module" src="/__versa/vueLoader.js"></script>
727
+ `;
728
+ },
707
729
  },
708
730
  },
709
731
  logLevel: 'debug',
@@ -713,12 +735,68 @@ const initChokidar = async () => {
713
735
  watchEvents: ['change', 'add', 'unlink', 'addDir', 'unlinkDir'],
714
736
  reloadDelay: 500,
715
737
  reloadDebounce: 500,
738
+ reloadOnRestart: true,
716
739
  notify: true,
717
740
  watchOptions: {
718
741
  ignoreInitial: true,
719
742
  ignored: ['node_modules', '.git'],
720
743
  },
721
- middleware: function (req, res, next) {
744
+ middleware: async function (req, res, next) {
745
+ // para evitar el error de CORS
746
+ res.setHeader('Access-Control-Allow-Origin', '*');
747
+ res.setHeader('Access-Control-Allow-Methods', '*');
748
+ res.setHeader('Access-Control-Allow-Headers', '*');
749
+ res.setHeader('Access-Control-Allow-Credentials', 'true');
750
+ res.setHeader('Access-Control-Max-Age', '3600');
751
+
752
+ //para redigir a la ubicación correcta
753
+ if (req.url === '/__versa/vueLoader.js') {
754
+ // Busca vueLoader.js en la carpeta de salida configurada
755
+ const vueLoaderPath = path.join(
756
+ __dirname,
757
+ 'services/vueLoader.js',
758
+ );
759
+ res.setHeader('Content-Type', 'application/javascript');
760
+ try {
761
+ const fileContent = await readFile(
762
+ vueLoaderPath,
763
+ 'utf-8',
764
+ );
765
+ res.end(fileContent);
766
+ } catch (error) {
767
+ console.error(
768
+ chalk.red(
769
+ `🚩 :Error al leer el archivo ${vueLoaderPath}: ${error.message}`,
770
+ ),
771
+ );
772
+ res.statusCode = 404;
773
+ res.end('// vueLoader.js not found');
774
+ }
775
+ return;
776
+ }
777
+ // Si la URL comienza con /__versa/hrm/, sirve los archivos de dist/hrm
778
+ if (req.url.startsWith('/__versa/hrm/')) {
779
+ // Sirve archivos de dist/hrm como /__versa/hrm/*
780
+ const filePath = path.join(
781
+ __dirname,
782
+ req.url.replace('/__versa/', ''),
783
+ );
784
+ res.setHeader('Content-Type', 'application/javascript');
785
+ try {
786
+ const fileContent = await readFile(filePath, 'utf-8');
787
+ res.end(fileContent);
788
+ } catch (error) {
789
+ console.error(
790
+ chalk.red(
791
+ `🚩 :Error al leer el archivo ${filePath}: ${error.message}`,
792
+ ),
793
+ );
794
+ res.statusCode = 404;
795
+ res.end('// Not found');
796
+ }
797
+ return;
798
+ }
799
+
722
800
  // detectar si es un archivo estático, puede que contenga un . y alguna extensión o dashUsers.js?v=1746559083866
723
801
  const isAssets = req.url.match(
724
802
  /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map|webp|avif|json|html|xml|txt|pdf|zip|mp4|mp3|wav|ogg)(\?.*)?$/i,
@@ -775,6 +853,9 @@ const initChokidar = async () => {
775
853
  next();
776
854
  },
777
855
  });
856
+ console.log(
857
+ '🟢 BrowserSync inicializado. Esperando conexiones de socket...',
858
+ );
778
859
  } catch (error) {
779
860
  console.error(
780
861
  chalk.red('🚩 :Error al iniciar:'),
@@ -1,4 +1,5 @@
1
1
  import * as Parser from 'acorn';
2
+ import chalk from 'chalk';
2
3
  /**
3
4
  * Parses the given JavaScript code using Acorn and returns the Abstract Syntax Tree (AST).
4
5
  *
@@ -8,7 +9,7 @@ import * as Parser from 'acorn';
8
9
  */
9
10
  export const checkSintaxysAcorn = async data => {
10
11
  try {
11
- const ast = Parser.parse(data, {
12
+ const ast = await Parser.parse(data, {
12
13
  ecmaVersion: 2020,
13
14
  sourceType: 'module',
14
15
  locations: true,
@@ -0,0 +1,324 @@
1
+ let socketReload,
2
+ getInstancia,
3
+ getVueInstance,
4
+ showErrorOverlay,
5
+ hideErrorOverlay,
6
+ reloadComponent,
7
+ reloadJS;
8
+
9
+ // En navegador: usar rutas relativas estáticas
10
+ (async () => {
11
+ try {
12
+ // Importa todas las dependencias HMR necesarias aquí
13
+ // CORREGIR RUTA DE IMPORTACIÓN PARA devMode.js
14
+ const devModeModule = await import('./hrm/devMode.js');
15
+ socketReload = devModeModule.socketReload;
16
+ reloadComponent = devModeModule.reloadComponent;
17
+ reloadJS = devModeModule.reloadJS;
18
+
19
+ // Log para verificar la carga de reloadComponent
20
+ if (typeof reloadComponent === 'function') {
21
+ console.log(
22
+ '[HMR] La función reloadComponent se cargó correctamente.',
23
+ );
24
+ } else {
25
+ console.error(
26
+ '[HMR] ERROR: La función reloadComponent NO se cargó o no es una función después de la importación.',
27
+ devModeModule,
28
+ );
29
+ }
30
+
31
+ getInstancia = (await import('./hrm/instanciaVue.js')).default; // Asumiendo que está en dist/hrm/
32
+ getVueInstance = (await import('./hrm/setupHMR.js')).getVueInstance; // Asumiendo que está en dist/hrm/
33
+
34
+ const errorScreenModule = await import('./hrm/errorScreen.js'); // Asumiendo que está en dist/hrm/
35
+ showErrorOverlay = errorScreenModule.showErrorOverlay;
36
+ hideErrorOverlay = errorScreenModule.hideErrorOverlay;
37
+
38
+ // Esperar a que BrowserSync client esté listo y haya definido ___browserSync___
39
+ let bsReadyRetries = 0;
40
+ const maxBsReadyRetries = 30; // 30 * 500ms = 15 segundos de espera máxima
41
+ const bsReadyCheckInterval = 500;
42
+
43
+ while (
44
+ !window.___browserSync___ &&
45
+ bsReadyRetries < maxBsReadyRetries
46
+ ) {
47
+ console.warn(
48
+ `[HMR] Esperando a que el script cliente de BrowserSync se inicialice... (${
49
+ bsReadyRetries + 1
50
+ }/${maxBsReadyRetries})`,
51
+ );
52
+ await new Promise(resolve =>
53
+ setTimeout(resolve, bsReadyCheckInterval),
54
+ );
55
+ bsReadyRetries++;
56
+ }
57
+
58
+ if (!window.___browserSync___) {
59
+ console.error(
60
+ '❌ Versa HMR: El script cliente de BrowserSync no se inicializó después de múltiples reintentos. HMR no funcionará.',
61
+ );
62
+ if (showErrorOverlay) {
63
+ // Verificar si showErrorOverlay se cargó
64
+ showErrorOverlay(
65
+ 'Falló la inicialización de HMR',
66
+ 'El cliente de BrowserSync no se inicializó. Revisa la consola y el servidor de BrowserSync.',
67
+ );
68
+ }
69
+ return; // Detener la ejecución si BrowserSync no está listo
70
+ }
71
+
72
+ console.log(
73
+ '✔️ Versa HMR: Cliente de BrowserSync detectado. Procediendo con la configuración de HMR.',
74
+ );
75
+ await initSocket(); // Llama a initSocket DESPUÉS de que las importaciones y la espera de BS hayan terminado
76
+ } catch (error) {
77
+ console.error(
78
+ '❌ Versa HMR: Error durante la carga dinámica de módulos o initSocket:',
79
+ error,
80
+ );
81
+ if (showErrorOverlay) {
82
+ showErrorOverlay(
83
+ 'Falló la inicialización de HMR',
84
+ error.stack || error.message,
85
+ );
86
+ } else {
87
+ const errDiv = document.createElement('div');
88
+ errDiv.textContent =
89
+ 'Falló la inicialización de HMR: ' + error.message;
90
+ errDiv.style.cssText =
91
+ 'position:fixed;top:10px;left:10px;padding:10px;background:red;color:white;z-index:1000000;';
92
+ document.body.appendChild(errDiv);
93
+ }
94
+ }
95
+ })();
96
+
97
+ // Obtención robusta de la instancia de Vue
98
+ const getInstanceVue = async () => {
99
+ let instance = getVueInstance && getVueInstance();
100
+ if (instance) return instance;
101
+ if (typeof window !== 'undefined') {
102
+ if (window.__VUE_APP__) return window.__VUE_APP__;
103
+ if (
104
+ window.__VUE_APP_INSTANCE__ &&
105
+ typeof window.__VUE_APP_INSTANCE__.get === 'function'
106
+ ) {
107
+ const winInstance = window.__VUE_APP_INSTANCE__.get();
108
+ if (winInstance) return winInstance;
109
+ }
110
+ }
111
+ instance = getInstancia && getInstancia.methods.get();
112
+ if (instance) return instance;
113
+ return null;
114
+ };
115
+
116
+ // Reintentos con backoff exponencial para obtener la instancia
117
+ async function waitForVueInstance(maxTries = 5, delay = 300) {
118
+ let tries = 0;
119
+ while (tries < maxTries) {
120
+ const instance = await getInstanceVue();
121
+ if (instance) return instance;
122
+ await new Promise(resolve =>
123
+ globalThis.setTimeout(resolve, delay * 2 ** tries),
124
+ );
125
+ tries++;
126
+ }
127
+ // Si no se encuentra, muestra overlay y no recarga
128
+ const errorMsg =
129
+ 'Instancia de Vue no encontrada después de múltiples reintentos.';
130
+ const detailMsg =
131
+ 'La aplicación no pudo iniciarse. Esperando una corrección para recargar.';
132
+ if (typeof showErrorOverlay === 'function') {
133
+ showErrorOverlay(errorMsg, detailMsg);
134
+ } else {
135
+ console.error(
136
+ '[HMR] showErrorOverlay no disponible, pero la instancia de Vue no se encontró.',
137
+ );
138
+ }
139
+ console.error(errorMsg);
140
+ return null; // Indica que falló
141
+ }
142
+
143
+ // Initialize socket connection and HMR con reconexión automática
144
+ const initSocket = async (retries = 0) => {
145
+ const maxRetries = 10;
146
+ const retryDelay = Math.min(2000 * (retries + 1), 10000); // backoff hasta 10s
147
+
148
+ // Se asume que window.___browserSync___ ya existe debido a la espera en la IIFE
149
+ // Pero se verifica window.___browserSync___.socket
150
+ if (window.___browserSync___ && window.___browserSync___.socket) {
151
+ const socket = window.___browserSync___.socket;
152
+ let connected = socket.connected; // Verificar estado inicial
153
+
154
+ // Listener para conexión
155
+ socket.on('connect', async () => {
156
+ connected = true;
157
+ if (typeof hideErrorOverlay === 'function') {
158
+ hideErrorOverlay();
159
+ } else {
160
+ console.warn(
161
+ '[HMR] hideErrorOverlay no es una función al momento de la conexión',
162
+ );
163
+ }
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
+ }
178
+ console.log('✔️ Versa HMR: Socket conectado');
179
+ });
180
+
181
+ // Listener para desconexión
182
+ socket.on('disconnect', () => {
183
+ connected = false;
184
+ console.log('❌ Versa HMR: Socket desconectado, reintentando...');
185
+ // La lógica de reintentos original para desconexión
186
+ setTimeout(() => {
187
+ if (!socket.connected && retries < maxRetries) {
188
+ // Usar socket.connected aquí
189
+ initSocket(retries + 1);
190
+ } else if (!socket.connected) {
191
+ console.error(
192
+ `❌ Versa HMR: Socket no conectado después de ${maxRetries} reintentos tras desconexión.`,
193
+ );
194
+ if (typeof showErrorOverlay === 'function') {
195
+ showErrorOverlay(
196
+ 'HMR Desconectado',
197
+ 'No se pudo reconectar a BrowserSync después de múltiples reintentos.',
198
+ );
199
+ }
200
+ }
201
+ }, retryDelay);
202
+ });
203
+
204
+ socket.on('vue:update', async data => {
205
+ // Log para verificar reloadComponent al inicio del evento
206
+ if (typeof reloadComponent !== 'function') {
207
+ console.error(
208
+ '[HMR] ERROR en vue:update: reloadComponent no es una función.',
209
+ );
210
+ if (typeof showErrorOverlay === 'function') {
211
+ showErrorOverlay(
212
+ 'Error Crítico de HMR',
213
+ 'reloadComponent no está disponible. Verifique la consola para errores de carga.',
214
+ );
215
+ }
216
+ return;
217
+ }
218
+
219
+ if (typeof hideErrorOverlay === 'function') hideErrorOverlay();
220
+ else
221
+ console.warn(
222
+ '[HMR] hideErrorOverlay no disponible en vue:update',
223
+ );
224
+
225
+ const { component, relativePath, extension, type, timestamp } =
226
+ data;
227
+ const appInstance = await getInstanceVue();
228
+
229
+ if (!appInstance) {
230
+ // Si la instancia de Vue no existe, la carga inicial probablemente falló.
231
+ // Una actualización de módulo (presumiblemente la corrección del error) ha llegado.
232
+ // Recargar la página completa para permitir que vue-loader.ts intente de nuevo.
233
+ console.log(
234
+ '[HMR] La instancia de Vue no existe (posible fallo en la carga inicial). Se recibió una actualización de módulo. Recargando la página...',
235
+ );
236
+ window.location.reload();
237
+ return; // Detener la ejecución adicional de HMR para esta actualización.
238
+ }
239
+
240
+ try {
241
+ if (extension === 'vue') {
242
+ const result = await reloadComponent(
243
+ appInstance,
244
+ component,
245
+ `${relativePath}`,
246
+ type,
247
+ extension,
248
+ );
249
+ if (result && result.msg) {
250
+ throw new Error(result.msg);
251
+ }
252
+ } else {
253
+ // Asumiendo que reloadJS existe
254
+ await reloadJS(`/${relativePath}?t=${timestamp}`);
255
+ }
256
+ } catch (hmrError) {
257
+ const errorMsg = `HMR falló para ${relativePath}`;
258
+ const errorStack =
259
+ hmrError.stack ||
260
+ (hmrError.toString
261
+ ? hmrError.toString()
262
+ : 'No hay stack disponible');
263
+ if (typeof showErrorOverlay === 'function') {
264
+ showErrorOverlay(errorMsg, errorStack);
265
+ } else {
266
+ console.error(
267
+ '[HMR] showErrorOverlay no disponible. Error HMR:',
268
+ errorMsg,
269
+ hmrError,
270
+ );
271
+ }
272
+ console.error(errorMsg, hmrError);
273
+ }
274
+ });
275
+
276
+ // Watchdog para la conexión inicial si el socket existe pero no está conectado
277
+ if (!connected) {
278
+ console.log(
279
+ `Versa HMR: Objeto socket encontrado, intentando conexión (Intento ${
280
+ retries + 1
281
+ }/${maxRetries})`,
282
+ );
283
+ setTimeout(() => {
284
+ if (!socket.connected && retries < maxRetries) {
285
+ console.warn(
286
+ 'Versa HMR: Sin conexión de socket después del tiempo de espera inicial, reintentando initSocket...',
287
+ );
288
+ initSocket(retries + 1);
289
+ } else if (!socket.connected) {
290
+ console.error(
291
+ `❌ Versa HMR: Socket aún no conectado después de ${maxRetries} intentos iniciales.`,
292
+ );
293
+ if (typeof showErrorOverlay === 'function') {
294
+ showErrorOverlay(
295
+ 'Falló HMR de BrowserSync',
296
+ 'No se pudo conectar al socket de BrowserSync después de intentos iniciales.',
297
+ );
298
+ }
299
+ }
300
+ }, 5000); // Timeout de 5s para el watchdog inicial
301
+ }
302
+ } else {
303
+ // Este bloque se ejecuta si window.___browserSync___ existe pero window.___browserSync___.socket no,
304
+ // o si window.___browserSync___ no existe (aunque la IIFE debería haberlo esperado).
305
+ console.warn(
306
+ `[HMR] Socket de BrowserSync no encontrado o BrowserSync no completamente inicializado. Reintentando initSocket... (${
307
+ retries + 1
308
+ }/${maxRetries})`,
309
+ );
310
+ if (retries < maxRetries) {
311
+ setTimeout(() => initSocket(retries + 1), retryDelay);
312
+ } else {
313
+ console.error(
314
+ `❌ Versa HMR: Socket de BrowserSync no encontrado después de ${maxRetries} reintentos.`,
315
+ );
316
+ if (typeof showErrorOverlay === 'function') {
317
+ showErrorOverlay(
318
+ 'Falló HMR de BrowserSync',
319
+ 'Socket o cliente de BrowserSync no encontrado después de múltiples reintentos.',
320
+ );
321
+ }
322
+ }
323
+ }
324
+ };
@@ -1,5 +1,5 @@
1
- import * as vCompiler from 'vue/compiler-sfc';
2
1
  import path from 'node:path';
2
+ import * as vCompiler from 'vue/compiler-sfc';
3
3
 
4
4
  const getComponentsVue = async data => {
5
5
  let components = [];
@@ -40,6 +40,34 @@ const _compileCustomBlock = async (_block, _source) => {};
40
40
  export const preCompileVue = async (data, source, isProd = false) => {
41
41
  try {
42
42
  const fileName = path.basename(source).replace('.vue', '');
43
+
44
+ if (!isProd) {
45
+ const ifExistsref = data.includes('ref(');
46
+
47
+ // esto es para HMR re re forzado
48
+ const varContent = `
49
+ ${ifExistsref ? '' : 'import { ref } from "vue";'};
50
+ const versaComponentKey = ref(0);
51
+ `;
52
+ const ifExistScript = data.includes('<script');
53
+ if (!ifExistScript) {
54
+ data = `<script setup>${varContent}</script>` + data;
55
+ } else {
56
+ data = data.replace(/(<script.*?>)/, `$1${varContent}`);
57
+ }
58
+
59
+ data = data.replace(
60
+ /(<template>[\s\S]*?)(<\w+)([^>]*)(\/?>)/,
61
+ (match, p1, p2, p3, p4) => {
62
+ // Si es self-closing (termina con '/>'), no agregar key
63
+ const existeSlash = p3.trim().slice(-1) === '/';
64
+
65
+ return `${p1} ${p2} ${existeSlash ? p3.trim().slice(0, -1) : p3} :key="versaComponentKey" ${existeSlash ? '/' : ''}${p4}`;
66
+ },
67
+ );
68
+ // console.log(data);
69
+ }
70
+
43
71
  const { descriptor, errors } = vCompiler.parse(data, {
44
72
  filename: fileName,
45
73
  sourceMap: false,
@@ -163,9 +191,6 @@ export const preCompileVue = async (data, source, isProd = false) => {
163
191
  ${compiledScript.content}
164
192
  ${compiledTemplate.code}
165
193
  `;
166
- //añardir instancia de app
167
-
168
- // output = `${appImport}${output}`;
169
194
 
170
195
  const componentName = `${fileName}_component`;
171
196
  const components = await getComponentsVue(data);
@@ -173,7 +198,9 @@ export const preCompileVue = async (data, source, isProd = false) => {
173
198
  __file: '${source}',
174
199
  __name: '${fileName}',
175
200
  name: '${fileName}',
176
- components: { ${components.join(', ')} },
201
+ components: {
202
+ ${components}
203
+ },
177
204
  `;
178
205
 
179
206
  // quitamos export default y añadimos el nombre del archivo
@@ -198,12 +225,16 @@ export const preCompileVue = async (data, source, isProd = false) => {
198
225
  output = output.replaceAll(/_ctx\.(?!\$)/g, '$setup.');
199
226
  output = output.replace(
200
227
  'export function render(_ctx, _cache) {',
201
- `function render(_ctx, _cache, $props, $setup, $data, $options) {`,
228
+ `
229
+ function render(_ctx, _cache, $props, $setup, $data, $options) {
230
+ `,
202
231
  );
203
232
  } else {
204
233
  output = output.replace(
205
234
  'export function render(_ctx, _cache) {',
206
- `function render(_ctx, _cache, $props, $setup, $data, $options) {`,
235
+ `
236
+ function render(_ctx, _cache, $props, $setup, $data, $options) {
237
+ `,
207
238
  );
208
239
  }
209
240
 
@@ -217,13 +248,6 @@ export const preCompileVue = async (data, source, isProd = false) => {
217
248
 
218
249
  output = `${output}\n${finishComponent}`;
219
250
 
220
- // await writeFile(
221
- // `./public/dashboard/js/${fileName}-temp.js`,
222
- // output,
223
- // 'utf-8',
224
- // );
225
- // await unlink(`./public/dashboard/js/${fileName}-temp.js`);
226
-
227
251
  return {
228
252
  lang: compiledScript.lang,
229
253
  error: null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "versacompiler",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
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": {
@@ -32,19 +32,21 @@
32
32
  "author": "Jorge Jara H (kriollone@gmail.com)",
33
33
  "license": "MIT",
34
34
  "dependencies": {
35
+ "acorn": "^8.14.1",
36
+ "browser-sync": "^3.0.4",
35
37
  "chalk": "5.4.1",
36
38
  "chokidar": "^4.0.3",
37
- "oxc-minify": "^0.68.1",
38
- "acorn": "^8.14.1",
39
- "vue": "3.5.13",
40
39
  "get-port": "^7.1.0",
40
+ "oxc-minify": "^0.69.0",
41
41
  "typescript": "^5.8.3",
42
- "oxlint": "^0.16.10",
43
- "browser-sync": "^3.0.4"
42
+ "vue": "3.5.13"
44
43
  },
45
44
  "devDependencies": {
46
- "prettier": "3.5.3",
45
+ "@tailwindcss/cli": "^4.1.6",
47
46
  "@types/node": "^22.15.17",
48
- "code-tag": "^1.2.0"
47
+ "code-tag": "^1.2.0",
48
+ "oxlint": "^0.16.10",
49
+ "prettier": "3.5.3",
50
+ "tailwindcss": "^4.1.6"
49
51
  }
50
52
  }