vatts 1.0.2-alpha.2 → 1.0.2-alpha.4
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/dist/bin/vatts.js +91 -100
- package/dist/builder.js +111 -123
- package/dist/client/clientRouter.d.ts +11 -54
- package/dist/client/clientRouter.js +39 -94
- package/dist/client/rpc.d.ts +1 -1
- package/dist/client/rpc.js +19 -2
- package/dist/env/env.d.ts +5 -0
- package/dist/env/env.js +69 -0
- package/dist/helpers.js +4 -1
- package/dist/index.js +44 -86
- package/dist/renderer.d.ts +3 -1
- package/dist/renderer.js +151 -170
- package/dist/types.d.ts +5 -0
- package/package.json +1 -1
package/dist/client/rpc.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export type RpcClient<TApi extends Record<string, any> = Record<string, any>> =
|
|
|
17
17
|
* Typing:
|
|
18
18
|
* - Without a generic: `const api = importServer('...')` -> `api.anyFn<MyReturn>()`
|
|
19
19
|
* - With a generic: `importServer<typeof import('../../backend/helper')>('...')`
|
|
20
|
-
*
|
|
20
|
+
* gives argument + default return types, while still allowing overrides.
|
|
21
21
|
*
|
|
22
22
|
* Note: server functions can be defined as `(req, ...args)`; the first arg is injected
|
|
23
23
|
* by the server, so the client signature automatically drops that first parameter.
|
package/dist/client/rpc.js
CHANGED
|
@@ -28,6 +28,21 @@ function asErrorMessage(err) {
|
|
|
28
28
|
return 'Unknown error';
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
+
// Detecta se estamos rodando no Node.js
|
|
32
|
+
const isServer = typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
|
|
33
|
+
function getRpcEndpoint() {
|
|
34
|
+
if (isServer) {
|
|
35
|
+
// Tenta pegar a porta das variáveis de ambiente ou usa 3000 como fallback.
|
|
36
|
+
// Nota: Se você usar uma porta customizada no vatts.config.ts sem setar ENV,
|
|
37
|
+
// precisará garantir que process.env.PORT esteja alinhado.
|
|
38
|
+
const port = process.env.PORT || 3000;
|
|
39
|
+
// Em SSR, sempre usamos HTTP e Loopback IP (127.0.0.1) para garantir
|
|
40
|
+
// que a requisição chegue no próprio servidor localmente sem sair pra rede externa.
|
|
41
|
+
return `http://127.0.0.1:${port}${types_1.RPC_ENDPOINT}`;
|
|
42
|
+
}
|
|
43
|
+
// No cliente (browser), URL relativa funciona perfeitamente
|
|
44
|
+
return types_1.RPC_ENDPOINT;
|
|
45
|
+
}
|
|
31
46
|
/**
|
|
32
47
|
* `importServer("src/backend/index.ts")` returns a Proxy where every property is
|
|
33
48
|
* a function that performs a POST to `/api/rpc`.
|
|
@@ -35,7 +50,7 @@ function asErrorMessage(err) {
|
|
|
35
50
|
* Typing:
|
|
36
51
|
* - Without a generic: `const api = importServer('...')` -> `api.anyFn<MyReturn>()`
|
|
37
52
|
* - With a generic: `importServer<typeof import('../../backend/helper')>('...')`
|
|
38
|
-
*
|
|
53
|
+
* gives argument + default return types, while still allowing overrides.
|
|
39
54
|
*
|
|
40
55
|
* Note: server functions can be defined as `(req, ...args)`; the first arg is injected
|
|
41
56
|
* by the server, so the client signature automatically drops that first parameter.
|
|
@@ -71,9 +86,11 @@ function importServer(file) {
|
|
|
71
86
|
}
|
|
72
87
|
}
|
|
73
88
|
};
|
|
89
|
+
// Resolve a URL correta (Absoluta no server, Relativa no client)
|
|
90
|
+
const endpoint = getRpcEndpoint();
|
|
74
91
|
let res;
|
|
75
92
|
try {
|
|
76
|
-
res = await fetch(
|
|
93
|
+
res = await fetch(endpoint, {
|
|
77
94
|
method: 'POST',
|
|
78
95
|
headers: {
|
|
79
96
|
'Content-Type': 'application/json'
|
package/dist/env/env.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadEnv = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const console_1 = __importDefault(require("../api/console"));
|
|
10
|
+
function parse(src) {
|
|
11
|
+
const obj = {};
|
|
12
|
+
const lines = src.toString().split('\n');
|
|
13
|
+
for (const line of lines) {
|
|
14
|
+
const trimmedLine = line.trim();
|
|
15
|
+
if (trimmedLine.startsWith('#') || trimmedLine === '') {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
const match = trimmedLine.match(/^([^=]+)=(.*)$/);
|
|
19
|
+
if (match) {
|
|
20
|
+
const key = match[1].trim();
|
|
21
|
+
let value = match[2].trim();
|
|
22
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
23
|
+
value = value.substring(1, value.length - 1);
|
|
24
|
+
}
|
|
25
|
+
obj[key] = value;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return obj;
|
|
29
|
+
}
|
|
30
|
+
function applyEnv(filePath) {
|
|
31
|
+
if (!fs_1.default.existsSync(filePath))
|
|
32
|
+
return;
|
|
33
|
+
try {
|
|
34
|
+
const fileContent = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
35
|
+
const parsed = parse(fileContent);
|
|
36
|
+
for (const key in parsed) {
|
|
37
|
+
if (!Object.prototype.hasOwnProperty.call(process.env, key)) {
|
|
38
|
+
process.env[key] = parsed[key];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
console_1.default.error(`Error loading env file ${filePath}:`, e);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const loadEnv = (options) => {
|
|
47
|
+
const { dir, dev, envFiles = [] } = options;
|
|
48
|
+
const filesToLoad = [".env", ...envFiles].map((file) => path_1.default.join(dir, file));
|
|
49
|
+
filesToLoad.forEach(applyEnv);
|
|
50
|
+
if (dev) {
|
|
51
|
+
for (const file of filesToLoad) {
|
|
52
|
+
if (fs_1.default.existsSync(file)) {
|
|
53
|
+
let watchTimeout;
|
|
54
|
+
fs_1.default.watch(file, (eventType) => {
|
|
55
|
+
if (eventType === 'change') {
|
|
56
|
+
// Limpa o timeout anterior para evitar execuções múltiplas
|
|
57
|
+
clearTimeout(watchTimeout);
|
|
58
|
+
// Define um novo timeout de 100ms
|
|
59
|
+
watchTimeout = setTimeout(() => {
|
|
60
|
+
console_1.default.info(`Reloading environment variables from ${path_1.default.basename(file)}.`);
|
|
61
|
+
applyEnv(file);
|
|
62
|
+
}, 100);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
exports.loadEnv = loadEnv;
|
package/dist/helpers.js
CHANGED
|
@@ -132,6 +132,7 @@ async function loadVattsConfig(projectDir, phase) {
|
|
|
132
132
|
individualRequestTimeout: 30000,
|
|
133
133
|
maxUrlLength: 2048,
|
|
134
134
|
accessLogging: true,
|
|
135
|
+
envFiles: [],
|
|
135
136
|
};
|
|
136
137
|
try {
|
|
137
138
|
// Tenta primeiro .ts, depois .js
|
|
@@ -317,10 +318,12 @@ const parseBody = (req) => {
|
|
|
317
318
|
*/
|
|
318
319
|
async function initNativeServer(vattsApp, options, port, hostname) {
|
|
319
320
|
const time = Date.now();
|
|
320
|
-
await vattsApp.prepare();
|
|
321
321
|
const projectDir = options.dir || process.cwd();
|
|
322
322
|
const phase = options.dev ? 'development' : 'production';
|
|
323
323
|
const vattsConfig = await loadVattsConfig(projectDir, phase);
|
|
324
|
+
// Passa envFiles da config para as opções do vatts
|
|
325
|
+
options.envFiles = vattsConfig.envFiles;
|
|
326
|
+
await vattsApp.prepare();
|
|
324
327
|
const handler = vattsApp.getRequestHandler();
|
|
325
328
|
const msg = console_1.default.dynamicLine(`${console_1.Colors.Bright}Starting Vatts.js on port ${options.port}${console_1.Colors.Reset}`);
|
|
326
329
|
// --- LÓGICA DO LISTENER (REUTILIZÁVEL) ---
|
package/dist/index.js
CHANGED
|
@@ -59,13 +59,14 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
59
59
|
const express_1 = require("./adapters/express");
|
|
60
60
|
const builder_1 = require("./builder");
|
|
61
61
|
const router_1 = require("./router");
|
|
62
|
-
const renderer_1 = require("./renderer");
|
|
62
|
+
const renderer_1 = require("./renderer"); // Usando a nova função de streaming
|
|
63
63
|
const http_1 = require("./api/http");
|
|
64
64
|
Object.defineProperty(exports, "VattsRequest", { enumerable: true, get: function () { return http_1.VattsRequest; } });
|
|
65
65
|
Object.defineProperty(exports, "VattsResponse", { enumerable: true, get: function () { return http_1.VattsResponse; } });
|
|
66
66
|
const hotReload_1 = require("./hotReload");
|
|
67
67
|
const factory_1 = require("./adapters/factory");
|
|
68
68
|
const console_1 = __importStar(require("./api/console"));
|
|
69
|
+
const env_1 = require("./env/env");
|
|
69
70
|
// RPC
|
|
70
71
|
const server_1 = require("./rpc/server");
|
|
71
72
|
const types_1 = require("./rpc/types");
|
|
@@ -120,13 +121,9 @@ function isLargeProject(projectDir) {
|
|
|
120
121
|
}
|
|
121
122
|
}
|
|
122
123
|
scanDirectory(srcDir);
|
|
123
|
-
// Considera projeto grande se:
|
|
124
|
-
// - Mais de 20 arquivos de frontend/style
|
|
125
|
-
// - Ou tamanho total > 500KB
|
|
126
124
|
return totalFiles > 20 || totalSize > 500 * 1024;
|
|
127
125
|
}
|
|
128
126
|
catch (error) {
|
|
129
|
-
// Em caso de erro, assume que não é um projeto grande
|
|
130
127
|
return false;
|
|
131
128
|
}
|
|
132
129
|
}
|
|
@@ -136,44 +133,32 @@ function createEntryFile(projectDir, routes) {
|
|
|
136
133
|
const tempDir = path_1.default.join(projectDir, '.vatts', 'temp');
|
|
137
134
|
fs_1.default.mkdirSync(tempDir, { recursive: true });
|
|
138
135
|
const entryFilePath = path_1.default.join(tempDir, 'entry.client.js');
|
|
139
|
-
// Verifica se há layout
|
|
140
136
|
const layout = (0, router_1.getLayout)();
|
|
141
|
-
// Verifica se há notFound personalizado
|
|
142
137
|
const notFound = (0, router_1.getNotFound)();
|
|
143
|
-
// Gera imports dinâmicos para cada componente
|
|
144
138
|
const imports = routes
|
|
145
139
|
.map((route, index) => {
|
|
146
140
|
const relativePath = path_1.default.relative(tempDir, route.componentPath).replace(/\\/g, '/');
|
|
147
141
|
return `import route${index} from '${relativePath}';`;
|
|
148
142
|
})
|
|
149
143
|
.join('\n');
|
|
150
|
-
// Import do layout se existir
|
|
151
144
|
const layoutImport = layout
|
|
152
145
|
? `import LayoutComponent from '${path_1.default.relative(tempDir, layout.componentPath).replace(/\\/g, '/')}';`
|
|
153
146
|
: '';
|
|
154
|
-
// Import do notFound se existir
|
|
155
147
|
const notFoundImport = notFound
|
|
156
148
|
? `import NotFoundComponent from '${path_1.default.relative(tempDir, notFound.componentPath).replace(/\\/g, '/')}';`
|
|
157
149
|
: '';
|
|
158
|
-
// Registra os componentes no window para o cliente acessar
|
|
159
150
|
const componentRegistration = routes
|
|
160
151
|
.map((route, index) => ` '${route.componentPath}': route${index}.component || route${index}.default?.component,`)
|
|
161
152
|
.join('\n');
|
|
162
|
-
// Registra o layout se existir
|
|
163
153
|
const layoutRegistration = layout
|
|
164
154
|
? `window.__VATTS_LAYOUT__ = LayoutComponent.default || LayoutComponent;`
|
|
165
155
|
: `window.__VATTS_LAYOUT__ = null;`;
|
|
166
|
-
// Registra o notFound se existir
|
|
167
156
|
const notFoundRegistration = notFound
|
|
168
157
|
? `window.__VATTS_NOT_FOUND__ = NotFoundComponent.default || NotFoundComponent;`
|
|
169
158
|
: `window.__VATTS_NOT_FOUND__ = null;`;
|
|
170
|
-
|
|
171
|
-
// IMPORTANT: quando o pacote é instalado via npm, bundlers (Rollup/Vite/etc.) não transpilam TSX em node_modules.
|
|
172
|
-
// Por isso, aqui a gente sempre aponta para os artefatos compilados em dist/.
|
|
173
|
-
const sdkDir = path_1.default.dirname(__dirname); // pasta do pacote (pai de dist/ e src/)
|
|
159
|
+
const sdkDir = path_1.default.dirname(__dirname);
|
|
174
160
|
const entryClientPath = path_1.default.join(sdkDir, 'dist', 'client', 'entry.client.js');
|
|
175
161
|
const relativeEntryPath = path_1.default.relative(tempDir, entryClientPath).replace(/\\/g, '/');
|
|
176
|
-
// Import do DefaultNotFound do SDK (compilado)
|
|
177
162
|
const defaultNotFoundPath = path_1.default.join(sdkDir, 'dist', 'client', 'DefaultNotFound.js');
|
|
178
163
|
const relativeDefaultNotFoundPath = path_1.default.relative(tempDir, defaultNotFoundPath).replace(/\\/g, '/');
|
|
179
164
|
const entryContent = `// Arquivo gerado automaticamente pelo vatts
|
|
@@ -182,28 +167,22 @@ ${layoutImport}
|
|
|
182
167
|
${notFoundImport}
|
|
183
168
|
import DefaultNotFound from '${relativeDefaultNotFoundPath}';
|
|
184
169
|
|
|
185
|
-
// Registra os componentes para o cliente
|
|
186
170
|
window.__VATTS_COMPONENTS__ = {
|
|
187
171
|
${componentRegistration}
|
|
188
172
|
};
|
|
189
173
|
|
|
190
|
-
// Registra o layout se existir
|
|
191
174
|
${layoutRegistration}
|
|
192
|
-
|
|
193
|
-
// Registra o notFound se existir
|
|
194
175
|
${notFoundRegistration}
|
|
195
176
|
|
|
196
|
-
|
|
197
177
|
window.__VATTS_DEFAULT_NOT_FOUND__ = DefaultNotFound;
|
|
198
178
|
|
|
199
|
-
// Importa e executa o entry.client.tsx
|
|
200
179
|
import '${relativeEntryPath}';
|
|
201
180
|
`;
|
|
202
181
|
try {
|
|
203
182
|
fs_1.default.writeFileSync(entryFilePath, entryContent);
|
|
204
183
|
}
|
|
205
184
|
catch (e) {
|
|
206
|
-
console.error("
|
|
185
|
+
console.error("Error writing entry file", e);
|
|
207
186
|
}
|
|
208
187
|
return entryFilePath;
|
|
209
188
|
}
|
|
@@ -213,77 +192,58 @@ import '${relativeEntryPath}';
|
|
|
213
192
|
}
|
|
214
193
|
}
|
|
215
194
|
function vatts(options) {
|
|
216
|
-
const { dev = true, dir = process.cwd(), port = 3000 } = options;
|
|
195
|
+
const { dev = true, dir = process.cwd(), port = 3000, envFiles } = options;
|
|
196
|
+
(0, env_1.loadEnv)({ dir, dev, envFiles });
|
|
217
197
|
// @ts-ignore
|
|
218
198
|
process.vatts = options;
|
|
199
|
+
// @ts-ignore
|
|
200
|
+
process.env.PORT = options.port;
|
|
219
201
|
const userWebDir = path_1.default.join(dir, 'src', 'web');
|
|
220
202
|
const userWebRoutesDir = path_1.default.join(userWebDir, 'routes');
|
|
221
203
|
const userBackendRoutesDir = path_1.default.join(dir, 'src', 'backend', 'routes');
|
|
222
|
-
/**
|
|
223
|
-
* Executa middlewares sequencialmente e depois o handler final
|
|
224
|
-
* @param middlewares Array de middlewares para executar
|
|
225
|
-
* @param finalHandler Handler final da rota
|
|
226
|
-
* @param request Requisição do Vatts.js
|
|
227
|
-
* @param params Parâmetros da rota
|
|
228
|
-
* @returns Resposta do middleware ou handler final
|
|
229
|
-
*/
|
|
230
204
|
async function executeMiddlewareChain(middlewares, finalHandler, request, params) {
|
|
231
205
|
if (!middlewares || middlewares.length === 0) {
|
|
232
|
-
// Não há middlewares, executa diretamente o handler final
|
|
233
206
|
return await finalHandler(request, params);
|
|
234
207
|
}
|
|
235
208
|
let currentIndex = 0;
|
|
236
|
-
// Função next que será chamada pelos middlewares
|
|
237
209
|
const next = async () => {
|
|
238
210
|
if (currentIndex < middlewares.length) {
|
|
239
|
-
// Ainda há middlewares para executar
|
|
240
211
|
const currentMiddleware = middlewares[currentIndex];
|
|
241
212
|
currentIndex++;
|
|
242
213
|
return await currentMiddleware(request, params, next);
|
|
243
214
|
}
|
|
244
215
|
else {
|
|
245
|
-
// Todos os middlewares foram executados, chama o handler final
|
|
246
216
|
return await finalHandler(request, params);
|
|
247
217
|
}
|
|
248
218
|
};
|
|
249
|
-
// Inicia a cadeia de execução
|
|
250
219
|
return await next();
|
|
251
220
|
}
|
|
252
221
|
let frontendRoutes = [];
|
|
253
222
|
let hotReloadManager = null;
|
|
254
223
|
let entryPoint;
|
|
255
|
-
let outfile;
|
|
256
|
-
// Função para regenerar o entry file
|
|
257
224
|
const regenerateEntryFile = () => {
|
|
258
|
-
// Recarrega todas as rotas e componentes
|
|
259
225
|
const newFrontendRoutes = (0, router_1.loadRoutes)(userWebRoutesDir);
|
|
260
226
|
const newLayout = (0, router_1.loadLayout)(userWebDir);
|
|
261
227
|
const newNotFound = (0, router_1.loadNotFound)(userWebDir);
|
|
262
|
-
// Se nada mudou, não reescreve o entry file (evita disparar rebuild extra)
|
|
263
228
|
const oldKey = frontendRoutes.map(r => `${r.pattern ?? ''}:${r.componentPath}`).join('|');
|
|
264
229
|
const newKey = newFrontendRoutes.map(r => `${r.pattern ?? ''}:${r.componentPath}`).join('|');
|
|
265
230
|
if (oldKey === newKey) {
|
|
266
|
-
// Ainda atualiza refs internas de layout/notFound, mas evita re-gerar o entry.
|
|
267
231
|
frontendRoutes = newFrontendRoutes;
|
|
268
232
|
return;
|
|
269
233
|
}
|
|
270
234
|
frontendRoutes = newFrontendRoutes;
|
|
271
|
-
// Regenera o entry file
|
|
272
235
|
entryPoint = createEntryFile(dir, frontendRoutes);
|
|
273
236
|
};
|
|
274
237
|
return {
|
|
275
238
|
prepare: async () => {
|
|
276
239
|
const isProduction = !dev;
|
|
277
240
|
if (!isProduction) {
|
|
278
|
-
// Inicia hot reload apenas em desenvolvimento (com suporte ao main)
|
|
279
241
|
hotReloadManager = new hotReload_1.HotReloadManager(dir);
|
|
280
242
|
await hotReloadManager.start();
|
|
281
|
-
// Adiciona callback para recarregar TUDO quando qualquer arquivo mudar
|
|
282
243
|
hotReloadManager.onBackendApiChange(() => {
|
|
283
244
|
(0, router_1.loadBackendRoutes)(userBackendRoutesDir);
|
|
284
|
-
(0, router_1.processWebSocketRoutes)();
|
|
245
|
+
(0, router_1.processWebSocketRoutes)();
|
|
285
246
|
});
|
|
286
|
-
// Adiciona callback para regenerar entry file quando frontend mudar
|
|
287
247
|
hotReloadManager.onFrontendChange(() => {
|
|
288
248
|
regenerateEntryFile();
|
|
289
249
|
});
|
|
@@ -295,13 +255,10 @@ function vatts(options) {
|
|
|
295
255
|
const spinner1 = setInterval(() => {
|
|
296
256
|
timee.update(` ${console_1.Colors.FgYellow}${spinnerFrames1[frameIndex1]}${console_1.Colors.Reset} Loading routes and components...`);
|
|
297
257
|
frameIndex1 = (frameIndex1 + 1) % spinnerFrames1.length;
|
|
298
|
-
}, 100);
|
|
299
|
-
// ORDEM IMPORTANTE: Carrega TUDO antes de criar o arquivo de entrada
|
|
258
|
+
}, 100);
|
|
300
259
|
frontendRoutes = (0, router_1.loadRoutes)(userWebRoutesDir);
|
|
301
260
|
(0, router_1.loadBackendRoutes)(userBackendRoutesDir);
|
|
302
|
-
// Processa rotas WebSocket após carregar backend
|
|
303
261
|
(0, router_1.processWebSocketRoutes)();
|
|
304
|
-
// Carrega layout.tsx ANTES de criar o entry file
|
|
305
262
|
const layout = (0, router_1.loadLayout)(userWebDir);
|
|
306
263
|
const notFound = (0, router_1.loadNotFound)(userWebDir);
|
|
307
264
|
const outDir = path_1.default.join(dir, '.vatts');
|
|
@@ -311,20 +268,18 @@ function vatts(options) {
|
|
|
311
268
|
timee.end(`Routes and components loaded in ${Date.now() - now}ms`);
|
|
312
269
|
if (isProduction) {
|
|
313
270
|
const time = console_1.default.dynamicLine(`Starting client build`);
|
|
314
|
-
// Spinner
|
|
315
271
|
const spinnerFrames = ['|', '/', '-', '\\'];
|
|
316
272
|
let frameIndex = 0;
|
|
317
273
|
const spinner = setInterval(() => {
|
|
318
274
|
time.update(` ${console_1.Colors.FgYellow}${spinnerFrames[frameIndex]}${console_1.Colors.Reset} Building...`);
|
|
319
275
|
frameIndex = (frameIndex + 1) % spinnerFrames.length;
|
|
320
|
-
}, 100);
|
|
276
|
+
}, 100);
|
|
321
277
|
const now = Date.now();
|
|
322
278
|
await (0, builder_1.buildWithChunks)(entryPoint, outDir, isProduction);
|
|
323
279
|
const elapsed = Date.now() - now;
|
|
324
|
-
clearInterval(spinner);
|
|
325
|
-
time.update("");
|
|
280
|
+
clearInterval(spinner);
|
|
281
|
+
time.update("");
|
|
326
282
|
time.end(`Client build completed in ${elapsed}ms`);
|
|
327
|
-
// Notifica o hot reload manager que o build foi concluído
|
|
328
283
|
if (hotReloadManager) {
|
|
329
284
|
hotReloadManager.onBuildComplete(true);
|
|
330
285
|
}
|
|
@@ -339,13 +294,10 @@ function vatts(options) {
|
|
|
339
294
|
}
|
|
340
295
|
},
|
|
341
296
|
executeInstrumentation: () => {
|
|
342
|
-
// verificar se dir/src/instrumentation.(tsx/jsx/js/ts) existe com regex
|
|
343
297
|
const instrumentationFile = fs_1.default.readdirSync(path_1.default.join(dir, 'src')).find(file => /^vattsweb\.(tsx|jsx|js|ts)$/.test(file));
|
|
344
298
|
if (instrumentationFile) {
|
|
345
299
|
const instrumentationPath = path_1.default.join(dir, 'src', instrumentationFile);
|
|
346
|
-
// dar require, e executar a função principal do arquivo
|
|
347
300
|
const instrumentation = require(instrumentationPath);
|
|
348
|
-
// Registra o listener de hot reload se existir
|
|
349
301
|
if (instrumentation.hotReloadListener && typeof instrumentation.hotReloadListener === 'function') {
|
|
350
302
|
if (hotReloadManager) {
|
|
351
303
|
hotReloadManager.setHotReloadListener(instrumentation.hotReloadListener);
|
|
@@ -364,17 +316,14 @@ function vatts(options) {
|
|
|
364
316
|
},
|
|
365
317
|
getRequestHandler: () => {
|
|
366
318
|
return async (req, res) => {
|
|
367
|
-
// Detecta o framework e cria request/response genéricos
|
|
368
319
|
const adapter = factory_1.FrameworkAdapterFactory.detectFramework(req, res);
|
|
369
320
|
const genericReq = adapter.parseRequest(req);
|
|
370
321
|
const genericRes = adapter.createResponse(res);
|
|
371
|
-
// Adiciona informações do hweb na requisição genérica
|
|
372
322
|
genericReq.hwebDev = dev;
|
|
373
323
|
genericReq.hotReloadManager = hotReloadManager;
|
|
374
324
|
const { hostname } = req.headers;
|
|
375
325
|
const method = (genericReq.method || 'GET').toUpperCase();
|
|
376
326
|
const pathname = new URL(genericReq.url, `http://${hostname}:${port}`).pathname;
|
|
377
|
-
// RPC endpoint (antes das rotas de backend)
|
|
378
327
|
if (pathname === types_1.RPC_ENDPOINT && method === 'POST') {
|
|
379
328
|
try {
|
|
380
329
|
const result = await (0, server_1.executeRpc)({
|
|
@@ -391,12 +340,9 @@ function vatts(options) {
|
|
|
391
340
|
return;
|
|
392
341
|
}
|
|
393
342
|
}
|
|
394
|
-
// 1. Verifica se é WebSocket upgrade para hot reload
|
|
395
343
|
if (pathname === '/hweb-hotreload/' && genericReq.headers.upgrade === 'websocket' && hotReloadManager) {
|
|
396
|
-
// Framework vai chamar o evento 'upgrade' do servidor HTTP
|
|
397
344
|
return;
|
|
398
345
|
}
|
|
399
|
-
// 2. Primeiro verifica se é um arquivo estático da pasta public
|
|
400
346
|
if (pathname !== '/' && !pathname.startsWith('/api/') && !pathname.startsWith('/.vatts')) {
|
|
401
347
|
const publicDir = path_1.default.join(dir, 'public');
|
|
402
348
|
if (!isSuspiciousPathname(pathname)) {
|
|
@@ -425,7 +371,6 @@ function vatts(options) {
|
|
|
425
371
|
'.zip': 'application/zip'
|
|
426
372
|
};
|
|
427
373
|
genericRes.header('Content-Type', contentTypes[ext] || 'application/octet-stream');
|
|
428
|
-
// Para arquivos estáticos, usamos o método nativo do framework
|
|
429
374
|
if (adapter.type === 'express') {
|
|
430
375
|
res.sendFile(filePath);
|
|
431
376
|
}
|
|
@@ -454,7 +399,6 @@ function vatts(options) {
|
|
|
454
399
|
'.map': 'application/json'
|
|
455
400
|
};
|
|
456
401
|
genericRes.header('Content-Type', contentTypes[ext] || 'text/plain');
|
|
457
|
-
// Para arquivos estáticos, usamos o método nativo do framework
|
|
458
402
|
if (adapter.type === 'express') {
|
|
459
403
|
res.sendFile(filePath);
|
|
460
404
|
}
|
|
@@ -470,18 +414,13 @@ function vatts(options) {
|
|
|
470
414
|
}
|
|
471
415
|
}
|
|
472
416
|
}
|
|
473
|
-
// 4. REMOVIDO: Verificação de arquivos React UMD - não precisamos mais
|
|
474
|
-
// O React agora será bundlado diretamente no main.js
|
|
475
|
-
// 5. Verifica se é uma rota de API (backend)
|
|
476
417
|
const backendMatch = (0, router_1.findMatchingBackendRoute)(pathname, method);
|
|
477
418
|
if (backendMatch) {
|
|
478
419
|
try {
|
|
479
420
|
const handler = backendMatch.route[method];
|
|
480
421
|
if (handler) {
|
|
481
422
|
const hwebReq = new http_1.VattsRequest(genericReq);
|
|
482
|
-
// Executa middlewares e depois o handler final
|
|
483
423
|
const hwebRes = await executeMiddlewareChain(backendMatch.route.middleware, handler, hwebReq, backendMatch.params);
|
|
484
|
-
// Aplica a resposta usando o adapter correto
|
|
485
424
|
hwebRes._applyTo(genericRes);
|
|
486
425
|
return;
|
|
487
426
|
}
|
|
@@ -492,24 +431,41 @@ function vatts(options) {
|
|
|
492
431
|
return;
|
|
493
432
|
}
|
|
494
433
|
}
|
|
495
|
-
//
|
|
434
|
+
// Renderização de Página (Frontend)
|
|
496
435
|
const pageMatch = (0, router_1.findMatchingRoute)(pathname);
|
|
436
|
+
// Determina o objeto de resposta "cru" para o stream do React
|
|
437
|
+
// Se for Fastify, o Writable stream está em res.raw. Se for Express/Native, é o próprio res.
|
|
438
|
+
const rawRes = (res.raw || res);
|
|
497
439
|
if (!pageMatch) {
|
|
498
|
-
// Em vez de enviar texto simples, renderiza a página 404 React
|
|
499
440
|
try {
|
|
500
|
-
// Cria uma "rota falsa" para a página 404
|
|
501
441
|
const notFoundRoute = {
|
|
502
442
|
pattern: '/__404__',
|
|
503
|
-
component: () => null, //
|
|
443
|
+
component: () => null, // Será renderizado via SSR se tiver componente de 404 definido no Client
|
|
504
444
|
componentPath: '__404__'
|
|
505
445
|
};
|
|
506
|
-
|
|
446
|
+
// Tenta usar componente 404 customizado se houver
|
|
447
|
+
const notFound = (0, router_1.getNotFound)();
|
|
448
|
+
if (notFound) {
|
|
449
|
+
// Carrega o componente 404 real para o SSR funcionar
|
|
450
|
+
try {
|
|
451
|
+
const nfModule = require(path_1.default.resolve(process.cwd(), notFound.componentPath));
|
|
452
|
+
// @ts-ignore
|
|
453
|
+
notFoundRoute.component = nfModule.default || nfModule;
|
|
454
|
+
}
|
|
455
|
+
catch (e) { }
|
|
456
|
+
}
|
|
457
|
+
// Define status 404 antes de iniciar o stream
|
|
458
|
+
if (rawRes.statusCode)
|
|
459
|
+
rawRes.statusCode = 404; // Native/Fastify
|
|
460
|
+
if (rawRes.status)
|
|
461
|
+
rawRes.status(404); // Express (mas pode quebrar se for raw, melhor usar statusCode)
|
|
462
|
+
await (0, renderer_1.renderAsStream)({
|
|
507
463
|
req: genericReq,
|
|
464
|
+
res: rawRes,
|
|
508
465
|
route: notFoundRoute,
|
|
509
466
|
params: {},
|
|
510
467
|
allRoutes: frontendRoutes
|
|
511
468
|
});
|
|
512
|
-
genericRes.status(404).header('Content-Type', 'text/html').send(html);
|
|
513
469
|
return;
|
|
514
470
|
}
|
|
515
471
|
catch (error) {
|
|
@@ -519,26 +475,28 @@ function vatts(options) {
|
|
|
519
475
|
}
|
|
520
476
|
}
|
|
521
477
|
try {
|
|
522
|
-
|
|
478
|
+
// Renderização via Stream (SSR + Streaming)
|
|
479
|
+
await (0, renderer_1.renderAsStream)({
|
|
523
480
|
req: genericReq,
|
|
481
|
+
res: rawRes,
|
|
524
482
|
route: pageMatch.route,
|
|
525
483
|
params: pageMatch.params,
|
|
526
484
|
allRoutes: frontendRoutes
|
|
527
485
|
});
|
|
528
|
-
genericRes.status(200).header('Content-Type', 'text/html').send(html);
|
|
529
486
|
}
|
|
530
487
|
catch (error) {
|
|
531
488
|
console_1.default.error(`Error rendering page ${pathname}:`, error);
|
|
532
|
-
|
|
489
|
+
// Se o stream já começou, não dá pra enviar erro 500 limpo.
|
|
490
|
+
// O renderAsStream tenta lidar com isso no onError.
|
|
491
|
+
if (!rawRes.headersSent) {
|
|
492
|
+
genericRes.status(500).text('Internal server error');
|
|
493
|
+
}
|
|
533
494
|
}
|
|
534
495
|
};
|
|
535
496
|
},
|
|
536
|
-
// Método para configurar WebSocket upgrade nos servidores Express e Fastify
|
|
537
497
|
setupWebSocket: (server) => {
|
|
538
|
-
// Detecta se é um servidor Express ou Fastify
|
|
539
498
|
const isExpressServer = factory_1.FrameworkAdapterFactory.getCurrentAdapter() instanceof express_1.ExpressAdapter;
|
|
540
499
|
const actualServer = isExpressServer ? server : (server.server || server);
|
|
541
|
-
// Usa o sistema coordenado de WebSocket upgrade que integra hot-reload e rotas de usuário
|
|
542
500
|
(0, router_1.setupWebSocketUpgrade)(actualServer, hotReloadManager);
|
|
543
501
|
},
|
|
544
502
|
stop: () => {
|
package/dist/renderer.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { RouteConfig } from './types';
|
|
|
2
2
|
import type { GenericRequest } from './types/framework';
|
|
3
3
|
interface RenderOptions {
|
|
4
4
|
req: GenericRequest;
|
|
5
|
+
res: any;
|
|
5
6
|
route: RouteConfig & {
|
|
6
7
|
componentPath: string;
|
|
7
8
|
};
|
|
@@ -10,5 +11,6 @@ interface RenderOptions {
|
|
|
10
11
|
componentPath: string;
|
|
11
12
|
})[];
|
|
12
13
|
}
|
|
13
|
-
export declare function
|
|
14
|
+
export declare function renderAsStream({ req, res, route, params, allRoutes }: RenderOptions): Promise<void>;
|
|
15
|
+
export declare function render(options: any): Promise<string>;
|
|
14
16
|
export {};
|