versacompiler 1.0.2 → 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';
@@ -22,7 +26,7 @@ import { minifyJS } from './services/minify.js';
22
26
  import { preCompileTS } from './services/typescript.js';
23
27
  import { preCompileVue } from './services/vuejs.js';
24
28
 
25
- import { mapRuta, showTimingForHumans } from './utils/utils.js';
29
+ import { addImportEndJs, mapRuta, showTimingForHumans } from './utils/utils.js';
26
30
 
27
31
  const log = console.log.bind(console);
28
32
  const error = console.error.bind(console);
@@ -161,16 +165,17 @@ 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);
165
171
 
166
- const stats = await stat(newPath).catch(() => null); // Verificar si el archivo existe
167
172
  if (!stats) {
168
173
  log(
169
174
  chalk.yellow(
170
175
  `⚠️ :El archivo o directorio no existe: ${newPath}`,
171
176
  ),
172
177
  );
173
- return { extension: null, normalizedPath: null, fileName: null }; // Devolver un objeto válido
178
+ return { extension: null, normalizedPath: null, fileName: null };
174
179
  }
175
180
 
176
181
  if (stats.isDirectory()) {
@@ -195,9 +200,10 @@ const deleteFile = async ruta => {
195
200
  } catch (errora) {
196
201
  error(
197
202
  chalk.red(
198
- `🚩 :Error al eliminar el archivo/directorio ${newPath}: ${errora}\n`,
203
+ `🚩 :Error al eliminar el archivo/directorio ${newPath}: ${errora.message}\n`,
199
204
  ),
200
205
  );
206
+ return { extension: null, normalizedPath: null, fileName: null }; // Asegurar que se devuelve un objeto en caso de otros errores
201
207
  }
202
208
  };
203
209
 
@@ -305,40 +311,6 @@ const removeCodeTagImport = async data => {
305
311
  return data;
306
312
  };
307
313
 
308
- /**
309
- * Agrega la extensión .js a las importaciones en la cadena de datos proporcionada.
310
- * @param {string} data - La cadena de entrada que contiene el código JavaScript.
311
- * @returns {Promise<string>} - Una promesa que se resuelve con la cadena modificada con las importaciones actualizadas.
312
- */
313
- const addImportEndJs = async data => {
314
- const importRegExp = /import\s+[\s\S]*?\s+from\s+['"](.*)['"]/g;
315
-
316
- return data.replace(importRegExp, match => {
317
- const ruta = match.match(/from\s+['"](.*)['"]/)[1];
318
-
319
- if (ruta.startsWith('@/')) {
320
- // Manejar alias de ruta como @/
321
- return match.replace(ruta, `${ruta}.js`);
322
- } else if (ruta.endsWith('.vue')) {
323
- const resultVue = match.match(/from\s+['"](.+\/(\w+))\.vue['"]/);
324
- if (resultVue) {
325
- const fullPath = resultVue[1].replace('.vue', '');
326
- const fileName = resultVue[2];
327
- return `import ${fileName} from '${fullPath}.js';`;
328
- }
329
- } else if (
330
- !ruta.endsWith('.js') &&
331
- !ruta.endsWith('.mjs') &&
332
- !ruta.endsWith('.css') &&
333
- ruta.includes('/')
334
- ) {
335
- return match.replace(ruta, `${ruta}.js`);
336
- }
337
-
338
- return match; // Devolver el match original si no se cumple ninguna condición
339
- });
340
- };
341
-
342
314
  /**
343
315
  * Elimina los comentarios con la etiqueta @preserve de la cadena de datos proporcionada.
344
316
  * @param {string} data - La cadena de entrada que contiene el código JavaScript.
@@ -740,13 +712,20 @@ const initChokidar = async () => {
740
712
  port: uiPort, // Puerto aleatorio para la interfaz de usuario
741
713
  },
742
714
  socket: {
743
- domain: `localhost:${port}`, // Mismo puerto que arriba
715
+ // domain: `localhost:${port}`, // Dominio para la conexión de socket
744
716
  path: '/browser-sync/socket.io', // Ruta correcta para socket.io
745
717
  },
746
718
  snippetOptions: {
747
719
  rule: {
748
720
  match: /<\/body>/i,
749
- 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
+ },
750
729
  },
751
730
  },
752
731
  logLevel: 'debug',
@@ -756,12 +735,68 @@ const initChokidar = async () => {
756
735
  watchEvents: ['change', 'add', 'unlink', 'addDir', 'unlinkDir'],
757
736
  reloadDelay: 500,
758
737
  reloadDebounce: 500,
738
+ reloadOnRestart: true,
759
739
  notify: true,
760
740
  watchOptions: {
761
741
  ignoreInitial: true,
762
742
  ignored: ['node_modules', '.git'],
763
743
  },
764
- 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
+
765
800
  // detectar si es un archivo estático, puede que contenga un . y alguna extensión o dashUsers.js?v=1746559083866
766
801
  const isAssets = req.url.match(
767
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,
@@ -818,6 +853,9 @@ const initChokidar = async () => {
818
853
  next();
819
854
  },
820
855
  });
856
+ console.log(
857
+ '🟢 BrowserSync inicializado. Esperando conexiones de socket...',
858
+ );
821
859
  } catch (error) {
822
860
  console.error(
823
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,
@@ -24,3 +24,25 @@ export const showTimingForHumans = timing => {
24
24
  */
25
25
  export const mapRuta = async (ruta, PATH_DIST, PATH_SOURCE) =>
26
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.2",
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
  }