hightjs 0.3.4 → 0.3.5

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.
@@ -8,11 +8,65 @@ interface LayoutProps {
8
8
  children: React.ReactNode;
9
9
  }
10
10
 
11
- // Metadata exportada (similar ao Next.js)
11
+
12
12
  export const metadata: Metadata = {
13
- title: "Hight JS",
14
- description: "O framework web mais rápido e simples para React!",
13
+ // --- Informações Básicas ---
14
+ // Título otimizado para abas do navegador e resultados de busca
15
+ title: "Hight JS | O Framework Web Rápido e Simples para React",
16
+ description: "O framework web mais rápido e simples para React! Comece a construir aplicações web performáticas hoje mesmo com Hight JS.",
17
+ keywords: ["Hight JS", "framework web", "React", "JavaScript", "TypeScript", "desenvolvimento web", "rápido", "simples", "SSR", "frontend"],
18
+ author: "Hight JS Team",
15
19
  favicon: "/favicon.ico",
20
+
21
+ // --- Mobile e Viewport ---
22
+ // Essencial para design responsivo e boa pontuação no mobile
23
+ viewport: "width=device-width, initial-scale=1.0",
24
+ // Cor da barra de navegação em browsers mobile (ex: Chrome no Android)
25
+ themeColor: "#0A0A0A", // Sugestão de cor escura, ajuste conforme sua marca
26
+
27
+ // --- SEO ---
28
+ // URL principal do site para evitar conteúdo duplicado
29
+ canonical: "https://hightjs.com", // Substitua pelo seu domínio real
30
+ // Instruções para robôs de busca (Google, Bing, etc.)
31
+ robots: "index, follow",
32
+
33
+ // --- Open Graph (para Redes Sociais como Facebook, LinkedIn, Discord) ---
34
+ openGraph: {
35
+ title: "Hight JS | O Framework Web Rápido e Simples para React",
36
+ description: "Descubra o Hight JS, o framework web focado em performance e simplicidade para suas aplicações React.",
37
+ type: "website",
38
+ url: "https://hightjs.com", // Seu domínio real
39
+ // URL de uma imagem de preview (idealmente 1200x630px)
40
+ image: "https://hightjs.com/og-image.png",
41
+ siteName: "Hight JS",
42
+ locale: "pt_BR",
43
+ },
44
+
45
+ // --- Twitter Card (para compartilhamento no Twitter) ---
46
+ twitter: {
47
+ card: "summary_large_image", // Mostra uma imagem grande no tweet
48
+ site: "@hightjs_team", // Handle do Twitter do projeto (exemplo)
49
+ creator: "@seu_criador", // Handle do Twitter do criador (exemplo)
50
+ title: "Hight JS | O Framework Web Rápido e Simples para React",
51
+ description: "Cansado de complexidade? Conheça o Hight JS e construa sites React mais rápidos e leves.",
52
+ // Imagem específica para Twitter (ou pode reusar a 'og-image')
53
+ image: "https://hightjs.com/twitter-image.png",
54
+ imageAlt: "Logo e slogan do framework Hight JS",
55
+ },
56
+
57
+ // --- Ícones e Manifest (PWA) ---
58
+ // Ícone para dispositivos Apple (quando salvo na tela inicial)
59
+ appleTouchIcon: "/apple-touch-icon.png", // (ex: 180x180px)
60
+ // Link para o manifest de PWA (Progressive Web App)
61
+ manifest: "/site.webmanifest",
62
+
63
+ // --- Outros ---
64
+ language: "pt-BR", // Define o idioma principal da página
65
+ charset: "UTF-8", // Define o charset (embora geralmente setado no HTML)
66
+ other: {
67
+ // Tag útil para compatibilidade com navegadores legados (Edge/IE)
68
+ "X-UA-Compatible": "IE=edge"
69
+ }
16
70
  };
17
71
 
