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.
- package/dist/api/console.js +1 -1
- package/dist/bin/hightjs.js +3 -3
- package/dist/client/entry.client.js +96 -6
- package/dist/helpers.js +40 -1
- package/dist/hotReload.js +13 -4
- package/dist/index.js +10 -6
- package/dist/renderer.js +137 -18
- package/dist/types.d.ts +39 -0
- package/docs/config.md +15 -0
- package/example/hightjs.config.ts +6 -0
- package/example/package-lock.json +1 -1
- package/example/package.json +2 -2
- package/example/src/web/layout.tsx +57 -3
- package/example/src/web/routes/index.tsx +1 -1
- package/package.json +1 -1
- package/src/api/console.ts +3 -1
- package/src/bin/hightjs.js +3 -3
- package/src/client/entry.client.tsx +123 -8
- package/src/helpers.ts +39 -1
- package/src/hotReload.ts +13 -4
- package/src/index.ts +13 -6
- package/src/renderer.tsx +142 -18
- package/src/router.ts +2 -0
- package/src/types.ts +56 -0
package/dist/api/console.js
CHANGED
|
@@ -185,7 +185,7 @@ class Console {
|
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
static logWithout(level, colors, ...args) {
|
|
188
|
-
const color = colors
|
|
188
|
+
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;
|
|
189
189
|
let output = "";
|
|
190
190
|
for (const arg of args) {
|
|
191
191
|
let msg = (arg instanceof Error) ? arg.stack : (typeof arg === 'string') ? arg : JSON.stringify(arg, null, 2);
|
package/dist/bin/hightjs.js
CHANGED
|
@@ -113,11 +113,11 @@ program
|
|
|
113
113
|
const app = teste.default({ dev: false, port: 3000, hostname: '0.0.0.0', framework: 'native' });
|
|
114
114
|
await app.prepare();
|
|
115
115
|
console.log('✅ Build complete\n');
|
|
116
|
-
// 3. Copia a pasta
|
|
117
|
-
const distDir = path.join(projectDir, '
|
|
116
|
+
// 3. Copia a pasta .hight para exported
|
|
117
|
+
const distDir = path.join(projectDir, '.hight');
|
|
118
118
|
if (fs.existsSync(distDir)) {
|
|
119
119
|
console.log('📦 Copying JavaScript files...');
|
|
120
|
-
const exportDistDir = path.join(exportDir, '
|
|
120
|
+
const exportDistDir = path.join(exportDir, '.hight');
|
|
121
121
|
fs.mkdirSync(exportDistDir, { recursive: true });
|
|
122
122
|
const files = fs.readdirSync(distDir);
|
|
123
123
|
files.forEach(file => {
|
|
@@ -157,10 +157,49 @@ const DEV_INDICATOR_CORNERS = [
|
|
|
157
157
|
function DevIndicator() {
|
|
158
158
|
const [corner, setCorner] = (0, react_1.useState)(3); // Canto atual (0-3)
|
|
159
159
|
const [isDragging, setIsDragging] = (0, react_1.useState)(false); // Estado de arrastar
|
|
160
|
+
const [isBuilding, setIsBuilding] = (0, react_1.useState)(false); // Estado de build
|
|
160
161
|
// Posição visual do indicador durante o arraste
|
|
161
162
|
const [position, setPosition] = (0, react_1.useState)({ top: 0, left: 0 });
|
|
162
163
|
const indicatorRef = (0, react_1.useRef)(null);
|
|
163
164
|
const dragStartRef = (0, react_1.useRef)(null);
|
|
165
|
+
// Escuta eventos de hot reload para mostrar estado de build
|
|
166
|
+
(0, react_1.useEffect)(() => {
|
|
167
|
+
if (typeof window === 'undefined')
|
|
168
|
+
return;
|
|
169
|
+
const handleHotReloadMessage = (event) => {
|
|
170
|
+
try {
|
|
171
|
+
const message = JSON.parse(event.data);
|
|
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
|
+
// Quando o build termina ou servidor fica pronto, desativa loading
|
|
179
|
+
if (message.type === 'server-ready' || message.type === 'build-complete') {
|
|
180
|
+
setIsBuilding(false);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch (e) {
|
|
184
|
+
// Ignora mensagens que não são JSON
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
// Intercepta mensagens WebSocket
|
|
188
|
+
const originalWebSocket = window.WebSocket;
|
|
189
|
+
window.WebSocket = class extends originalWebSocket {
|
|
190
|
+
constructor(url, protocols) {
|
|
191
|
+
super(url, protocols);
|
|
192
|
+
this.addEventListener('message', (event) => {
|
|
193
|
+
if (url.toString().includes('hweb-hotreload')) {
|
|
194
|
+
handleHotReloadMessage(event);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
return () => {
|
|
200
|
+
window.WebSocket = originalWebSocket;
|
|
201
|
+
};
|
|
202
|
+
}, []);
|
|
164
203
|
// --- Estilos Dinâmicos ---
|
|
165
204
|
const getIndicatorStyle = () => {
|
|
166
205
|
const baseStyle = {
|
|
@@ -169,17 +208,22 @@ function DevIndicator() {
|
|
|
169
208
|
width: DEV_INDICATOR_SIZE,
|
|
170
209
|
height: DEV_INDICATOR_SIZE,
|
|
171
210
|
borderRadius: '50%',
|
|
172
|
-
background:
|
|
211
|
+
background: isBuilding
|
|
212
|
+
? 'linear-gradient(135deg, #f093fb, #f5576c)' // Gradiente Rosa/Vermelho quando building
|
|
213
|
+
: 'linear-gradient(135deg, #8e2de2, #4a00e0)', // Gradiente Roxo normal
|
|
173
214
|
color: 'white',
|
|
174
215
|
fontWeight: 'bold',
|
|
175
216
|
fontSize: 28,
|
|
176
|
-
boxShadow:
|
|
217
|
+
boxShadow: isBuilding
|
|
218
|
+
? '0 4px 25px rgba(245, 87, 108, 0.6)' // Shadow mais forte quando building
|
|
219
|
+
: '0 4px 15px rgba(0,0,0,0.2)',
|
|
177
220
|
display: 'flex',
|
|
178
221
|
alignItems: 'center',
|
|
179
222
|
justifyContent: 'center',
|
|
180
223
|
cursor: isDragging ? 'grabbing' : 'grab',
|
|
181
224
|
userSelect: 'none',
|
|
182
|
-
transition: isDragging ? 'none' : 'all 0.3s ease-out',
|
|
225
|
+
transition: isDragging ? 'none' : 'all 0.3s ease-out',
|
|
226
|
+
animation: isBuilding ? 'hweb-pulse 1.5s ease-in-out infinite' : 'none',
|
|
183
227
|
};
|
|
184
228
|
if (isDragging) {
|
|
185
229
|
return {
|
|
@@ -259,13 +303,59 @@ function DevIndicator() {
|
|
|
259
303
|
window.removeEventListener('mouseup', handleMouseUp);
|
|
260
304
|
};
|
|
261
305
|
}, [isDragging, handleMouseMove, handleMouseUp]);
|
|
262
|
-
return ((0, jsx_runtime_1.
|
|
306
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("style", { children: `
|
|
307
|
+
@keyframes hweb-pulse {
|
|
308
|
+
0%, 100% {
|
|
309
|
+
transform: scale(1);
|
|
310
|
+
opacity: 1;
|
|
311
|
+
}
|
|
312
|
+
50% {
|
|
313
|
+
transform: scale(1.1);
|
|
314
|
+
opacity: 0.8;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
@keyframes hweb-spin {
|
|
319
|
+
from {
|
|
320
|
+
transform: rotate(0deg);
|
|
321
|
+
}
|
|
322
|
+
to {
|
|
323
|
+
transform: rotate(360deg);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
` }), (0, jsx_runtime_1.jsx)("div", { ref: indicatorRef, style: getIndicatorStyle(), onMouseDown: handleMouseDown, title: isBuilding ? "Building..." : "Modo Dev HightJS", children: isBuilding ? ((0, jsx_runtime_1.jsx)("span", { style: { animation: 'hweb-spin 1s linear infinite' }, children: "\u27F3" })) : ('H') })] }));
|
|
263
327
|
}
|
|
264
328
|
// --- Inicialização do Cliente (CSR - Client-Side Rendering) ---
|
|
329
|
+
function deobfuscateData(obfuscated) {
|
|
330
|
+
try {
|
|
331
|
+
// Remove o hash fake
|
|
332
|
+
const parts = obfuscated.split('.');
|
|
333
|
+
const base64 = parts.length > 1 ? parts[1] : parts[0];
|
|
334
|
+
// Decodifica base64
|
|
335
|
+
const jsonStr = atob(base64);
|
|
336
|
+
// Parse JSON
|
|
337
|
+
return JSON.parse(jsonStr);
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
console.error('[hweb] Failed to decode data:', error);
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
265
344
|
function initializeClient() {
|
|
266
|
-
|
|
345
|
+
// Lê os dados do atributo data-h
|
|
346
|
+
const dataElement = document.getElementById('__hight_data__');
|
|
347
|
+
if (!dataElement) {
|
|
348
|
+
console.error('[hweb] Initial data script not found.');
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
const obfuscated = dataElement.getAttribute('data-h');
|
|
352
|
+
if (!obfuscated) {
|
|
353
|
+
console.error('[hweb] Data attribute not found.');
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const initialData = deobfuscateData(obfuscated);
|
|
267
357
|
if (!initialData) {
|
|
268
|
-
console.error('[hweb]
|
|
358
|
+
console.error('[hweb] Failed to parse initial data.');
|
|
269
359
|
return;
|
|
270
360
|
}
|
|
271
361
|
// Cria o mapa de componentes dinamicamente a partir dos módulos carregados
|
package/dist/helpers.js
CHANGED
|
@@ -119,6 +119,7 @@ async function loadHightConfig(projectDir, phase) {
|
|
|
119
119
|
serverTimeout: 35000,
|
|
120
120
|
individualRequestTimeout: 30000,
|
|
121
121
|
maxUrlLength: 2048,
|
|
122
|
+
accessLogging: true,
|
|
122
123
|
};
|
|
123
124
|
try {
|
|
124
125
|
// Tenta primeiro .ts, depois .js
|
|
@@ -250,6 +251,9 @@ async function initNativeServer(hwebApp, options, port, hostname) {
|
|
|
250
251
|
// Extraímos a lógica principal para uma variável
|
|
251
252
|
// para que possa ser usada tanto pelo servidor HTTP quanto HTTPS.
|
|
252
253
|
const requestListener = async (req, res) => {
|
|
254
|
+
const requestStartTime = Date.now();
|
|
255
|
+
const method = req.method || 'GET';
|
|
256
|
+
const url = req.url || '/';
|
|
253
257
|
// Configurações de segurança básicas
|
|
254
258
|
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
255
259
|
res.setHeader('X-Frame-Options', 'DENY');
|
|
@@ -264,10 +268,45 @@ async function initNativeServer(hwebApp, options, port, hostname) {
|
|
|
264
268
|
req.setTimeout(hightConfig.individualRequestTimeout || 30000, () => {
|
|
265
269
|
res.statusCode = 408; // Request Timeout
|
|
266
270
|
res.end('Request timeout');
|
|
271
|
+
// Log de timeout
|
|
272
|
+
if (hightConfig.accessLogging) {
|
|
273
|
+
const duration = Date.now() - requestStartTime;
|
|
274
|
+
console_1.default.info(`${console_1.Colors.FgYellow}${method}${console_1.Colors.Reset} ${url} ${console_1.Colors.FgRed}408${console_1.Colors.Reset} ${console_1.Colors.FgGray}${duration}ms${console_1.Colors.Reset}`);
|
|
275
|
+
}
|
|
267
276
|
});
|
|
277
|
+
// Intercepta o método end() para logar quando a resposta for enviada
|
|
278
|
+
const originalEnd = res.end.bind(res);
|
|
279
|
+
let hasEnded = false;
|
|
280
|
+
res.end = function (...args) {
|
|
281
|
+
if (!hasEnded && hightConfig.accessLogging) {
|
|
282
|
+
hasEnded = true;
|
|
283
|
+
const duration = Date.now() - requestStartTime;
|
|
284
|
+
const statusCode = res.statusCode || 200;
|
|
285
|
+
// Define cor baseada no status code
|
|
286
|
+
let statusColor = console_1.Colors.FgGreen; // 2xx
|
|
287
|
+
if (statusCode >= 500)
|
|
288
|
+
statusColor = console_1.Colors.FgRed; // 5xx
|
|
289
|
+
else if (statusCode >= 400)
|
|
290
|
+
statusColor = console_1.Colors.FgYellow; // 4xx
|
|
291
|
+
else if (statusCode >= 300)
|
|
292
|
+
statusColor = console_1.Colors.FgCyan; // 3xx
|
|
293
|
+
// Formata o método com cor
|
|
294
|
+
let methodColor = console_1.Colors.BgCyan;
|
|
295
|
+
if (method === 'POST')
|
|
296
|
+
methodColor = console_1.Colors.BgGreen;
|
|
297
|
+
else if (method === 'PUT')
|
|
298
|
+
methodColor = console_1.Colors.BgYellow;
|
|
299
|
+
else if (method === 'DELETE')
|
|
300
|
+
methodColor = console_1.Colors.BgRed;
|
|
301
|
+
else if (method === 'PATCH')
|
|
302
|
+
methodColor = console_1.Colors.BgMagenta;
|
|
303
|
+
console_1.default.logCustomLevel(method, true, methodColor, `${url} ${statusColor}${statusCode}${console_1.Colors.Reset} ${console_1.Colors.FgGray}${duration}ms${console_1.Colors.Reset}`);
|
|
304
|
+
}
|
|
305
|
+
// @ts-ignore
|
|
306
|
+
return originalEnd.apply(this, args);
|
|
307
|
+
};
|
|
268
308
|
try {
|
|
269
309
|
// Validação básica de URL (usa configuração personalizada)
|
|
270
|
-
const url = req.url || '/';
|
|
271
310
|
const maxUrlLength = hightConfig.maxUrlLength || 2048;
|
|
272
311
|
if (url.length > maxUrlLength) {
|
|
273
312
|
res.statusCode = 414; // URI Too Long
|
package/dist/hotReload.js
CHANGED
|
@@ -312,19 +312,21 @@ class HotReloadManager {
|
|
|
312
312
|
if (typeof window !== 'undefined') {
|
|
313
313
|
let ws;
|
|
314
314
|
let reconnectAttempts = 0;
|
|
315
|
-
let maxReconnectInterval = 30000;
|
|
316
|
-
let reconnectInterval = 1000;
|
|
315
|
+
let maxReconnectInterval = 30000;
|
|
316
|
+
let reconnectInterval = 1000;
|
|
317
317
|
let reconnectTimer;
|
|
318
318
|
let isConnected = false;
|
|
319
319
|
|
|
320
320
|
function connect() {
|
|
321
|
-
//
|
|
321
|
+
const url = window.location; // Objeto com info da URL atual
|
|
322
|
+
const protocol = url.protocol === "https:" ? "wss:" : "ws:"; // Usa wss se for https
|
|
323
|
+
const wsUrl = protocol + '//' + url.host + '/hweb-hotreload/';
|
|
322
324
|
if (ws && (ws.readyState === WebSocket.CONNECTING || ws.readyState === WebSocket.OPEN)) {
|
|
323
325
|
return;
|
|
324
326
|
}
|
|
325
327
|
|
|
326
328
|
try {
|
|
327
|
-
ws = new WebSocket(
|
|
329
|
+
ws = new WebSocket(wsUrl);
|
|
328
330
|
|
|
329
331
|
ws.onopen = function() {
|
|
330
332
|
console.log('🔌 Hot-reload connected');
|
|
@@ -454,6 +456,13 @@ class HotReloadManager {
|
|
|
454
456
|
this.buildCompleteResolve = null;
|
|
455
457
|
}
|
|
456
458
|
this.isBuilding = false;
|
|
459
|
+
// Notifica os clientes que o build terminou
|
|
460
|
+
if (success) {
|
|
461
|
+
this.notifyClients('build-complete', { success: true });
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
this.notifyClients('build-error', { success: false });
|
|
465
|
+
}
|
|
457
466
|
}
|
|
458
467
|
}
|
|
459
468
|
exports.HotReloadManager = HotReloadManager;
|
package/dist/index.js
CHANGED
|
@@ -260,7 +260,7 @@ function hweb(options) {
|
|
|
260
260
|
// Carrega layout.tsx ANTES de criar o entry file
|
|
261
261
|
const layout = (0, router_1.loadLayout)(userWebDir);
|
|
262
262
|
const notFound = (0, router_1.loadNotFound)(userWebDir);
|
|
263
|
-
const outDir = path_1.default.join(dir, '
|
|
263
|
+
const outDir = path_1.default.join(dir, '.hight');
|
|
264
264
|
fs_1.default.mkdirSync(outDir, { recursive: true });
|
|
265
265
|
entryPoint = createEntryFile(dir, frontendRoutes);
|
|
266
266
|
clearInterval(spinner1);
|
|
@@ -280,6 +280,10 @@ function hweb(options) {
|
|
|
280
280
|
clearInterval(spinner); // para o spinner
|
|
281
281
|
time.update(""); // limpa a linha
|
|
282
282
|
time.end(` ${console_1.Colors.BgGreen} build ${console_1.Colors.Reset} Client build completed in ${elapsed}ms`);
|
|
283
|
+
// Notifica o hot reload manager que o build foi concluído
|
|
284
|
+
if (hotReloadManager) {
|
|
285
|
+
hotReloadManager.onBuildComplete(true);
|
|
286
|
+
}
|
|
283
287
|
}
|
|
284
288
|
else {
|
|
285
289
|
const time = console_1.default.dynamicLine(` ${console_1.Colors.BgYellow} watcher ${console_1.Colors.Reset} Starting client watch`);
|
|
@@ -330,7 +334,7 @@ function hweb(options) {
|
|
|
330
334
|
return;
|
|
331
335
|
}
|
|
332
336
|
// 2. Primeiro verifica se é um arquivo estático da pasta public
|
|
333
|
-
if (pathname !== '/' && !pathname.startsWith('/api/') && !pathname.startsWith('
|
|
337
|
+
if (pathname !== '/' && !pathname.startsWith('/api/') && !pathname.startsWith('/.hight')) {
|
|
334
338
|
const publicDir = path_1.default.join(dir, 'public');
|
|
335
339
|
const filePath = path_1.default.join(publicDir, pathname);
|
|
336
340
|
if (fs_1.default.existsSync(filePath) && fs_1.default.statSync(filePath).isFile()) {
|
|
@@ -372,10 +376,10 @@ function hweb(options) {
|
|
|
372
376
|
return;
|
|
373
377
|
}
|
|
374
378
|
}
|
|
375
|
-
// 3. Verifica se é um arquivo estático do
|
|
376
|
-
if (pathname.startsWith('/
|
|
377
|
-
const staticPath = path_1.default.join(dir, '
|
|
378
|
-
const filePath = path_1.default.join(staticPath, pathname.replace('/
|
|
379
|
+
// 3. Verifica se é um arquivo estático do .hight
|
|
380
|
+
if (pathname.startsWith('/_hight/')) {
|
|
381
|
+
const staticPath = path_1.default.join(dir, '.hight');
|
|
382
|
+
const filePath = path_1.default.join(staticPath, pathname.replace('/_hight/', ''));
|
|
379
383
|
if (fs_1.default.existsSync(filePath)) {
|
|
380
384
|
const ext = path_1.default.extname(filePath).toLowerCase();
|
|
381
385
|
const contentTypes = {
|
package/dist/renderer.js
CHANGED
|
@@ -7,18 +7,123 @@ exports.render = render;
|
|
|
7
7
|
const router_1 = require("./router");
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
|
-
//
|
|
11
|
-
function
|
|
12
|
-
|
|
10
|
+
// Função para gerar todas as meta tags
|
|
11
|
+
function generateMetaTags(metadata) {
|
|
12
|
+
const tags = [];
|
|
13
|
+
// Charset
|
|
14
|
+
tags.push(`<meta charset="${metadata.charset || 'UTF-8'}">`);
|
|
15
|
+
// Viewport
|
|
16
|
+
tags.push(`<meta name="viewport" content="${metadata.viewport || 'width=device-width, initial-scale=1.0'}">`);
|
|
17
|
+
// Description
|
|
18
|
+
if (metadata.description) {
|
|
19
|
+
tags.push(`<meta name="description" content="${metadata.description}">`);
|
|
20
|
+
}
|
|
21
|
+
// Keywords
|
|
22
|
+
if (metadata.keywords) {
|
|
23
|
+
const keywordsStr = Array.isArray(metadata.keywords)
|
|
24
|
+
? metadata.keywords.join(', ')
|
|
25
|
+
: metadata.keywords;
|
|
26
|
+
tags.push(`<meta name="keywords" content="${keywordsStr}">`);
|
|
27
|
+
}
|
|
28
|
+
// Author
|
|
29
|
+
if (metadata.author) {
|
|
30
|
+
tags.push(`<meta name="author" content="${metadata.author}">`);
|
|
31
|
+
}
|
|
32
|
+
// Theme color
|
|
33
|
+
if (metadata.themeColor) {
|
|
34
|
+
tags.push(`<meta name="theme-color" content="${metadata.themeColor}">`);
|
|
35
|
+
}
|
|
36
|
+
// Robots
|
|
37
|
+
if (metadata.robots) {
|
|
38
|
+
tags.push(`<meta name="robots" content="${metadata.robots}">`);
|
|
39
|
+
}
|
|
40
|
+
// Canonical
|
|
41
|
+
if (metadata.canonical) {
|
|
42
|
+
tags.push(`<link rel="canonical" href="${metadata.canonical}">`);
|
|
43
|
+
}
|
|
44
|
+
// Favicon
|
|
45
|
+
if (metadata.favicon) {
|
|
46
|
+
tags.push(`<link rel="icon" href="${metadata.favicon}">`);
|
|
47
|
+
}
|
|
48
|
+
// Apple Touch Icon
|
|
49
|
+
if (metadata.appleTouchIcon) {
|
|
50
|
+
tags.push(`<link rel="apple-touch-icon" href="${metadata.appleTouchIcon}">`);
|
|
51
|
+
}
|
|
52
|
+
// Manifest
|
|
53
|
+
if (metadata.manifest) {
|
|
54
|
+
tags.push(`<link rel="manifest" href="${metadata.manifest}">`);
|
|
55
|
+
}
|
|
56
|
+
// Open Graph
|
|
57
|
+
if (metadata.openGraph) {
|
|
58
|
+
const og = metadata.openGraph;
|
|
59
|
+
if (og.title)
|
|
60
|
+
tags.push(`<meta property="og:title" content="${og.title}">`);
|
|
61
|
+
if (og.description)
|
|
62
|
+
tags.push(`<meta property="og:description" content="${og.description}">`);
|
|
63
|
+
if (og.type)
|
|
64
|
+
tags.push(`<meta property="og:type" content="${og.type}">`);
|
|
65
|
+
if (og.url)
|
|
66
|
+
tags.push(`<meta property="og:url" content="${og.url}">`);
|
|
67
|
+
if (og.siteName)
|
|
68
|
+
tags.push(`<meta property="og:site_name" content="${og.siteName}">`);
|
|
69
|
+
if (og.locale)
|
|
70
|
+
tags.push(`<meta property="og:locale" content="${og.locale}">`);
|
|
71
|
+
if (og.image) {
|
|
72
|
+
if (typeof og.image === 'string') {
|
|
73
|
+
tags.push(`<meta property="og:image" content="${og.image}">`);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
tags.push(`<meta property="og:image" content="${og.image.url}">`);
|
|
77
|
+
if (og.image.width)
|
|
78
|
+
tags.push(`<meta property="og:image:width" content="${og.image.width}">`);
|
|
79
|
+
if (og.image.height)
|
|
80
|
+
tags.push(`<meta property="og:image:height" content="${og.image.height}">`);
|
|
81
|
+
if (og.image.alt)
|
|
82
|
+
tags.push(`<meta property="og:image:alt" content="${og.image.alt}">`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Twitter Card
|
|
87
|
+
if (metadata.twitter) {
|
|
88
|
+
const tw = metadata.twitter;
|
|
89
|
+
if (tw.card)
|
|
90
|
+
tags.push(`<meta name="twitter:card" content="${tw.card}">`);
|
|
91
|
+
if (tw.site)
|
|
92
|
+
tags.push(`<meta name="twitter:site" content="${tw.site}">`);
|
|
93
|
+
if (tw.creator)
|
|
94
|
+
tags.push(`<meta name="twitter:creator" content="${tw.creator}">`);
|
|
95
|
+
if (tw.title)
|
|
96
|
+
tags.push(`<meta name="twitter:title" content="${tw.title}">`);
|
|
97
|
+
if (tw.description)
|
|
98
|
+
tags.push(`<meta name="twitter:description" content="${tw.description}">`);
|
|
99
|
+
if (tw.image)
|
|
100
|
+
tags.push(`<meta name="twitter:image" content="${tw.image}">`);
|
|
101
|
+
if (tw.imageAlt)
|
|
102
|
+
tags.push(`<meta name="twitter:image:alt" content="${tw.imageAlt}">`);
|
|
103
|
+
}
|
|
104
|
+
// Custom meta tags
|
|
105
|
+
if (metadata.other) {
|
|
106
|
+
for (const [key, value] of Object.entries(metadata.other)) {
|
|
107
|
+
tags.push(`<meta name="${key}" content="${value}">`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return tags.join('\n');
|
|
111
|
+
}
|
|
112
|
+
// Função para ofuscar dados (não é criptografia, apenas ofuscação)
|
|
113
|
+
function obfuscateData(data) {
|
|
114
|
+
// 1. Serializa para JSON minificado
|
|
13
115
|
const jsonStr = JSON.stringify(data);
|
|
116
|
+
// 2. Converte para base64
|
|
14
117
|
const base64 = Buffer.from(jsonStr).toString('base64');
|
|
15
|
-
|
|
118
|
+
// 3. Adiciona um hash fake no início para parecer um token
|
|
119
|
+
const hash = Buffer.from(Date.now().toString()).toString('base64').substring(0, 8);
|
|
120
|
+
return `${hash}.${base64}`;
|
|
16
121
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
122
|
+
// Função para criar script ofuscado
|
|
123
|
+
function createInitialDataScript(data) {
|
|
124
|
+
const obfuscated = obfuscateData(data);
|
|
125
|
+
// Usa um atributo data-* ao invés de JSON visível
|
|
126
|
+
return `<script id="__hight_data__" type="text/plain" data-h="${obfuscated}"></script>`;
|
|
22
127
|
}
|
|
23
128
|
async function render({ req, route, params, allRoutes }) {
|
|
24
129
|
const { generateMetadata } = route;
|
|
@@ -43,22 +148,36 @@ async function render({ req, route, params, allRoutes }) {
|
|
|
43
148
|
initialComponentPath: route.componentPath,
|
|
44
149
|
initialParams: params,
|
|
45
150
|
};
|
|
46
|
-
//
|
|
47
|
-
const
|
|
151
|
+
// Cria script JSON limpo
|
|
152
|
+
const initialDataScript = createInitialDataScript(initialData);
|
|
48
153
|
// Script de hot reload apenas em desenvolvimento
|
|
49
154
|
const hotReloadScript = !isProduction && hotReloadManager
|
|
50
155
|
? hotReloadManager.getClientScript()
|
|
51
156
|
: '';
|
|
52
|
-
|
|
157
|
+
// Gera todas as meta tags
|
|
158
|
+
const metaTags = generateMetaTags(metadata);
|
|
53
159
|
// Determina quais arquivos JavaScript carregar
|
|
54
160
|
const jsFiles = getJavaScriptFiles(req);
|
|
161
|
+
const htmlLang = metadata.language || 'pt-BR';
|
|
55
162
|
// HTML base sem SSR - apenas o container e scripts para client-side rendering
|
|
56
|
-
return `<!DOCTYPE html
|
|
163
|
+
return `<!DOCTYPE html>
|
|
164
|
+
<html lang="${htmlLang}">
|
|
165
|
+
<head>
|
|
166
|
+
${metaTags}
|
|
167
|
+
<title>${metadata.title || 'App hweb'}</title>
|
|
168
|
+
</head>
|
|
169
|
+
<body>
|
|
170
|
+
<div id="root"></div>
|
|
171
|
+
${initialDataScript}
|
|
172
|
+
${jsFiles}
|
|
173
|
+
${hotReloadScript}
|
|
174
|
+
</body>
|
|
175
|
+
</html>`;
|
|
57
176
|
}
|
|
58
177
|
// Função para determinar quais arquivos JavaScript carregar
|
|
59
178
|
function getJavaScriptFiles(req) {
|
|
60
179
|
const projectDir = process.cwd();
|
|
61
|
-
const distDir = path_1.default.join(projectDir, '
|
|
180
|
+
const distDir = path_1.default.join(projectDir, '.hight');
|
|
62
181
|
try {
|
|
63
182
|
// Verifica se existe um manifesto de chunks (gerado pelo ESBuild com splitting)
|
|
64
183
|
const manifestPath = path_1.default.join(distDir, 'manifest.json');
|
|
@@ -67,7 +186,7 @@ function getJavaScriptFiles(req) {
|
|
|
67
186
|
const manifest = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf8'));
|
|
68
187
|
const scripts = Object.values(manifest)
|
|
69
188
|
.filter((file) => file.endsWith('.js'))
|
|
70
|
-
.map((file) => `<script src="/
|
|
189
|
+
.map((file) => `<script src="/_hight/${file}"></script>`)
|
|
71
190
|
.join('');
|
|
72
191
|
return scripts;
|
|
73
192
|
}
|
|
@@ -91,17 +210,17 @@ function getJavaScriptFiles(req) {
|
|
|
91
210
|
if (jsFiles.length >= 1) {
|
|
92
211
|
// Modo chunks sem manifesto
|
|
93
212
|
return jsFiles
|
|
94
|
-
.map(file => `<script src="/
|
|
213
|
+
.map(file => `<script src="/_hight/${file}"></script>`)
|
|
95
214
|
.join('');
|
|
96
215
|
}
|
|
97
216
|
else {
|
|
98
217
|
// Modo tradicional - único arquivo
|
|
99
|
-
return '<script src="/
|
|
218
|
+
return '<script src="/_hight/main.js"></script>';
|
|
100
219
|
}
|
|
101
220
|
}
|
|
102
221
|
}
|
|
103
222
|
catch (error) {
|
|
104
223
|
// Fallback para o modo tradicional
|
|
105
|
-
return '<script src="/
|
|
224
|
+
return '<script src="/_hight/main.js"></script>';
|
|
106
225
|
}
|
|
107
226
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -62,6 +62,11 @@ export interface HightConfig {
|
|
|
62
62
|
* Padrão: 2048
|
|
63
63
|
*/
|
|
64
64
|
maxUrlLength?: number;
|
|
65
|
+
/**
|
|
66
|
+
* Habilita o log de acesso HTTP (ex: GET /api/users 200 15ms).
|
|
67
|
+
* Padrão: false
|
|
68
|
+
*/
|
|
69
|
+
accessLogging?: boolean;
|
|
65
70
|
}
|
|
66
71
|
/**
|
|
67
72
|
* Tipo da função de configuração que pode ser exportada no hightjs.config.js
|
|
@@ -72,7 +77,41 @@ export type HightConfigFunction = (phase: string, context: {
|
|
|
72
77
|
export interface Metadata {
|
|
73
78
|
title?: string;
|
|
74
79
|
description?: string;
|
|
80
|
+
keywords?: string | string[];
|
|
81
|
+
author?: string;
|
|
75
82
|
favicon?: string;
|
|
83
|
+
viewport?: string;
|
|
84
|
+
themeColor?: string;
|
|
85
|
+
canonical?: string;
|
|
86
|
+
robots?: string;
|
|
87
|
+
openGraph?: {
|
|
88
|
+
title?: string;
|
|
89
|
+
description?: string;
|
|
90
|
+
type?: string;
|
|
91
|
+
url?: string;
|
|
92
|
+
image?: string | {
|
|
93
|
+
url: string;
|
|
94
|
+
width?: number;
|
|
95
|
+
height?: number;
|
|
96
|
+
alt?: string;
|
|
97
|
+
};
|
|
98
|
+
siteName?: string;
|
|
99
|
+
locale?: string;
|
|
100
|
+
};
|
|
101
|
+
twitter?: {
|
|
102
|
+
card?: 'summary' | 'summary_large_image' | 'app' | 'player';
|
|
103
|
+
site?: string;
|
|
104
|
+
creator?: string;
|
|
105
|
+
title?: string;
|
|
106
|
+
description?: string;
|
|
107
|
+
image?: string;
|
|
108
|
+
imageAlt?: string;
|
|
109
|
+
};
|
|
110
|
+
language?: string;
|
|
111
|
+
charset?: string;
|
|
112
|
+
appleTouchIcon?: string;
|
|
113
|
+
manifest?: string;
|
|
114
|
+
other?: Record<string, string>;
|
|
76
115
|
}
|
|
77
116
|
export interface RouteConfig {
|
|
78
117
|
pattern: string;
|
package/docs/config.md
CHANGED
|
@@ -63,9 +63,18 @@ export default config;
|
|
|
63
63
|
| `serverTimeout` | `number` | `35000` | Timeout geral do servidor em ms |
|
|
64
64
|
| `individualRequestTimeout` | `number` | `30000` | Timeout por requisição individual em ms |
|
|
65
65
|
| `maxUrlLength` | `number` | `2048` | Tamanho máximo da URL em caracteres |
|
|
66
|
+
| `accessLogging` | `boolean` | `false` | Habilita logs de acesso HTTP (ex: `GET /api/users 200 15ms`) |
|
|
66
67
|
|
|
67
68
|
## 🎯 Casos de Uso
|
|
68
69
|
|
|
70
|
+
### Habilitar Logs de Acesso
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
const config: HightConfig = {
|
|
74
|
+
accessLogging: true, // Mostra: GET /api/users 200 15ms
|
|
75
|
+
};
|
|
76
|
+
```
|
|
77
|
+
|
|
69
78
|
### Aumentar Timeouts para Debugging
|
|
70
79
|
|
|
71
80
|
```typescript
|
|
@@ -153,6 +162,9 @@ const hightConfig: HightConfigFunction = (phase, { defaultConfig }) => {
|
|
|
153
162
|
|
|
154
163
|
// Ajustes por ambiente
|
|
155
164
|
if (phase === 'development') {
|
|
165
|
+
// Habilita logs de acesso em dev
|
|
166
|
+
config.accessLogging = true;
|
|
167
|
+
|
|
156
168
|
// Timeouts maiores para debugging
|
|
157
169
|
config.requestTimeout = 120000;
|
|
158
170
|
config.individualRequestTimeout = 120000;
|
|
@@ -160,6 +172,9 @@ const hightConfig: HightConfigFunction = (phase, { defaultConfig }) => {
|
|
|
160
172
|
// URLs longas para testes
|
|
161
173
|
config.maxUrlLength = 4096;
|
|
162
174
|
} else if (phase === 'production') {
|
|
175
|
+
// Logs de acesso também podem ser úteis em produção
|
|
176
|
+
config.accessLogging = true;
|
|
177
|
+
|
|
163
178
|
// Mais restritivo em produção
|
|
164
179
|
config.maxHeadersCount = 50;
|
|
165
180
|
config.maxUrlLength = 1024;
|
|
@@ -47,6 +47,12 @@ const hightConfig: HightConfigFunction = (phase, { defaultConfig }) => {
|
|
|
47
47
|
* Default: 2048
|
|
48
48
|
*/
|
|
49
49
|
maxUrlLength: 2048,
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Enable HTTP access logging (e.g., GET /api/users 200 15ms)
|
|
53
|
+
* Default: false
|
|
54
|
+
*/
|
|
55
|
+
accessLogging: true,
|
|
50
56
|
};
|
|
51
57
|
|
|
52
58
|
// You can customize settings based on the phase
|
package/example/package.json
CHANGED