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
|
@@ -78,107 +78,107 @@ function DevIndicator({ hasBuildError = false, onClickBuildError, }) {
|
|
|
78
78
|
const isError = !!hasBuildError;
|
|
79
79
|
// Criamos o elemento via Portal para injetar no final do <body>
|
|
80
80
|
return (0, react_dom_1.createPortal)(react_1.default.createElement(react_1.default.Fragment, null,
|
|
81
|
-
react_1.default.createElement("style", null, `
|
|
82
|
-
@keyframes vatts-pulse {
|
|
83
|
-
0% { opacity: 0.4; }
|
|
84
|
-
50% { opacity: 1; }
|
|
85
|
-
100% { opacity: 0.4; }
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
@keyframes vatts-spin {
|
|
89
|
-
0% { transform: rotate(0deg); }
|
|
90
|
-
100% { transform: rotate(360deg); }
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
.vatts-dev-badge {
|
|
94
|
-
position: fixed;
|
|
95
|
-
bottom: 20px;
|
|
96
|
-
right: 20px;
|
|
97
|
-
/* Z-index absurdo para garantir que fique acima de tudo */
|
|
98
|
-
z-index: 2147483647;
|
|
99
|
-
|
|
100
|
-
display: flex;
|
|
101
|
-
align-items: center;
|
|
102
|
-
gap: 12px;
|
|
103
|
-
padding: 8px 14px;
|
|
104
|
-
background: rgba(0, 0, 0, 0.85);
|
|
105
|
-
backdrop-filter: blur(12px);
|
|
106
|
-
-webkit-backdrop-filter: blur(12px);
|
|
107
|
-
|
|
108
|
-
border-radius: 10px;
|
|
109
|
-
color: #fff;
|
|
110
|
-
font-family: 'Inter', system-ui, sans-serif;
|
|
111
|
-
font-size: 11px;
|
|
112
|
-
font-weight: 600;
|
|
113
|
-
letter-spacing: 0.05em;
|
|
114
|
-
|
|
115
|
-
/* Estilo Monocromático Next.js */
|
|
116
|
-
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
117
|
-
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
|
|
118
|
-
transition: all 0.2s ease;
|
|
119
|
-
cursor: default;
|
|
120
|
-
user-select: none;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
.vatts-dev-badge.clickable {
|
|
124
|
-
cursor: pointer;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
.vatts-dev-badge:hover {
|
|
128
|
-
border-color: rgba(255, 255, 255, 0.3);
|
|
129
|
-
transform: translateY(-2px);
|
|
130
|
-
background: rgba(10, 10, 10, 0.95);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
.vatts-status-dot {
|
|
134
|
-
width: 7px;
|
|
135
|
-
height: 7px;
|
|
136
|
-
background: #ffffff; /* Branco para status OK (Estilo Vercel) */
|
|
137
|
-
border-radius: 50%;
|
|
138
|
-
box-shadow: 0 0 8px rgba(255, 255, 255, 0.3);
|
|
139
|
-
animation: vatts-pulse 2.5s infinite ease-in-out;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
.vatts-status-dot.reloading {
|
|
143
|
-
background: #64748b; /* Slate */
|
|
144
|
-
box-shadow: none;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
.vatts-status-dot.error {
|
|
148
|
-
background: #ef4444; /* Mantido vermelho por semântica de erro */
|
|
149
|
-
box-shadow: 0 0 10px #ef4444;
|
|
150
|
-
animation: vatts-pulse 1s infinite ease-in-out;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
.vatts-spinner {
|
|
154
|
-
width: 10px;
|
|
155
|
-
height: 10px;
|
|
156
|
-
border-radius: 50%;
|
|
157
|
-
border: 2px solid rgba(255,255,255,0.1);
|
|
158
|
-
border-top-color: #ffffff;
|
|
159
|
-
animation: vatts-spin 0.8s linear infinite;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
.vatts-logo {
|
|
163
|
-
color: #ffffff;
|
|
164
|
-
font-weight: 800;
|
|
165
|
-
display: flex;
|
|
166
|
-
align-items: center;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
.vatts-logo span {
|
|
170
|
-
color: #64748b;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
.vatts-error-pill {
|
|
174
|
-
margin-left: 8px;
|
|
175
|
-
padding: 2px 6px;
|
|
176
|
-
border-radius: 4px;
|
|
177
|
-
background: #ffffff;
|
|
178
|
-
color: #000000;
|
|
179
|
-
font-size: 9px;
|
|
180
|
-
font-weight: 900;
|
|
181
|
-
}
|
|
81
|
+
react_1.default.createElement("style", null, `
|
|
82
|
+
@keyframes vatts-pulse {
|
|
83
|
+
0% { opacity: 0.4; }
|
|
84
|
+
50% { opacity: 1; }
|
|
85
|
+
100% { opacity: 0.4; }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@keyframes vatts-spin {
|
|
89
|
+
0% { transform: rotate(0deg); }
|
|
90
|
+
100% { transform: rotate(360deg); }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.vatts-dev-badge {
|
|
94
|
+
position: fixed;
|
|
95
|
+
bottom: 20px;
|
|
96
|
+
right: 20px;
|
|
97
|
+
/* Z-index absurdo para garantir que fique acima de tudo */
|
|
98
|
+
z-index: 2147483647;
|
|
99
|
+
|
|
100
|
+
display: flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
gap: 12px;
|
|
103
|
+
padding: 8px 14px;
|
|
104
|
+
background: rgba(0, 0, 0, 0.85);
|
|
105
|
+
backdrop-filter: blur(12px);
|
|
106
|
+
-webkit-backdrop-filter: blur(12px);
|
|
107
|
+
|
|
108
|
+
border-radius: 10px;
|
|
109
|
+
color: #fff;
|
|
110
|
+
font-family: 'Inter', system-ui, sans-serif;
|
|
111
|
+
font-size: 11px;
|
|
112
|
+
font-weight: 600;
|
|
113
|
+
letter-spacing: 0.05em;
|
|
114
|
+
|
|
115
|
+
/* Estilo Monocromático Next.js */
|
|
116
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
117
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
|
|
118
|
+
transition: all 0.2s ease;
|
|
119
|
+
cursor: default;
|
|
120
|
+
user-select: none;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.vatts-dev-badge.clickable {
|
|
124
|
+
cursor: pointer;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.vatts-dev-badge:hover {
|
|
128
|
+
border-color: rgba(255, 255, 255, 0.3);
|
|
129
|
+
transform: translateY(-2px);
|
|
130
|
+
background: rgba(10, 10, 10, 0.95);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.vatts-status-dot {
|
|
134
|
+
width: 7px;
|
|
135
|
+
height: 7px;
|
|
136
|
+
background: #ffffff; /* Branco para status OK (Estilo Vercel) */
|
|
137
|
+
border-radius: 50%;
|
|
138
|
+
box-shadow: 0 0 8px rgba(255, 255, 255, 0.3);
|
|
139
|
+
animation: vatts-pulse 2.5s infinite ease-in-out;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.vatts-status-dot.reloading {
|
|
143
|
+
background: #64748b; /* Slate */
|
|
144
|
+
box-shadow: none;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.vatts-status-dot.error {
|
|
148
|
+
background: #ef4444; /* Mantido vermelho por semântica de erro */
|
|
149
|
+
box-shadow: 0 0 10px #ef4444;
|
|
150
|
+
animation: vatts-pulse 1s infinite ease-in-out;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.vatts-spinner {
|
|
154
|
+
width: 10px;
|
|
155
|
+
height: 10px;
|
|
156
|
+
border-radius: 50%;
|
|
157
|
+
border: 2px solid rgba(255,255,255,0.1);
|
|
158
|
+
border-top-color: #ffffff;
|
|
159
|
+
animation: vatts-spin 0.8s linear infinite;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.vatts-logo {
|
|
163
|
+
color: #ffffff;
|
|
164
|
+
font-weight: 800;
|
|
165
|
+
display: flex;
|
|
166
|
+
align-items: center;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.vatts-logo span {
|
|
170
|
+
color: #64748b;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.vatts-error-pill {
|
|
174
|
+
margin-left: 8px;
|
|
175
|
+
padding: 2px 6px;
|
|
176
|
+
border-radius: 4px;
|
|
177
|
+
background: #ffffff;
|
|
178
|
+
color: #000000;
|
|
179
|
+
font-size: 9px;
|
|
180
|
+
font-weight: 900;
|
|
181
|
+
}
|
|
182
182
|
`),
|
|
183
183
|
react_1.default.createElement("div", { className: `vatts-dev-badge${isError ? ' clickable' : ''}`, onClick: () => isError && onClickBuildError?.() },
|
|
184
184
|
isReloading ? (react_1.default.createElement("div", { className: "vatts-spinner" })) : (react_1.default.createElement("div", { className: `vatts-status-dot${isReloading ? ' reloading' : ''}${isError ? ' error' : ''}` })),
|
|
@@ -290,7 +290,6 @@ function initializeClient() {
|
|
|
290
290
|
console.warn('[Vatts] Warning during unmount:', e);
|
|
291
291
|
}
|
|
292
292
|
}
|
|
293
|
-
console.log(window.__VATTS_COMPONENTS__, initialData);
|
|
294
293
|
// Usar createRoot para render inicial (CSR)
|
|
295
294
|
const root = (0, client_1.createRoot)(container);
|
|
296
295
|
// Salva a referência globalmente
|
|
@@ -301,13 +300,13 @@ function initializeClient() {
|
|
|
301
300
|
console.error('[Watts] Critical Error rendering application:', error);
|
|
302
301
|
// Exibe erro na tela caso algo crítico falhe
|
|
303
302
|
if (typeof document !== 'undefined') {
|
|
304
|
-
document.body.innerHTML = `
|
|
305
|
-
<div style="font-family: monospace; padding: 20px; color: #ff4444; background: #1a1a1a; min-height: 100vh;">
|
|
306
|
-
<h1>Vatts Client Error</h1>
|
|
307
|
-
<p>A critical error occurred while initializing the application.</p>
|
|
308
|
-
<pre style="background: #000; padding: 15px; border-radius: 5px; overflow: auto;">${error?.message || error}</pre>
|
|
309
|
-
<pre style="color: #666; font-size: 12px; margin-top: 10px;">${error?.stack || ''}</pre>
|
|
310
|
-
</div>
|
|
303
|
+
document.body.innerHTML = `
|
|
304
|
+
<div style="font-family: monospace; padding: 20px; color: #ff4444; background: #1a1a1a; min-height: 100vh;">
|
|
305
|
+
<h1>Vatts Client Error</h1>
|
|
306
|
+
<p>A critical error occurred while initializing the application.</p>
|
|
307
|
+
<pre style="background: #000; padding: 15px; border-radius: 5px; overflow: auto;">${error?.message || error}</pre>
|
|
308
|
+
<pre style="color: #666; font-size: 12px; margin-top: 10px;">${error?.stack || ''}</pre>
|
|
309
|
+
</div>
|
|
311
310
|
`;
|
|
312
311
|
}
|
|
313
312
|
}
|
|
@@ -32,7 +32,7 @@ const Image = ({ src, width, height, quality = 75, priority = false, className,
|
|
|
32
32
|
function optimizeSrc(src, baseUrl) {
|
|
33
33
|
if (!baseUrl)
|
|
34
34
|
return src;
|
|
35
|
-
if (src.startsWith(baseUrl)) {
|
|
35
|
+
if (src.startsWith && src.startsWith(baseUrl)) {
|
|
36
36
|
return src.slice(baseUrl.length) || '/';
|
|
37
37
|
}
|
|
38
38
|
return src;
|
|
@@ -26,6 +26,189 @@ const router_1 = require("../router");
|
|
|
26
26
|
const fs_1 = __importDefault(require("fs"));
|
|
27
27
|
const path_1 = __importDefault(require("path"));
|
|
28
28
|
const BuildingPage_1 = __importDefault(require("../react/BuildingPage"));
|
|
29
|
+
const server_error_1 = __importDefault(require("../react/server-error"));
|
|
30
|
+
function stripScriptTags(html) {
|
|
31
|
+
if (!html)
|
|
32
|
+
return '';
|
|
33
|
+
return html.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, '');
|
|
34
|
+
}
|
|
35
|
+
function getRequestUrl(req) {
|
|
36
|
+
return req?.originalUrl || req?.url;
|
|
37
|
+
}
|
|
38
|
+
function toError(err) {
|
|
39
|
+
if (err instanceof Error)
|
|
40
|
+
return err;
|
|
41
|
+
if (typeof err === 'string')
|
|
42
|
+
return new Error(err);
|
|
43
|
+
try {
|
|
44
|
+
return new Error(JSON.stringify(err));
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return new Error(String(err));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// --- Polyfill para Browser Env no Server ---
|
|
51
|
+
// Cria objetos globais falsos para evitar que bibliotecas client-side quebrem no SSR
|
|
52
|
+
function polyfillBrowserEnv() {
|
|
53
|
+
if (typeof window === 'undefined') {
|
|
54
|
+
const win = {
|
|
55
|
+
document: {
|
|
56
|
+
createElement: () => ({ style: {}, setAttribute: () => { }, classList: { add: () => { }, remove: () => { } } }),
|
|
57
|
+
getElementById: () => null,
|
|
58
|
+
getElementsByTagName: () => [],
|
|
59
|
+
querySelector: () => null,
|
|
60
|
+
querySelectorAll: () => [],
|
|
61
|
+
head: {},
|
|
62
|
+
body: { style: {} },
|
|
63
|
+
addEventListener: () => { },
|
|
64
|
+
removeEventListener: () => { },
|
|
65
|
+
cookie: '',
|
|
66
|
+
location: { href: '', origin: '' }
|
|
67
|
+
},
|
|
68
|
+
navigator: {
|
|
69
|
+
userAgent: 'Node.js/VattsSSR',
|
|
70
|
+
},
|
|
71
|
+
location: {
|
|
72
|
+
href: 'http://localhost',
|
|
73
|
+
origin: 'http://localhost',
|
|
74
|
+
pathname: '/',
|
|
75
|
+
search: '',
|
|
76
|
+
hash: '',
|
|
77
|
+
assign: () => { },
|
|
78
|
+
replace: () => { },
|
|
79
|
+
reload: () => { },
|
|
80
|
+
},
|
|
81
|
+
history: {
|
|
82
|
+
pushState: () => { },
|
|
83
|
+
replaceState: () => { },
|
|
84
|
+
},
|
|
85
|
+
screen: { width: 1920, height: 1080 },
|
|
86
|
+
addEventListener: () => { },
|
|
87
|
+
removeEventListener: () => { },
|
|
88
|
+
matchMedia: () => ({ matches: false, addListener: () => { }, removeListener: () => { } }),
|
|
89
|
+
requestAnimationFrame: (cb) => setTimeout(cb, 0),
|
|
90
|
+
cancelAnimationFrame: (id) => clearTimeout(id),
|
|
91
|
+
setTimeout: setTimeout,
|
|
92
|
+
clearTimeout: clearTimeout,
|
|
93
|
+
setInterval: setInterval,
|
|
94
|
+
clearInterval: clearInterval,
|
|
95
|
+
localStorage: {
|
|
96
|
+
getItem: () => null,
|
|
97
|
+
setItem: () => { },
|
|
98
|
+
removeItem: () => { },
|
|
99
|
+
clear: () => { },
|
|
100
|
+
},
|
|
101
|
+
sessionStorage: {
|
|
102
|
+
getItem: () => null,
|
|
103
|
+
setItem: () => { },
|
|
104
|
+
removeItem: () => { },
|
|
105
|
+
clear: () => { },
|
|
106
|
+
},
|
|
107
|
+
// MOCK SILENCIOSO: Substitui o console original por funções vazias no ambiente window fake
|
|
108
|
+
// para evitar que logs do client-side poluam o terminal do servidor durante o SSR.
|
|
109
|
+
console: {
|
|
110
|
+
log: () => { },
|
|
111
|
+
warn: () => { },
|
|
112
|
+
error: () => { },
|
|
113
|
+
info: () => { },
|
|
114
|
+
debug: () => { },
|
|
115
|
+
trace: () => { },
|
|
116
|
+
dir: () => { },
|
|
117
|
+
},
|
|
118
|
+
Image: class {
|
|
119
|
+
constructor() { }
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
const globalAny = global;
|
|
123
|
+
// Helper para definir globais de forma segura
|
|
124
|
+
// Node 21+ tem globais 'navigator', 'performance' etc que são getter-only e quebram se tentar sobrescrever
|
|
125
|
+
const setGlobal = (key, value) => {
|
|
126
|
+
try {
|
|
127
|
+
if (typeof globalAny[key] === 'undefined') {
|
|
128
|
+
globalAny[key] = value;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch (e) {
|
|
132
|
+
// Se falhar (propriedade read-only), ignoramos silenciosamente
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
setGlobal('window', win);
|
|
136
|
+
setGlobal('document', win.document);
|
|
137
|
+
setGlobal('navigator', win.navigator);
|
|
138
|
+
setGlobal('location', win.location);
|
|
139
|
+
setGlobal('localStorage', win.localStorage);
|
|
140
|
+
setGlobal('sessionStorage', win.sessionStorage);
|
|
141
|
+
setGlobal('requestAnimationFrame', win.requestAnimationFrame);
|
|
142
|
+
setGlobal('cancelAnimationFrame', win.cancelAnimationFrame);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function buildShellHtml(options) {
|
|
146
|
+
const { lang, title, metaTagsHtml, stylesHtml, hotReloadScript, obfuscatedData, scriptsHtml } = options;
|
|
147
|
+
return `<!DOCTYPE html>
|
|
148
|
+
<html lang="${lang}">
|
|
149
|
+
<head>
|
|
150
|
+
<meta charset="utf-8" />
|
|
151
|
+
<title>${title}</title>
|
|
152
|
+
${metaTagsHtml || ''}
|
|
153
|
+
${stylesHtml || ''}
|
|
154
|
+
</head>
|
|
155
|
+
<body>
|
|
156
|
+
<script id="__vatts_data__" type="text/plain" data-h="${obfuscatedData}"></script>
|
|
157
|
+
<div id="root"></div>
|
|
158
|
+
${scriptsHtml || ''}
|
|
159
|
+
${hotReloadScript ? `<div style="display:none">${hotReloadScript}</div>` : ''}
|
|
160
|
+
</body>
|
|
161
|
+
</html>`;
|
|
162
|
+
}
|
|
163
|
+
async function sendReactSsrFallback(options) {
|
|
164
|
+
const { req, res, isProduction, error, assets, lang, title, metaTagsHtml, stylesHtml, hotReloadScript, obfuscatedData, } = options;
|
|
165
|
+
const scriptsHtml = assets.scripts
|
|
166
|
+
.map((src) => `<script type="module" src="${src}"></script>`)
|
|
167
|
+
.join('\n');
|
|
168
|
+
// No DEV a gente remove scripts do head pra garantir que só aparece a página de erro.
|
|
169
|
+
const safeMetaTagsHtmlForDev = stripScriptTags(metaTagsHtml);
|
|
170
|
+
if (res.headersSent) {
|
|
171
|
+
try {
|
|
172
|
+
res.end();
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
// ignore
|
|
176
|
+
}
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
180
|
+
res.statusCode = isProduction ? 200 : 500;
|
|
181
|
+
if (isProduction) {
|
|
182
|
+
res.end(buildShellHtml({
|
|
183
|
+
lang,
|
|
184
|
+
title,
|
|
185
|
+
metaTagsHtml,
|
|
186
|
+
stylesHtml,
|
|
187
|
+
hotReloadScript: '',
|
|
188
|
+
obfuscatedData,
|
|
189
|
+
scriptsHtml,
|
|
190
|
+
}));
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const err = toError(error);
|
|
194
|
+
return new Promise((resolve) => {
|
|
195
|
+
const { pipe } = (0, server_1.renderToPipeableStream)(react_1.default.createElement(ServerRoot, { lang: lang, title: title, metaTagsHtml: safeMetaTagsHtmlForDev, stylesHtml: stylesHtml, initialDataScript: `/* Data Injection */`, hotReloadScript: '', dataScript: react_1.default.createElement("script", { id: "__vatts_data__", type: "text/plain", "data-h": obfuscatedData }) },
|
|
196
|
+
react_1.default.createElement(server_error_1.default, { error: err, requestUrl: getRequestUrl(req), hint: "SSR failed to render this route. See the error below." })), {
|
|
197
|
+
onAllReady() {
|
|
198
|
+
pipe(res);
|
|
199
|
+
resolve();
|
|
200
|
+
},
|
|
201
|
+
onError() {
|
|
202
|
+
// ignore (já estamos numa tela de erro)
|
|
203
|
+
},
|
|
204
|
+
onShellError() {
|
|
205
|
+
// fallback extremo
|
|
206
|
+
res.end('<h1>SSR Error</h1>');
|
|
207
|
+
resolve();
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
}
|
|
29
212
|
// --- Helpers de Servidor ---
|
|
30
213
|
// Função auxiliar para importar módulos ignorando CSS (mesma lógica do router.ts)
|
|
31
214
|
function requireWithoutStyles(modulePath) {
|
|
@@ -222,12 +405,12 @@ function getBuildAssets(req) {
|
|
|
222
405
|
}
|
|
223
406
|
function ServerRoot({ lang, title, metaTagsHtml, stylesHtml, initialDataScript, hotReloadScript, dataScript, children }) {
|
|
224
407
|
// Concatena tudo que vai no head em uma única string
|
|
225
|
-
const headContent = `
|
|
226
|
-
<meta charset="utf-8" />
|
|
227
|
-
<title>${title}</title>
|
|
228
|
-
${initialDataScript ? `<script>${initialDataScript}</script>` : ''}
|
|
229
|
-
${metaTagsHtml || ''}
|
|
230
|
-
${stylesHtml || ''}
|
|
408
|
+
const headContent = `
|
|
409
|
+
<meta charset="utf-8" />
|
|
410
|
+
<title>${title}</title>
|
|
411
|
+
${initialDataScript ? `<script>${initialDataScript}</script>` : ''}
|
|
412
|
+
${metaTagsHtml || ''}
|
|
413
|
+
${stylesHtml || ''}
|
|
231
414
|
`;
|
|
232
415
|
return (react_1.default.createElement("html", { lang: lang },
|
|
233
416
|
react_1.default.createElement("head", { dangerouslySetInnerHTML: { __html: headContent } }),
|
|
@@ -237,26 +420,30 @@ function ServerRoot({ lang, title, metaTagsHtml, stylesHtml, initialDataScript,
|
|
|
237
420
|
hotReloadScript && (react_1.default.createElement("div", { style: { display: 'none' }, dangerouslySetInnerHTML: { __html: hotReloadScript } })))));
|
|
238
421
|
}
|
|
239
422
|
async function render({ req, res, route, params, allRoutes }) {
|
|
423
|
+
// ATENÇÃO: Polyfill executado aqui para garantir que window/document existam
|
|
424
|
+
// antes de qualquer lógica de componente ser executada.
|
|
425
|
+
polyfillBrowserEnv();
|
|
240
426
|
const { generateMetadata } = route;
|
|
241
427
|
const isProduction = !req.hwebDev;
|
|
242
428
|
const hotReloadManager = req.hotReloadManager;
|
|
243
429
|
// SILENCIAR CONSOLE: Inicia o silêncio para evitar logs de renderização
|
|
430
|
+
let assets = null;
|
|
431
|
+
let metadata = { title: 'Vatts App' };
|
|
432
|
+
let layoutInfo = null;
|
|
244
433
|
try {
|
|
245
434
|
// 1. Verificar Build - Se não tiver scripts, retorna tela de Loading
|
|
246
|
-
|
|
435
|
+
assets = getBuildAssets(req);
|
|
247
436
|
if (!assets || assets.scripts.length === 0) {
|
|
248
|
-
// Se falhar o build, restauramos o console para o erro aparecer se necessário
|
|
249
|
-
// Usando stream para a tela de loading também, agora via React Component
|
|
250
437
|
const { pipe } = (0, server_1.renderToPipeableStream)(react_1.default.createElement(BuildingPage_1.default, null), {
|
|
251
438
|
onShellReady() {
|
|
252
|
-
res.setHeader('Content-Type', 'text/html');
|
|
439
|
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
253
440
|
pipe(res);
|
|
254
|
-
}
|
|
441
|
+
},
|
|
255
442
|
});
|
|
256
443
|
return;
|
|
257
444
|
}
|
|
258
445
|
// 2. Preparar Layout
|
|
259
|
-
|
|
446
|
+
layoutInfo = (0, router_1.getLayout)();
|
|
260
447
|
let LayoutComponent = null;
|
|
261
448
|
if (layoutInfo) {
|
|
262
449
|
try {
|
|
@@ -269,7 +456,6 @@ async function render({ req, res, route, params, allRoutes }) {
|
|
|
269
456
|
}
|
|
270
457
|
}
|
|
271
458
|
// 3. Preparar Metadata
|
|
272
|
-
let metadata = { title: 'Vatts App' };
|
|
273
459
|
if (layoutInfo && layoutInfo.metadata) {
|
|
274
460
|
metadata = { ...metadata, ...layoutInfo.metadata };
|
|
275
461
|
}
|
|
@@ -311,37 +497,88 @@ async function render({ req, res, route, params, allRoutes }) {
|
|
|
311
497
|
if (LayoutComponent) {
|
|
312
498
|
AppTree = react_1.default.createElement(LayoutComponent, null, AppTree);
|
|
313
499
|
}
|
|
314
|
-
// 6. Streaming
|
|
315
|
-
return new Promise((resolve
|
|
500
|
+
// 6. Streaming (segura até onAllReady para conseguir fallback sem HTML parcial)
|
|
501
|
+
return new Promise((resolve) => {
|
|
316
502
|
let didError = false;
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
initialDataScript: `/* Data Injection */`, hotReloadScript: hotReloadScript, dataScript: react_1.default.createElement("script", { id: "__vatts_data__", type: "text/plain", "data-h": obfuscatedData }) }, AppTree), {
|
|
320
|
-
// Usar bootstrapModules para scripts tipo módulo (ESM)
|
|
503
|
+
let firstError = null;
|
|
504
|
+
const stream = (0, server_1.renderToPipeableStream)(react_1.default.createElement(ServerRoot, { lang: htmlLang, title: metadata.title || 'Vatts.js', metaTagsHtml: metaTagsHtml, stylesHtml: stylesHtml, initialDataScript: `/* Data Injection */`, hotReloadScript: hotReloadScript, dataScript: react_1.default.createElement("script", { id: "__vatts_data__", type: "text/plain", "data-h": obfuscatedData }) }, AppTree), {
|
|
321
505
|
bootstrapModules: assets.scripts,
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
506
|
+
onAllReady() {
|
|
507
|
+
if (didError) {
|
|
508
|
+
stream.abort();
|
|
509
|
+
sendReactSsrFallback({
|
|
510
|
+
req,
|
|
511
|
+
res,
|
|
512
|
+
isProduction,
|
|
513
|
+
error: firstError || new Error('SSR error'),
|
|
514
|
+
assets: assets,
|
|
515
|
+
lang: htmlLang,
|
|
516
|
+
title: metadata.title || 'Vatts.js',
|
|
517
|
+
metaTagsHtml,
|
|
518
|
+
stylesHtml,
|
|
519
|
+
hotReloadScript,
|
|
520
|
+
obfuscatedData,
|
|
521
|
+
}).then(resolve);
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
525
|
+
stream.pipe(res);
|
|
325
526
|
resolve();
|
|
326
527
|
},
|
|
327
528
|
onShellError(error) {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
res.setHeader('Content-Type', 'text/html');
|
|
331
|
-
res.end('<h1>Internal Server Error</h1>');
|
|
332
|
-
resolve();
|
|
529
|
+
firstError ||= error;
|
|
530
|
+
didError = true;
|
|
333
531
|
},
|
|
334
532
|
onError(error) {
|
|
533
|
+
firstError ||= error;
|
|
335
534
|
didError = true;
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
}
|
|
535
|
+
if (!isProduction) {
|
|
536
|
+
console.error('Streaming Error:', error);
|
|
537
|
+
}
|
|
538
|
+
},
|
|
340
539
|
});
|
|
341
540
|
});
|
|
342
541
|
}
|
|
343
542
|
catch (err) {
|
|
344
|
-
|
|
345
|
-
|
|
543
|
+
if (!assets) {
|
|
544
|
+
// sem assets não dá pra montar shell completo; evita crash
|
|
545
|
+
if (!res.headersSent) {
|
|
546
|
+
res.statusCode = 500;
|
|
547
|
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
548
|
+
res.end(isProduction ? '' : '<h1>SSR Error</h1>');
|
|
549
|
+
}
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
await sendReactSsrFallback({
|
|
553
|
+
req,
|
|
554
|
+
res,
|
|
555
|
+
isProduction,
|
|
556
|
+
error: err,
|
|
557
|
+
assets,
|
|
558
|
+
lang: metadata?.language || 'pt-BR',
|
|
559
|
+
title: metadata.title || 'Vatts.js',
|
|
560
|
+
metaTagsHtml: (() => {
|
|
561
|
+
try {
|
|
562
|
+
return generateMetaTags(metadata);
|
|
563
|
+
}
|
|
564
|
+
catch {
|
|
565
|
+
return '';
|
|
566
|
+
}
|
|
567
|
+
})(),
|
|
568
|
+
stylesHtml: assets.styles.map((styleUrl) => `<link rel="stylesheet" href="${styleUrl}">`).join('\n'),
|
|
569
|
+
hotReloadScript: !isProduction && hotReloadManager ? hotReloadManager.getClientScript() : '',
|
|
570
|
+
obfuscatedData: (() => {
|
|
571
|
+
try {
|
|
572
|
+
return obfuscateData({
|
|
573
|
+
routes: [],
|
|
574
|
+
initialComponentPath: route.componentPath,
|
|
575
|
+
initialParams: params,
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
catch {
|
|
579
|
+
return '';
|
|
580
|
+
}
|
|
581
|
+
})(),
|
|
582
|
+
});
|
|
346
583
|
}
|
|
347
584
|
}
|