versacompiler 2.0.0 → 2.0.1

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.
@@ -1,3 +1,4 @@
1
+ import { createHash } from 'node:crypto';
1
2
  import path from 'node:path';
2
3
  import * as vCompiler from 'vue/compiler-sfc';
3
4
  import { logger } from '../servicios/logger.js';
@@ -10,6 +11,123 @@ async function loadChalk() {
10
11
  }
11
12
  return chalk;
12
13
  }
14
+ class VueHMRInjectionCache {
15
+ static instance;
16
+ cache = new Map();
17
+ MAX_CACHE_SIZE = 100;
18
+ CACHE_TTL = 5 * 60 * 1000; // 5 minutos
19
+ static getInstance() {
20
+ if (!VueHMRInjectionCache.instance) {
21
+ VueHMRInjectionCache.instance = new VueHMRInjectionCache();
22
+ }
23
+ return VueHMRInjectionCache.instance;
24
+ }
25
+ /**
26
+ * Genera un hash del contenido original para detectar cambios
27
+ */
28
+ generateContentHash(data) {
29
+ return createHash('md5').update(data).digest('hex');
30
+ }
31
+ /**
32
+ * Obtiene código HMR inyectado desde cache o lo genera
33
+ */
34
+ getOrGenerateHMRInjection(originalData, fileName) {
35
+ const contentHash = this.generateContentHash(originalData);
36
+ const cacheKey = `${fileName}:${contentHash}`;
37
+ // Verificar cache
38
+ const cached = this.cache.get(cacheKey);
39
+ if (cached && Date.now() - cached.timestamp < this.CACHE_TTL) {
40
+ return {
41
+ injectedData: cached.injectedCode,
42
+ cached: true,
43
+ };
44
+ }
45
+ // Generar nueva inyección HMR
46
+ const vueImportPattern = /import\s*\{[^}]*\bref\b[^}]*\}\s*from\s*['"]vue['"]/;
47
+ const hasRefImport = vueImportPattern.test(originalData);
48
+ const varContent = `
49
+ ${hasRefImport ? '' : 'import { ref } from "/node_modules/vue/dist/vue.esm-browser.js";'}
50
+ const versaComponentKey = ref(0);
51
+ `;
52
+ let injectedData;
53
+ const ifExistScript = originalData.includes('<script');
54
+ if (!ifExistScript) {
55
+ injectedData =
56
+ `<script setup lang="ts">${varContent}</script>/n` +
57
+ originalData;
58
+ }
59
+ else {
60
+ injectedData = originalData.replace(/(<script.*?>)/, `$1${varContent}`);
61
+ }
62
+ // Inyectar :key en el template
63
+ injectedData = injectedData.replace(/(<template[^>]*>[\s\S]*?)(<(\w+)([^>]*?))(\/?>)/, (match, p1, p2, p3, p4, p5) => {
64
+ if (p4.includes(':key=') || p4.includes('key=')) {
65
+ return match;
66
+ }
67
+ const isSelfClosing = p5 === '/>';
68
+ if (isSelfClosing) {
69
+ return `${p1}<${p3}${p4} :key="versaComponentKey" />`;
70
+ }
71
+ else {
72
+ return `${p1}<${p3}${p4} :key="versaComponentKey">`;
73
+ }
74
+ });
75
+ // Cachear resultado
76
+ this.cache.set(cacheKey, {
77
+ contentHash,
78
+ injectedCode: injectedData,
79
+ hasRefImport,
80
+ timestamp: Date.now(),
81
+ });
82
+ // Limpiar cache si es necesario
83
+ this.evictIfNeeded();
84
+ return {
85
+ injectedData,
86
+ cached: false,
87
+ };
88
+ }
89
+ /**
90
+ * Limpia entradas de cache cuando se excede el límite
91
+ */
92
+ evictIfNeeded() {
93
+ if (this.cache.size <= this.MAX_CACHE_SIZE)
94
+ return;
95
+ const entries = Array.from(this.cache.entries());
96
+ entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
97
+ // Eliminar las entradas más antiguas
98
+ const toDelete = entries.slice(0, entries.length - this.MAX_CACHE_SIZE);
99
+ toDelete.forEach(([key]) => this.cache.delete(key));
100
+ }
101
+ /**
102
+ * Limpia entradas expiradas
103
+ */
104
+ cleanExpired() {
105
+ const now = Date.now();
106
+ for (const [key, entry] of this.cache.entries()) {
107
+ if (now - entry.timestamp > this.CACHE_TTL) {
108
+ this.cache.delete(key);
109
+ }
110
+ }
111
+ }
112
+ /**
113
+ * Obtiene estadísticas del cache
114
+ */
115
+ getStats() {
116
+ return {
117
+ size: this.cache.size,
118
+ maxSize: this.MAX_CACHE_SIZE,
119
+ ttl: this.CACHE_TTL,
120
+ };
121
+ }
122
+ /**
123
+ * Limpia todo el cache
124
+ */
125
+ clear() {
126
+ this.cache.clear();
127
+ }
128
+ }
129
+ // Instancia global del cache HMR
130
+ const hmrInjectionCache = VueHMRInjectionCache.getInstance();
13
131
  const getComponentsVueMap = async (ast) => {
14
132
  let components = [];
15
133
  const importsStatic = ast?.module?.staticImports;
@@ -22,12 +140,6 @@ const getComponentsVueMap = async (ast) => {
22
140
  }
23
141
  return components;
24
142
  };
