vatts 2.0.2 → 2.1.0-canary.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.
- package/LICENSE +12 -12
- package/README.md +63 -63
- package/dist/api/console.d.ts +28 -5
- package/dist/api/console.js +305 -137
- package/dist/api/native-server.js +25 -4
- package/dist/bin/vatts.js +192 -4
- package/dist/builder.js +70 -69
- package/dist/core-go/core-linux-arm64.node +0 -0
- package/dist/core-go/core-linux-x64.node +0 -0
- package/dist/core-go/core-win-x64.node +0 -0
- package/dist/global/global.d.ts +176 -176
- package/dist/helpers.d.ts +2 -2
- package/dist/helpers.js +20 -41
- package/dist/hotReload.js +205 -205
- package/dist/index.d.ts +2 -1
- package/dist/index.js +104 -19
- package/dist/loaders.js +15 -15
- package/dist/react/BuildingPage.js +202 -202
- package/dist/react/DefaultNotFound.js +16 -16
- package/dist/react/DevIndicator.js +101 -101
- package/dist/react/entry.client.js +7 -8
- package/dist/react/image/Image.js +1 -1
- package/dist/react/renderer-react.js +270 -33
- package/dist/react/server-error.d.ts +8 -0
- package/dist/react/server-error.js +346 -0
- package/dist/router.js +1 -65
- package/dist/types.d.ts +1 -5
- package/dist/utils/utils.d.ts +1 -0
- package/dist/utils/utils.js +68 -0
- package/dist/vue/App.vue +191 -191
- package/dist/vue/BuildingPage.vue +280 -280
- package/dist/vue/DefaultNotFound.vue +328 -328
- package/dist/vue/DevIndicator.vue +225 -225
- package/dist/vue/ErrorModal.vue +316 -316
- package/dist/vue/Link.vue +38 -38
- package/dist/vue/entry.client.js +7 -7
- package/dist/vue/image/Image.vue +106 -106
- package/dist/vue/renderer.vue.js +266 -46
- package/dist/vue/server-error.vue +351 -0
- package/package.json +1 -1
package/dist/hotReload.js
CHANGED
|
@@ -412,211 +412,211 @@ class HotReloadManager {
|
|
|
412
412
|
// ... (rest of methods like getClientScript, clearBackendCache keep unchanged or minimally adjusted)
|
|
413
413
|
// Script do cliente otimizado com reconnection backoff
|
|
414
414
|
getClientScript() {
|
|
415
|
-
return `
|
|
416
|
-
<script>
|
|
417
|
-
(function() {
|
|
418
|
-
if (typeof window !== 'undefined') {
|
|
419
|
-
let ws;
|
|
420
|
-
let reconnectAttempts = 0;
|
|
421
|
-
let maxReconnectInterval = 30000;
|
|
422
|
-
let reconnectInterval = 1000;
|
|
423
|
-
let reconnectTimer;
|
|
424
|
-
let isConnected = false;
|
|
425
|
-
let lastBuildError = null;
|
|
426
|
-
|
|
427
|
-
function emitBuildError(error) {
|
|
428
|
-
try {
|
|
429
|
-
lastBuildError = error;
|
|
430
|
-
// Não persistimos erro no client. Ele sempre vem do servidor via WebSocket.
|
|
431
|
-
window.dispatchEvent(new CustomEvent('vatts:build-error', { detail: error }));
|
|
432
|
-
} catch {}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
function clearBuildError() {
|
|
436
|
-
try {
|
|
437
|
-
lastBuildError = null;
|
|
438
|
-
window.dispatchEvent(new CustomEvent('vatts:build-ok', { detail: { ts: Date.now() } }));
|
|
439
|
-
} catch {}
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
// Não faz bootstrap de erro salvo (sem sessionStorage/localStorage).
|
|
443
|
-
|
|
444
|
-
function notifyHotReloadState(state, payload) {
|
|
445
|
-
try {
|
|
446
|
-
window.__VATTS_HOT_RELOAD__ = { state: state, payload: payload || null, ts: Date.now() };
|
|
447
|
-
const ev = new CustomEvent('vatts:hotreload', { detail: window.__VATTS_HOT_RELOAD__ });
|
|
448
|
-
window.dispatchEvent(ev);
|
|
449
|
-
} catch {}
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
function requestBuildStatus(reason) {
|
|
453
|
-
try {
|
|
454
|
-
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
455
|
-
ws.send(JSON.stringify({ type: 'status-request', reason: reason || 'unknown', ts: Date.now() }));
|
|
456
|
-
} catch {}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
async function waitForMainToBeUpdated(timeoutMs, pollMs) {
|
|
460
|
-
try {
|
|
461
|
-
const script = document.querySelector('script[src*="main.js"]');
|
|
462
|
-
if (!script || !script.src) return true;
|
|
463
|
-
const mainUrl = script.src.split('?')[0];
|
|
464
|
-
const start = Date.now();
|
|
465
|
-
let baseline = '';
|
|
466
|
-
try {
|
|
467
|
-
const r0 = await fetch(mainUrl + '?t=' + Date.now(), { cache: 'no-store' });
|
|
468
|
-
baseline = await r0.text();
|
|
469
|
-
} catch { return true; }
|
|
470
|
-
|
|
471
|
-
while (Date.now() - start < timeoutMs) {
|
|
472
|
-
await new Promise(r => setTimeout(r, pollMs));
|
|
473
|
-
try {
|
|
474
|
-
const r = await fetch(mainUrl + '?t=' + Date.now(), { cache: 'no-store' });
|
|
475
|
-
const txt = await r.text();
|
|
476
|
-
if (txt && txt !== baseline) return true;
|
|
477
|
-
} catch {}
|
|
478
|
-
}
|
|
479
|
-
return false;
|
|
480
|
-
} catch { return true; }
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
function connect() {
|
|
484
|
-
const url = window.location;
|
|
485
|
-
const protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
486
|
-
const wsUrl = protocol + '//' + url.host + '/hweb-hotreload/';
|
|
487
|
-
if (ws && (ws.readyState === WebSocket.CONNECTING || ws.readyState === WebSocket.OPEN)) return;
|
|
488
|
-
|
|
489
|
-
try {
|
|
490
|
-
ws = new WebSocket(wsUrl);
|
|
491
|
-
ws.onopen = function() {
|
|
492
|
-
console.log('\u001b[32m[vatts]\u001b[0m Hot-reload connected');
|
|
493
|
-
isConnected = true;
|
|
494
|
-
reconnectAttempts = 0;
|
|
495
|
-
clearTimeout(reconnectTimer);
|
|
496
|
-
// Ao conectar, sempre pede o status atual ao servidor.
|
|
497
|
-
requestBuildStatus('ws-open');
|
|
498
|
-
};
|
|
499
|
-
|
|
500
|
-
ws.onmessage = function(event) {
|
|
501
|
-
try {
|
|
502
|
-
const message = JSON.parse(event.data);
|
|
503
|
-
switch(message.type) {
|
|
504
|
-
case 'frontend-reload':
|
|
505
|
-
handleFrontendReload(message.data);
|
|
506
|
-
break;
|
|
507
|
-
case 'backend-api-reload':
|
|
508
|
-
console.log('[vatts] Backend changed, reloading page...');
|
|
509
|
-
window.location.reload();
|
|
510
|
-
break;
|
|
511
|
-
case 'server-restart':
|
|
512
|
-
console.log('[vatts] Server restarting...');
|
|
513
|
-
break;
|
|
514
|
-
case 'server-ready':
|
|
515
|
-
setTimeout(() => window.location.reload(), 500);
|
|
516
|
-
break;
|
|
517
|
-
case 'build-error':
|
|
518
|
-
console.log('Erro detectado')
|
|
519
|
-
notifyHotReloadState('build-error', message.data);
|
|
520
|
-
emitBuildError(message.data);
|
|
521
|
-
break;
|
|
522
|
-
case 'build-complete':
|
|
523
|
-
notifyHotReloadState('idle', { build: 'ok' });
|
|
524
|
-
clearBuildError();
|
|
525
|
-
console.log('[vatts] Build complete');
|
|
526
|
-
break;
|
|
527
|
-
}
|
|
528
|
-
} catch (e) {
|
|
529
|
-
console.error('[vatts] Error processing msg:', e);
|
|
530
|
-
}
|
|
531
|
-
};
|
|
532
|
-
|
|
533
|
-
async function handleFrontendReload(data) {
|
|
534
|
-
notifyHotReloadState('reloading', { type: 'frontend', data: data || null });
|
|
535
|
-
if (!data || !data.file) {
|
|
536
|
-
window.dispatchEvent(new CustomEvent('hmr:component-update', { detail: { file: null, timestamp: Date.now() } }));
|
|
537
|
-
return;
|
|
538
|
-
}
|
|
539
|
-
const file = (data.file || '').toLowerCase();
|
|
540
|
-
console.log('[vatts] Frontend changed:', data.file);
|
|
541
|
-
const needsFullReload = file.includes('layout.tsx') || file.includes('not-found.tsx') || file.endsWith('.css');
|
|
542
|
-
if (needsFullReload) {
|
|
543
|
-
notifyHotReloadState('full-reload', { reason: 'layout/css', file: data.file });
|
|
544
|
-
window.location.reload();
|
|
545
|
-
return;
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
// Recarrega main.js (o bundle do app) com cache-busting.
|
|
549
|
-
// Em DEV, React/ReactDOM agora são externos, então isso não duplica hooks.
|
|
550
|
-
await waitForMainToBeUpdated(5000, 120);
|
|
551
|
-
|
|
552
|
-
const script = document.querySelector('script[src*="main.js"]');
|
|
553
|
-
if (script) {
|
|
554
|
-
const mainUrl = (script.getAttribute('src') || script.src || '');
|
|
555
|
-
const base = String(mainUrl).split('?')[0];
|
|
556
|
-
|
|
557
|
-
// Remove scripts antigos de main.js para evitar múltiplas execuções do bundle
|
|
558
|
-
try {
|
|
559
|
-
document.querySelectorAll('script[src*="main.js"]').forEach(s => {
|
|
560
|
-
if (s && s.parentNode) s.parentNode.removeChild(s);
|
|
561
|
-
});
|
|
562
|
-
} catch {}
|
|
563
|
-
|
|
564
|
-
const newScript = document.createElement('script');
|
|
565
|
-
newScript.type = 'module';
|
|
566
|
-
newScript.src = base + '?t=' + Date.now();
|
|
567
|
-
|
|
568
|
-
newScript.onload = () => {
|
|
569
|
-
// depois de carregar o novo bundle, pede render/update
|
|
570
|
-
const ts = Date.now();
|
|
571
|
-
window.dispatchEvent(new CustomEvent('hmr:component-update', { detail: { file: data.file, timestamp: ts } }));
|
|
572
|
-
setTimeout(() => requestBuildStatus('after-frontend-reload'), 50);
|
|
573
|
-
};
|
|
574
|
-
|
|
575
|
-
newScript.onerror = () => {
|
|
576
|
-
window.location.reload();
|
|
577
|
-
};
|
|
578
|
-
|
|
579
|
-
document.head.appendChild(newScript);
|
|
580
|
-
} else {
|
|
581
|
-
// fallback
|
|
582
|
-
const ts = Date.now();
|
|
583
|
-
window.dispatchEvent(new CustomEvent('hmr:component-update', { detail: { file: data.file, timestamp: ts } }));
|
|
584
|
-
setTimeout(() => requestBuildStatus('after-frontend-reload'), 50);
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
setTimeout(() => {
|
|
588
|
-
if (window.__HMR_SUCCESS__) {
|
|
589
|
-
notifyHotReloadState('idle', { success: true, file: data.file });
|
|
590
|
-
requestBuildStatus('after-hmr-success');
|
|
591
|
-
return;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
window.location.reload();
|
|
595
|
-
}, 1600);
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
ws.onclose = function(event) {
|
|
599
|
-
isConnected = false;
|
|
600
|
-
if (event.code === 1000) return;
|
|
601
|
-
scheduleReconnect();
|
|
602
|
-
};
|
|
603
|
-
ws.onerror = function() {};
|
|
604
|
-
} catch (error) { scheduleReconnect(); }
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
function scheduleReconnect() {
|
|
608
|
-
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
609
|
-
reconnectAttempts++;
|
|
610
|
-
const baseInterval = Math.min(reconnectInterval * Math.pow(1.5, reconnectAttempts - 1), maxReconnectInterval);
|
|
611
|
-
reconnectTimer = setTimeout(() => { if (!isConnected) connect(); }, baseInterval + Math.random() * 1000);
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
window.addEventListener('beforeunload', function() { if (ws && ws.readyState === WebSocket.OPEN) ws.close(1000, 'Page unloading'); });
|
|
615
|
-
document.addEventListener('visibilitychange', function() { if (!document.hidden && !isConnected) { reconnectAttempts = 0; connect(); } });
|
|
616
|
-
connect();
|
|
617
|
-
}
|
|
618
|
-
})();
|
|
619
|
-
</script>
|
|
415
|
+
return `
|
|
416
|
+
<script>
|
|
417
|
+
(function() {
|
|
418
|
+
if (typeof window !== 'undefined') {
|
|
419
|
+
let ws;
|
|
420
|
+
let reconnectAttempts = 0;
|
|
421
|
+
let maxReconnectInterval = 30000;
|
|
422
|
+
let reconnectInterval = 1000;
|
|
423
|
+
let reconnectTimer;
|
|
424
|
+
let isConnected = false;
|
|
425
|
+
let lastBuildError = null;
|
|
426
|
+
|
|
427
|
+
function emitBuildError(error) {
|
|
428
|
+
try {
|
|
429
|
+
lastBuildError = error;
|
|
430
|
+
// Não persistimos erro no client. Ele sempre vem do servidor via WebSocket.
|
|
431
|
+
window.dispatchEvent(new CustomEvent('vatts:build-error', { detail: error }));
|
|
432
|
+
} catch {}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function clearBuildError() {
|
|
436
|
+
try {
|
|
437
|
+
lastBuildError = null;
|
|
438
|
+
window.dispatchEvent(new CustomEvent('vatts:build-ok', { detail: { ts: Date.now() } }));
|
|
439
|
+
} catch {}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Não faz bootstrap de erro salvo (sem sessionStorage/localStorage).
|
|
443
|
+
|
|
444
|
+
function notifyHotReloadState(state, payload) {
|
|
445
|
+
try {
|
|
446
|
+
window.__VATTS_HOT_RELOAD__ = { state: state, payload: payload || null, ts: Date.now() };
|
|
447
|
+
const ev = new CustomEvent('vatts:hotreload', { detail: window.__VATTS_HOT_RELOAD__ });
|
|
448
|
+
window.dispatchEvent(ev);
|
|
449
|
+
} catch {}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function requestBuildStatus(reason) {
|
|
453
|
+
try {
|
|
454
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
455
|
+
ws.send(JSON.stringify({ type: 'status-request', reason: reason || 'unknown', ts: Date.now() }));
|
|
456
|
+
} catch {}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
async function waitForMainToBeUpdated(timeoutMs, pollMs) {
|
|
460
|
+
try {
|
|
461
|
+
const script = document.querySelector('script[src*="main.js"]');
|
|
462
|
+
if (!script || !script.src) return true;
|
|
463
|
+
const mainUrl = script.src.split('?')[0];
|
|
464
|
+
const start = Date.now();
|
|
465
|
+
let baseline = '';
|
|
466
|
+
try {
|
|
467
|
+
const r0 = await fetch(mainUrl + '?t=' + Date.now(), { cache: 'no-store' });
|
|
468
|
+
baseline = await r0.text();
|
|
469
|
+
} catch { return true; }
|
|
470
|
+
|
|
471
|
+
while (Date.now() - start < timeoutMs) {
|
|
472
|
+
await new Promise(r => setTimeout(r, pollMs));
|
|
473
|
+
try {
|
|
474
|
+
const r = await fetch(mainUrl + '?t=' + Date.now(), { cache: 'no-store' });
|
|
475
|
+
const txt = await r.text();
|
|
476
|
+
if (txt && txt !== baseline) return true;
|
|
477
|
+
} catch {}
|
|
478
|
+
}
|
|
479
|
+
return false;
|
|
480
|
+
} catch { return true; }
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function connect() {
|
|
484
|
+
const url = window.location;
|
|
485
|
+
const protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
486
|
+
const wsUrl = protocol + '//' + url.host + '/hweb-hotreload/';
|
|
487
|
+
if (ws && (ws.readyState === WebSocket.CONNECTING || ws.readyState === WebSocket.OPEN)) return;
|
|
488
|
+
|
|
489
|
+
try {
|
|
490
|
+
ws = new WebSocket(wsUrl);
|
|
491
|
+
ws.onopen = function() {
|
|
492
|
+
console.log('\u001b[32m[vatts]\u001b[0m Hot-reload connected');
|
|
493
|
+
isConnected = true;
|
|
494
|
+
reconnectAttempts = 0;
|
|
495
|
+
clearTimeout(reconnectTimer);
|
|
496
|
+
// Ao conectar, sempre pede o status atual ao servidor.
|
|
497
|
+
requestBuildStatus('ws-open');
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
ws.onmessage = function(event) {
|
|
501
|
+
try {
|
|
502
|
+
const message = JSON.parse(event.data);
|
|
503
|
+
switch(message.type) {
|
|
504
|
+
case 'frontend-reload':
|
|
505
|
+
handleFrontendReload(message.data);
|
|
506
|
+
break;
|
|
507
|
+
case 'backend-api-reload':
|
|
508
|
+
console.log('[vatts] Backend changed, reloading page...');
|
|
509
|
+
window.location.reload();
|
|
510
|
+
break;
|
|
511
|
+
case 'server-restart':
|
|
512
|
+
console.log('[vatts] Server restarting...');
|
|
513
|
+
break;
|
|
514
|
+
case 'server-ready':
|
|
515
|
+
setTimeout(() => window.location.reload(), 500);
|
|
516
|
+
break;
|
|
517
|
+
case 'build-error':
|
|
518
|
+
console.log('Erro detectado')
|
|
519
|
+
notifyHotReloadState('build-error', message.data);
|
|
520
|
+
emitBuildError(message.data);
|
|
521
|
+
break;
|
|
522
|
+
case 'build-complete':
|
|
523
|
+
notifyHotReloadState('idle', { build: 'ok' });
|
|
524
|
+
clearBuildError();
|
|
525
|
+
console.log('[vatts] Build complete');
|
|
526
|
+
break;
|
|
527
|
+
}
|
|
528
|
+
} catch (e) {
|
|
529
|
+
console.error('[vatts] Error processing msg:', e);
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
async function handleFrontendReload(data) {
|
|
534
|
+
notifyHotReloadState('reloading', { type: 'frontend', data: data || null });
|
|
535
|
+
if (!data || !data.file) {
|
|
536
|
+
window.dispatchEvent(new CustomEvent('hmr:component-update', { detail: { file: null, timestamp: Date.now() } }));
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
const file = (data.file || '').toLowerCase();
|
|
540
|
+
console.log('[vatts] Frontend changed:', data.file);
|
|
541
|
+
const needsFullReload = file.includes('layout.tsx') || file.includes('not-found.tsx') || file.endsWith('.css');
|
|
542
|
+
if (needsFullReload) {
|
|
543
|
+
notifyHotReloadState('full-reload', { reason: 'layout/css', file: data.file });
|
|
544
|
+
window.location.reload();
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Recarrega main.js (o bundle do app) com cache-busting.
|
|
549
|
+
// Em DEV, React/ReactDOM agora são externos, então isso não duplica hooks.
|
|
550
|
+
await waitForMainToBeUpdated(5000, 120);
|
|
551
|
+
|
|
552
|
+
const script = document.querySelector('script[src*="main.js"]');
|
|
553
|
+
if (script) {
|
|
554
|
+
const mainUrl = (script.getAttribute('src') || script.src || '');
|
|
555
|
+
const base = String(mainUrl).split('?')[0];
|
|
556
|
+
|
|
557
|
+
// Remove scripts antigos de main.js para evitar múltiplas execuções do bundle
|
|
558
|
+
try {
|
|
559
|
+
document.querySelectorAll('script[src*="main.js"]').forEach(s => {
|
|
560
|
+
if (s && s.parentNode) s.parentNode.removeChild(s);
|
|
561
|
+
});
|
|
562
|
+
} catch {}
|
|
563
|
+
|
|
564
|
+
const newScript = document.createElement('script');
|
|
565
|
+
newScript.type = 'module';
|
|
566
|
+
newScript.src = base + '?t=' + Date.now();
|
|
567
|
+
|
|
568
|
+
newScript.onload = () => {
|
|
569
|
+
// depois de carregar o novo bundle, pede render/update
|
|
570
|
+
const ts = Date.now();
|
|
571
|
+
window.dispatchEvent(new CustomEvent('hmr:component-update', { detail: { file: data.file, timestamp: ts } }));
|
|
572
|
+
setTimeout(() => requestBuildStatus('after-frontend-reload'), 50);
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
newScript.onerror = () => {
|
|
576
|
+
window.location.reload();
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
document.head.appendChild(newScript);
|
|
580
|
+
} else {
|
|
581
|
+
// fallback
|
|
582
|
+
const ts = Date.now();
|
|
583
|
+
window.dispatchEvent(new CustomEvent('hmr:component-update', { detail: { file: data.file, timestamp: ts } }));
|
|
584
|
+
setTimeout(() => requestBuildStatus('after-frontend-reload'), 50);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
setTimeout(() => {
|
|
588
|
+
if (window.__HMR_SUCCESS__) {
|
|
589
|
+
notifyHotReloadState('idle', { success: true, file: data.file });
|
|
590
|
+
requestBuildStatus('after-hmr-success');
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
window.location.reload();
|
|
595
|
+
}, 1600);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
ws.onclose = function(event) {
|
|
599
|
+
isConnected = false;
|
|
600
|
+
if (event.code === 1000) return;
|
|
601
|
+
scheduleReconnect();
|
|
602
|
+
};
|
|
603
|
+
ws.onerror = function() {};
|
|
604
|
+
} catch (error) { scheduleReconnect(); }
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function scheduleReconnect() {
|
|
608
|
+
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
609
|
+
reconnectAttempts++;
|
|
610
|
+
const baseInterval = Math.min(reconnectInterval * Math.pow(1.5, reconnectAttempts - 1), maxReconnectInterval);
|
|
611
|
+
reconnectTimer = setTimeout(() => { if (!isConnected) connect(); }, baseInterval + Math.random() * 1000);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
window.addEventListener('beforeunload', function() { if (ws && ws.readyState === WebSocket.OPEN) ws.close(1000, 'Page unloading'); });
|
|
615
|
+
document.addEventListener('visibilitychange', function() { if (!document.hidden && !isConnected) { reconnectAttempts = 0; connect(); } });
|
|
616
|
+
connect();
|
|
617
|
+
}
|
|
618
|
+
})();
|
|
619
|
+
</script>
|
|
620
620
|
`;
|
|
621
621
|
}
|
|
622
622
|
// Mantendo métodos de cache iguais
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BackendHandler, BackendRouteConfig, VattsOptions, RequestHandler } from './types';
|
|
2
2
|
import { VattsRequest, VattsResponse } from './api/http';
|
|
3
|
+
export declare function printTotalStats(outDir: string, https: boolean): Promise<void>;
|
|
3
4
|
export { VattsRequest, VattsResponse };
|
|
4
5
|
export type { BackendRouteConfig, BackendHandler };
|
|
5
6
|
export { ExpressAdapter } from './adapters/express';
|
|
@@ -10,7 +11,7 @@ export { app } from './helpers';
|
|
|
10
11
|
export type { WebSocketContext, WebSocketHandler } from './types';
|
|
11
12
|
export type { VattsConfig, VattsConfigFunction } from './types';
|
|
12
13
|
export default function vatts(options: VattsOptions): {
|
|
13
|
-
prepare: () => Promise<void>;
|
|
14
|
+
prepare: (skipBuild?: boolean) => Promise<void>;
|
|
14
15
|
executeInstrumentation: () => void;
|
|
15
16
|
getRequestHandler: () => RequestHandler;
|
|
16
17
|
setupWebSocket: (server: any) => void;
|
package/dist/index.js
CHANGED
|
@@ -53,9 +53,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
53
53
|
};
|
|
54
54
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
55
55
|
exports.app = exports.FrameworkAdapterFactory = exports.FastifyAdapter = exports.ExpressAdapter = exports.VattsResponse = exports.VattsRequest = void 0;
|
|
56
|
+
exports.printTotalStats = printTotalStats;
|
|
56
57
|
exports.default = vatts;
|
|
57
58
|
const path_1 = __importDefault(require("path"));
|
|
58
59
|
const fs_1 = __importDefault(require("fs"));
|
|
60
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
59
61
|
const crypto_1 = __importDefault(require("crypto")); // Adicionado para gerar hash do cache
|
|
60
62
|
const express_1 = require("./adapters/express");
|
|
61
63
|
const builder_1 = require("./builder");
|
|
@@ -225,6 +227,77 @@ async function handleImageOptimization(req, res, projectDir) {
|
|
|
225
227
|
res.status(500).text('Image optimization failed');
|
|
226
228
|
}
|
|
227
229
|
}
|
|
230
|
+
const node_zlib_1 = __importDefault(require("node:zlib")); // Certifique-se de ter este import no topo
|
|
231
|
+
async function printTotalStats(outDir, https) {
|
|
232
|
+
let totalOrig = 0;
|
|
233
|
+
let totalGzip = 0;
|
|
234
|
+
let totalBr = 0;
|
|
235
|
+
// REMOVIDO: O loop que lia 'entries' para somar originais foi apagado.
|
|
236
|
+
// Walk recursivo no outDir pra somar .gz e .br e calcular o original reverso
|
|
237
|
+
async function walk(dir) {
|
|
238
|
+
let dirents;
|
|
239
|
+
try {
|
|
240
|
+
dirents = await promises_1.default.readdir(dir, { withFileTypes: true });
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
for (const d of dirents) {
|
|
246
|
+
const full = path_1.default.join(dir, d.name);
|
|
247
|
+
if (d.isDirectory()) {
|
|
248
|
+
await walk(full);
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
if (!d.isFile())
|
|
252
|
+
continue;
|
|
253
|
+
let st;
|
|
254
|
+
try {
|
|
255
|
+
st = await promises_1.default.stat(full);
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
const name = d.name;
|
|
261
|
+
const isGz = name.endsWith(".gz");
|
|
262
|
+
const isBr = name.endsWith(".br");
|
|
263
|
+
if (isGz)
|
|
264
|
+
totalGzip += st.size;
|
|
265
|
+
else if (isBr)
|
|
266
|
+
totalBr += st.size;
|
|
267
|
+
// Lógica nova: Se o arquivo for do tipo que estamos priorizando (https = br, !https = gz),
|
|
268
|
+
// descomprimimos para pegar o tamanho original.
|
|
269
|
+
if ((https && isBr) || (!https && isGz)) {
|
|
270
|
+
try {
|
|
271
|
+
const content = await promises_1.default.readFile(full);
|
|
272
|
+
if (isBr) {
|
|
273
|
+
// Descomprime Brotli síncrono para pegar tamanho
|
|
274
|
+
totalOrig += node_zlib_1.default.brotliDecompressSync(content).length;
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
// Descomprime Gzip síncrono para pegar tamanho
|
|
278
|
+
totalOrig += node_zlib_1.default.gunzipSync(content).length;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
catch (e) {
|
|
282
|
+
// Ignora erro de descompressão se houver
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
await walk(outDir);
|
|
288
|
+
// O totalOrig agora é calculado baseado na descompressão, se for 0 não tem o que mostrar
|
|
289
|
+
if (totalOrig <= 0)
|
|
290
|
+
return;
|
|
291
|
+
const finalSize = https ? totalBr : totalGzip;
|
|
292
|
+
if (finalSize <= 0)
|
|
293
|
+
return;
|
|
294
|
+
const diff = totalOrig - finalSize;
|
|
295
|
+
const pct = (diff / totalOrig) * 100;
|
|
296
|
+
const original = ` Original : ${console_1.Colors.Bright}${console_1.Colors.FgGreen}${(totalOrig / 1024).toFixed(2)} KB`;
|
|
297
|
+
const finalStr = ` Final : ${console_1.Colors.Bright}${console_1.Colors.FgGreen}${(finalSize / 1024).toFixed(2)} KB`;
|
|
298
|
+
const saved = ` Saved : ${console_1.Colors.Bright}${console_1.Colors.FgGreen}${(diff / 1024).toFixed(2)} KB ${console_1.Colors.Reset}${console_1.Colors.FgGray}(${pct.toFixed(2)}%)${console_1.Colors.Reset} \n`;
|
|
299
|
+
console_1.default.logCustomLevel("", false, undefined, "", `${console_1.Colors.FgBlue}${console_1.Colors.Bright}Optimization summary:${console_1.Colors.Reset}`, original, finalStr, saved);
|
|
300
|
+
}
|
|
228
301
|
// Exporta os adapters para uso manual se necessário
|
|
229
302
|
var express_2 = require("./adapters/express");
|
|
230
303
|
Object.defineProperty(exports, "ExpressAdapter", { enumerable: true, get: function () { return express_2.ExpressAdapter; } });
|
|
@@ -275,7 +348,6 @@ function vatts(options) {
|
|
|
275
348
|
notFound: null,
|
|
276
349
|
framework: framework,
|
|
277
350
|
projectDir: dir,
|
|
278
|
-
pathRouter: helpers_1.config?.pathRouter
|
|
279
351
|
};
|
|
280
352
|
const updateBuilderOptions = () => {
|
|
281
353
|
const newFrontendRoutes = (0, router_1.loadRoutes)(userWebRoutesDir);
|
|
@@ -294,7 +366,7 @@ function vatts(options) {
|
|
|
294
366
|
vattsBuilderOptions.notFound = newNotFound;
|
|
295
367
|
};
|
|
296
368
|
return {
|
|
297
|
-
prepare: async () => {
|
|
369
|
+
prepare: async (skipBuild = false) => {
|
|
298
370
|
const isProduction = !dev;
|
|
299
371
|
if (!isProduction) {
|
|
300
372
|
hotReloadManager = new hotReload_1.HotReloadManager(dir);
|
|
@@ -329,32 +401,45 @@ function vatts(options) {
|
|
|
329
401
|
clearInterval(spinner1);
|
|
330
402
|
timee.end(`Routes and components loaded in ${Date.now() - now}ms`);
|
|
331
403
|
if (isProduction) {
|
|
332
|
-
|
|
404
|
+
// Pula o build se skipBuild for true (usado quando já existe build)
|
|
405
|
+
if (!skipBuild) {
|
|
406
|
+
const time = console_1.default.dynamicLine(`Starting client build`);
|
|
407
|
+
const spinnerFrames = ['|', '/', '-', '\\'];
|
|
408
|
+
let frameIndex = 0;
|
|
409
|
+
const spinner = setInterval(() => {
|
|
410
|
+
time.update(` ${console_1.Colors.FgGreen}${spinnerFrames[frameIndex]}${console_1.Colors.Reset} Building...`);
|
|
411
|
+
frameIndex = (frameIndex + 1) % spinnerFrames.length;
|
|
412
|
+
}, 100);
|
|
413
|
+
const now = Date.now();
|
|
414
|
+
// Passa o objeto de opções ao invés do caminho do arquivo
|
|
415
|
+
await (0, builder_1.buildWithChunks)(vattsBuilderOptions, outDir, isProduction);
|
|
416
|
+
const elapsed = Date.now() - now;
|
|
417
|
+
clearInterval(spinner);
|
|
418
|
+
time.update("");
|
|
419
|
+
await printTotalStats(outDir, helpers_1.config?.ssl !== undefined && helpers_1.config.ssl !== null);
|
|
420
|
+
time.end(`Client build completed in ${elapsed}ms`);
|
|
421
|
+
if (hotReloadManager) {
|
|
422
|
+
hotReloadManager.onBuildComplete(true);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
const time = console_1.default.dynamicLine(` ${console_1.Colors.BgYellow} watcher ${console_1.Colors.Reset} Building initial assets`);
|
|
333
428
|
const spinnerFrames = ['|', '/', '-', '\\'];
|
|
334
429
|
let frameIndex = 0;
|
|
335
430
|
const spinner = setInterval(() => {
|
|
336
|
-
time.update(` ${console_1.Colors.
|
|
431
|
+
time.update(` ${console_1.Colors.FgYellow}${spinnerFrames[frameIndex]}${console_1.Colors.Reset} Building initial assets...`);
|
|
337
432
|
frameIndex = (frameIndex + 1) % spinnerFrames.length;
|
|
338
433
|
}, 100);
|
|
339
|
-
const
|
|
340
|
-
// Passa o objeto de opções ao invés do caminho do arquivo
|
|
341
|
-
await (0, builder_1.buildWithChunks)(vattsBuilderOptions, outDir, isProduction);
|
|
342
|
-
const elapsed = Date.now() - now;
|
|
343
|
-
clearInterval(spinner);
|
|
344
|
-
time.update("");
|
|
345
|
-
time.end(`Client build completed in ${elapsed}ms`);
|
|
346
|
-
if (hotReloadManager) {
|
|
347
|
-
hotReloadManager.onBuildComplete(true);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
else {
|
|
351
|
-
const time = console_1.default.dynamicLine(` ${console_1.Colors.BgYellow} watcher ${console_1.Colors.Reset} Starting client watch`);
|
|
434
|
+
const buildStart = Date.now();
|
|
352
435
|
// @ts-ignore
|
|
353
436
|
// Passa o objeto de opções ao invés do caminho do arquivo
|
|
354
|
-
|
|
437
|
+
// Aguarda o primeiro build completar para evitar lentidão na primeira requisição
|
|
438
|
+
await (0, builder_1.watchWithChunks)(vattsBuilderOptions, outDir, hotReloadManager).catch(err => {
|
|
355
439
|
console_1.default.error(`Error starting watch`, err);
|
|
356
440
|
});
|
|
357
|
-
|
|
441
|
+
clearInterval(spinner);
|
|
442
|
+
time.end(`Initial build complete in ${Date.now() - buildStart}ms - watching for changes...`);
|
|
358
443
|
}
|
|
359
444
|
},
|
|
360
445
|
executeInstrumentation: () => {
|
package/dist/loaders.js
CHANGED
|
@@ -164,21 +164,21 @@ function registerLoaders(options = {}) {
|
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
166
|
// 4. Montagem do Código Final (ESM Virtual)
|
|
167
|
-
finalEsm = `
|
|
168
|
-
${scriptContent}
|
|
169
|
-
${templateContent}
|
|
170
|
-
|
|
171
|
-
// Anexa a função de renderização SSR ao componente principal
|
|
172
|
-
if (typeof _sfc_main !== 'undefined') {
|
|
173
|
-
if (typeof ssrRender !== 'undefined') {
|
|
174
|
-
_sfc_main.ssrRender = ssrRender;
|
|
175
|
-
}
|
|
176
|
-
if (typeof render !== 'undefined') {
|
|
177
|
-
_sfc_main.render = render;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export default _sfc_main;
|
|
167
|
+
finalEsm = `
|
|
168
|
+
${scriptContent}
|
|
169
|
+
${templateContent}
|
|
170
|
+
|
|
171
|
+
// Anexa a função de renderização SSR ao componente principal
|
|
172
|
+
if (typeof _sfc_main !== 'undefined') {
|
|
173
|
+
if (typeof ssrRender !== 'undefined') {
|
|
174
|
+
_sfc_main.ssrRender = ssrRender;
|
|
175
|
+
}
|
|
176
|
+
if (typeof render !== 'undefined') {
|
|
177
|
+
_sfc_main.render = render;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export default _sfc_main;
|
|
182
182
|
`;
|
|
183
183
|
// 5. Transformação final para CommonJS (Node.js) via Esbuild
|
|
184
184
|
const result = esbuild.transformSync(finalEsm, {
|