hightjs 0.1.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 (131) hide show
  1. package/.idea/HightJS.iml +9 -0
  2. package/.idea/copilot.data.migration.agent.xml +6 -0
  3. package/.idea/copilot.data.migration.ask.xml +6 -0
  4. package/.idea/copilot.data.migration.ask2agent.xml +6 -0
  5. package/.idea/copilot.data.migration.edit.xml +6 -0
  6. package/.idea/inspectionProfiles/Project_Default.xml +13 -0
  7. package/.idea/libraries/test_package.xml +9 -0
  8. package/.idea/libraries/ts_commonjs_default_export.xml +9 -0
  9. package/.idea/misc.xml +7 -0
  10. package/.idea/modules.xml +8 -0
  11. package/.idea/vcs.xml +6 -0
  12. package/LICENSE +13 -0
  13. package/README.md +508 -0
  14. package/dist/adapters/express.d.ts +7 -0
  15. package/dist/adapters/express.js +63 -0
  16. package/dist/adapters/factory.d.ts +23 -0
  17. package/dist/adapters/factory.js +122 -0
  18. package/dist/adapters/fastify.d.ts +25 -0
  19. package/dist/adapters/fastify.js +61 -0
  20. package/dist/adapters/native.d.ts +8 -0
  21. package/dist/adapters/native.js +203 -0
  22. package/dist/adapters/starters/express.d.ts +0 -0
  23. package/dist/adapters/starters/express.js +1 -0
  24. package/dist/adapters/starters/factory.d.ts +0 -0
  25. package/dist/adapters/starters/factory.js +1 -0
  26. package/dist/adapters/starters/fastify.d.ts +0 -0
  27. package/dist/adapters/starters/fastify.js +1 -0
  28. package/dist/adapters/starters/index.d.ts +0 -0
  29. package/dist/adapters/starters/index.js +1 -0
  30. package/dist/adapters/starters/native.d.ts +0 -0
  31. package/dist/adapters/starters/native.js +1 -0
  32. package/dist/api/console.d.ts +92 -0
  33. package/dist/api/console.js +276 -0
  34. package/dist/api/http.d.ts +180 -0
  35. package/dist/api/http.js +467 -0
  36. package/dist/auth/client.d.ts +14 -0
  37. package/dist/auth/client.js +68 -0
  38. package/dist/auth/components.d.ts +29 -0
  39. package/dist/auth/components.js +84 -0
  40. package/dist/auth/core.d.ts +38 -0
  41. package/dist/auth/core.js +124 -0
  42. package/dist/auth/index.d.ts +7 -0
  43. package/dist/auth/index.js +27 -0
  44. package/dist/auth/jwt.d.ts +41 -0
  45. package/dist/auth/jwt.js +169 -0
  46. package/dist/auth/providers.d.ts +5 -0
  47. package/dist/auth/providers.js +14 -0
  48. package/dist/auth/react/index.d.ts +6 -0
  49. package/dist/auth/react/index.js +32 -0
  50. package/dist/auth/react.d.ts +22 -0
  51. package/dist/auth/react.js +175 -0
  52. package/dist/auth/routes.d.ts +16 -0
  53. package/dist/auth/routes.js +104 -0
  54. package/dist/auth/types.d.ts +62 -0
  55. package/dist/auth/types.js +2 -0
  56. package/dist/bin/hightjs.d.ts +2 -0
  57. package/dist/bin/hightjs.js +35 -0
  58. package/dist/builder.d.ts +32 -0
  59. package/dist/builder.js +341 -0
  60. package/dist/client/DefaultNotFound.d.ts +1 -0
  61. package/dist/client/DefaultNotFound.js +53 -0
  62. package/dist/client/ErrorBoundary.d.ts +16 -0
  63. package/dist/client/ErrorBoundary.js +181 -0
  64. package/dist/client/clientRouter.d.ts +58 -0
  65. package/dist/client/clientRouter.js +116 -0
  66. package/dist/client/entry.client.d.ts +1 -0
  67. package/dist/client/entry.client.js +271 -0
  68. package/dist/client/routerContext.d.ts +26 -0
  69. package/dist/client/routerContext.js +62 -0
  70. package/dist/client.d.ts +3 -0
  71. package/dist/client.js +8 -0
  72. package/dist/components/Link.d.ts +7 -0
  73. package/dist/components/Link.js +13 -0
  74. package/dist/eslint/index.d.ts +32 -0
  75. package/dist/eslint/index.js +15 -0
  76. package/dist/eslint/use-client-rule.d.ts +19 -0
  77. package/dist/eslint/use-client-rule.js +99 -0
  78. package/dist/eslintSetup.d.ts +0 -0
  79. package/dist/eslintSetup.js +1 -0
  80. package/dist/example/src/web/routes/index.d.ts +3 -0
  81. package/dist/example/src/web/routes/index.js +15 -0
  82. package/dist/helpers.d.ts +18 -0
  83. package/dist/helpers.js +318 -0
  84. package/dist/hotReload.d.ts +23 -0
  85. package/dist/hotReload.js +292 -0
  86. package/dist/index.d.ts +17 -0
  87. package/dist/index.js +480 -0
  88. package/dist/renderer.d.ts +14 -0
  89. package/dist/renderer.js +106 -0
  90. package/dist/router.d.ts +78 -0
  91. package/dist/router.js +359 -0
  92. package/dist/types/framework.d.ts +37 -0
  93. package/dist/types/framework.js +2 -0
  94. package/dist/types.d.ts +43 -0
  95. package/dist/types.js +2 -0
  96. package/dist/typescript/use-client-plugin.d.ts +5 -0
  97. package/dist/typescript/use-client-plugin.js +113 -0
  98. package/dist/validation.d.ts +0 -0
  99. package/dist/validation.js +1 -0
  100. package/package.json +72 -0
  101. package/src/adapters/express.ts +70 -0
  102. package/src/adapters/factory.ts +96 -0
  103. package/src/adapters/fastify.ts +88 -0
  104. package/src/adapters/native.ts +223 -0
  105. package/src/api/console.ts +285 -0
  106. package/src/api/http.ts +515 -0
  107. package/src/auth/client.ts +74 -0
  108. package/src/auth/components.tsx +109 -0
  109. package/src/auth/core.ts +143 -0
  110. package/src/auth/index.ts +9 -0
  111. package/src/auth/jwt.ts +194 -0
  112. package/src/auth/providers.ts +13 -0
  113. package/src/auth/react/index.ts +9 -0
  114. package/src/auth/react.tsx +209 -0
  115. package/src/auth/routes.ts +133 -0
  116. package/src/auth/types.ts +73 -0
  117. package/src/bin/hightjs.js +40 -0
  118. package/src/builder.js +362 -0
  119. package/src/client/DefaultNotFound.tsx +68 -0
  120. package/src/client/clientRouter.ts +137 -0
  121. package/src/client/entry.client.tsx +302 -0
  122. package/src/client.ts +8 -0
  123. package/src/components/Link.tsx +22 -0
  124. package/src/helpers.ts +316 -0
  125. package/src/hotReload.ts +289 -0
  126. package/src/index.ts +514 -0
  127. package/src/renderer.tsx +122 -0
  128. package/src/router.ts +400 -0
  129. package/src/types/framework.ts +42 -0
  130. package/src/types.ts +54 -0
  131. package/tsconfig.json +17 -0