25
- /**
26
- * Compila un bloque personalizado.
27
- * @param {Object} block - El bloque personalizado a compilar.
28
- * @param {string} source - La fuente del bloque.
29
- */
30
- const _compileCustomBlock = async (_block, _source) => { };
31
143
  /**
32
144
  * Precompila un componente Vue.
33
145
  * @param {string} data - El código del componente Vue.
@@ -45,36 +157,8 @@ export const preCompileVue = async (data, source, isProd = false) => {
45
157
  };
46
158
  }
47
159
  if (!isProd) {
48
- // Verificar si ya existe una importación de ref desde vue de manera más precisa
49
- const vueImportPattern = /import\s*\{[^}]*\bref\b[^}]*\}\s*from\s*['"]vue['"]/;
50
- const hasRefImport = vueImportPattern.test(data);
51
- // esto es para HMR re re forzado
52
- const varContent = `
53
- ${hasRefImport ? '' : 'import { ref } from "/node_modules/vue/dist/vue.esm-browser.js";'}
54
- const versaComponentKey = ref(0);
55
- `;
56
- const ifExistScript = data.includes('<script');
57
- if (!ifExistScript) {
58
- data =
59
- `<script setup lang="ts">${varContent}</script>/n` + data;
60
- }
61
- else {
62
- data = data.replace(/(<script.*?>)/, `$1${varContent}`);
63
- }
64
- data = data.replace(/(<template[^>]*>[\s\S]*?)(<(\w+)([^>]*?))(\/?>)/, (match, p1, p2, p3, p4, p5) => {
65
- // Si ya tiene :key, no agregarlo de nuevo
66
- if (p4.includes(':key=') || p4.includes('key=')) {
67
- return match;
68
- }
69
- // Si es self-closing (termina con '/>'), manejar diferente
70
- const isSelfClosing = p5 === '/>';
71
- if (isSelfClosing) {
72
- return `${p1}<${p3}${p4} :key="versaComponentKey" />`;
73
- }
74
- else {
75
- return `${p1}<${p3}${p4} :key="versaComponentKey">`;
76
- }
77
- });
160
+ const { injectedData } = hmrInjectionCache.getOrGenerateHMRInjection(data, fileName);
161
+ data = injectedData;
78
162
  }
79
163
  const { descriptor, errors } = vCompiler.parse(data, {
80
164
  filename: fileName,
@@ -132,7 +216,7 @@ export const preCompileVue = async (data, source, isProd = false) => {
132
216
  const ast = await parser(`temp.${scriptLang}`, scriptContent, scriptLang);
133
217
  if (ast?.errors.length > 0) {
134
218
  throw new Error(`Error al analizar el script del componente Vue ${source}:\n${ast.errors
135
- .map(e => e.message)
219
+ .map((e) => e.message)
136
220
  .join('\n')}`);
137
221
  }
138
222
  const components = await getComponentsVueMap(ast);
@@ -293,4 +377,14 @@ export const preCompileVue = async (data, source, isProd = false) => {
293
377
  };
294
378
  }
295
379
  };
380
+ // ✨ NUEVA FUNCIÓN: Exportar funcionalidades del cache HMR para uso externo
381
+ export const getVueHMRCacheStats = () => {
382
+ return hmrInjectionCache.getStats();
383
+ };
384
+ export const clearVueHMRCache = () => {
385
+ hmrInjectionCache.clear();
386
+ };
387
+ export const cleanExpiredVueHMRCache = () => {
388
+ hmrInjectionCache.cleanExpired();
389
+ };
296
390
  //# sourceMappingURL=vuejs.js.map
package/dist/main.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import path from 'node:path'; // Importar el módulo path
3
- import { env } from 'node:process';
3
+ import process, { env } from 'node:process';
4
4
  // Lazy loading optimizations - Only import lightweight modules synchronously
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { logger } from './servicios/logger.js';
@@ -35,7 +35,7 @@ async function loadBrowserSyncModule() {
35
35
  return await import('./servicios/browserSync.js');
36
36
  }
37
37
  async function loadChokidarModule() {
38
- return await import('./servicios/chokidar.js');
38
+ return await import('./servicios/file-watcher.js');
39
39
  }
40
40
  async function loadConfigModule() {
41
41
  return await import('./servicios/readConfig.js');
@@ -100,7 +100,7 @@ async function main() {
100
100
  .alias('y', 'yes')
101
101
  .option('typeCheck', {
102
102
  type: 'boolean',
103
- description: 'Habilitar/Deshabilitar la verificación de tipos. Por defecto --typeCheck=true',
103
+ description: 'Habilitar/Deshabilitar la verificación de tipos. Por defecto --typeCheck=false',
104
104
  default: false,
105
105
  })
106
106
  .alias('t', 'typeCheck');
@@ -199,8 +199,7 @@ async function main() {
199
199
  }
200
200
  if (argv.file) {
201
201
  // Compilar archivo individual
202
- logger.info(chalk.yellow(`📄 Compilando archivo: ${argv.file}`));
203
- // Verificar si el archivo existe
202
+ logger.info(chalk.yellow(`📄 Compilando archivo: ${argv.file}`)); // Verificar si el archivo existe
204
203
  const fs = await import('node:fs/promises');
205
204
  const { compileFile } = await loadCompilerModule();
206
205
  let absolutePathFile;
@@ -212,7 +211,7 @@ async function main() {
212
211
  logger.error(chalk.red(`❌ Error: El archivo '${argv.file}' no existe.`));
213
212
  process.exit(1);
214
213
  }
215
- // Compilar el archivo
214
+ // Compilar el archivo (absolutePathFile está garantizado aquí)
216
215
  const result = await compileFile(absolutePathFile);
217
216
  if (result.success) {
218
217
  logger.info(chalk.green(`✅ Archivo compilado exitosamente: ${result.output}`));
@@ -1,10 +1,276 @@
1
+ import { createHash } from 'node:crypto';
1
2
  import { promises as fs } from 'node:fs';
2
3
  import path from 'node:path';
3
- import { env } from 'node:process';
4
+ import process, { env } from 'node:process';
4
5
  import browserSync from 'browser-sync';
5
6
 
6
7
  import getPort from 'get-port';
7
8
  import { logger } from './logger.js';
9
+ class BrowserSyncFileCache {
10
+ static instance;
11
+ cache = new Map();
12
+ MAX_CACHE_SIZE = 200; // Máximo archivos en cache
13
+ MAX_CACHE_MEMORY = 50 * 1024 * 1024; // 50MB límite
14
+ CACHE_TTL = 15 * 60 * 1000; // 15 minutos para archivos estáticos
15
+ currentMemoryUsage = 0;
16
+ // Métricas
17
+ cacheHits = 0;
18
+ cacheMisses = 0;
19
+ totalRequests = 0;
20
+ static getInstance() {
21
+ if (!BrowserSyncFileCache.instance) {
22
+ BrowserSyncFileCache.instance = new BrowserSyncFileCache();
23
+ }
24
+ return BrowserSyncFileCache.instance;
25
+ }
26
+ /**
27
+ * Genera ETag para el archivo basado en contenido y timestamp
28
+ */
29
+ generateETag(filePath, stats) {
30
+ const hash = createHash('md5')
31
+ .update(`${filePath}:${stats.mtime.getTime()}:${stats.size}`)
32
+ .digest('hex');
33
+ return `"${hash}"`;
34
+ }
35
+ /**
36
+ * Determina el Content-Type basado en la extensión del archivo
37
+ */
38
+ getContentType(filePath) {
39
+ const ext = path.extname(filePath).toLowerCase();
40
+ const mimeTypes = {
41
+ '.js': 'application/javascript',
42
+ '.mjs': 'application/javascript',
43
+ '.ts': 'application/javascript', // Se transpila a JS
44
+ '.css': 'text/css',
45
+ '.html': 'text/html',
46
+ '.json': 'application/json',
47
+ '.png': 'image/png',
48
+ '.jpg': 'image/jpeg',
49
+ '.jpeg': 'image/jpeg',
50
+ '.gif': 'image/gif',
51
+ '.svg': 'image/svg+xml',
52
+ '.ico': 'image/x-icon',
53
+ '.webp': 'image/webp',
54
+ '.avif': 'image/avif',
55
+ '.woff': 'font/woff',
56
+ '.woff2': 'font/woff2',
57
+ '.ttf': 'font/ttf',
58
+ '.eot': 'application/vnd.ms-fontobject',
59
+ '.map': 'application/json',
60
+ '.xml': 'application/xml',
61
+ '.txt': 'text/plain',
62
+ '.pdf': 'application/pdf',
63
+ '.zip': 'application/zip',
64
+ '.mp4': 'video/mp4',
65
+ '.mp3': 'audio/mpeg',
66
+ '.wav': 'audio/wav',
67
+ '.ogg': 'audio/ogg',
68
+ };
69
+ return mimeTypes[ext] || 'application/octet-stream';
70
+ }
71
+ /**
72
+ * Verifica si el archivo debe ser cacheado
73
+ */
74
+ shouldCache(filePath) {
75
+ const ext = path.extname(filePath).toLowerCase();
76
+ const cacheableExtensions = [
77
+ '.js',
78
+ '.mjs',
79
+ '.css',
80
+ '.json',
81
+ '.png',
82
+ '.jpg',
83
+ '.jpeg',
84
+ '.gif',
85
+ '.svg',
86
+ '.ico',
87
+ '.webp',
88
+ '.avif',
89
+ '.woff',
90
+ '.woff2',
91
+ '.ttf',
92
+ '.eot',
93
+ '.map',
94
+ ];
95
+ return cacheableExtensions.includes(ext);
96
+ }
97
+ /**
98
+ * Obtiene archivo desde cache o lo lee del disco
99
+ */
100
+ async getOrReadFile(filePath) {
101
+ this.totalRequests++;
102
+ try {
103
+ // Verificar si el archivo existe y obtener stats
104
+ const stats = await fs.stat(filePath);
105
+ const lastModified = stats.mtime.getTime();
106
+ const etag = this.generateETag(filePath, stats);
107
+ // Si no debe ser cacheado, leer directamente
108
+ if (!this.shouldCache(filePath)) {
109
+ const content = await fs.readFile(filePath, 'utf-8');
110
+ return {
111
+ content,
112
+ contentType: this.getContentType(filePath),
113
+ etag,
114
+ cached: false,
115
+ };
116
+ }
117
+ // Verificar cache
118
+ const cached = this.cache.get(filePath);
119
+ if (cached && cached.lastModified === lastModified) {
120
+ this.cacheHits++;
121
+ return {
122
+ content: cached.content,
123
+ contentType: cached.contentType,
124
+ etag: cached.etag,
125
+ cached: true,
126
+ };
127
+ }
128
+ // Cache miss - leer archivo
129
+ this.cacheMisses++;
130
+ const isBinary = this.isBinaryFile(filePath);
131
+ const content = await fs.readFile(filePath, isBinary ? undefined : 'utf-8');
132
+ const contentType = this.getContentType(filePath);
133
+ // Cachear resultado
134
+ this.addToCache(filePath, {
135
+ content,
136
+ contentType,
137
+ lastModified,
138
+ etag,
139
+ size: stats.size,
140
+ });
141
+ return {
142
+ content,
143
+ contentType,
144
+ etag,
145
+ cached: false,
146
+ };
147
+ }
148
+ catch {
149
+ return null;
150
+ }
151
+ }
152
+ /**
153
+ * Determina si un archivo es binario
154
+ */
155
+ isBinaryFile(filePath) {
156
+ const ext = path.extname(filePath).toLowerCase();
157
+ const binaryExtensions = [
158
+ '.png',
159
+ '.jpg',
160
+ '.jpeg',
161
+ '.gif',
162
+ '.ico',
163
+ '.webp',
164
+ '.avif',
165
+ '.woff',
166
+ '.woff2',
167
+ '.ttf',
168
+ '.eot',
169
+ '.pdf',
170
+ '.zip',
171
+ '.mp4',
172
+ '.mp3',
173
+ '.wav',
174
+ '.ogg',
175
+ ];
176
+ return binaryExtensions.includes(ext);
177
+ }
178
+ /**
179
+ * Añade archivo al cache con gestión de memoria
180
+ */
181
+ addToCache(filePath, fileData) {
182
+ try {
183
+ // Aplicar políticas de eviction si es necesario
184
+ this.evictIfNeeded(fileData.size);
185
+ const cacheEntry = {
186
+ content: fileData.content,
187
+ contentType: fileData.contentType,
188
+ lastModified: fileData.lastModified,
189
+ etag: fileData.etag,
190
+ size: fileData.size,
191
+ };
192
+ this.cache.set(filePath, cacheEntry);
193
+ this.currentMemoryUsage += fileData.size;
194
+ }
195
+ catch (error) {
196
+ console.warn('[BrowserSyncFileCache] Error cacheando archivo:', error);
197
+ }
198
+ }
199
+ /**
200
+ * Aplica políticas de eviction LRU si es necesario
201
+ */
202
+ evictIfNeeded(newFileSize) {
203
+ // Verificar límite de archivos
204
+ while (this.cache.size >= this.MAX_CACHE_SIZE) {
205
+ this.evictOldest();
206
+ }
207
+ // Verificar límite de memoria
208
+ while (this.currentMemoryUsage + newFileSize > this.MAX_CACHE_MEMORY &&
209
+ this.cache.size > 0) {
210
+ this.evictOldest();
211
+ }
212
+ }
213
+ /**
214
+ * Elimina el archivo más antiguo del cache
215
+ */
216
+ evictOldest() {
217
+ let oldestPath = '';
218
+ let oldestTime = Infinity;
219
+ for (const [filePath, entry] of this.cache) {
220
+ if (entry.lastModified < oldestTime) {
221
+ oldestTime = entry.lastModified;
222
+ oldestPath = filePath;
223
+ }
224
+ }
225
+ if (oldestPath) {
226
+ const entry = this.cache.get(oldestPath);
227
+ if (entry) {
228
+ this.currentMemoryUsage -= entry.size;
229
+ this.cache.delete(oldestPath);
230
+ }
231
+ }
232
+ }
233
+ /**
234
+ * Invalidar cache para un archivo específico
235
+ */
236
+ invalidateFile(filePath) {
237
+ const entry = this.cache.get(filePath);
238
+ if (entry) {
239
+ this.currentMemoryUsage -= entry.size;
240
+ this.cache.delete(filePath);
241
+ }
242
+ }
243
+ /**
244
+ * Obtiene estadísticas del cache
245
+ */
246
+ getStats() {
247
+ const hitRate = this.totalRequests > 0
248
+ ? Math.round((this.cacheHits / this.totalRequests) * 100)
249
+ : 0;
250
+ return {
251
+ cacheHits: this.cacheHits,
252
+ cacheMisses: this.cacheMisses,
253
+ hitRate,
254
+ totalRequests: this.totalRequests,
255
+ cacheSize: this.cache.size,
256
+ maxCacheSize: this.MAX_CACHE_SIZE,
257
+ memoryUsage: this.currentMemoryUsage,
258
+ maxMemoryUsage: this.MAX_CACHE_MEMORY,
259
+ };
260
+ }
261
+ /**
262
+ * Limpia todo el cache
263
+ */
264
+ clear() {
265
+ this.cache.clear();
266
+ this.currentMemoryUsage = 0;
267
+ this.cacheHits = 0;
268
+ this.cacheMisses = 0;
269
+ this.totalRequests = 0;
270
+ }
271
+ }
272
+ // Instancia global del cache de archivos
273
+ const fileCache = BrowserSyncFileCache.getInstance();
8
274
  // Lazy loading para chalk
