hightjs 0.3.4 → 0.4.0
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/README.md +1 -1
- package/dist/api/console.js +1 -1
- package/dist/bin/hightjs.js +3 -3
- package/dist/builder.js +60 -5
- package/dist/client/entry.client.js +155 -7
- package/dist/helpers.js +40 -1
- package/dist/hotReload.js +96 -7
- package/dist/index.js +66 -51
- package/dist/renderer.js +137 -18
- package/dist/types.d.ts +39 -0
- package/docs/checklist.md +1 -1
- package/docs/config.md +15 -0
- package/docs/middlewares.md +1 -1
- package/docs/rotas-backend.md +3 -3
- package/docs/websocket.md +1 -1
- package/example/hightjs.config.ts +6 -0
- package/example/package-lock.json +1 -1
- package/example/package.json +2 -2
- package/example/src/backend/routes/auth.ts +3 -0
- package/example/src/{web/backend → backend}/routes/version.ts +1 -1
- package/example/src/web/components/Home.tsx +140 -0
- package/example/src/web/components/LoginPage.tsx +149 -0
- package/example/src/web/layout.tsx +57 -3
- package/example/src/web/routes/index.tsx +1 -141
- package/example/src/web/routes/login.tsx +1 -146
- package/package.json +3 -1
- package/src/api/console.ts +3 -1
- package/src/bin/hightjs.js +3 -3
- package/src/builder.js +66 -5
- package/src/client/entry.client.tsx +192 -10
- package/src/helpers.ts +39 -1
- package/src/hotReload.ts +96 -7
- package/src/index.ts +79 -63
- package/src/renderer.tsx +142 -18
- package/src/router.ts +2 -0
- package/src/types.ts +56 -0
- package/example/src/web/backend/routes/auth.ts +0 -3
- /package/example/src/{auth.ts → backend/auth.ts} +0 -0
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ Caso tenha alguma dúvida, entre em contato por uma das redes abaixo:
|
|
|
34
34
|
|
|
35
35
|
## ✨ Principais Recursos
|
|
36
36
|
|
|
37
|
-
- **Roteamento automático** de páginas [`src/web/routes`] e APIs [`src/
|
|
37
|
+
- **Roteamento automático** de páginas [`src/web/routes`] e APIs [`src/backend/routes`]
|
|
38
38
|
- **React 19** com client-side hydration
|
|
39
39
|
- **TypeScript** first (totalmente tipado)
|
|
40
40
|
- **WebSockets** nativo nas rotas backend
|
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 => {
|
package/dist/builder.js
CHANGED
|
@@ -19,6 +19,8 @@ const esbuild = require('esbuild');
|
|
|
19
19
|
const path = require('path');
|
|
20
20
|
const Console = require("./api/console").default;
|
|
21
21
|
const fs = require('fs');
|
|
22
|
+
const { readdir, stat } = require("node:fs/promises");
|
|
23
|
+
const { rm } = require("fs-extra");
|
|
22
24
|
// Lista de módulos nativos do Node.js para marcar como externos (apenas os built-ins do Node)
|
|
23
25
|
const nodeBuiltIns = [
|
|
24
26
|
'assert', 'buffer', 'child_process', 'cluster', 'crypto', 'dgram', 'dns',
|
|
@@ -175,6 +177,41 @@ const reactResolvePlugin = {
|
|
|
175
177
|
});
|
|
176
178
|
}
|
|
177
179
|
};
|
|
180
|
+
/**
|
|
181
|
+
* Plugin para adicionar suporte a HMR (Hot Module Replacement)
|
|
182
|
+
*/
|
|
183
|
+
const hmrPlugin = {
|
|
184
|
+
name: 'hmr-plugin',
|
|
185
|
+
setup(build) {
|
|
186
|
+
// Adiciona runtime de HMR para arquivos TSX/JSX
|
|
187
|
+
build.onLoad({ filter: /\.(tsx|jsx)$/ }, async (args) => {
|
|
188
|
+
// Ignora arquivos de node_modules
|
|
189
|
+
if (args.path.includes('node_modules')) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
const fs = require('fs');
|
|
193
|
+
const contents = await fs.promises.readFile(args.path, 'utf8');
|
|
194
|
+
// Adiciona código de HMR apenas em componentes de rota
|
|
195
|
+
if (args.path.includes('/routes/') || args.path.includes('\\routes\\')) {
|
|
196
|
+
const hmrCode = `
|
|
197
|
+
// HMR Runtime
|
|
198
|
+
if (typeof window !== 'undefined' && window.__HWEB_HMR__) {
|
|
199
|
+
const moduleId = ${JSON.stringify(args.path)};
|
|
200
|
+
if (!window.__HWEB_HMR_MODULES__) {
|
|
201
|
+
window.__HWEB_HMR_MODULES__ = new Map();
|
|
202
|
+
}
|
|
203
|
+
window.__HWEB_HMR_MODULES__.set(moduleId, module.exports);
|
|
204
|
+
}
|
|
205
|
+
`;
|
|
206
|
+
return {
|
|
207
|
+
contents: contents + '\n' + hmrCode,
|
|
208
|
+
loader: 'tsx'
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
return null;
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
};
|
|
178
215
|
/**
|
|
179
216
|
* Builds with code splitting into multiple chunks based on module types.
|
|
180
217
|
* @param {string} entryPoint - The path to the entry file.
|
|
@@ -183,8 +220,8 @@ const reactResolvePlugin = {
|
|
|
183
220
|
* @returns {Promise<void>}
|
|
184
221
|
*/
|
|
185
222
|
async function buildWithChunks(entryPoint, outdir, isProduction = false) {
|
|
186
|
-
// limpar diretorio
|
|
187
|
-
|
|
223
|
+
// limpar diretorio, menos a pasta temp
|
|
224
|
+
await cleanDirectoryExcept(outdir, 'temp');
|
|
188
225
|
try {
|
|
189
226
|
await esbuild.build({
|
|
190
227
|
entryPoints: [entryPoint],
|
|
@@ -226,7 +263,7 @@ async function buildWithChunks(entryPoint, outdir, isProduction = false) {
|
|
|
226
263
|
*/
|
|
227
264
|
async function watchWithChunks(entryPoint, outdir, hotReloadManager = null) {
|
|
228
265
|
// limpar diretorio
|
|
229
|
-
|
|
266
|
+
await cleanDirectoryExcept(outdir, 'temp');
|
|
230
267
|
try {
|
|
231
268
|
// Plugin para notificar quando o build termina
|
|
232
269
|
const buildCompletePlugin = {
|
|
@@ -256,7 +293,7 @@ async function watchWithChunks(entryPoint, outdir, hotReloadManager = null) {
|
|
|
256
293
|
outdir: outdir,
|
|
257
294
|
loader: { '.js': 'jsx', '.ts': 'tsx' },
|
|
258
295
|
external: nodeBuiltIns,
|
|
259
|
-
plugins: [postcssPlugin, npmDependenciesPlugin, reactResolvePlugin, buildCompletePlugin],
|
|
296
|
+
plugins: [postcssPlugin, npmDependenciesPlugin, reactResolvePlugin, hmrPlugin, buildCompletePlugin],
|
|
260
297
|
format: 'esm',
|
|
261
298
|
jsx: 'automatic',
|
|
262
299
|
define: {
|
|
@@ -289,7 +326,7 @@ async function watchWithChunks(entryPoint, outdir, hotReloadManager = null) {
|
|
|
289
326
|
async function build(entryPoint, outfile, isProduction = false) {
|
|
290
327
|
// limpar diretorio do outfile
|
|
291
328
|
const outdir = path.dirname(outfile);
|
|
292
|
-
|
|
329
|
+
await cleanDirectoryExcept(outdir, 'temp');
|
|
293
330
|
try {
|
|
294
331
|
await esbuild.build({
|
|
295
332
|
entryPoints: [entryPoint],
|
|
@@ -388,4 +425,22 @@ async function watch(entryPoint, outfile, hotReloadManager = null) {
|
|
|
388
425
|
throw error;
|
|
389
426
|
}
|
|
390
427
|
}
|
|
428
|
+
/**
|
|
429
|
+
* Remove todo o conteúdo de um diretório,
|
|
430
|
+
* exceto a pasta (ou pastas) especificada(s) em excludeFolder.
|
|
431
|
+
*
|
|
432
|
+
* @param {string} dirPath - Caminho do diretório a limpar
|
|
433
|
+
* @param {string|string[]} excludeFolder - Nome ou nomes das pastas a manter
|
|
434
|
+
*/
|
|
435
|
+
async function cleanDirectoryExcept(dirPath, excludeFolder) {
|
|
436
|
+
const excludes = Array.isArray(excludeFolder) ? excludeFolder : [excludeFolder];
|
|
437
|
+
const items = await readdir(dirPath);
|
|
438
|
+
for (const item of items) {
|
|
439
|
+
if (excludes.includes(item))
|
|
440
|
+
continue; // pula as pastas excluídas
|
|
441
|
+
const itemPath = path.join(dirPath, item);
|
|
442
|
+
const info = await stat(itemPath);
|
|
443
|
+
await rm(itemPath, { recursive: info.isDirectory(), force: true });
|
|
444
|
+
}
|
|
445
|
+
}
|
|
391
446
|
module.exports = { build, watch, buildWithChunks, watchWithChunks };
|
|
@@ -55,6 +55,7 @@ const client_1 = require("react-dom/client");
|
|
|
55
55
|
const clientRouter_1 = require("./clientRouter");
|
|
56
56
|
function App({ componentMap, routes, initialComponentPath, initialParams, layoutComponent }) {
|
|
57
57
|
// Estado que guarda o componente a ser renderizado atualmente
|
|
58
|
+
const [hmrTimestamp, setHmrTimestamp] = (0, react_1.useState)(Date.now());
|
|
58
59
|
const [CurrentPageComponent, setCurrentPageComponent] = (0, react_1.useState)(() => {
|
|
59
60
|
// Se for a rota especial __404__, não busca no componentMap
|
|
60
61
|
if (initialComponentPath === '__404__') {
|
|
@@ -63,6 +64,62 @@ function App({ componentMap, routes, initialComponentPath, initialParams, layout
|
|
|
63
64
|
return componentMap[initialComponentPath];
|
|
64
65
|
});
|
|
65
66
|
const [params, setParams] = (0, react_1.useState)(initialParams);
|
|
67
|
+
// HMR: Escuta eventos de hot reload
|
|
68
|
+
(0, react_1.useEffect)(() => {
|
|
69
|
+
// Ativa o sistema de HMR
|
|
70
|
+
window.__HWEB_HMR__ = true;
|
|
71
|
+
const handleHMRUpdate = async (event) => {
|
|
72
|
+
const { file, timestamp } = event.detail;
|
|
73
|
+
const fileName = file ? file.split('/').pop()?.split('\\').pop() : 'unknown';
|
|
74
|
+
console.log('🔥 HMR: Hot reloading...', fileName);
|
|
75
|
+
try {
|
|
76
|
+
// Aguarda um pouco para o esbuild terminar de recompilar
|
|
77
|
+
await new Promise(resolve => setTimeout(resolve, 300));
|
|
78
|
+
// Re-importa o módulo principal com cache busting
|
|
79
|
+
const mainScript = document.querySelector('script[src*="main.js"]');
|
|
80
|
+
if (mainScript) {
|
|
81
|
+
const mainSrc = mainScript.src.split('?')[0];
|
|
82
|
+
const cacheBustedSrc = `${mainSrc}?t=${timestamp}`;
|
|
83
|
+
// Cria novo script
|
|
84
|
+
const newScript = document.createElement('script');
|
|
85
|
+
newScript.type = 'module';
|
|
86
|
+
newScript.src = cacheBustedSrc;
|
|
87
|
+
// Quando o novo script carregar, força re-render
|
|
88
|
+
newScript.onload = () => {
|
|
89
|
+
console.log('✅ HMR: Modules reloaded');
|
|
90
|
+
// Força re-render do componente
|
|
91
|
+
setHmrTimestamp(timestamp);
|
|
92
|
+
// Marca sucesso
|
|
93
|
+
window.__HMR_SUCCESS__ = true;
|
|
94
|
+
setTimeout(() => {
|
|
95
|
+
window.__HMR_SUCCESS__ = false;
|
|
96
|
+
}, 3000);
|
|
97
|
+
};
|
|
98
|
+
newScript.onerror = () => {
|
|
99
|
+
console.error('❌ HMR: Failed to reload modules');
|
|
100
|
+
window.__HMR_SUCCESS__ = false;
|
|
101
|
+
};
|
|
102
|
+
// Remove o script antigo e adiciona o novo
|
|
103
|
+
// (não remove para não quebrar o app)
|
|
104
|
+
document.head.appendChild(newScript);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
// Se não encontrou o script, apenas força re-render
|
|
108
|
+
console.log('⚡ HMR: Forcing re-render');
|
|
109
|
+
setHmrTimestamp(timestamp);
|
|
110
|
+
window.__HMR_SUCCESS__ = true;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
console.error('❌ HMR Error:', error);
|
|
115
|
+
window.__HMR_SUCCESS__ = false;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
window.addEventListener('hmr:component-update', handleHMRUpdate);
|
|
119
|
+
return () => {
|
|
120
|
+
window.removeEventListener('hmr:component-update', handleHMRUpdate);
|
|
121
|
+
};
|
|
122
|
+
}, []);
|
|
66
123
|
const findRouteForPath = (0, react_1.useCallback)((path) => {
|
|
67
124
|
for (const route of routes) {
|
|
68
125
|
const regexPattern = route.pattern
|
|
@@ -138,7 +195,8 @@ function App({ componentMap, routes, initialComponentPath, initialParams, layout
|
|
|
138
195
|
}
|
|
139
196
|
}
|
|
140
197
|
// Renderiza o componente atual (sem Context, usa o router diretamente)
|
|
141
|
-
|
|
198
|
+
// Usa key com timestamp para forçar re-mount durante HMR
|
|
199
|
+
const PageContent = (0, jsx_runtime_1.jsx)(CurrentPageComponent, { params: params }, `page-${hmrTimestamp}`);
|
|
142
200
|
// SEMPRE usa o layout - se não existir, cria um wrapper padrão
|
|
143
201
|
const content = layoutComponent
|
|
144
202
|
? react_1.default.createElement(layoutComponent, { children: PageContent })
|
|
@@ -157,10 +215,49 @@ const DEV_INDICATOR_CORNERS = [
|
|
|
157
215
|
function DevIndicator() {
|
|
158
216
|
const [corner, setCorner] = (0, react_1.useState)(3); // Canto atual (0-3)
|
|
159
217
|
const [isDragging, setIsDragging] = (0, react_1.useState)(false); // Estado de arrastar
|
|
218
|
+
const [isBuilding, setIsBuilding] = (0, react_1.useState)(false); // Estado de build
|
|
160
219
|
// Posição visual do indicador durante o arraste
|
|
161
220
|
const [position, setPosition] = (0, react_1.useState)({ top: 0, left: 0 });
|
|
162
221
|
const indicatorRef = (0, react_1.useRef)(null);
|
|
163
222
|
const dragStartRef = (0, react_1.useRef)(null);
|
|
223
|
+
// Escuta eventos de hot reload para mostrar estado de build
|
|
224
|
+
(0, react_1.useEffect)(() => {
|
|
225
|
+
if (typeof window === 'undefined')
|
|
226
|
+
return;
|
|
227
|
+
const handleHotReloadMessage = (event) => {
|
|
228
|
+
try {
|
|
229
|
+
const message = JSON.parse(event.data);
|
|
230
|
+
// Quando detecta mudança em arquivo, ativa loading
|
|
231
|
+
if (message.type === 'frontend-reload' ||
|
|
232
|
+
message.type === 'backend-api-reload' ||
|
|
233
|
+
message.type === 'src-reload') {
|
|
234
|
+
setIsBuilding(true);
|
|
235
|
+
}
|
|
236
|
+
// Quando o build termina ou servidor fica pronto, desativa loading
|
|
237
|
+
if (message.type === 'server-ready' || message.type === 'build-complete') {
|
|
238
|
+
setIsBuilding(false);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
catch (e) {
|
|
242
|
+
// Ignora mensagens que não são JSON
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
// Intercepta mensagens WebSocket
|
|
246
|
+
const originalWebSocket = window.WebSocket;
|
|
247
|
+
window.WebSocket = class extends originalWebSocket {
|
|
248
|
+
constructor(url, protocols) {
|
|
249
|
+
super(url, protocols);
|
|
250
|
+
this.addEventListener('message', (event) => {
|
|
251
|
+
if (url.toString().includes('hweb-hotreload')) {
|
|
252
|
+
handleHotReloadMessage(event);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
return () => {
|
|
258
|
+
window.WebSocket = originalWebSocket;
|
|
259
|
+
};
|
|
260
|
+
}, []);
|
|
164
261
|
// --- Estilos Dinâmicos ---
|
|
165
262
|
const getIndicatorStyle = () => {
|
|
166
263
|
const baseStyle = {
|
|
@@ -169,17 +266,22 @@ function DevIndicator() {
|
|
|
169
266
|
width: DEV_INDICATOR_SIZE,
|
|
170
267
|
height: DEV_INDICATOR_SIZE,
|
|
171
268
|
borderRadius: '50%',
|
|
172
|
-
background:
|
|
269
|
+
background: isBuilding
|
|
270
|
+
? 'linear-gradient(135deg, #f093fb, #f5576c)' // Gradiente Rosa/Vermelho quando building
|
|
271
|
+
: 'linear-gradient(135deg, #8e2de2, #4a00e0)', // Gradiente Roxo normal
|
|
173
272
|
color: 'white',
|
|
174
273
|
fontWeight: 'bold',
|
|
175
274
|
fontSize: 28,
|
|
176
|
-
boxShadow:
|
|
275
|
+
boxShadow: isBuilding
|
|
276
|
+
? '0 4px 25px rgba(245, 87, 108, 0.6)' // Shadow mais forte quando building
|
|
277
|
+
: '0 4px 15px rgba(0,0,0,0.2)',
|
|
177
278
|
display: 'flex',
|
|
178
279
|
alignItems: 'center',
|
|
179
280
|
justifyContent: 'center',
|
|
180
281
|
cursor: isDragging ? 'grabbing' : 'grab',
|
|
181
282
|
userSelect: 'none',
|
|
182
|
-
transition: isDragging ? 'none' : 'all 0.3s ease-out',
|
|
283
|
+
transition: isDragging ? 'none' : 'all 0.3s ease-out',
|
|
284
|
+
animation: isBuilding ? 'hweb-pulse 1.5s ease-in-out infinite' : 'none',
|
|
183
285
|
};
|
|
184
286
|
if (isDragging) {
|
|
185
287
|
return {
|
|
@@ -259,13 +361,59 @@ function DevIndicator() {
|
|
|
259
361
|
window.removeEventListener('mouseup', handleMouseUp);
|
|
260
362
|
};
|
|
261
363
|
}, [isDragging, handleMouseMove, handleMouseUp]);
|
|
262
|
-
return ((0, jsx_runtime_1.
|
|
364
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("style", { children: `
|
|
365
|
+
@keyframes hweb-pulse {
|
|
366
|
+
0%, 100% {
|
|
367
|
+
transform: scale(1);
|
|
368
|
+
opacity: 1;
|
|
369
|
+
}
|
|
370
|
+
50% {
|
|
371
|
+
transform: scale(1.1);
|
|
372
|
+
opacity: 0.8;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
@keyframes hweb-spin {
|
|
377
|
+
from {
|
|
378
|
+
transform: rotate(0deg);
|
|
379
|
+
}
|
|
380
|
+
to {
|
|
381
|
+
transform: rotate(360deg);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
` }), (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
385
|
}
|
|
264
386
|
// --- Inicialização do Cliente (CSR - Client-Side Rendering) ---
|
|
387
|
+
function deobfuscateData(obfuscated) {
|
|
388
|
+
try {
|
|
389
|
+
// Remove o hash fake
|
|
390
|
+
const parts = obfuscated.split('.');
|
|
391
|
+
const base64 = parts.length > 1 ? parts[1] : parts[0];
|
|
392
|
+
// Decodifica base64
|
|
393
|
+
const jsonStr = atob(base64);
|
|
394
|
+
// Parse JSON
|
|
395
|
+
return JSON.parse(jsonStr);
|
|
396
|
+
}
|
|
397
|
+
catch (error) {
|
|
398
|
+
console.error('[hweb] Failed to decode data:', error);
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
265
402
|
function initializeClient() {
|
|
266
|
-
|
|
403
|
+
// Lê os dados do atributo data-h
|
|
404
|
+
const dataElement = document.getElementById('__hight_data__');
|
|
405
|
+
if (!dataElement) {
|
|
406
|
+
console.error('[hweb] Initial data script not found.');
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
const obfuscated = dataElement.getAttribute('data-h');
|
|
410
|
+
if (!obfuscated) {
|
|
411
|
+
console.error('[hweb] Data attribute not found.');
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
const initialData = deobfuscateData(obfuscated);
|
|
267
415
|
if (!initialData) {
|
|
268
|
-
console.error('[hweb]
|
|
416
|
+
console.error('[hweb] Failed to parse initial data.');
|
|
269
417
|
return;
|
|
270
418
|
}
|
|
271
419
|
// 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
|
@@ -195,7 +195,7 @@ class HotReloadManager {
|
|
|
195
195
|
filePath.includes('not-found.tsx') ||
|
|
196
196
|
filePath.endsWith('.tsx') ||
|
|
197
197
|
filePath.endsWith('.jsx');
|
|
198
|
-
const isBackendFile = filePath.includes(path.join('src', '
|
|
198
|
+
const isBackendFile = filePath.includes(path.join('src', 'backend')) ||
|
|
199
199
|
(filePath.includes(path.join('src', 'web')) && !isFrontendFile);
|
|
200
200
|
// Limpa o cache do arquivo alterado
|
|
201
201
|
(0, router_1.clearFileCache)(filePath);
|
|
@@ -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');
|
|
@@ -340,10 +342,11 @@ class HotReloadManager {
|
|
|
340
342
|
|
|
341
343
|
switch(message.type) {
|
|
342
344
|
case 'frontend-reload':
|
|
343
|
-
|
|
345
|
+
handleFrontendReload(message.data);
|
|
344
346
|
break;
|
|
345
347
|
case 'backend-api-reload':
|
|
346
|
-
//
|
|
348
|
+
// Backend sempre precisa recarregar
|
|
349
|
+
console.log('🔄 Backend changed, reloading...');
|
|
347
350
|
window.location.reload();
|
|
348
351
|
break;
|
|
349
352
|
case 'server-restart':
|
|
@@ -355,12 +358,91 @@ class HotReloadManager {
|
|
|
355
358
|
case 'frontend-error':
|
|
356
359
|
console.error('❌ Frontend error:', message.data);
|
|
357
360
|
break;
|
|
361
|
+
case 'hmr-update':
|
|
362
|
+
handleHMRUpdate(message.data);
|
|
363
|
+
break;
|
|
358
364
|
}
|
|
359
365
|
} catch (e) {
|
|
360
366
|
console.error('Erro ao processar mensagem do hot-reload:', e);
|
|
361
367
|
}
|
|
362
368
|
};
|
|
363
369
|
|
|
370
|
+
function handleFrontendReload(data) {
|
|
371
|
+
if (!data || !data.file) {
|
|
372
|
+
window.location.reload();
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const file = data.file.toLowerCase();
|
|
377
|
+
|
|
378
|
+
// Mudanças que exigem reload completo
|
|
379
|
+
const needsFullReload =
|
|
380
|
+
file.includes('layout.tsx') ||
|
|
381
|
+
file.includes('layout.jsx') ||
|
|
382
|
+
file.includes('not-found.tsx') ||
|
|
383
|
+
file.includes('not-found.jsx') ||
|
|
384
|
+
file.endsWith('.css');
|
|
385
|
+
|
|
386
|
+
if (needsFullReload) {
|
|
387
|
+
console.log('⚡ Layout/CSS changed, full reload...');
|
|
388
|
+
window.location.reload();
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Mudanças em rotas: tenta HMR
|
|
393
|
+
if (file.includes('/routes/') || file.includes('\\\\routes\\\\')) {
|
|
394
|
+
console.log('⚡ Route component changed, hot reloading...');
|
|
395
|
+
|
|
396
|
+
// Dispara evento para forçar re-render
|
|
397
|
+
const event = new CustomEvent('hmr:component-update', {
|
|
398
|
+
detail: { file: data.file, timestamp: Date.now() }
|
|
399
|
+
});
|
|
400
|
+
window.dispatchEvent(event);
|
|
401
|
+
|
|
402
|
+
// Aguarda 500ms para ver se o HMR foi bem-sucedido
|
|
403
|
+
setTimeout(() => {
|
|
404
|
+
const hmrSuccess = window.__HMR_SUCCESS__;
|
|
405
|
+
if (!hmrSuccess) {
|
|
406
|
+
console.log('⚠️ HMR failed, falling back to full reload');
|
|
407
|
+
window.location.reload();
|
|
408
|
+
} else {
|
|
409
|
+
console.log('✅ HMR successful!');
|
|
410
|
+
}
|
|
411
|
+
}, 500);
|
|
412
|
+
} else {
|
|
413
|
+
// Outros arquivos: reload completo por segurança
|
|
414
|
+
window.location.reload();
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function handleHMRUpdate(data) {
|
|
419
|
+
console.log('🔥 HMR Update:', data);
|
|
420
|
+
|
|
421
|
+
// Dispara evento customizado para o React capturar
|
|
422
|
+
const event = new CustomEvent('hmr:update', {
|
|
423
|
+
detail: data
|
|
424
|
+
});
|
|
425
|
+
window.dispatchEvent(event);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function attemptHMR(changedFile) {
|
|
429
|
+
// Tenta fazer Hot Module Replacement
|
|
430
|
+
// Dispara evento para o React App capturar
|
|
431
|
+
const event = new CustomEvent('hmr:component-update', {
|
|
432
|
+
detail: { file: changedFile, timestamp: Date.now() }
|
|
433
|
+
});
|
|
434
|
+
window.dispatchEvent(event);
|
|
435
|
+
|
|
436
|
+
// Fallback: se após 2s não houve sucesso, reload
|
|
437
|
+
setTimeout(() => {
|
|
438
|
+
const hmrSuccess = window.__HMR_SUCCESS__;
|
|
439
|
+
if (!hmrSuccess) {
|
|
440
|
+
console.log('⚠️ HMR failed, falling back to full reload');
|
|
441
|
+
window.location.reload();
|
|
442
|
+
}
|
|
443
|
+
}, 2000);
|
|
444
|
+
}
|
|
445
|
+
|
|
364
446
|
ws.onclose = function(event) {
|
|
365
447
|
isConnected = false;
|
|
366
448
|
|
|
@@ -454,6 +536,13 @@ class HotReloadManager {
|
|
|
454
536
|
this.buildCompleteResolve = null;
|
|
455
537
|
}
|
|
456
538
|
this.isBuilding = false;
|
|
539
|
+
// Notifica os clientes que o build terminou
|
|
540
|
+
if (success) {
|
|
541
|
+
this.notifyClients('build-complete', { success: true });
|
|
542
|
+
}
|
|
543
|
+
else {
|
|
544
|
+
this.notifyClients('build-error', { success: false });
|
|
545
|
+
}
|
|
457
546
|
}
|
|
458
547
|
}
|
|
459
548
|
exports.HotReloadManager = HotReloadManager;
|