18
72
  export default function Layout({ children }: LayoutProps) {
@@ -21,7 +21,7 @@ function Home() {
21
21
  {status === 'loading' && <p>Carregando...</p>}
22
22
  {status === 'authenticated' && session?.user && (
23
23
  <>
24
- <p className="text-lg">Você está logado como <span className="font-bold text-purple-400">{session.user.name}</span> !!!</p>
24
+ <p className="text-lg">Você está logado como <span className="font-bold text-purple-400">{session.user.name}</span>!</p>
25
25
  <button
26
26
  onClick={() => signOut({callbackUrl: '/'})}
27
27
  className="rounded-full border border-solid border-transparent transition-all duration-300 flex items-center justify-center bg-purple-600 text-white gap-2 hover:bg-purple-500 font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-40 shadow-[0_0_15px_-3px_theme(colors.purple.600)] hover:shadow-[0_0_25px_-3px_theme(colors.purple.500)]"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hightjs",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "HightJS is a high-level framework for building web applications with ease and speed. It provides a robust set of tools and features to streamline development and enhance productivity.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -202,7 +202,9 @@ export default class Console {
202
202
  }
203
203
 
204
204
  static logWithout(level: Levels, colors?:Colors, ...args: any[]): void {
205
- const color = colors || level === Levels.ERROR ? Colors.BgRed : level === Levels.WARN ? Colors.BgYellow : level === Levels.INFO ? Colors.BgMagenta : level === Levels.SUCCESS ? Colors.BgGreen : Colors.BgCyan;
205
+
206
+ const color = colors ? colors : level === Levels.ERROR ? Colors.BgRed : level === Levels.WARN ? Colors.BgYellow : level === Levels.INFO ? Colors.BgMagenta : level === Levels.SUCCESS ? Colors.BgGreen : Colors.BgCyan;
207
+
206
208
  let output = "";
207
209
  for (const arg of args) {
208
210
  let msg = (arg instanceof Error) ? arg.stack : (typeof arg === 'string') ? arg : JSON.stringify(arg, null, 2);
@@ -133,11 +133,11 @@ program
133
133
  await app.prepare();
134
134
  console.log('✅ Build complete\n');
135
135
 
136
- // 3. Copia a pasta hweb-dist para exported
137
- const distDir = path.join(projectDir, 'hweb-dist');
136
+ // 3. Copia a pasta .hight para exported
137
+ const distDir = path.join(projectDir, '.hight');
138
138
  if (fs.existsSync(distDir)) {
139
139
  console.log('📦 Copying JavaScript files...');
140
- const exportDistDir = path.join(exportDir, 'hweb-dist');
140
+ const exportDistDir = path.join(exportDir, '.hight');
141
141
  fs.mkdirSync(exportDistDir, { recursive: true });
142
142
 
143
143
  const files = fs.readdirSync(distDir);
@@ -153,6 +153,7 @@ const DEV_INDICATOR_CORNERS = [
153
153
  function DevIndicator() {
154
154
  const [corner, setCorner] = useState(3); // Canto atual (0-3)
155
155
  const [isDragging, setIsDragging] = useState(false); // Estado de arrastar
156
+ const [isBuilding, setIsBuilding] = useState(false); // Estado de build
156
157
 
157
158
  // Posição visual do indicador durante o arraste
158
159
  const [position, setPosition] = useState<{ top: number; left: number }>({ top: 0, left: 0 });
@@ -160,6 +161,49 @@ function DevIndicator() {
160
161
  const indicatorRef = useRef<HTMLDivElement>(null);
161
162
  const dragStartRef = useRef<{ x: number; y: number; moved: boolean } | null>(null);
162
163
 
164
+ // Escuta eventos de hot reload para mostrar estado de build
165
+ useEffect(() => {
166
+ if (typeof window === 'undefined') return;
167
+
168
+ const handleHotReloadMessage = (event: MessageEvent) => {
169
+ try {
170
+ const message = JSON.parse(event.data);
171
+
172
+ // Quando detecta mudança em arquivo, ativa loading
173
+ if (message.type === 'frontend-reload' ||
174
+ message.type === 'backend-api-reload' ||
175
+ message.type === 'src-reload') {
176
+ setIsBuilding(true);
177
+ }
178
+
179
+ // Quando o build termina ou servidor fica pronto, desativa loading
180
+ if (message.type === 'server-ready' || message.type === 'build-complete') {
181
+ setIsBuilding(false);
182
+ }
183
+ } catch (e) {
184
+ // Ignora mensagens que não são JSON
185
+ }
186
+ };
187
+
188
+ // Intercepta mensagens WebSocket
189
+ const originalWebSocket = window.WebSocket;
190
+ window.WebSocket = class extends originalWebSocket {
191
+ constructor(url: string | URL, protocols?: string | string[]) {
192
+ super(url, protocols);
193
+
194
+ this.addEventListener('message', (event) => {
195
+ if (url.toString().includes('hweb-hotreload')) {
196
+ handleHotReloadMessage(event);
197
+ }
198
+ });
199
+ }
200
+ } as any;
201
+
202
+ return () => {
203
+ window.WebSocket = originalWebSocket;
204
+ };
205
+ }, []);
206
+
163
207
  // --- Estilos Dinâmicos ---
164
208
  const getIndicatorStyle = (): React.CSSProperties => {
165
209
  const baseStyle: React.CSSProperties = {
@@ -168,17 +212,22 @@ function DevIndicator() {
168
212
  width: DEV_INDICATOR_SIZE,
169
213
  height: DEV_INDICATOR_SIZE,
170
214
  borderRadius: '50%',
171
- background: 'linear-gradient(135deg, #8e2de2, #4a00e0)', // Gradiente Roxo
215
+ background: isBuilding
216
+ ? 'linear-gradient(135deg, #f093fb, #f5576c)' // Gradiente Rosa/Vermelho quando building
217
+ : 'linear-gradient(135deg, #8e2de2, #4a00e0)', // Gradiente Roxo normal
172
218
  color: 'white',
173
219
  fontWeight: 'bold',
174
220
  fontSize: 28,
175
- boxShadow: '0 4px 15px rgba(0,0,0,0.2)',
221
+ boxShadow: isBuilding
222
+ ? '0 4px 25px rgba(245, 87, 108, 0.6)' // Shadow mais forte quando building
223
+ : '0 4px 15px rgba(0,0,0,0.2)',
176
224
  display: 'flex',
177
225
  alignItems: 'center',
178
226
  justifyContent: 'center',
179
227
  cursor: isDragging ? 'grabbing' : 'grab',
180
228
  userSelect: 'none',
181
- transition: isDragging ? 'none' : 'all 0.3s ease-out', // Animação suave ao soltar
229
+ transition: isDragging ? 'none' : 'all 0.3s ease-out',
230
+ animation: isBuilding ? 'hweb-pulse 1.5s ease-in-out infinite' : 'none',
182
231
  };
183
232
 
184
233
  if (isDragging) {
@@ -272,19 +321,85 @@ function DevIndicator() {
272
321
 
273
322
 
274
323
  return (
275
- <div ref={indicatorRef} style={getIndicatorStyle()} onMouseDown={handleMouseDown} title="Modo Dev HightJS">
276
- H
277
- </div>
324
+ <>
325
+ <style>
326
+ {`
327
+ @keyframes hweb-pulse {
328
+ 0%, 100% {
329
+ transform: scale(1);
330
+ opacity: 1;
331
+ }
332
+ 50% {
333
+ transform: scale(1.1);
334
+ opacity: 0.8;
335
+ }
336
+ }
337
+
338
+ @keyframes hweb-spin {
339
+ from {
340
+ transform: rotate(0deg);
341
+ }
342
+ to {
343
+ transform: rotate(360deg);
344
+ }
345
+ }
346
+ `}
347
+ </style>
348
+ <div
349
+ ref={indicatorRef}
350
+ style={getIndicatorStyle()}
351
+ onMouseDown={handleMouseDown}
352
+ title={isBuilding ? "Building..." : "Modo Dev HightJS"}
353
+ >
354
+ {isBuilding ? (
355
+ <span style={{ animation: 'hweb-spin 1s linear infinite' }}>⟳</span>
356
+ ) : (
357
+ 'H'
358
+ )}
359
+ </div>
360
+ </>
278
361
  );
279
362
  }
280
363
 
281
364
  // --- Inicialização do Cliente (CSR - Client-Side Rendering) ---
282
365
 
366
+ function deobfuscateData(obfuscated: string): any {
367
+ try {
368
+ // Remove o hash fake
369
+ const parts = obfuscated.split('.');
370
+ const base64 = parts.length > 1 ? parts[1] : parts[0];
371
+
372
+ // Decodifica base64
373
+ const jsonStr = atob(base64);
374
+
375
+ // Parse JSON
376
+ return JSON.parse(jsonStr);
377
+ } catch (error) {
378
+ console.error('[hweb] Failed to decode data:', error);
379
+ return null;
380
+ }
381
+ }
382
+
283
383
  function initializeClient() {
284
- const initialData = (window as any).__HWEB_INITIAL_DATA__;
384
+ // os dados do atributo data-h
385
+ const dataElement = document.getElementById('__hight_data__');
386
+
387
+ if (!dataElement) {
388
+ console.error('[hweb] Initial data script not found.');
389
+ return;
390
+ }
391
+
392
+ const obfuscated = dataElement.getAttribute('data-h');
393
+
394
+ if (!obfuscated) {
395
+ console.error('[hweb] Data attribute not found.');
396
+ return;
397
+ }
398
+
399
+ const initialData = deobfuscateData(obfuscated);
285
400
 
286
401
  if (!initialData) {
287
- console.error('[hweb] Initial data not found on page.');
402
+ console.error('[hweb] Failed to parse initial data.');
288
403
  return;
289
404
  }
290
405
 
package/src/helpers.ts CHANGED
@@ -112,6 +112,7 @@ async function loadHightConfig(projectDir: string, phase: string): Promise<Hight
112
112
  serverTimeout: 35000,
113
113
  individualRequestTimeout: 30000,
114
114
  maxUrlLength: 2048,
115
+ accessLogging: true,
115
116
  };
116
117
 
117
118
  try {
@@ -265,6 +266,10 @@ async function initNativeServer(hwebApp: HWebApp, options: HightJSOptions, port:
265
266
  // Extraímos a lógica principal para uma variável
266
267
  // para que possa ser usada tanto pelo servidor HTTP quanto HTTPS.
267
268
  const requestListener = async (req: HWebIncomingMessage, res: ServerResponse) => {
269
+ const requestStartTime = Date.now();
270
+ const method = req.method || 'GET';
271
+ const url = req.url || '/';
272
+
268
273
  // Configurações de segurança básicas
269
274
  res.setHeader('X-Content-Type-Options', 'nosniff');
270
275
  res.setHeader('X-Frame-Options', 'DENY');
@@ -281,11 +286,44 @@ async function initNativeServer(hwebApp: HWebApp, options: HightJSOptions, port:
281
286
  req.setTimeout(hightConfig.individualRequestTimeout || 30000, () => {
282
287
  res.statusCode = 408; // Request Timeout
283
288
  res.end('Request timeout');
289
+
290
+ // Log de timeout
291
+ if (hightConfig.accessLogging) {
292
+ const duration = Date.now() - requestStartTime;
293
+ Console.info(`${Colors.FgYellow}${method}${Colors.Reset} ${url} ${Colors.FgRed}408${Colors.Reset} ${Colors.FgGray}${duration}ms${Colors.Reset}`);
294
+ }
284
295
  });
285
296
 
297
+ // Intercepta o método end() para logar quando a resposta for enviada
298
+ const originalEnd = res.end.bind(res);
299
+ let hasEnded = false;
300
+
301
+ res.end = function(this: ServerResponse, ...args: any[]): any {
302
+ if (!hasEnded && hightConfig.accessLogging) {
303
+ hasEnded = true;
304
+ const duration = Date.now() - requestStartTime;
305
+ const statusCode = res.statusCode || 200;
306
+
307
+ // Define cor baseada no status code
308
+ let statusColor = Colors.FgGreen; // 2xx
309
+ if (statusCode >= 500) statusColor = Colors.FgRed; // 5xx
310
+ else if (statusCode >= 400) statusColor = Colors.FgYellow; // 4xx
311
+ else if (statusCode >= 300) statusColor = Colors.FgCyan; // 3xx
312
+
313
+ // Formata o método com cor
314
+ let methodColor = Colors.BgCyan;
315
+ if (method === 'POST') methodColor = Colors.BgGreen;
316
+ else if (method === 'PUT') methodColor = Colors.BgYellow;
317
+ else if (method === 'DELETE') methodColor = Colors.BgRed;
318
+ else if (method === 'PATCH') methodColor = Colors.BgMagenta;
319
+ Console.logCustomLevel(method, true, methodColor, `${url} ${statusColor}${statusCode}${Colors.Reset} ${Colors.FgGray}${duration}ms${Colors.Reset}`);
320
+ }
321
+ // @ts-ignore
322
+ return originalEnd.apply(this, args);
323
+ } as any;
324
+
286
325
  try {
287
326
  // Validação básica de URL (usa configuração personalizada)
288
- const url = req.url || '/';
289
327
  const maxUrlLength = hightConfig.maxUrlLength || 2048;
290
328
  if (url.length > maxUrlLength) {
291
329
  res.statusCode = 414; // URI Too Long
package/src/hotReload.ts CHANGED
@@ -327,19 +327,21 @@ export class HotReloadManager {
327
327
  if (typeof window !== 'undefined') {
328
328
  let ws;
329
329
  let reconnectAttempts = 0;
330
- let maxReconnectInterval = 30000; // 30 segundos max
331
- let reconnectInterval = 1000; // Começa com 1 segundo
330
+ let maxReconnectInterval = 30000;
331
+ let reconnectInterval = 1000;
332
332
  let reconnectTimer;
333
333
  let isConnected = false;
334
334
 
335
335
  function connect() {
336
- // Evita múltiplas tentativas simultâneas
336
+ const url = window.location; // Objeto com info da URL atual
337
+ const protocol = url.protocol === "https:" ? "wss:" : "ws:"; // Usa wss se for https
338
+ const wsUrl = protocol + '//' + url.host + '/hweb-hotreload/';
337
339
  if (ws && (ws.readyState === WebSocket.CONNECTING || ws.readyState === WebSocket.OPEN)) {
338
340
  return;
339
341
  }
340
342
 
341
343
  try {
342
- ws = new WebSocket('ws://localhost:3000/hweb-hotreload/');
344
+ ws = new WebSocket(wsUrl);
343
345
 
344
346
  ws.onopen = function() {
345
347
  console.log('🔌 Hot-reload connected');
@@ -476,5 +478,12 @@ export class HotReloadManager {
476
478
  this.buildCompleteResolve = null;
477
479
  }
478
480
  this.isBuilding = false;
481
+
482
+ // Notifica os clientes que o build terminou
483
+ if (success) {
484
+ this.notifyClients('build-complete', { success: true });
485
+ } else {
486
+ this.notifyClients('build-error', { success: false });
487
+ }
479
488
  }
480
489
  }
package/src/index.ts CHANGED
@@ -289,7 +289,7 @@ export default function hweb(options: HightJSOptions) {
289
289
 
290
290
  const notFound = loadNotFound(userWebDir);
291
291
 
292
- const outDir = path.join(dir, 'hweb-dist');
292
+ const outDir = path.join(dir, '.hight');
293
293
  fs.mkdirSync(outDir, {recursive: true});
294
294
 
295
295
  entryPoint = createEntryFile(dir, frontendRoutes);
@@ -317,6 +317,11 @@ export default function hweb(options: HightJSOptions) {
317
317
  time.update(""); // limpa a linha
318
318
  time.end(` ${Colors.BgGreen} build ${Colors.Reset} Client build completed in ${elapsed}ms`);
319
319
 
320
+ // Notifica o hot reload manager que o build foi concluído
321
+ if (hotReloadManager) {
322
+ hotReloadManager.onBuildComplete(true);
323
+ }
324
+
320
325
  } else {
321
326
  const time = Console.dynamicLine(` ${Colors.BgYellow} watcher ${Colors.Reset} Starting client watch`);
322
327
  watchWithChunks(entryPoint, outDir, hotReloadManager!).catch(err => {
@@ -373,7 +378,7 @@ export default function hweb(options: HightJSOptions) {
373
378
  }
374
379
 
375
380
  // 2. Primeiro verifica se é um arquivo estático da pasta public
376
- if (pathname !== '/' && !pathname.startsWith('/api/') && !pathname.startsWith('/hweb-')) {
381
+ if (pathname !== '/' && !pathname.startsWith('/api/') && !pathname.startsWith('/.hight')) {
377
382
  const publicDir = path.join(dir, 'public');
378
383
  const filePath = path.join(publicDir, pathname);
379
384
 
@@ -417,12 +422,14 @@ export default function hweb(options: HightJSOptions) {
417
422
  }
418
423
  }
419
424
 
420
- // 3. Verifica se é um arquivo estático do hweb-dist
421
- if (pathname.startsWith('/hweb-dist/')) {
422
- const staticPath = path.join(dir, 'hweb-dist');
423
- const filePath = path.join(staticPath, pathname.replace('/hweb-dist/', ''));
425
+ // 3. Verifica se é um arquivo estático do .hight
426
+ if (pathname.startsWith('/_hight/')) {
427
+
428
+ const staticPath = path.join(dir, '.hight');
429
+ const filePath = path.join(staticPath, pathname.replace('/_hight/', ''));
424
430
 
425
431
  if (fs.existsSync(filePath)) {
432
+
426
433
  const ext = path.extname(filePath).toLowerCase();
427
434
  const contentTypes: Record<string, string> = {
428
435
  '.js': 'application/javascript',
package/src/renderer.tsx CHANGED
@@ -21,19 +21,128 @@ import type { GenericRequest } from './types/framework';
21
21
  import fs from 'fs';
22
22
  import path from 'path';
23
23
 
24
- // Funções para codificar/decodificar dados (disfarça o JSON no HTML)
25
- function encodeInitialData(data: any): string {
26
- // Converte para JSON, depois para base64, e adiciona um prefixo fake
24
+ // Função para gerar todas as meta tags
25
+ function generateMetaTags(metadata: Metadata): string {
26
+ const tags: string[] = [];
27
+
28
+ // Charset
29
+ tags.push(`<meta charset="${metadata.charset || 'UTF-8'}">`);
30
+
31
+ // Viewport
32
+ tags.push(`<meta name="viewport" content="${metadata.viewport || 'width=device-width, initial-scale=1.0'}">`);
33
+
34
+ // Description
35
+ if (metadata.description) {
36
+ tags.push(`<meta name="description" content="${metadata.description}">`);
37
+ }
38
+
39
+ // Keywords
40
+ if (metadata.keywords) {
41
+ const keywordsStr = Array.isArray(metadata.keywords)
42
+ ? metadata.keywords.join(', ')
43
+ : metadata.keywords;
44
+ tags.push(`<meta name="keywords" content="${keywordsStr}">`);
45
+ }
46
+
47
+ // Author
48
+ if (metadata.author) {
49
+ tags.push(`<meta name="author" content="${metadata.author}">`);
50
+ }
51
+
52
+ // Theme color
53
+ if (metadata.themeColor) {
54
+ tags.push(`<meta name="theme-color" content="${metadata.themeColor}">`);
55
+ }
56
+
57
+ // Robots
58
+ if (metadata.robots) {
59
+ tags.push(`<meta name="robots" content="${metadata.robots}">`);
60
+ }
61
+
62
+ // Canonical
63
+ if (metadata.canonical) {
64
+ tags.push(`<link rel="canonical" href="${metadata.canonical}">`);
65
+ }
66
+
67
+ // Favicon
68
+ if (metadata.favicon) {
69
+ tags.push(`<link rel="icon" href="${metadata.favicon}">`);
70
+ }
71
+
72
+ // Apple Touch Icon
73
+ if (metadata.appleTouchIcon) {
74
+ tags.push(`<link rel="apple-touch-icon" href="${metadata.appleTouchIcon}">`);
75
+ }
76
+
77
+ // Manifest
78
+ if (metadata.manifest) {
79
+ tags.push(`<link rel="manifest" href="${metadata.manifest}">`);
80
+ }
81
+
82
+ // Open Graph
83
+ if (metadata.openGraph) {
84
+ const og = metadata.openGraph;
85
+ if (og.title) tags.push(`<meta property="og:title" content="${og.title}">`);
86
+ if (og.description) tags.push(`<meta property="og:description" content="${og.description}">`);
87
+ if (og.type) tags.push(`<meta property="og:type" content="${og.type}">`);
88
+ if (og.url) tags.push(`<meta property="og:url" content="${og.url}">`);
89
+ if (og.siteName) tags.push(`<meta property="og:site_name" content="${og.siteName}">`);
90
+ if (og.locale) tags.push(`<meta property="og:locale" content="${og.locale}">`);
91
+
92
+ if (og.image) {
93
+ if (typeof og.image === 'string') {
94
+ tags.push(`<meta property="og:image" content="${og.image}">`);
95
+ } else {
96
+ tags.push(`<meta property="og:image" content="${og.image.url}">`);
97
+ if (og.image.width) tags.push(`<meta property="og:image:width" content="${og.image.width}">`);
98
+ if (og.image.height) tags.push(`<meta property="og:image:height" content="${og.image.height}">`);
99
+ if (og.image.alt) tags.push(`<meta property="og:image:alt" content="${og.image.alt}">`);
100
+ }
101
+ }
102
+ }
103
+
104
+ // Twitter Card
105
+ if (metadata.twitter) {
106
+ const tw = metadata.twitter;
107
+ if (tw.card) tags.push(`<meta name="twitter:card" content="${tw.card}">`);
108
+ if (tw.site) tags.push(`<meta name="twitter:site" content="${tw.site}">`);
109
+ if (tw.creator) tags.push(`<meta name="twitter:creator" content="${tw.creator}">`);
110
+ if (tw.title) tags.push(`<meta name="twitter:title" content="${tw.title}">`);
111
+ if (tw.description) tags.push(`<meta name="twitter:description" content="${tw.description}">`);
112
+ if (tw.image) tags.push(`<meta name="twitter:image" content="${tw.image}">`);
113
+ if (tw.imageAlt) tags.push(`<meta name="twitter:image:alt" content="${tw.imageAlt}">`);
114
+ }
115
+
116
+ // Custom meta tags
117
+ if (metadata.other) {
118
+ for (const [key, value] of Object.entries(metadata.other)) {
119
+ tags.push(`<meta name="${key}" content="${value}">`);
120
+ }
121
+ }
122
+
123
+ return tags.join('\n');
124
+ }
125
+
126
+ // Função para ofuscar dados (não é criptografia, apenas ofuscação)
127
+ function obfuscateData(data: any): string {
128
+ // 1. Serializa para JSON minificado
27
129
  const jsonStr = JSON.stringify(data);
130
+
131
+ // 2. Converte para base64
28
132
  const base64 = Buffer.from(jsonStr).toString('base64');
29
- return `hweb_${base64}_config`;
133
+
134
+ // 3. Adiciona um hash fake no início para parecer um token
135
+ const hash = Buffer.from(Date.now().toString()).toString('base64').substring(0, 8);
136
+
137
+ return `${hash}.${base64}`;
30
138
  }
31
139
 
32
- function createDecodeScript(): string {
33
- return `
34
-
35
- window.__HWEB_DECODE__ = function(encoded) { const base64 = encoded.replace('hweb_', '').replace('_config', ''); const jsonStr = atob(base64); return JSON.parse(jsonStr); };
36
- `;
140
+ // Função para criar script ofuscado
141
+ function createInitialDataScript(data: any): string {
142
+ const obfuscated = obfuscateData(data);
143
+
144
+ // Usa um atributo data-* ao invés de JSON visível
145
+ return `<script id="__hight_data__" type="text/plain" data-h="${obfuscated}"></script>`;
37
146
  }
38
147
 
39
148
  // Interface para opções de renderização apenas do cliente
@@ -74,27 +183,42 @@ export async function render({ req, route, params, allRoutes }: RenderOptions):
74
183
  initialParams: params,
75
184
  };
76
185
 
77
- // Codifica os dados para disfarçar
78
- const encodedData = encodeInitialData(initialData);
186
+ // Cria script JSON limpo
187
+ const initialDataScript = createInitialDataScript(initialData);
79
188
 
80
189
  // Script de hot reload apenas em desenvolvimento
81
190
  const hotReloadScript = !isProduction && hotReloadManager
82
191
  ? hotReloadManager.getClientScript()
83
192
  : '';
84
193
 
85
- const favicon = metadata.favicon ? `<link rel="icon" href="${metadata.favicon}">` : '';
194
+ // Gera todas as meta tags
195
+ const metaTags = generateMetaTags(metadata);
86
196
 
87
197
  // Determina quais arquivos JavaScript carregar
88
198
  const jsFiles = getJavaScriptFiles(req);
89
199
 
200
+ const htmlLang = metadata.language || 'pt-BR';
201
+
90
202
  // HTML base sem SSR - apenas o container e scripts para client-side rendering
91
- 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>`;
203
+ return `<!DOCTYPE html>
204
+ <html lang="${htmlLang}">
205
+ <head>
206
+ ${metaTags}
207
+ <title>${metadata.title || 'App hweb'}</title>
208
+ </head>
209
+ <body>
210
+ <div id="root"></div>
211
+ ${initialDataScript}
212
+ ${jsFiles}
213
+ ${hotReloadScript}
214
+ </body>
215
+ </html>`;
92
216
  }
93
217
 
94
218
  // Função para determinar quais arquivos JavaScript carregar
95
219
  function getJavaScriptFiles(req: GenericRequest): string {
96
220
  const projectDir = process.cwd();
97
- const distDir = path.join(projectDir, 'hweb-dist');
221
+ const distDir = path.join(projectDir, '.hight');
98
222
 
99
223
  try {
100
224
  // Verifica se existe um manifesto de chunks (gerado pelo ESBuild com splitting)
@@ -105,7 +229,7 @@ function getJavaScriptFiles(req: GenericRequest): string {
105
229
  const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
106
230
  const scripts = Object.values(manifest)
107
231
  .filter((file: any) => file.endsWith('.js'))
108
- .map((file: any) => `<script src="/hweb-dist/${file}"></script>`)
232
+ .map((file: any) => `<script src="/_hight/${file}"></script>`)
109
233
  .join('');
110
234
  return scripts;
111
235
  } else {
@@ -125,15 +249,15 @@ function getJavaScriptFiles(req: GenericRequest): string {
125
249
  if (jsFiles.length >= 1) {
126
250
  // Modo chunks sem manifesto
127
251
  return jsFiles
128
- .map(file => `<script src="/hweb-dist/${file}"></script>`)
252
+ .map(file => `<script src="/_hight/${file}"></script>`)
129
253
  .join('');
130
254
  } else {
131
255
  // Modo tradicional - único arquivo
132
- return '<script src="/hweb-dist/main.js"></script>';
256
+ return '<script src="/_hight/main.js"></script>';
133
257
  }
134
258
  }
135
259
  } catch (error) {
136
260
  // Fallback para o modo tradicional
137
- return '<script src="/hweb-dist/main.js"></script>';
261
+ return '<script src="/_hight/main.js"></script>';
138
262
  }
139
263
  }