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.
Files changed (40) hide show
  1. package/LICENSE +12 -12
  2. package/README.md +63 -63
  3. package/dist/api/console.d.ts +28 -5
  4. package/dist/api/console.js +305 -137
  5. package/dist/api/native-server.js +25 -4
  6. package/dist/bin/vatts.js +192 -4
  7. package/dist/builder.js +70 -69
  8. package/dist/core-go/core-linux-arm64.node +0 -0
  9. package/dist/core-go/core-linux-x64.node +0 -0
  10. package/dist/core-go/core-win-x64.node +0 -0
  11. package/dist/global/global.d.ts +176 -176
  12. package/dist/helpers.d.ts +2 -2
  13. package/dist/helpers.js +20 -41
  14. package/dist/hotReload.js +205 -205
  15. package/dist/index.d.ts +2 -1
  16. package/dist/index.js +104 -19
  17. package/dist/loaders.js +15 -15
  18. package/dist/react/BuildingPage.js +202 -202
  19. package/dist/react/DefaultNotFound.js +16 -16
  20. package/dist/react/DevIndicator.js +101 -101
  21. package/dist/react/entry.client.js +7 -8
  22. package/dist/react/image/Image.js +1 -1
  23. package/dist/react/renderer-react.js +270 -33
  24. package/dist/react/server-error.d.ts +8 -0
  25. package/dist/react/server-error.js +346 -0
  26. package/dist/router.js +1 -65
  27. package/dist/types.d.ts +1 -5
  28. package/dist/utils/utils.d.ts +1 -0
  29. package/dist/utils/utils.js +68 -0
  30. package/dist/vue/App.vue +191 -191
  31. package/dist/vue/BuildingPage.vue +280 -280
  32. package/dist/vue/DefaultNotFound.vue +328 -328
  33. package/dist/vue/DevIndicator.vue +225 -225
  34. package/dist/vue/ErrorModal.vue +316 -316
  35. package/dist/vue/Link.vue +38 -38
  36. package/dist/vue/entry.client.js +7 -7
  37. package/dist/vue/image/Image.vue +106 -106
  38. package/dist/vue/renderer.vue.js +266 -46
  39. package/dist/vue/server-error.vue +351 -0
  40. 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
- const assets = getBuildAssets(req);
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
- const layoutInfo = (0, router_1.getLayout)();
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, reject) => {
500
+ // 6. Streaming (segura até onAllReady para conseguir fallback sem HTML parcial)
501
+ return new Promise((resolve) => {
316
502
  let didError = false;
317
- const { pipe } = (0, server_1.renderToPipeableStream)(react_1.default.createElement(ServerRoot, { lang: htmlLang, title: metadata.title || 'Vatts.js', metaTagsHtml: metaTagsHtml, stylesHtml: stylesHtml,
318
- // Recriando o script de dados exatamente como o client espera
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
- onShellReady() {
323
- res.setHeader('Content-Type', 'text/html');
324
- pipe(res);
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
- console.error('Streaming Shell Error:', error);
329
- res.statusCode = 500;
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
- // Log de erro de stream ainda é importante, mas podemos filtrar se quiser
337
- // Aqui mantemos o log de erro original do React que vai para stderr
338
- console.error('Streaming Error:', error);
339
- }
535
+ if (!isProduction) {
536
+ console.error('Streaming Error:', error);
537
+ }
538
+ },
340
539
  });
341
540
  });
342
541
  }
343
542
  catch (err) {
344
- console.error("Critical Render Error:", err);
345
- throw err;
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
  }
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ export interface ServerErrorProps {
3
+ title?: string;
4
+ error?: unknown;
5
+ hint?: string;
6
+ requestUrl?: string;
7
+ }
8
+ export default function ServerError({ title, error, hint, requestUrl, }: ServerErrorProps): React.JSX.Element;