9
275
  const loadChalk = async () => {
10
276
  const { default: chalk } = await import('chalk');
@@ -79,16 +345,21 @@ export async function browserSyncServer() {
79
345
  res.setHeader('Expires', '0');
80
346
  //para redigir a la ubicación correcta
81
347
  if (req.url === '/__versa/initHRM.js') {
82
- // Busca vueLoader.js en la carpeta de salida configurada
348
+ // OPTIMIZADO: Usar cache para archivos HRM
83
349
  const vueLoaderPath = path.join(relativeHrmPath, '/initHRM.js');
84
- res.setHeader('Content-Type', 'application/javascript');
85
- try {
86
- const fileContent = await fs.readFile(vueLoaderPath, 'utf-8');
87
- res.end(fileContent);
350
+ const cachedFile = await fileCache.getOrReadFile(vueLoaderPath);
351
+ if (cachedFile) {
352
+ res.setHeader('Content-Type', cachedFile.contentType);
353
+ res.setHeader('ETag', cachedFile.etag);
354
+ if (process.env.VERBOSE === 'true' &&
355
+ cachedFile.cached) {
356
+ logger.info(`🚀 File cache hit para ${vueLoaderPath}`);
357
+ }
358
+ res.end(cachedFile.content);
88
359
  }
89
- catch (error) {
360
+ else {
90
361
  const chalkInstance = await loadChalk();
91
- logger.error(chalkInstance.red(`🚩 :Error al leer el archivo ${vueLoaderPath}: ${error instanceof Error ? error.message : String(error)}/n ${error instanceof Error ? error.stack : ''}`));
362
+ logger.error(chalkInstance.red(`🚩 :Error al leer el archivo ${vueLoaderPath}`));
92
363
  res.statusCode = 404;
93
364
  res.end('// vueLoader.js not found');
94
365
  }
@@ -96,16 +367,21 @@ export async function browserSyncServer() {
96
367
  }
97
368
  // Si la URL comienza con /__versa/hrm/, sirve los archivos de dist/hrm
98
369
  if (req.url.startsWith('/__versa/')) {
99
- // Sirve archivos de dist/hrm como /__versa/hrm/*
370
+ // OPTIMIZADO: Usar cache para archivos Versa
100
371
  const filePath = path.join(relativeHrmPath, req.url.replace('/__versa/', ''));
101
- res.setHeader('Content-Type', 'application/javascript');
102
- try {
103
- const fileContent = await fs.readFile(filePath, 'utf-8');
104
- res.end(fileContent);
372
+ const cachedFile = await fileCache.getOrReadFile(filePath);
373
+ if (cachedFile) {
374
+ res.setHeader('Content-Type', cachedFile.contentType);
375
+ res.setHeader('ETag', cachedFile.etag);
376
+ if (process.env.VERBOSE === 'true' &&
377
+ cachedFile.cached) {
378
+ logger.info(`🚀 File cache hit para ${filePath}`);
379
+ }
380
+ res.end(cachedFile.content);
105
381
  }
106
- catch (error) {
382
+ else {
107
383
  const chalkInstance = await loadChalk();
108
- logger.error(chalkInstance.red(`🚩 :Error al leer el archivo ${filePath}: ${error instanceof Error ? error.message : String(error)}`));
384
+ logger.error(chalkInstance.red(`🚩 :Error al leer el archivo ${filePath}`));
109
385
  res.statusCode = 404;
110
386
  res.end('// Not found');
111
387
  }
@@ -113,15 +389,21 @@ export async function browserSyncServer() {
113
389
  }
114
390
  // Si la URL comienza con /node_modules/, sirve los archivos de node_modules
115
391
  if (req.url.startsWith('/node_modules/')) {
392
+ // ✨ OPTIMIZADO: Usar cache para módulos de node_modules
116
393
  const modulePath = path.join(process.cwd(), req.url);
117
- res.setHeader('Content-Type', 'application/javascript');
118
- try {
119
- const fileContent = await fs.readFile(modulePath, 'utf-8');
120
- res.end(fileContent);
394
+ const cachedFile = await fileCache.getOrReadFile(modulePath);
395
+ if (cachedFile) {
396
+ res.setHeader('Content-Type', cachedFile.contentType);
397
+ res.setHeader('ETag', cachedFile.etag);
398
+ if (process.env.VERBOSE === 'true' &&
399
+ cachedFile.cached) {
400
+ logger.info(`🚀 Module cache hit para ${modulePath}`);
401
+ }
402
+ res.end(cachedFile.content);
121
403
  }
122
- catch (error) {
404
+ else {
123
405
  const chalkInstance = await loadChalk();
124
- logger.error(chalkInstance.red(`🚩 Error al leer el módulo ${modulePath}: ${error instanceof Error ? error.message : String(error)}`));
406
+ logger.error(chalkInstance.red(`🚩 Error al leer el módulo ${modulePath}`));
125
407
  res.statusCode = 404;
126
408
  res.end('// Module not found');
127
409
  }
@@ -174,4 +456,14 @@ export async function emitirCambios(bs, action, filePath) {
174
456
  const nameFile = path.basename(normalizedPath, path.extname(normalizedPath));
175
457
  bs.sockets.emit(action, { action, filePath, normalizedPath, nameFile });
176
458
  }
459
+ // ✨ NUEVAS FUNCIONES: Exportar funcionalidades del cache de archivos para uso externo
460
+ export const getBrowserSyncCacheStats = () => {
461
+ return fileCache.getStats();
462
+ };
463
+ export const clearBrowserSyncCache = () => {
464
+ fileCache.clear();
465
+ };
466
+ export const invalidateBrowserSyncFile = (filePath) => {
467
+ fileCache.invalidateFile(filePath);
468
+ };
177
469
  //# sourceMappingURL=browserSync.js.map