versacompiler 2.4.0 → 2.4.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.
@@ -19,8 +19,8 @@ export class TypeScriptWorkerPool {
19
19
  workerPath;
20
20
  initPromise = null;
21
21
  isInitialized = false; // Configuración optimizada con reciclaje de workers
22
- TASK_TIMEOUT = 8000; // 8 segundos por tarea (reducido para mayor velocidad)
23
- WORKER_INIT_TIMEOUT = 3000; // 3 segundos para inicializar (reducido)
22
+ TASK_TIMEOUT = 60000; // 60 segundos por tarea - workers son secuenciales, necesitan tiempo
23
+ WORKER_INIT_TIMEOUT = 10000; // 10 segundos para inicializar
24
24
  MAX_TASKS_PER_WORKER = 200; // ✨ OPTIMIZADO: Aumentado de 50 a 200 para reducir overhead de reciclaje
25
25
  WORKER_MEMORY_CHECK_INTERVAL = 500; // ✨ OPTIMIZADO: Verificar cada 500 tareas (reducir overhead)
26
26
  // ✨ FIX #1: Referencias a timers para limpieza adecuada
@@ -28,7 +28,7 @@ export class TypeScriptWorkerPool {
28
28
  cleanupInterval = null;
29
29
  // Cola de tareas para cuando todos los workers están ocupados
30
30
  taskQueue = [];
31
- MAX_QUEUE_SIZE = 50;
31
+ MAX_QUEUE_SIZE = 1000; // Suficiente para proyectos de 300+ archivos
32
32
  // Flag para evitar concurrencia en ensureWorkerCapacity
33
33
  isScalingUp = false;
34
34
  // Métricas de rendimiento
@@ -85,8 +85,11 @@ export class TypeScriptWorkerPool {
85
85
  // ✨ ISSUE #4: Obtener memoria real del worker
86
86
  const memoryInfo = await this.getWorkerMemoryUsage(poolWorker);
87
87
  poolWorker.memoryUsage = memoryInfo.heapUsed;
88
- // Verificar límites de memoria y reciclaje automático
89
- if (this.shouldRecycleWorker(poolWorker)) {
88
+ // Verificar límites de memoria y reciclaje automático.
89
+ // Solo reciclar workers INACTIVOS para evitar rechazar tareas en vuelo.
90
+ // Los workers con tareas pendientes se reciclan vía drainQueue cuando quedan libres.
91
+ const isIdle = !poolWorker.busy && poolWorker.pendingTasks.size === 0;
92
+ if (isIdle && this.shouldRecycleWorker(poolWorker)) {
90
93
  const reason = this.getRecycleReason(poolWorker);
91
94
  console.warn(`[WorkerPool] Worker ${poolWorker.id} requiere reciclaje: ${reason}`);
92
95
  await this.recycleWorker(poolWorker);
@@ -198,8 +201,8 @@ export class TypeScriptWorkerPool {
198
201
  const cpuCount = os.cpus().length;
199
202
  switch (mode) {
200
203
  case 'batch':
201
- // Para modo batch, hasta 3 workers conservando al menos 1 CPU libre
202
- this.poolSize = Math.min(3, Math.max(1, cpuCount - 1));
204
+ // Para modo batch, hasta 4 workers para mejor rendimiento en proyectos grandes
205
+ this.poolSize = Math.min(4, Math.max(1, cpuCount - 1));
203
206
  break;
204
207
  case 'watch':
205
208
  // Para modo watch, hasta 2 workers para menor impacto en sistema
@@ -376,9 +379,10 @@ export class TypeScriptWorkerPool {
376
379
  });
377
380
  }
378
381
  else {
379
- this.failedTasks++;
380
382
  const errorMessage = response.error || 'Error desconocido del worker';
381
383
  // ✨ FIX MEMORIA: Error simple sin referencias pesadas
384
+ // failedTasks se incrementa en el wrapper de queued.reject para tareas de cola,
385
+ // o en handleWorkerError/Exit para tareas directas que se re-encolan.
382
386
  const error = new Error(errorMessage);
383
387
  reject(error);
384
388
  }
@@ -404,7 +408,8 @@ export class TypeScriptWorkerPool {
404
408
  const pendingTasksArray = Array.from(poolWorker.pendingTasks.entries());
405
409
  for (const [taskId, task] of pendingTasksArray) {
406
410
  clearTimeout(task.timeout);
407
- this.failedTasks++;
411
+ // failedTasks se contabiliza en el wrapper de queued.reject (para cola)
412
+ // o via re-encolado en typeCheck() catch (para tareas directas).
408
413
  task.reject(new Error(`Worker ${poolWorker.id} failed`));
409
414
  poolWorker.pendingTasks.delete(taskId);
410
415
  }
@@ -436,6 +441,8 @@ export class TypeScriptWorkerPool {
436
441
  if (index !== -1) {
437
442
  this.workers[index] = newWorker;
438
443
  }
444
+ // Drenar la cola con el nuevo worker para evitar tareas atascadas
445
+ setImmediate(() => this.drainAllWorkers());
439
446
  }
440
447
  catch {
441
448
  // Silencioso
@@ -451,7 +458,8 @@ export class TypeScriptWorkerPool {
451
458
  // 1. Rechazar tareas pendientes con cleanup
452
459
  poolWorker.pendingTasks.forEach(task => {
453
460
  clearTimeout(task.timeout);
454
- this.failedTasks++;
461
+ // failedTasks se contabiliza en el wrapper de queued.reject (para cola)
462
+ // o via re-encolado en typeCheck() catch (para tareas directas).
455
463
  task.reject(new Error(`Worker ${poolWorker.id} exited with code ${code}`));
456
464
  });
457
465
  poolWorker.pendingTasks.clear();
@@ -543,6 +551,8 @@ export class TypeScriptWorkerPool {
543
551
  if (index !== -1) {
544
552
  this.workers[index] = newWorker;
545
553
  }
554
+ // Drenar la cola con el nuevo worker listo
555
+ setImmediate(() => this.drainQueue(newWorker));
546
556
  }
547
557
  catch (error) {
548
558
  console.error(`[WorkerPool] Error reciclando worker ${poolWorker.id}:`, error);
@@ -573,6 +583,18 @@ export class TypeScriptWorkerPool {
573
583
  }
574
584
  return null; // Todos los workers están muy ocupados
575
585
  }
586
+ /**
587
+ * Drena la cola en todos los workers disponibles
588
+ */
589
+ drainAllWorkers() {
590
+ for (const worker of this.workers) {
591
+ if (!worker.busy && worker.pendingTasks.size === 0) {
592
+ this.drainQueue(worker);
593
+ if (this.taskQueue.length === 0)
594
+ break;
595
+ }
596
+ }
597
+ }
576
598
  /**
577
599
  * Procesa la siguiente tarea de la cola en el worker liberado
578
600
  */
@@ -581,19 +603,27 @@ export class TypeScriptWorkerPool {
581
603
  return;
582
604
  if (poolWorker.busy || poolWorker.pendingTasks.size > 0)
583
605
  return;
584
- if (poolWorker.taskCounter >= this.MAX_TASKS_PER_WORKER)
606
+ // Si el worker alcanzó el límite de tareas, reciclarlo y luego drenar
607
+ if (poolWorker.taskCounter >= this.MAX_TASKS_PER_WORKER) {
608
+ this.recycleWorker(poolWorker)
609
+ .then(() => setImmediate(() => this.drainAllWorkers()))
610
+ .catch(() => { });
585
611
  return;
612
+ }
586
613
  const queued = this.taskQueue.shift();
587
614
  if (!queued)
588
615
  return;
589
616
  const waitMs = Date.now() - queued.queuedAt;
590
617
  if (waitMs > 30000) {
618
+ // queued.reject incluye failedTasks++ en el wrapper (ver typeCheck())
591
619
  queued.reject(new Error(`Queue task expired after ${waitMs}ms`));
620
+ // Continuar drenando la cola con la siguiente tarea
621
+ setImmediate(() => this.drainQueue(poolWorker));
592
622
  return;
593
623
  }
594
624
  this.typeCheckWithWorker(poolWorker, queued.fileName, queued.content, queued.compilerOptions)
595
625
  .then(queued.resolve)
596
- .catch(queued.reject);
626
+ .catch(queued.reject); // queued.reject incluye failedTasks++ en el wrapper
597
627
  }
598
628
  /**
599
629
  * Realiza type checking usando el pool de workers
@@ -613,23 +643,38 @@ export class TypeScriptWorkerPool {
613
643
  return await this.typeCheckWithWorker(availableWorker, fileName, content, compilerOptions);
614
644
  }
615
645
  catch {
616
- // Worker falló; intentar encolar
646
+ // Worker falló o llegó al límite de tareas
647
+ // Si alcanzó el límite, disparar reciclaje inmediato
648
+ if (availableWorker.taskCounter >= this.MAX_TASKS_PER_WORKER) {
649
+ this.recycleWorker(availableWorker)
650
+ .then(() => setImmediate(() => this.drainAllWorkers()))
651
+ .catch(() => { });
652
+ }
617
653
  }
618
654
  }
619
655
  // Ningún worker disponible: encolar la tarea
620
656
  if (this.taskQueue.length >= this.MAX_QUEUE_SIZE) {
621
- // Cola llena → fallback síncrono para no perder el type check
622
- return this.typeCheckWithSyncFallback(fileName, content, compilerOptions);
657
+ // Cola llena → omitir type check para no bloquear el event loop
658
+ // (con MAX_QUEUE_SIZE=1000 esto solo ocurre en proyectos extremadamente grandes)
659
+ console.warn(`[TypeCheckQueue] Cola llena (${this.MAX_QUEUE_SIZE} tareas). Type check omitido para: ${path.basename(fileName)}`);
660
+ this.completedTasks++; // Contabilizar como completado para mantener métricas consistentes
661
+ return { diagnostics: [], hasErrors: false };
623
662
  }
624
663
  // Escalar el pool si es posible
625
664
  this.ensureWorkerCapacity().catch(() => { });
626
665
  return new Promise((resolve, reject) => {
666
+ // Wrapper para reject que garantiza que failedTasks se incrementa exactamente una vez
667
+ // para todas las tareas que pasan por la cola (drainQueue path).
668
+ const wrappedReject = (err) => {
669
+ this.failedTasks++;
670
+ reject(err);
671
+ };
627
672
  this.taskQueue.push({
628
673
  fileName,
629
674
  content,
630
675
  compilerOptions,
631
676
  resolve,
632
- reject,
677
+ reject: wrappedReject,
633
678
  queuedAt: Date.now(),
634
679
  });
635
680
  });
@@ -639,9 +684,9 @@ export class TypeScriptWorkerPool {
639
684
  * ✨ FIX MEMORIA: Optimizado para prevenir fugas de memoria
640
685
  */
641
686
  async typeCheckWithWorker(poolWorker, fileName, content, compilerOptions) {
642
- // Si el worker alcanzó su límite de tareas, usar fallback síncrono
687
+ // Si el worker alcanzó su límite de tareas, encolar para reciclaje
643
688
  if (poolWorker.taskCounter >= this.MAX_TASKS_PER_WORKER) {
644
- return this.typeCheckWithSyncFallback(fileName, content, compilerOptions);
689
+ throw new Error(`Worker ${poolWorker.id} ha alcanzado el límite de tareas`);
645
690
  }
646
691
  return new Promise((resolve, reject) => {
647
692
  const taskId = `task-${poolWorker.id}-${++poolWorker.taskCounter}-${Date.now()}`;
@@ -660,6 +705,8 @@ export class TypeScriptWorkerPool {
660
705
  poolWorker.busy = false;
661
706
  }
662
707
  reject(new Error(`Timeout (${dynamicTimeout}ms) en type checking para ${fileName}`));
708
+ // Drenar la cola cuando se libera la tarea por timeout
709
+ setImmediate(() => this.drainQueue(poolWorker));
663
710
  }
664
711
  }, dynamicTimeout);
665
712
  // ✨ FIX MEMORIA: Wrapper para resolver/rechazar que limpia el timeout
@@ -764,7 +811,7 @@ export class TypeScriptWorkerPool {
764
811
  complexityMultiplier = Math.min(complexityMultiplier, 5); // Máximo 5x el timeout base
765
812
  complexityMultiplier = Math.max(complexityMultiplier, 0.5); // Mínimo 0.5x el timeout base
766
813
  const finalTimeout = Math.round(baseTimeout * complexityMultiplier);
767
- return Math.min(finalTimeout, 60000); // Máximo absoluto de 60 segundos
814
+ return Math.min(finalTimeout, 120000); // Máximo absoluto de 120 segundos
768
815
  }
769
816
  /**
770
817
  * Fallback síncrono para type checking
@@ -154,6 +154,38 @@ class WorkerTypeScriptLanguageServiceHost {
154
154
  }
155
155
  }
156
156
 
157
+ /**
158
+ * Language Service persistente para reusar entre tareas (evita cold-start de 200-500ms por tarea)
159
+ * Se resetea periódicamente para controlar el uso de memoria
160
+ */
161
+ let _persistentHost = null;
162
+ let _persistentLS = null;
163
+ let _persistentCompilerOptionsStr = null;
164
+ let _tasksSinceReset = 0;
165
+ const MAX_TASKS_BEFORE_LS_RESET = 50; // Resetear cada 50 tareas para controlar memoria
166
+
167
+ function getOrResetLanguageService(compilerOptions) {
168
+ const optionsStr = JSON.stringify(compilerOptions);
169
+ const needsReset =
170
+ !_persistentLS ||
171
+ optionsStr !== _persistentCompilerOptionsStr ||
172
+ _tasksSinceReset >= MAX_TASKS_BEFORE_LS_RESET;
173
+
174
+ if (needsReset) {
175
+ if (_persistentLS) {
176
+ try { _persistentLS.dispose(); } catch { /* ignore */ }
177
+ _persistentLS = null;
178
+ }
179
+ _persistentHost = new WorkerTypeScriptLanguageServiceHost(compilerOptions);
180
+ _persistentLS = ts.createLanguageService(_persistentHost);
181
+ _persistentCompilerOptionsStr = optionsStr;
182
+ _tasksSinceReset = 0;
183
+ }
184
+
185
+ _tasksSinceReset++;
186
+ return { host: _persistentHost, ls: _persistentLS };
187
+ }
188
+
157
189
  /**
158
190
  * Realiza validación de tipos en el worker thread
159
191
  */
@@ -167,30 +199,16 @@ function validateTypesInWorker(fileName, content, compilerOptions) {
167
199
  return { diagnostics: [], hasErrors: false };
168
200
  }
169
201
 
170
- // Crear Language Service Host
171
- const host = new WorkerTypeScriptLanguageServiceHost(compilerOptions); // Para archivos Vue, crear un archivo virtual .ts
202
+ // Obtener o crear Language Service persistente (rápido después del primer uso)
203
+ const { host, ls: languageService } = getOrResetLanguageService(compilerOptions);
204
+
205
+ // Para archivos Vue, crear un archivo virtual .ts
172
206
  if (fileName.endsWith('.vue')) {
173
- // Crear un nombre de archivo virtual único
174
207
  const virtualFileName = `${fileName}.ts`;
175
208
  host.addFile(virtualFileName, scriptContent);
176
209
  actualFileName = virtualFileName;
177
- // console.log('[Worker] Archivo Vue agregado como:', virtualFileName);
178
- } else {
179
- // Para archivos virtuales, usar el nombre tal como viene
180
- host.addFile(fileName, scriptContent);
181
- actualFileName = fileName;
182
- // console.log('[Worker] Archivo agregado como:', fileName);
183
- }
184
-
185
- // console.log(
186
- // '[Worker] Contenido del archivo:',
187
- // scriptContent.substring(0, 100) + '...',
188
- // );
189
- // console.log('[Worker] Archivos en host:', host.getScriptFileNames());
190
210
 
191
- // Agregar declaraciones básicas de tipos para Vue si es necesario
192
- if (fileName.endsWith('.vue')) {
193
- // Usar el directorio del archivo actual para las declaraciones
211
+ // Agregar declaraciones de tipos Vue si es necesario
194
212
  const projectDir = path.dirname(actualFileName);
195
213
  const vueTypesPath = path.join(projectDir, 'vue-types.d.ts');
196
214
  const vueTypesDeclaration = `// Declaraciones de tipos Vue para validación
@@ -214,10 +232,27 @@ function validateTypesInWorker(fileName, content, compilerOptions) {
214
232
  }
215
233
  export {};`;
216
234
  host.addFile(vueTypesPath, vueTypesDeclaration);
217
- }
218
235
 
219
- // Crear Language Service
220
- const languageService = ts.createLanguageService(host);
236
+ // Eliminar archivos de tareas anteriores para evitar que el programa crezca.
237
+ // Se hace DESPUÉS de addFile para que la versión se incremente correctamente
238
+ // (evita que el LS use resultados cacheados de versiones anteriores).
239
+ for (const key of host.files.keys()) {
240
+ if (key !== virtualFileName && key !== vueTypesPath) {
241
+ host.files.delete(key);
242
+ }
243
+ }
244
+ } else {
245
+ host.addFile(fileName, scriptContent);
246
+ actualFileName = fileName;
247
+
248
+ // Eliminar archivos de tareas anteriores para evitar que el programa crezca.
249
+ // Se hace DESPUÉS de addFile para que la versión se incremente correctamente.
250
+ for (const key of host.files.keys()) {
251
+ if (key !== fileName) {
252
+ host.files.delete(key);
253
+ }
254
+ }
255
+ }
221
256
 
222
257
  try {
223
258
  // Verificar que el archivo existe en el host antes de solicitar diagnósticos
@@ -225,17 +260,12 @@ function validateTypesInWorker(fileName, content, compilerOptions) {
225
260
  return { diagnostics: [], hasErrors: false };
226
261
  }
227
262
 
228
-
229
263
  // Obtener diagnósticos de tipos con manejo de errores
230
264
  let syntacticDiagnostics = [];
231
265
  let semanticDiagnostics = [];
232
266
  try {
233
267
  syntacticDiagnostics =
234
268
  languageService.getSyntacticDiagnostics(actualFileName);
235
- // console.log(
236
- // '[Worker] Diagnósticos sintácticos:',
237
- // syntacticDiagnostics.length,
238
- // );
239
269
  } catch (error) {
240
270
  console.error(
241
271
  '[Worker] Error obteniendo diagnósticos sintácticos:',
@@ -246,37 +276,18 @@ function validateTypesInWorker(fileName, content, compilerOptions) {
246
276
  try {
247
277
  semanticDiagnostics =
248
278
  languageService.getSemanticDiagnostics(actualFileName);
249
- // console.log(
250
- // '[Worker] Diagnósticos semánticos:',
251
- // semanticDiagnostics.length,
252
- // );
253
279
  } catch (error) {
254
280
  console.error(
255
281
  '[Worker] Error obteniendo diagnósticos semánticos:',
256
282
  error.message,
257
283
  );
258
- } // Combinar todos los diagnósticos
284
+ }
285
+
259
286
  const allDiagnostics = [
260
287
  ...syntacticDiagnostics,
261
288
  ...semanticDiagnostics,
262
289
  ];
263
290
 
264
- // console.log(
265
- // '[Worker] Total diagnósticos encontrados:',
266
- // allDiagnostics.length,
267
- // );
268
-
269
- // Log de todos los diagnósticos antes del filtrado
270
- // allDiagnostics.forEach((diag, index) => {
271
- // const messageText = ts.flattenDiagnosticMessageText(
272
- // diag.messageText,
273
- // '\n',
274
- // );
275
- // console.log(
276
- // `[Worker] Diagnóstico ${index + 1}: [${diag.category}] ${messageText}`,
277
- // );
278
- // });
279
-
280
291
  // Filtrar diagnósticos relevantes
281
292
  const filteredDiagnostics = allDiagnostics.filter(diag => {
282
293
  const messageText = ts.flattenDiagnosticMessageText(
@@ -287,7 +298,9 @@ function validateTypesInWorker(fileName, content, compilerOptions) {
287
298
  // Solo errores de categoría Error
288
299
  if (diag.category !== ts.DiagnosticCategory.Error) {
289
300
  return false;
290
- } // Ignorar SOLO errores específicos de infraestructura Vue y rutas de módulos
301
+ }
302
+
303
+ // Ignorar errores de infraestructura Vue y rutas de módulos
291
304
  return (
292
305
  !messageText.includes('Cannot find module') &&
293
306
  !messageText.includes('Could not find source file') &&
@@ -315,17 +328,12 @@ function validateTypesInWorker(fileName, content, compilerOptions) {
315
328
  !messageText.includes(
316
329
  "Parameter '_cache' implicitly has an 'any' type",
317
330
  ) &&
318
- // Ignorar errores específicos de decorators cuando están mal configurados
319
331
  !messageText.includes(
320
332
  'Unable to resolve signature of method decorator when called as an expression',
321
333
  ) &&
322
334
  !messageText.includes(
323
335
  'The runtime will invoke the decorator with',
324
336
  ) &&
325
- // NO ignorar errores TS7031 y TS7006 de forma general, solo para parámetros específicos de Vue
326
- // diag.code !== 7031 &&
327
- // diag.code !== 7006 &&
328
- // Ignorar errores TS1241 (decorator signature mismatch) durante desarrollo
329
337
  diag.code !== 1241 &&
330
338
  !(
331
339
  messageText.includes("implicitly has an 'any' type") &&
@@ -345,8 +353,6 @@ function validateTypesInWorker(fileName, content, compilerOptions) {
345
353
  };
346
354
  } catch {
347
355
  return { diagnostics: [], hasErrors: false };
348
- } finally {
349
- try { languageService.dispose(); } catch { /* ignore dispose errors */ }
350
356
  }
351
357
  } catch (error) {
352
358
  // En caso de error, devolver diagnóstico de error
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "versacompiler",
3
- "version": "2.4.0",
3
+ "version": "2.4.1",
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/main.js",
6
6
  "bin": {
@@ -21,7 +21,7 @@
21
21
  "build": "tsx src/main.ts --all -t --cc --co -y --verbose",
22
22
  "compileDev": "tsx src/main.ts --all --ci --cc -y -t --linter --verbose",
23
23
  "vlint": "tsx src/main.ts --all --cc --co -y --linter",
24
- "vtlint": "tsx src/main.ts --all --cc --co -y -t",
24
+ "vtlint": "tsx src/main.ts --all --cc --co -y -t --verbose",
25
25
  "test": "vitest run",
26
26
  "test:watch": "vitest",
27
27
  "test:ui": "vitest --ui",