package/src/index.ts ADDED
@@ -0,0 +1,514 @@
1
+ import express from 'express';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+ import { ExpressAdapter } from './adapters/express';
5
+ import { build, watch, buildWithChunks, watchWithChunks } from './builder';
6
+ import { HightJSOptions, RequestHandler, RouteConfig, BackendRouteConfig, BackendHandler, HightMiddleware } from './types';
7
+ import { loadRoutes, findMatchingRoute, loadBackendRoutes, findMatchingBackendRoute, loadLayout, getLayout, loadNotFound, getNotFound } from './router';
8
+ import { render } from './renderer';
9
+ import { HightJSRequest, HightJSResponse } from './api/http';
10
+ import { HotReloadManager } from './hotReload';
11
+ import { FrameworkAdapterFactory } from './adapters/factory';
12
+ import { GenericRequest, GenericResponse } from './types/framework';
13
+ import Console, {Colors} from "./api/console"
14
+
15
+ // Exporta apenas os tipos e classes para o backend
16
+ export { HightJSRequest, HightJSResponse };
17
+ export type { BackendRouteConfig, BackendHandler };
18
+
19
+ // Exporta os adapters para uso manual se necessário
20
+ export { ExpressAdapter } from './adapters/express';
21
+ export { FastifyAdapter } from './adapters/fastify';
22
+ export { FrameworkAdapterFactory } from './adapters/factory';
23
+ export type { GenericRequest, GenericResponse, CookieOptions } from './types/framework';
24
+
25
+ // Exporta os helpers para facilitar integração
26
+ export { app } from './helpers';
27
+
28
+ // Exporta o sistema de autenticação
29
+
30
+ // Função para verificar se o projeto é grande o suficiente para se beneficiar de chunks
31
+ function isLargeProject(projectDir: string): boolean {
32
+ try {
33
+ const srcDir = path.join(projectDir, 'src');
34
+ if (!fs.existsSync(srcDir)) return false;
35
+
36
+ let totalFiles = 0;
37
+ let totalSize = 0;
38
+
39
+ function scanDirectory(dir: string) {
40
+ const items = fs.readdirSync(dir, { withFileTypes: true });
41
+
42
+ for (const item of items) {
43
+ const fullPath = path.join(dir, item.name);
44
+
45
+ if (item.isDirectory() && item.name !== 'node_modules' && item.name !== '.git') {
46
+ scanDirectory(fullPath);
47
+ } else if (item.isFile() && /\.(tsx?|jsx?|css|scss|less)$/i.test(item.name)) {
48
+ totalFiles++;
49
+ totalSize += fs.statSync(fullPath).size;
50
+ }
51
+ }
52
+ }
53
+
54
+ scanDirectory(srcDir);
55
+
56
+ // Considera projeto grande se:
57
+ // - Mais de 20 arquivos de frontend/style
58
+ // - Ou tamanho total > 500KB
59
+ return totalFiles > 20 || totalSize > 500 * 1024;
60
+ } catch (error) {
61
+ // Em caso de erro, assume que não é um projeto grande
62
+ return false;
63
+ }
64
+ }
65
+
66
+ // Função para gerar o arquivo de entrada para o esbuild
67
+ function createEntryFile(projectDir: string, routes: (RouteConfig & { componentPath: string })[]): string {
68
+ const tempDir = path.join(projectDir, '.hweb');
69
+ fs.mkdirSync(tempDir, { recursive: true });
70
+
71
+ const entryFilePath = path.join(tempDir, 'entry.client.js');
72
+
73
+ // Verifica se há layout
74
+ const layout = getLayout();
75
+
76
+ // Verifica se há notFound personalizado
77
+ const notFound = getNotFound();
78
+
79
+ // Gera imports dinâmicos para cada componente
80
+ const imports = routes
81
+ .map((route, index) => {
82
+ const relativePath = path.relative(tempDir, route.componentPath).replace(/\\/g, '/');
83
+ return `import route${index} from '${relativePath}';`;
84
+ })
85
+ .join('\n');
86
+
87
+ // Import do layout se existir
88
+ const layoutImport = layout
89
+ ? `import LayoutComponent from '${path.relative(tempDir, layout.componentPath).replace(/\\/g, '/')}';`
90
+ : '';
91
+
92
+ // Import do notFound se existir
93
+ const notFoundImport = notFound
94
+ ? `import NotFoundComponent from '${path.relative(tempDir, notFound.componentPath).replace(/\\/g, '/')}';`
95
+ : '';
96
+
97
+ // Registra os componentes no window para o cliente acessar
98
+ const componentRegistration = routes
99
+ .map((route, index) => ` '${route.componentPath}': route${index}.component || route${index}.default?.component,`)
100
+ .join('\n');
101
+
102
+ // Registra o layout se existir
103
+ const layoutRegistration = layout
104
+ ? `window.__HWEB_LAYOUT__ = LayoutComponent.default || LayoutComponent;`
105
+ : `window.__HWEB_LAYOUT__ = null;`;
106
+
107
+ // Registra o notFound se existir
108
+ const notFoundRegistration = notFound
109
+ ? `window.__HWEB_NOT_FOUND__ = NotFoundComponent.default || NotFoundComponent;`
110
+ : `window.__HWEB_NOT_FOUND__ = null;`;
111
+
112
+ // Caminho correto para o entry.client.tsx
113
+ const sdkDir = path.dirname(__dirname); // Vai para a pasta pai de src (onde está o hweb-sdk)
114
+ const entryClientPath = path.join(sdkDir, 'src', 'client', 'entry.client.tsx');
115
+ const relativeEntryPath = path.relative(tempDir, entryClientPath).replace(/\\/g, '/');
116
+
117
+ // Import do DefaultNotFound do SDK
118
+ const defaultNotFoundPath = path.join(sdkDir, 'src', 'client', 'DefaultNotFound.tsx');
119
+ const relativeDefaultNotFoundPath = path.relative(tempDir, defaultNotFoundPath).replace(/\\/g, '/');
120
+
121
+ const entryContent = `// Arquivo gerado automaticamente pelo hweb
122
+ ${imports}
123
+ ${layoutImport}
124
+ ${notFoundImport}
125
+ import DefaultNotFound from '${relativeDefaultNotFoundPath}';
126
+
127
+ // Registra os componentes para o cliente
128
+ window.__HWEB_COMPONENTS__ = {
129
+ ${componentRegistration}
130
+ };
131
+
132
+ // Registra o layout se existir
133
+ ${layoutRegistration}
134
+
135
+ // Registra o notFound se existir
136
+ ${notFoundRegistration}
137
+
138
+ // Registra o DefaultNotFound do hweb
139
+ window.__HWEB_DEFAULT_NOT_FOUND__ = DefaultNotFound;
140
+
141
+ // Importa e executa o entry.client.tsx
142
+ import '${relativeEntryPath}';
143
+ `;
144
+
145
+ fs.writeFileSync(entryFilePath, entryContent);
146
+
147
+ return entryFilePath;
148
+ }
149
+
150
+ export default function hweb(options: HightJSOptions) {
151
+ const { dev = true, dir = process.cwd(), port = 3000 } = options;
152
+ const userWebDir = path.join(dir, 'src', 'web');
153
+ const userWebRoutesDir = path.join(userWebDir, 'routes');
154
+ const userBackendRoutesDir = path.join(userWebDir, 'backend', 'routes');
155
+
156
+ /**
157
+ * Executa middlewares sequencialmente e depois o handler final
158
+ * @param middlewares Array de middlewares para executar
159
+ * @param finalHandler Handler final da rota
160
+ * @param request Requisição do HightJS
161
+ * @param params Parâmetros da rota
162
+ * @returns Resposta do middleware ou handler final
163
+ */
164
+ async function executeMiddlewareChain(
165
+ middlewares: HightMiddleware[] | undefined,
166
+ finalHandler: BackendHandler,
167
+ request: HightJSRequest,
168
+ params: { [key: string]: string }
169
+ ): Promise<HightJSResponse> {
170
+ if (!middlewares || middlewares.length === 0) {
171
+ // Não há middlewares, executa diretamente o handler final
172
+ return await finalHandler(request, params);
173
+ }
174
+
175
+ let currentIndex = 0;
176
+
177
+ // Função next que será chamada pelos middlewares
178
+ const next = async (): Promise<HightJSResponse> => {
179
+ if (currentIndex < middlewares.length) {
180
+ // Ainda há middlewares para executar
181
+ const currentMiddleware = middlewares[currentIndex];
182
+ currentIndex++;
183
+ return await currentMiddleware(request, params, next);
184
+ } else {
185
+ // Todos os middlewares foram executados, chama o handler final
186
+ return await finalHandler(request, params);
187
+ }
188
+ };
189
+
190
+ // Inicia a cadeia de execução
191
+ return await next();
192
+ }
193
+
194
+ let frontendRoutes: (RouteConfig & { componentPath: string })[] = [];
195
+ let hotReloadManager: HotReloadManager | null = null;
196
+ let entryPoint: string;
197
+ let outfile: string;
198
+
199
+ // Função para regenerar o entry file
200
+ const regenerateEntryFile = () => {
201
+ // Recarrega todas as rotas e componentes
202
+ frontendRoutes = loadRoutes(userWebRoutesDir);
203
+ loadLayout(userWebDir);
204
+ loadNotFound(userWebDir);
205
+
206
+ // Regenera o entry file
207
+ entryPoint = createEntryFile(dir, frontendRoutes);
208
+ };
209
+
210
+ const app = {
211
+ prepare: async () => {
212
+ const isProduction = !dev;
213
+
214
+ if (!isProduction) {
215
+ // Inicia hot reload apenas em desenvolvimento (sem logs)
216
+ hotReloadManager = new HotReloadManager(dir);
217
+ await hotReloadManager.start();
218
+
219
+ // Adiciona callback para recarregar rotas de backend quando mudarem
220
+ hotReloadManager.onBackendApiChange(() => {
221
+ loadBackendRoutes(userBackendRoutesDir);
222
+ });
223
+
224
+ // Adiciona callback para regenerar entry file quando frontend mudar
225
+ hotReloadManager.onFrontendChange(() => {
226
+ regenerateEntryFile();
227
+ });
228
+ }
229
+
230
+ // ORDEM IMPORTANTE: Carrega TUDO antes de criar o arquivo de entrada
231
+ frontendRoutes = loadRoutes(userWebRoutesDir);
232
+ loadBackendRoutes(userBackendRoutesDir);
233
+
234
+ // Carrega layout.tsx ANTES de criar o entry file
235
+ const layout = loadLayout(userWebDir);
236
+
237
+ const notFound = loadNotFound(userWebDir);
238
+
239
+ const outDir = path.join(dir, 'hweb-dist');
240
+ fs.mkdirSync(outDir, { recursive: true });
241
+
242
+ entryPoint = createEntryFile(dir, frontendRoutes);
243
+
244
+ // Usa chunks quando há muitas rotas ou o projeto é grande
245
+ const shouldUseChunks = frontendRoutes.length > 5 || isLargeProject(dir);
246
+
247
+ if (shouldUseChunks) {
248
+ const outDir = path.join(dir, 'hweb-dist');
249
+
250
+ if (isProduction) {
251
+ await buildWithChunks(entryPoint, outDir, isProduction);
252
+ Console.info(`✅ Build com chunks finalizado! ${frontendRoutes.length} rotas processadas.`);
253
+ } else {
254
+ watchWithChunks(entryPoint, outDir, hotReloadManager!).catch(err => {
255
+ Console.error(`Erro ao iniciar o watch com chunks`, err);
256
+ });
257
+ Console.info(`🚀 Modo watch com chunks ativo para ${frontendRoutes.length} rotas.`);
258
+ }
259
+ } else {
260
+ outfile = path.join(dir, 'hweb-dist', 'main.js');
261
+
262
+ if (isProduction) {
263
+ await build(entryPoint, outfile, isProduction);
264
+ } else {
265
+ watch(entryPoint, outfile, hotReloadManager!).catch(err => {
266
+ Console.error(`Erro ao iniciar o watch`, err);
267
+ });
268
+ }
269
+ }
270
+
271
+ },
272
+
273
+ executeInstrumentation: () => {
274
+
275
+ // verificar se dir/src/instrumentation.(tsx/jsx/js/ts) existe com regex
276
+ const instrumentationFile = fs.readdirSync(path.join(dir, 'src')).find(file => /^hightweb\.(tsx|jsx|js|ts)$/.test(file));
277
+ if (instrumentationFile) {
278
+ const instrumentationPath = path.join(dir, 'src', instrumentationFile);
279
+ // dar require, e executar a função principal do arquivo
280
+ const instrumentation = require(instrumentationPath);
281
+ if (typeof instrumentation === 'function') {
282
+ instrumentation();
283
+ } else if (typeof instrumentation.default === 'function') {
284
+ instrumentation.default();
285
+ } else {
286
+ Console.warn(`O arquivo de instrumentação ${instrumentationFile} não exporta uma função padrão.`);
287
+ }
288
+ }
289
+ },
290
+ getRequestHandler: (): RequestHandler => {
291
+ return async (req: any, res: any) => {
292
+ // Detecta automaticamente o framework e cria o adapter apropriado
293
+ const adapter = FrameworkAdapterFactory.detectFramework(req, res);
294
+ const genericReq = adapter.parseRequest(req);
295
+ const genericRes = adapter.createResponse(res);
296
+
297
+ // Adiciona informações do hweb na requisição genérica
298
+ (genericReq as any).hwebDev = dev;
299
+ (genericReq as any).hotReloadManager = hotReloadManager;
300
+
301
+ const { pathname } = new URL(genericReq.url, `http://${genericReq.headers.host || 'localhost'}`);
302
+ const method = genericReq.method.toUpperCase();
303
+
304
+ // 1. Verifica se é WebSocket upgrade para hot reload
305
+ if (pathname === '/hweb-hotreload/' && genericReq.headers.upgrade === 'websocket' && hotReloadManager) {
306
+ // Framework vai chamar o evento 'upgrade' do servidor HTTP
307
+ return;
308
+ }
309
+
310
+ // 2. Primeiro verifica se é um arquivo estático da pasta public
311
+ if (pathname !== '/' && !pathname.startsWith('/api/') && !pathname.startsWith('/hweb-')) {
312
+ const publicDir = path.join(dir, 'public');
313
+ const filePath = path.join(publicDir, pathname);
314
+
315
+ if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
316
+ const ext = path.extname(filePath).toLowerCase();
317
+ const contentTypes: Record<string, string> = {
318
+ '.html': 'text/html',
319
+ '.css': 'text/css',
320
+ '.js': 'application/javascript',
321
+ '.json': 'application/json',
322
+ '.png': 'image/png',
323
+ '.jpg': 'image/jpeg',
324
+ '.jpeg': 'image/jpeg',
325
+ '.gif': 'image/gif',
326
+ '.svg': 'image/svg+xml',
327
+ '.ico': 'image/x-icon',
328
+ '.webp': 'image/webp',
329
+ '.mp4': 'video/mp4',
330
+ '.webm': 'video/webm',
331
+ '.mp3': 'audio/mpeg',
332
+ '.wav': 'audio/wav',
333
+ '.pdf': 'application/pdf',
334
+ '.txt': 'text/plain',
335
+ '.xml': 'application/xml',
336
+ '.zip': 'application/zip'
337
+ };
338
+
339
+ genericRes.header('Content-Type', contentTypes[ext] || 'application/octet-stream');
340
+
341
+ // Para arquivos estáticos, usamos o método nativo do framework
342
+ if (adapter.type === 'express') {
343
+ (res as any).sendFile(filePath);
344
+ } else if (adapter.type === 'fastify') {
345
+ const fileContent = fs.readFileSync(filePath);
346
+ genericRes.send(fileContent);
347
+ } else if (adapter.type === 'native') {
348
+ const fileContent = fs.readFileSync(filePath);
349
+ genericRes.send(fileContent);
350
+ }
351
+ return;
352
+ }
353
+ }
354
+
355
+ // 3. Verifica se é um arquivo estático do hweb-dist
356
+ if (pathname.startsWith('/hweb-dist/')) {
357
+ const staticPath = path.join(dir, 'hweb-dist');
358
+ const filePath = path.join(staticPath, pathname.replace('/hweb-dist/', ''));
359
+
360
+ if (fs.existsSync(filePath)) {
361
+ const ext = path.extname(filePath).toLowerCase();
362
+ const contentTypes: Record<string, string> = {
363
+ '.js': 'application/javascript',
364
+ '.css': 'text/css',
365
+ '.map': 'application/json'
366
+ };
367
+
368
+ genericRes.header('Content-Type', contentTypes[ext] || 'text/plain');
369
+
370
+ // Para arquivos estáticos, usamos o método nativo do framework
371
+ if (adapter.type === 'express') {
372
+ (res as any).sendFile(filePath);
373
+ } else if (adapter.type === 'fastify') {
374
+ const fileContent = fs.readFileSync(filePath);
375
+ genericRes.send(fileContent);
376
+ } else if (adapter.type === 'native') {
377
+ const fileContent = fs.readFileSync(filePath);
378
+ genericRes.send(fileContent);
379
+ }
380
+ return;
381
+ }
382
+ }
383
+
384
+ // 4. REMOVIDO: Verificação de arquivos React UMD - não precisamos mais
385
+ // O React agora será bundlado diretamente no main.js
386
+
387
+ // 5. Verifica se é uma rota de API (backend)
388
+ const backendMatch = findMatchingBackendRoute(pathname, method);
389
+ if (backendMatch) {
390
+ try {
391
+ const handler = backendMatch.route[method as keyof BackendRouteConfig] as BackendHandler;
392
+ if (handler) {
393
+ const hwebReq = new HightJSRequest(genericReq);
394
+
395
+ // Executa middlewares e depois o handler final
396
+ const hwebRes = await executeMiddlewareChain(
397
+ backendMatch.route.middleware,
398
+ handler,
399
+ hwebReq,
400
+ backendMatch.params
401
+ );
402
+
403
+ // Aplica a resposta usando o adapter correto
404
+ hwebRes._applyTo(genericRes);
405
+ return;
406
+ }
407
+ } catch (error) {
408
+ Console.error(`Erro na rota de API ${pathname}:`, error);
409
+ genericRes.status(500).text('Erro interno do servidor na API');
410
+ return;
411
+ }
412
+ }
413
+
414
+ // 6. Por último, tenta renderizar uma página (frontend) ou 404
415
+ const pageMatch = findMatchingRoute(pathname);
416
+
417
+ if (!pageMatch) {
418
+ // Em vez de enviar texto simples, renderiza a página 404 React
419
+ try {
420
+ // Cria uma "rota falsa" para a página 404
421
+ const notFoundRoute = {
422
+ pattern: '/__404__',
423
+ component: () => null, // Componente vazio, será tratado no cliente
424
+ componentPath: '__404__'
425
+ };
426
+
427
+ const html = await render({
428
+ req: genericReq,
429
+ route: notFoundRoute,
430
+ params: {},
431
+ allRoutes: frontendRoutes
432
+ });
433
+ genericRes.status(404).header('Content-Type', 'text/html').send(html);
434
+ return;
435
+ } catch (error) {
436
+ Console.error(`Erro ao renderizar página 404:`, error);
437
+ genericRes.status(404).text('Página não encontrada');
438
+ return;
439
+ }
440
+ }
441
+
442
+ try {
443
+ const html = await render({
444
+ req: genericReq,
445
+ route: pageMatch.route,
446
+ params: pageMatch.params,
447
+ allRoutes: frontendRoutes
448
+ });
449
+ genericRes.status(200).header('Content-Type', 'text/html').send(html);
450
+ } catch (error) {
451
+ Console.error(`Erro ao renderizar a página ${pathname}:`, error);
452
+ genericRes.status(500).text('Erro interno do servidor');
453
+ }
454
+ };
455
+ },
456
+
457
+ // Método para configurar WebSocket upgrade nos servidores Express e Fastify
458
+ setupWebSocket: (server: any) => {
459
+ if (hotReloadManager) {
460
+ // Detecta se é um servidor Express ou Fastify
461
+ const isExpressServer = FrameworkAdapterFactory.getCurrentAdapter() instanceof ExpressAdapter;
462
+
463
+
464
+ if (isExpressServer) {
465
+
466
+ server.on('upgrade', (request: any, socket: any, head: Buffer) => {
467
+ const { pathname } = new URL(request.url, `http://${request.headers.host}`);
468
+
469
+ if (pathname === '/hweb-hotreload/') {
470
+ hotReloadManager!.handleUpgrade(request, socket, head);
471
+ } else {
472
+ socket.destroy();
473
+ }
474
+ });
475
+ } else {
476
+
477
+ // Fastify usa um approach diferente para WebSockets
478
+ const actualServer = server.server || server;
479
+ actualServer.on('upgrade', (request: any, socket: any, head: Buffer) => {
480
+ const { pathname } = new URL(request.url, `http://${request.headers.host}`);
481
+
482
+ if (pathname === '/hweb-hotreload/') {
483
+ hotReloadManager!.handleUpgrade(request, socket, head);
484
+ } else {
485
+ socket.destroy();
486
+ }
487
+ });
488
+ }
489
+ }
490
+ },
491
+
492
+ build: async () => {
493
+ const msg = Console.dynamicLine(` ${Colors.FgYellow}● ${Colors.Reset}Iniciando build do cliente para produção`);
494
+ const outDir = path.join(dir, 'hweb-dist');
495
+ fs.mkdirSync(outDir, { recursive: true });
496
+
497
+ const routes = loadRoutes(userWebRoutesDir);
498
+ const entryPoint = createEntryFile(dir, routes);
499
+ const outfile = path.join(outDir, 'main.js');
500
+
501
+ await build(entryPoint, outfile, true); // Força produção no build manual
502
+
503
+ msg.end(` ${Colors.FgGreen}● ${Colors.Reset}Build do cliente concluído: ${outfile}`);
504
+ },
505
+
506
+ stop: () => {
507
+ if (hotReloadManager) {
508
+ hotReloadManager.stop();
509
+ }
510
+ }
511
+ };
512
+
513
+ return app;
514
+ }
@@ -0,0 +1,122 @@
1
+ import React from 'react';
2
+ import { RouteConfig, Metadata } from './types';
3
+ import { getLayout } from './router';
4
+ import type { GenericRequest } from './types/framework';
5
+ import fs from 'fs';
6
+ import path from 'path';
7
+
8
+ // Funções para codificar/decodificar dados (disfarça o JSON no HTML)
9
+ function encodeInitialData(data: any): string {
10
+ // Converte para JSON, depois para base64, e adiciona um prefixo fake
11
+ const jsonStr = JSON.stringify(data);
12
+ const base64 = Buffer.from(jsonStr).toString('base64');
13
+ return `hweb_${base64}_config`;
14
+ }
15
+
16
+ function createDecodeScript(): string {
17
+ return `
18
+ const process = { env: { NODE_ENV: '${process.env.NODE_ENV || 'development'}' } };
19
+ window.__HWEB_DECODE__ = function(encoded) { const base64 = encoded.replace('hweb_', '').replace('_config', ''); const jsonStr = atob(base64); return JSON.parse(jsonStr); };
20
+ `;
21
+ }
22
+
23
+ // Interface para opções de renderização apenas do cliente
24
+ interface RenderOptions {
25
+ req: GenericRequest;
26
+ route: RouteConfig & { componentPath: string };
27
+ params: Record<string, string>;
28
+ allRoutes: (RouteConfig & { componentPath: string })[];
29
+ }
30
+
31
+ export async function render({ req, route, params, allRoutes }: RenderOptions): Promise<string> {
32
+ const { generateMetadata } = route;
33
+
34
+ // Pega a opção dev e hot reload manager do req
35
+ const isProduction = !(req as any).hwebDev;
36
+ const hotReloadManager = (req as any).hotReloadManager;
37
+
38
+ // Pega o layout se existir
39
+ const layout = getLayout();
40
+
41
+ let metadata: Metadata = { title: 'App hweb' };
42
+
43
+ // Primeiro usa o metadata do layout se existir
44
+ if (layout && layout.metadata) {
45
+ metadata = { ...metadata, ...layout.metadata };
46
+ }
47
+
48
+ // Depois sobrescreve com metadata específico da rota se existir
49
+ if (generateMetadata) {
50
+ const routeMetadata = await Promise.resolve(generateMetadata(params, req));
51
+ metadata = { ...metadata, ...routeMetadata };
52
+ }
53
+
54
+ // Prepara os dados para injetar na janela do navegador
55
+ const initialData = {
56
+ routes: allRoutes.map(r => ({ pattern: r.pattern, componentPath: r.componentPath })),
57
+ initialComponentPath: route.componentPath,
58
+ initialParams: params,
59
+ };
60
+
61
+ // Codifica os dados para disfarçar
62
+ const encodedData = encodeInitialData(initialData);
63
+
64
+ // Script de hot reload apenas em desenvolvimento
65
+ const hotReloadScript = !isProduction && hotReloadManager
66
+ ? hotReloadManager.getClientScript()
67
+ : '';
68
+
69
+ const favicon = metadata.favicon ? `<link rel="icon" href="${metadata.favicon}">` : '';
70
+
71
+ // Determina quais arquivos JavaScript carregar
72
+ const jsFiles = getJavaScriptFiles(req);
73
+
74
+ // HTML base sem SSR - apenas o container e scripts para client-side rendering
75
+ return `<!DOCTYPE html><html lang="pt-BR"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>${metadata.title || 'App hweb'}</title>${metadata.description ? `<meta name="description" content="${metadata.description}">` : ''}${favicon}</head><body><div id="root"></div><script>${createDecodeScript()}window.__HWEB_INITIAL_DATA__ = window.__HWEB_DECODE__('${encodedData}');</script>${jsFiles}${hotReloadScript}</body></html>`;
76
+ }
77
+
78
+ // Função para determinar quais arquivos JavaScript carregar
79
+ function getJavaScriptFiles(req: GenericRequest): string {
80
+ const projectDir = process.cwd();
81
+ const distDir = path.join(projectDir, 'hweb-dist');
82
+
83
+ try {
84
+ // Verifica se existe um manifesto de chunks (gerado pelo ESBuild com splitting)
85
+ const manifestPath = path.join(distDir, 'manifest.json');
86
+
87
+ if (fs.existsSync(manifestPath)) {
88
+ // Modo chunks - carrega todos os arquivos necessários
89
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
90
+ const scripts = Object.values(manifest)
91
+ .filter((file: any) => file.endsWith('.js'))
92
+ .map((file: any) => `<script src="/hweb-dist/${file}"></script>`)
93
+ .join('');
94
+ return scripts;
95
+ } else {
96
+ // Verifica se existem múltiplos arquivos JS (chunks sem manifesto)
97
+ const jsFiles = fs.readdirSync(distDir)
98
+ .filter(file => file.endsWith('.js') && !file.endsWith('.map'))
99
+ .sort((a, b) => {
100
+ // Ordena para carregar arquivos principais primeiro
101
+ if (a.includes('main')) return -1;
102
+ if (b.includes('main')) return 1;
103
+ if (a.includes('vendor') || a.includes('react')) return -1;
104
+ if (b.includes('vendor') || b.includes('react')) return 1;
105
+ return a.localeCompare(b);
106
+ });
107
+
108
+ if (jsFiles.length > 1) {
109
+ // Modo chunks sem manifesto
110
+ return jsFiles
111
+ .map(file => `<script src="/hweb-dist/${file}"></script>`)
112
+ .join('');
113
+ } else {
114
+ // Modo tradicional - único arquivo
115
+ return '<script src="/hweb-dist/main.js"></script>';
116
+ }
117
+ }
118
+ } catch (error) {
119
+ // Fallback para o modo tradicional
120
+ return '<script src="/hweb-dist/main.js"></script>';
121
+ }
122
+ }