vatts 1.3.1-test.3 → 1.4.1-test.2

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.
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Interface para as opções do servidor Proxy HTTP/3.
3
+ */
4
+ export interface ProxyOptions {
5
+ /** Porta HTTP para redirecionamento (ex: ":80") */
6
+ httpPort: string;
7
+ /** Porta HTTPS/HTTP3 (ex: ":443") */
8
+ httpsPort: string;
9
+ /** URL do servidor backend (ex: "http://localhost:3000") */
10
+ backendUrl: string;
11
+ /** Caminho para o arquivo de certificado SSL (.pem) */
12
+ certPath: string;
13
+ /** Caminho para o arquivo de chave privada SSL (.pem) */
14
+ keyPath: string;
15
+ /** Habilita suporte a WebTransport */
16
+ enableWebTransport?: boolean;
17
+ /** (Opcional) Sobrescreve o caminho da biblioteca manualmente */
18
+ customLibPath?: string;
19
+ }
20
+ /**
21
+ * Interface para os callbacks do WebTransport.
22
+ * Use isso para tipar o handler ao chamar addTransport.
23
+ */
24
+ export interface WebTransportCallbacks {
25
+ /** Chamado quando um cliente conecta na rota */
26
+ onConnect?: (client: WebTransportClient) => void;
27
+ /** Chamado quando uma mensagem é recebida do cliente */
28
+ onMessage: (client: WebTransportClient, msg: string) => void;
29
+ /** Chamado quando a conexão é fechada */
30
+ onClose?: (client: WebTransportClient) => void;
31
+ }
32
+ export declare class WebTransportClient {
33
+ readonly id: string;
34
+ private sendFunc;
35
+ private closeFunc;
36
+ constructor(id: string, sendFunc: Function, closeFunc: Function);
37
+ send(data: string): void;
38
+ close(): void;
39
+ }
40
+ export declare class NativeProxy {
41
+ private static instanceStart;
42
+ private static instanceSend;
43
+ private static instanceClose;
44
+ private static transportRoutes;
45
+ private static activeClients;
46
+ /**
47
+ * Registra uma rota WebTransport.
48
+ * @param route Caminho da rota (ex: "/chat")
49
+ * @param handler Objeto com callbacks
50
+ */
51
+ static addTransportRoute(route: string, handler: WebTransportCallbacks): void;
52
+ /**
53
+ * Detecta a plataforma e arquitetura para montar o nome do arquivo.
54
+ */
55
+ static getLibPath(): string;
56
+ private static loadLibrary;
57
+ private static handleGoCallback;
58
+ static start(options: ProxyOptions): void;
59
+ }
60
+ export declare const startProxy: (options: ProxyOptions) => void;
61
+ export declare const addTransport: (route: string, handler: WebTransportCallbacks) => void;
62
+ export default startProxy;
@@ -0,0 +1,162 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.addTransport = exports.startProxy = exports.NativeProxy = exports.WebTransportClient = void 0;
7
+ const koffi_1 = __importDefault(require("koffi"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const os_1 = __importDefault(require("os"));
11
+ // Wrapper para interagir com o cliente conectado via Go
12
+ class WebTransportClient {
13
+ id;
14
+ sendFunc;
15
+ closeFunc;
16
+ constructor(id, sendFunc, closeFunc) {
17
+ this.id = id;
18
+ this.sendFunc = sendFunc;
19
+ this.closeFunc = closeFunc;
20
+ }
21
+ // Envia dados para o cliente (Vue)
22
+ send(data) {
23
+ this.sendFunc(this.id, data);
24
+ }
25
+ // Força o fechamento da conexão
26
+ close() {
27
+ this.closeFunc(this.id);
28
+ }
29
+ }
30
+ exports.WebTransportClient = WebTransportClient;
31
+ class NativeProxy {
32
+ static instanceStart = null;
33
+ static instanceSend = null;
34
+ static instanceClose = null;
35
+ // Mapa de rotas -> callbacks
36
+ static transportRoutes = new Map();
37
+ // Cache de clientes ativos para evitar recriar o objeto a cada mensagem
38
+ static activeClients = new Map();
39
+ /**
40
+ * Registra uma rota WebTransport.
41
+ * @param route Caminho da rota (ex: "/chat")
42
+ * @param handler Objeto com callbacks
43
+ */
44
+ static addTransportRoute(route, handler) {
45
+ this.transportRoutes.set(route, handler);
46
+ }
47
+ /**
48
+ * Detecta a plataforma e arquitetura para montar o nome do arquivo.
49
+ */
50
+ static getLibPath() {
51
+ const platform = os_1.default.platform();
52
+ const arch = os_1.default.arch();
53
+ let osName = '';
54
+ let ext = '.node';
55
+ switch (platform) {
56
+ case 'win32':
57
+ osName = 'win';
58
+ break;
59
+ case 'linux':
60
+ osName = 'linux';
61
+ break;
62
+ case 'darwin':
63
+ osName = 'darwin';
64
+ break;
65
+ default: throw new Error(`Sistema operacional não suportado: ${platform}`);
66
+ }
67
+ let archName = '';
68
+ switch (arch) {
69
+ case 'x64':
70
+ archName = 'x64';
71
+ break;
72
+ case 'arm64':
73
+ archName = 'arm64';
74
+ break;
75
+ default: throw new Error(`Arquitetura não suportada: ${arch}`);
76
+ }
77
+ const filename = `core-${osName}-${archName}${ext}`;
78
+ return path_1.default.resolve(__dirname, '..', 'core-go', filename);
79
+ }
80
+ static loadLibrary(customPath) {
81
+ if (this.instanceStart)
82
+ return;
83
+ const libPath = customPath || this.getLibPath();
84
+ if (!fs_1.default.existsSync(libPath)) {
85
+ throw new Error(`Biblioteca nativa não encontrada: ${libPath}.\n`);
86
+ }
87
+ try {
88
+ const lib = koffi_1.default.load(libPath);
89
+ // 1. Define o callback do C (Go chama isso)
90
+ const OnWebTransportMessage = koffi_1.default.proto('void OnWebTransportMessage(str id, str path, str event, str data)');
91
+ // 2. Mapeia as funções do Go
92
+ this.instanceStart = lib.func('StartServer', 'str', ['str', 'str', 'str', 'str', 'str', 'str']);
93
+ this.instanceSend = lib.func('WebTransportSend', 'void', ['str', 'str']);
94
+ this.instanceClose = lib.func('WebTransportClose', 'void', ['str']);
95
+ const registerCallback = lib.func('RegisterWebTransportCallback', 'void', [koffi_1.default.pointer(OnWebTransportMessage)]);
96
+ // 3. Registra a função JS que o Go vai chamar
97
+ const jsCallback = (id, path, event, data) => {
98
+ this.handleGoCallback(id, path, event, data);
99
+ };
100
+ registerCallback(jsCallback);
101
+ }
102
+ catch (error) {
103
+ throw new Error(`Falha ao carregar a biblioteca nativa: ${error}`);
104
+ }
105
+ }
106
+ static handleGoCallback(id, path, event, data) {
107
+ const routeHandler = this.transportRoutes.get(path);
108
+ // Se não tiver rota registrada, o Go vai manter a conexão aberta mas sem lógica,
109
+ // ou podemos fechar. Por enquanto, ignoramos.
110
+ if (!routeHandler)
111
+ return;
112
+ let client = this.activeClients.get(id);
113
+ if (!client) {
114
+ client = new WebTransportClient(id, this.instanceSend, this.instanceClose);
115
+ this.activeClients.set(id, client);
116
+ }
117
+ try {
118
+ if (event === 'CONNECT') {
119
+ if (routeHandler.onConnect)
120
+ routeHandler.onConnect(client);
121
+ }
122
+ else if (event === 'MESSAGE') {
123
+ if (routeHandler.onMessage)
124
+ routeHandler.onMessage(client, data);
125
+ }
126
+ else if (event === 'CLOSE') {
127
+ if (routeHandler.onClose)
128
+ routeHandler.onClose(client);
129
+ this.activeClients.delete(id);
130
+ }
131
+ }
132
+ catch (e) {
133
+ console.error(`[Vatts WebTransport Error] Handler failed for ${path}:`, e);
134
+ }
135
+ }
136
+ static start(options) {
137
+ const { httpPort, httpsPort, backendUrl, certPath, keyPath, customLibPath, enableWebTransport } = options;
138
+ this.loadLibrary(customLibPath);
139
+ const absCert = path_1.default.resolve(certPath);
140
+ const absKey = path_1.default.resolve(keyPath);
141
+ if (!fs_1.default.existsSync(absCert))
142
+ throw new Error(`Certificado não encontrado: ${absCert}`);
143
+ if (!fs_1.default.existsSync(absKey))
144
+ throw new Error(`Chave não encontrada: ${absKey}`);
145
+ const strWebTransport = enableWebTransport ? "true" : "false";
146
+ // Chama StartServer no Go
147
+ const errorMsg = this.instanceStart(httpPort, httpsPort, backendUrl, absCert, absKey, strWebTransport);
148
+ if (errorMsg) {
149
+ throw new Error(`[Vatts Proxy Error] ${errorMsg}`);
150
+ }
151
+ }
152
+ }
153
+ exports.NativeProxy = NativeProxy;
154
+ const startProxy = (options) => {
155
+ return NativeProxy.start(options);
156
+ };
157
+ exports.startProxy = startProxy;
158
+ const addTransport = (route, handler) => {
159
+ return NativeProxy.addTransportRoute(route, handler);
160
+ };
161
+ exports.addTransport = addTransport;
162
+ exports.default = exports.startProxy;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Interface para as opções do otimizador.
3
+ */
4
+ export interface OptimizerOptions {
5
+ /** Diretório onde estão os arquivos a serem otimizados (ex: .vatts/exported) */
6
+ targetDir: string;
7
+ /** (Opcional) Diretório de saída. Padrão: 'optimized' dentro do target */
8
+ outputDir?: string;
9
+ /** (Opcional) Lista de arquivos ou pastas a serem ignorados */
10
+ ignoredPatterns?: string[];
11
+ /** (Opcional) Sobrescreve o caminho da biblioteca manualmemte */
12
+ customLibPath?: string;
13
+ }
14
+ export declare class NativeOptimizer {
15
+ private static instance;
16
+ /**
17
+ * Detecta a plataforma e arquitetura para montar o nome do arquivo.
18
+ * Padrão esperado: optimizer-{os}-{arch}.{ext}
19
+ * Ex: optimizer-win-64.dll, optimizer-linux-arm64.so
20
+ */
21
+ static getLibPath(): string;
22
+ /**
23
+ * Carrega a biblioteca nativa usando Koffi.
24
+ */
25
+ private static loadLibrary;
26
+ /**
27
+ * Executa a otimização.
28
+ */
29
+ static run(options: OptimizerOptions): void;
30
+ }
31
+ /**
32
+ * API Pública
33
+ */
34
+ export declare const runOptimizer: (options: OptimizerOptions) => void;
35
+ export default runOptimizer;
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.runOptimizer = exports.NativeOptimizer = void 0;
7
+ const koffi_1 = __importDefault(require("koffi"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const os_1 = __importDefault(require("os"));
11
+ class NativeOptimizer {
12
+ static instance = null;
13
+ /**
14
+ * Detecta a plataforma e arquitetura para montar o nome do arquivo.
15
+ * Padrão esperado: optimizer-{os}-{arch}.{ext}
16
+ * Ex: optimizer-win-64.dll, optimizer-linux-arm64.so
17
+ */
18
+ static getLibPath() {
19
+ const platform = os_1.default.platform(); // 'win32', 'linux', 'darwin'
20
+ const arch = os_1.default.arch(); // 'x64', 'arm64'
21
+ let osName = '';
22
+ let ext = '.node';
23
+ // Mapeamento de SO
24
+ switch (platform) {
25
+ case 'win32':
26
+ osName = 'win';
27
+ break;
28
+ case 'linux':
29
+ osName = 'linux';
30
+ break;
31
+ case 'darwin':
32
+ osName = 'darwin'; // ou 'darwin', ajuste conforme seu build
33
+ break;
34
+ default:
35
+ throw new Error(`Sistema operacional não suportado: ${platform}`);
36
+ }
37
+ // Mapeamento de Arquitetura (user pediu '64' para x64)
38
+ let archName = '';
39
+ switch (arch) {
40
+ case 'x64':
41
+ archName = 'x64';
42
+ break;
43
+ case 'arm64':
44
+ archName = 'arm64';
45
+ break;
46
+ default:
47
+ throw new Error(`Arquitetura não suportada: ${arch}`);
48
+ }
49
+ const filename = `core-${osName}-${archName}${ext}`;
50
+ // Caminho relativo: sobe um nível (fora do dist/src) e entra em builds
51
+ // Ajuste o '..' conforme a estrutura final da sua pasta de compilação
52
+ return path_1.default.resolve(__dirname, '..', 'core-go', filename);
53
+ }
54
+ /**
55
+ * Carrega a biblioteca nativa usando Koffi.
56
+ */
57
+ static loadLibrary(customPath) {
58
+ if (this.instance)
59
+ return this.instance;
60
+ const libPath = customPath || this.getLibPath();
61
+ if (!fs_1.default.existsSync(libPath)) {
62
+ throw new Error(`Biblioteca nativa não encontrada: ${libPath}.\n`);
63
+ }
64
+ try {
65
+ const lib = koffi_1.default.load(libPath);
66
+ // Mapeia a função Go: func Optimize(...) *C.char
67
+ // Em Koffi, 'str' como retorno significa char* (string C)
68
+ // Se o Go retornar nil, o Koffi converte para null no JS
69
+ this.instance = lib.func('Optimize', 'str', ['str', 'str', 'str']);
70
+ return this.instance;
71
+ }
72
+ catch (error) {
73
+ throw new Error(`Falha ao carregar a biblioteca nativa em ${libPath}: ${error}`);
74
+ }
75
+ }
76
+ /**
77
+ * Executa a otimização.
78
+ */
79
+ static run(options) {
80
+ const { targetDir, outputDir = '', ignoredPatterns = [], customLibPath } = options;
81
+ const optimize = this.loadLibrary(customLibPath);
82
+ const absTarget = path_1.default.resolve(targetDir);
83
+ const absOutput = outputDir ? path_1.default.resolve(outputDir) : '';
84
+ const ignoredStr = ignoredPatterns.join(',');
85
+ // Validação básica
86
+ if (!fs_1.default.existsSync(absTarget)) {
87
+ return;
88
+ }
89
+ const errorMsg = optimize(absTarget, absOutput, ignoredStr);
90
+ if (errorMsg) {
91
+ throw new Error(errorMsg);
92
+ }
93
+ }
94
+ }
95
+ exports.NativeOptimizer = NativeOptimizer;
96
+ /**
97
+ * API Pública
98
+ */
99
+ const runOptimizer = (options) => {
100
+ return NativeOptimizer.run(options);
101
+ };
102
+ exports.runOptimizer = runOptimizer;
103
+ // Export padrão também para facilitar imports
104
+ exports.default = exports.runOptimizer;
package/dist/bin/vatts.js CHANGED
@@ -94,6 +94,7 @@ program
94
94
  .option('--no-html', 'Do not generate index.html (assets only)')
95
95
  .option('--output-h-data <file>', 'Write the data-h value into this file')
96
96
  .action(async (options) => {
97
+ process.env.NODE_ENV = 'production';
97
98
  const projectDir = process.cwd();
98
99
  const outputInput = (typeof options.output === 'string' && options.output.trim()) || 'exported';
99
100
  const exportDirResolved = path.resolve(projectDir, outputInput);
package/dist/builder.js CHANGED
@@ -1,4 +1,12 @@
1
1
  "use strict";
2
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
3
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
4
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
5
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
6
+ });
7
+ }
8
+ return path;
9
+ };
2
10
  /*
3
11
  * This file is part of the Vatts.js Project.
4
12
  * Copyright (c) 2026 mfraz
@@ -15,19 +23,12 @@
15
23
  * See the License for the specific language governing permissions and
16
24
  * limitations under the License.
17
25
  */
18
- var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
19
- if (typeof path === "string" && /^\.\.?\//.test(path)) {
20
- return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
21
- return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
22
- });
23
- }
24
- return path;
25
- };
26
26
  const { rollup, watch: rollupWatch } = require('rollup');
27
27
  const path = require('path');
28
28
  const Console = require("./api/console").default;
29
29
  const fs = require('fs');
30
- const { readdir, stat, rm } = require("node:fs/promises");
30
+ const crypto = require('crypto');
31
+ const { readdir, stat, rm, rename } = require("node:fs/promises");
31
32
  const { loadTsConfigPaths, resolveTsConfigAlias } = require('./tsconfigPaths');
32
33
  // --- Optimization Plugins ---
33
34
  let terser, replace;
@@ -137,12 +138,9 @@ const customPostCssPlugin = (isProduction, isWatch = false) => {
137
138
  };
138
139
  return {
139
140
  name: 'custom-postcss-plugin',
140
- // O load hook anterior estava causando conflitos com o watch interno do Rollup.
141
- // Removemos ele e usamos addWatchFile no transform.
142
141
  async transform(code, id) {
143
142
  if (!id.endsWith('.css'))
144
143
  return null;
145
- // Garante que o Rollup vigie este arquivo explicitamente
146
144
  if (isWatch) {
147
145
  this.addWatchFile(id);
148
146
  }
@@ -157,13 +155,13 @@ const customPostCssPlugin = (isProduction, isWatch = false) => {
157
155
  Console.warn(`PostCSS process error:`, e.message);
158
156
  }
159
157
  }
160
- const cleanName = path.basename(id).split('?')[0];
158
+ // --- FIX: Evitar colisão de nomes usando Hash do ID ---
159
+ const hash = crypto.createHash('md5').update(id).digest('hex').slice(0, 8);
160
+ const baseName = path.basename(id).split('?')[0];
161
+ const uniqueName = `${hash}-${baseName}`;
161
162
  // Sanitiza o nome para usar como ID no DOM
162
- const safeId = cleanName.replace(/[^a-zA-Z0-9-_]/g, '_');
163
- const referenceId = this.emitFile({ type: 'asset', name: cleanName, source: processedCss });
164
- // Lógica melhorada para injetar o CSS:
165
- // 1. Usa um ID único para evitar tags <link> duplicadas.
166
- // 2. Adiciona um timestamp (?t=...) no href se estiver em dev para quebrar o cache do navegador.
163
+ const safeId = uniqueName.replace(/[^a-zA-Z0-9-_]/g, '_');
164
+ const referenceId = this.emitFile({ type: 'asset', name: uniqueName, source: processedCss });
167
165
  return {
168
166
  code: `
169
167
  const cssUrl = String(import.meta.ROLLUP_FILE_URL_${referenceId});
@@ -178,7 +176,6 @@ const customPostCssPlugin = (isProduction, isWatch = false) => {
178
176
  document.head.appendChild(link);
179
177
  }
180
178
 
181
- // Em dev, força o reload do CSS adicionando timestamp
182
179
  const timestamp = ${isWatch ? 'Date.now()' : 'null'};
183
180
  link.href = timestamp ? (cssUrl + '?t=' + timestamp) : cssUrl;
184
181
  }
@@ -218,6 +215,10 @@ const smartAssetPlugin = (isProduction) => {
218
215
  }
219
216
  let buffer = await fs.promises.readFile(cleanId);
220
217
  const size = buffer.length;
218
+ // --- FIX: Evitar colisão de nomes em assets estáticos ---
219
+ const hash = crypto.createHash('md5').update(cleanId).digest('hex').slice(0, 8);
220
+ const baseName = path.basename(cleanId);
221
+ const uniqueName = `${hash}-${baseName}`;
221
222
  if (type === 'svg') {
222
223
  if (size < INLINE_LIMIT) {
223
224
  const content = buffer.toString('utf8');
@@ -228,7 +229,7 @@ const smartAssetPlugin = (isProduction) => {
228
229
  `;
229
230
  }
230
231
  else {
231
- const referenceId = this.emitFile({ type: 'asset', name: path.basename(cleanId), source: buffer });
232
+ const referenceId = this.emitFile({ type: 'asset', name: uniqueName, source: buffer });
232
233
  const content = buffer.toString('utf8');
233
234
  return `
234
235
  export default String(import.meta.ROLLUP_FILE_URL_${referenceId});
@@ -241,7 +242,7 @@ const smartAssetPlugin = (isProduction) => {
241
242
  return `export default "data:${type};base64,${base64}";`;
242
243
  }
243
244
  else {
244
- const referenceId = this.emitFile({ type: 'asset', name: path.basename(cleanId), source: buffer });
245
+ const referenceId = this.emitFile({ type: 'asset', name: uniqueName, source: buffer });
245
246
  return `export default String(import.meta.ROLLUP_FILE_URL_${referenceId});`;
246
247
  }
247
248
  }
@@ -262,7 +263,10 @@ function getOptimizationPlugins(isProduction) {
262
263
  'process.env.NODE_ENV': JSON.stringify(env),
263
264
  'process.env': JSON.stringify({ NODE_ENV: env }),
264
265
  'process.browser': 'true',
265
- '__REACT_DEVTOOLS_GLOBAL_HOOK__': '({ isDisabled: true })',
266
+ // [FIX] Use bracket notation string replacement.
267
+ // This prevents SyntaxError "window.({ isDisabled: true })"
268
+ // and bypasses recursive replacements by downstream plugins.
269
+ '__REACT_DEVTOOLS_GLOBAL_HOOK__': 'window["__REACT_DEVTOOLS_GLOBAL_HOOK__"]',
266
270
  preventAssignment: true,
267
271
  objectGuards: true
268
272
  }));
@@ -366,7 +370,8 @@ async function buildWithChunks(entryPoint, outdir, isProduction = false) {
366
370
  if (replacePlugin)
367
371
  inputOptions.plugins.unshift(replacePlugin);
368
372
  inputOptions.plugins.push(...otherPlugins);
369
- const processPolyfill = `var process = { env: { NODE_ENV: "${isProduction ? 'production' : 'development'}" } };`;
373
+ // [FIX] Injected polyfill for React DevTools Hook to prevent ReferenceError
374
+ const processPolyfill = `var process = { env: { NODE_ENV: "${isProduction ? 'production' : 'development'}" } }; try { if (typeof window !== 'undefined' && typeof window.__REACT_DEVTOOLS_GLOBAL_HOOK__ === 'undefined') { window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = { isDisabled: true }; } } catch(e) {}`;
370
375
  const outputOptions = {
371
376
  dir: outdir,
372
377
  format: 'es',
@@ -379,12 +384,10 @@ async function buildWithChunks(entryPoint, outdir, isProduction = false) {
379
384
  manualChunks(id) {
380
385
  if (id.includes('node_modules')) {
381
386
  const normalizedId = id.replace(/\\/g, '/');
382
- // --- VUE SPLITTING AVANÇADO ---
383
387
  if (/\/node_modules\/vue-router\//.test(normalizedId))
384
388
  return 'vendor-vue-router';
385
389
  if (/\/node_modules\/(pinia|vuex)\//.test(normalizedId))
386
390
  return 'vendor-vue-store';
387
- // Separa os modulos internos do Vue para evitar um chunk gigante
388
391
  if (/\/node_modules\/(vue|@vue)\//.test(normalizedId)) {
389
392
  if (normalizedId.includes('/runtime-core'))
390
393
  return 'vendor-vue-runtime-core';
@@ -398,8 +401,6 @@ async function buildWithChunks(entryPoint, outdir, isProduction = false) {
398
401
  return 'vendor-vue-compiler';
399
402
  return 'vendor-vue-core';
400
403
  }
401
- // --- REACT SPLITTING AVANÇADO ---
402
- // Separa DOM de Core e Scheduler
403
404
  if (/\/node_modules\/react-dom\//.test(normalizedId))
404
405
  return 'vendor-react-dom';
405
406
  if (/\/node_modules\/scheduler\//.test(normalizedId))
@@ -408,7 +409,6 @@ async function buildWithChunks(entryPoint, outdir, isProduction = false) {
408
409
  return 'vendor-react-router';
409
410
  if (/\/node_modules\/react\//.test(normalizedId))
410
411
  return 'vendor-react-core';
411
- // --- UI LIBS (Granular) ---
412
412
  if (id.includes('framer-motion'))
413
413
  return 'vendor-framer';
414
414
  if (id.includes('@radix-ui'))
@@ -417,15 +417,12 @@ async function buildWithChunks(entryPoint, outdir, isProduction = false) {
417
417
  return 'vendor-headless';
418
418
  if (id.includes('@heroicons'))
419
419
  return 'vendor-icons';
420
- // --- UTILS ---
421
420
  if (id.includes('lodash'))
422
421
  return 'vendor-lodash';
423
422
  if (id.includes('date-fns') || id.includes('moment'))
424
423
  return 'vendor-date';
425
424
  if (id.includes('axios'))
426
425
  return 'vendor-axios';
427
- // Resto cai em vendor-libs genérico para não criar 1 arquivo por pacote,
428
- // mas já tiramos o peso pesado acima.
429
426
  return 'vendor-libs';
430
427
  }
431
428
  }
@@ -433,6 +430,51 @@ async function buildWithChunks(entryPoint, outdir, isProduction = false) {
433
430
  const bundle = await rollup(inputOptions);
434
431
  await bundle.write(outputOptions);
435
432
  await bundle.close();
433
+ // --- Native Optimization Integration ---
434
+ if (isProduction) {
435
+ try {
436
+ // Dynamically require to avoid issues if file doesn't exist in dev/non-opt environments
437
+ // Assuming ./api/optimizer matches the transpiled location of src/optimizer.ts
438
+ const { runOptimizer } = require('./api/optimizer');
439
+ const optimizedDir = path.join(outdir, 'optimized');
440
+ // 1. Run Optimizer
441
+ runOptimizer({
442
+ targetDir: outdir,
443
+ outputDir: optimizedDir,
444
+ // Ignore already optimized assets or node_modules if present
445
+ ignoredPatterns: ['assets']
446
+ });
447
+ // 2. Cleanup: Remove original main*.js and chunks folder from root
448
+ const rootFiles = await readdir(outdir);
449
+ for (const file of rootFiles) {
450
+ // Match main.js, main.hash.js, etc and the chunks directory
451
+ if ((file.startsWith('main') && file.endsWith('.js')) || file === 'chunks') {
452
+ await rm(path.join(outdir, file), { recursive: true, force: true });
453
+ }
454
+ }
455
+ // 3. Move optimized files back to root
456
+ if (fs.existsSync(optimizedDir)) {
457
+ const optFiles = await readdir(optimizedDir);
458
+ for (const file of optFiles) {
459
+ const srcPath = path.join(optimizedDir, file);
460
+ const destPath = path.join(outdir, file);
461
+ // Force remove destination if it exists (e.g. if assets folder exists and we are moving optimized/assets)
462
+ // This prevents EEXIST errors on rename
463
+ await rm(destPath, { recursive: true, force: true });
464
+ await rename(srcPath, destPath);
465
+ }
466
+ // Remove the now empty optimized directory
467
+ await rm(optimizedDir, { recursive: true, force: true });
468
+ }
469
+ Console.log("✅ Build successfully optimized with native optimizer.");
470
+ }
471
+ catch (err) {
472
+ Console.error('Native optimization failed:', err);
473
+ // Not exiting process here to allow build to "succeed" even if optimization fails,
474
+ // though files might be in a weird state.
475
+ // If critical, uncomment: process.exit(1);
476
+ }
477
+ }
436
478
  }
437
479
  catch (error) {
438
480
  Console.error('An error occurred while building with chunks:', error);
@@ -458,7 +500,8 @@ async function build(entryPoint, outfile, isProduction = false) {
458
500
  if (replacePlugin)
459
501
  inputOptions.plugins.unshift(replacePlugin);
460
502
  inputOptions.plugins.push(...otherPlugins);
461
- const processPolyfill = `var process = { env: { NODE_ENV: "${isProduction ? 'production' : 'development'}" } };`;
503
+ // [FIX] Injected polyfill for React DevTools Hook
504
+ const processPolyfill = `var process = { env: { NODE_ENV: "${isProduction ? 'production' : 'development'}" } }; try { if (typeof window !== 'undefined' && typeof window.__REACT_DEVTOOLS_GLOBAL_HOOK__ === 'undefined') { window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = { isDisabled: true }; } } catch(e) {}`;
462
505
  const outputOptions = {
463
506
  file: outfile,
464
507
  format: 'iife',
@@ -536,12 +579,12 @@ async function watchWithChunks(entryPoint, outdir, hotReloadManager = null) {
536
579
  inputOptions.external = nodeBuiltIns;
537
580
  const optimizationPlugins = getOptimizationPlugins(false);
538
581
  inputOptions.plugins = [...inputOptions.plugins, ...optimizationPlugins];
539
- const processPolyfill = `var process = { env: { NODE_ENV: "development" } };`;
582
+ // [FIX] Injected polyfill for React DevTools Hook
583
+ const processPolyfill = `var process = { env: { NODE_ENV: "development" } }; try { if (typeof window !== 'undefined' && typeof window.__REACT_DEVTOOLS_GLOBAL_HOOK__ === 'undefined') { window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = { isDisabled: true }; } } catch(e) {}`;
540
584
  const outputOptions = {
541
585
  dir: outdir,
542
586
  format: 'es',
543
587
  entryFileNames: 'main.js',
544
- // CHANGE: Remove hash in watch mode to prevent file accumulation in assets folder
545
588
  assetFileNames: 'assets/[name][extname]',
546
589
  sourcemap: true,
547
590
  intro: processPolyfill
@@ -569,11 +612,11 @@ async function watch(entryPoint, outfile, hotReloadManager = null) {
569
612
  inputOptions.external = nodeBuiltIns;
570
613
  const optimizationPlugins = getOptimizationPlugins(false);
571
614
  inputOptions.plugins = [...inputOptions.plugins, ...optimizationPlugins];
572
- const processPolyfill = `var process = { env: { NODE_ENV: "development" } };`;
615
+ // [FIX] Injected polyfill for React DevTools Hook
616
+ const processPolyfill = `var process = { env: { NODE_ENV: "development" } }; try { if (typeof window !== 'undefined' && typeof window.__REACT_DEVTOOLS_GLOBAL_HOOK__ === 'undefined') { window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = { isDisabled: true }; } } catch(e) {}`;
573
617
  const outputOptions = {
574
618
  file: outfile,
575
619
  format: 'es',
576
- // CHANGE: Remove hash in watch mode to prevent file accumulation
577
620
  assetFileNames: 'assets/[name][extname]',
578
621
  sourcemap: true,
579
622
  intro: processPolyfill
Binary file
Binary file
Binary file
Binary file
package/dist/helpers.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import http, { Server } from 'http';
3
3
  import type { VattsOptions, VattsConfig } from './types';
4
- import http2 from 'http2';
5
4
  /**
6
5
  * Carrega o arquivo de configuração vatts.config.ts ou vatts.config.js do projeto
7
6
  * @param projectDir Diretório raiz do projeto
@@ -20,7 +19,7 @@ export declare function app(options?: VattsOptions): {
20
19
  /**
21
20
  * Inicia um servidor Vatts.js fechado (o usuário não tem acesso ao framework)
22
21
  */
23
- init: () => Promise<http.Server<typeof http.IncomingMessage, typeof http.ServerResponse> | http2.Http2SecureServer<typeof http.IncomingMessage, typeof http.ServerResponse, typeof http2.Http2ServerRequest, typeof http2.Http2ServerResponse>>;
22
+ init: () => Promise<http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>>;
24
23
  prepare: () => Promise<void>;
25
24
  getRequestHandler: () => (req: any, res: any, next?: any) => Promise<void> | void;
26
25
  setupWebSocket: (server: Server | any) => void;
package/dist/helpers.js CHANGED
@@ -65,8 +65,8 @@ const path_1 = __importDefault(require("path"));
65
65
  // Helpers para integração com diferentes frameworks
66
66
  const index_js_1 = __importStar(require("./index.js")); // Importando o tipo
67
67
  const console_1 = __importStar(require("./api/console"));
68
- const http2_1 = __importDefault(require("http2")); // <-- ADICIONADO: Import do HTTP/2
69
68
  const fs_1 = __importDefault(require("fs"));
69
+ const http3_1 = __importStar(require("./api/http3"));
70
70
  // Registra loaders customizados para importar arquivos não-JS
71
71
  const { registerLoaders } = require('./loaders');
72
72
  registerLoaders({ projectDir: process.cwd() });
@@ -325,7 +325,7 @@ function setConfig(newConfig) {
325
325
  /**
326
326
  * Inicializa servidor nativo do Vatts.js usando HTTP ou HTTPS
327
327
  */
328
- async function initNativeServer(vattsApp, options, port, hostname) {
328
+ async function initNativeServer(vattsApp, options, hostname) {
329
329
  const time = Date.now();
330
330
  const projectDir = options.dir || process.cwd();
331
331
  const phase = options.dev ? 'development' : 'production';
@@ -425,7 +425,7 @@ async function initNativeServer(vattsApp, options, port, hostname) {
425
425
  // Parse do body com proteções
426
426
  req.body = await parseBody(req); // Assumindo que parseBody existe
427
427
  // Adiciona host se não existir (necessário para `new URL`)
428
- req.headers.host = req.headers.host || `localhost:${port}`;
428
+ req.headers.host = req.headers.host || `localhost:${exports.config?.port}`;
429
429
  await handler(req, res);
430
430
  }
431
431
  catch (error) {
@@ -465,51 +465,12 @@ async function initNativeServer(vattsApp, options, port, hostname) {
465
465
  }
466
466
  };
467
467
  // --- FIM DO LISTENER ---
468
- let server; // <-- ADICIONADO: Suporte a Http2SecureServer
469
- const isSSL = exports.config.ssl && exports.config.ssl.key && exports.config.ssl.cert;
470
- if (isSSL && exports.config.ssl) {
471
- const sslOptions = {
472
- key: fs_1.default.readFileSync(exports.config.ssl.key, 'utf8'),
473
- cert: fs_1.default.readFileSync(exports.config.ssl.cert, 'utf8'),
474
- ca: exports.config.ssl.ca ? fs_1.default.readFileSync(exports.config.ssl.ca, 'utf8') : undefined,
475
- allowHTTP1: true // <-- IMPORTANTE: Garante compatibilidade com clientes HTTP/1.1
476
- };
477
- // 1. Cria o servidor HTTPS com HTTP/2
478
- // Substituído https.createServer por http2.createSecureServer
479
- server = http2_1.default.createSecureServer(sslOptions, requestListener);
480
- // 2. Cria o servidor de REDIRECIONAMENTO (HTTP -> HTTPS)
481
- const httpRedirectPort = exports.config.ssl.redirectPort;
482
- http_1.default.createServer((req, res) => {
483
- // Evita host header injection/open redirect: prefere hostname configurado
484
- const rawHost = String(req.headers['host'] || '').trim();
485
- const configuredHost = (options.hostname || hostname || '').trim();
486
- let hostToUse = configuredHost;
487
- // Se não tiver hostname configurado, tenta usar Host do request com validação básica
488
- if (!hostToUse) {
489
- const hostWithoutPort = rawHost.split(':')[0];
490
- // allowlist: hostname simples (sem espaços, sem barras), e sem caracteres perigosos
491
- const isValidHost = /^[a-zA-Z0-9.-]+$/.test(hostWithoutPort) && !hostWithoutPort.includes('..');
492
- hostToUse = isValidHost ? hostWithoutPort : 'localhost';
493
- }
494
- // Monta a URL de redirecionamento
495
- let redirectUrl = `https://${hostToUse}`;
496
- // Adiciona a porta HTTPS apenas se não for a padrão (443)
497
- if (port !== 443) {
498
- redirectUrl += `:${port}`;
499
- }
500
- redirectUrl += req.url || '/';
501
- res.writeHead(301, { 'Location': redirectUrl });
502
- res.end();
503
- }).listen(httpRedirectPort, hostname, () => { });
504
- }
505
- else {
506
- // --- MODO HTTP (Original) ---
507
- // Cria o servidor HTTP nativo
508
- server = http_1.default.createServer(requestListener);
509
- }
510
- // Configurações de segurança do servidor (usa configuração personalizada)
511
- server.setTimeout(vattsConfig.serverTimeout || 35000); // Timeout geral do servidor
512
- // @ts-ignore (Propriedades específicas do HTTP/1, HTTP/2 gerencia isso de forma diferente, mas mantemos para o server HTTP padrão)
468
+ // Sempre criamos um servidor HTTP padrão do Node.js.
469
+ // Se estivermos em modo SSL (HTTP/3), este servidor será apenas o "backend" local.
470
+ const server = http_1.default.createServer(requestListener);
471
+ // Configurações de timeout (aplicam-se ao backend)
472
+ server.setTimeout(vattsConfig.serverTimeout || 35000);
473
+ // @ts-ignore
513
474
  if (server.maxHeadersCount)
514
475
  server.maxHeadersCount = vattsConfig.maxHeadersCount || 100;
515
476
  // @ts-ignore
@@ -518,11 +479,95 @@ async function initNativeServer(vattsApp, options, port, hostname) {
518
479
  // @ts-ignore
519
480
  if (server.requestTimeout)
520
481
  server.requestTimeout = vattsConfig.requestTimeout || 30000;
521
- server.listen(port, hostname, () => {
522
- sendBox({ ...options });
523
- msg.end(`${console_1.Colors.Bright}Ready on port ${console_1.Colors.BgGreen} ${exports.config?.port} ${console_1.Colors.Reset}${console_1.Colors.Bright} in ${Date.now() - time}ms${console_1.Colors.Reset}\n`);
524
- });
525
- // Configura WebSocket para hot reload (Comum a ambos)
482
+ const isSSL = exports.config.ssl && exports.config.ssl.key && exports.config.ssl.cert;
483
+ if (isSSL) {
484
+ // --- MODO SSL (HTTP/3 Proxy Nativo) ---
485
+ // 1. Definição da Porta do Backend (Onde o Node.js vai rodar escondido)
486
+ // Se a porta pública for 3000, usamos 3001 para o backend para não dar conflito de porta presa.
487
+ const backendPort = exports.config.ssl?.backendPort;
488
+ // 2. Portas Públicas
489
+ const publicPort = exports.config.port || 443;
490
+ const redirectPort = exports.config.ssl?.redirectPort || 80;
491
+ // 3. Inicia o Node.js em localhost (Backend)
492
+ server.listen(backendPort, '127.0.0.1', () => {
493
+ try {
494
+ // 4. Inicia o Proxy Go (HTTP/3 + Fallback H2/H1)
495
+ // Isso não bloqueia o Node, roda em background via CGO
496
+ (0, http3_1.default)({
497
+ httpPort: `:${redirectPort}`, // Porta para redirecionar HTTP -> HTTPS
498
+ httpsPort: `:${publicPort}`, // Porta Principal (UDP/TCP)
499
+ backendUrl: `http://127.0.0.1:${backendPort}`, // Para onde enviar o tráfego
500
+ certPath: exports.config?.ssl?.cert,
501
+ keyPath: exports.config?.ssl?.key,
502
+ enableWebTransport: true
503
+ });
504
+ // Lista de clientes conectados na sala
505
+ const connectedClients = new Set();
506
+ // Configura a rota do WebTransport
507
+ (0, http3_1.addTransport)('/chat', {
508
+ onConnect: (client) => {
509
+ console.log(`[Chat] Cliente conectado: ${client.id}`);
510
+ connectedClients.add(client);
511
+ // Avisa todo mundo que alguém entrou
512
+ broadcast(client.id, JSON.stringify({
513
+ type: 'system',
514
+ text: `Usuário ${client.id.substring(0, 4)} entrou na sala.`
515
+ }));
516
+ },
517
+ onMessage: (client, message) => {
518
+ try {
519
+ // O frontend manda JSON, a gente repassa pra geral
520
+ console.log(`[Chat] Msg de ${client.id}: ${message}`);
521
+ const payload = JSON.stringify({
522
+ type: 'user',
523
+ sender: client.id.substring(0, 4),
524
+ text: message
525
+ });
526
+ broadcast(null, payload); // Null = envia para todos
527
+ }
528
+ catch (e) {
529
+ console.error('Erro ao processar mensagem:', e);
530
+ }
531
+ },
532
+ onClose: (client) => {
533
+ console.log(`[Chat] Cliente desconectou: ${client.id}`);
534
+ connectedClients.delete(client);
535
+ broadcast(null, JSON.stringify({
536
+ type: 'system',
537
+ text: `Usuário ${client.id.substring(0, 4)} saiu.`
538
+ }));
539
+ }
540
+ });
541
+ // Função auxiliar para enviar mensagem para todos
542
+ function broadcast(senderId, msg) {
543
+ for (const client of connectedClients) {
544
+ // Opcional: Não enviar de volta para quem mandou (se quiser echo, remova o if)
545
+ // if (senderId && client.id === senderId) continue;
546
+ client.send(msg);
547
+ }
548
+ }
549
+ // Atualiza o box de info para mostrar a porta pública correta
550
+ sendBox({ ...options });
551
+ msg.end(`${console_1.Colors.Bright}Ready on port ${console_1.Colors.BgGreen} ${publicPort} (HTTP/3 Enabled) ${console_1.Colors.Reset}\n` +
552
+ `${console_1.Colors.Dim} ↳ Backend active on 127.0.0.1:${backendPort}${console_1.Colors.Reset}\n` +
553
+ `${console_1.Colors.Bright} in ${Date.now() - time}ms${console_1.Colors.Reset}\n`);
554
+ }
555
+ catch (err) {
556
+ console_1.default.error(`${console_1.Colors.FgRed}[Critical] Failed to start Native HTTP/3 Proxy:${console_1.Colors.Reset} ${err.message}`);
557
+ // Opcional: Fallback ou exit. Geralmente se o SSL falhar, melhor parar.
558
+ process.exit(1);
559
+ }
560
+ });
561
+ }
562
+ else {
563
+ // --- MODO HTTP CLÁSSICO (Sem SSL) ---
564
+ // Roda direto na porta configurada, exposto para o mundo
565
+ server.listen(exports.config?.port, hostname, () => {
566
+ sendBox({ ...options });
567
+ msg.end(`${console_1.Colors.Bright}Ready on port ${console_1.Colors.BgGreen} ${exports.config?.port} ${console_1.Colors.Reset}${console_1.Colors.Bright} in ${Date.now() - time}ms${console_1.Colors.Reset}\n`);
568
+ });
569
+ }
570
+ // Configura WebSocket (Funciona através do Proxy pois ele suporta upgrade de conexão)
526
571
  vattsApp.setupWebSocket(server);
527
572
  vattsApp.executeInstrumentation();
528
573
  return server;
@@ -618,12 +663,11 @@ ${console_1.Colors.Bright + console_1.Colors.FgCyan} \\ / /\\ | | /__\
618
663
  ${console_1.Colors.Bright + console_1.Colors.FgCyan} \\/ /~~\\ | | .__/ .${console_1.Colors.FgWhite} \\__/ .__/ ${message}
619
664
 
620
665
  `);
621
- const actualPort = exports.config?.port || 3000;
622
666
  const actualHostname = options.hostname || "0.0.0.0";
623
667
  if (framework !== 'native') {
624
668
  console_1.default.warn(`The "${framework}" framework was selected, but the init() method only works with the "native" framework. Starting native server...`);
625
669
  }
626
- return await initNativeServer(vattsApp, options, actualPort, actualHostname);
670
+ return await initNativeServer(vattsApp, options, actualHostname);
627
671
  }
628
672
  };
629
673
  }
package/dist/index.js CHANGED
@@ -60,7 +60,6 @@ const crypto_1 = __importDefault(require("crypto")); // Adicionado para gerar ha
60
60
  const express_1 = require("./adapters/express");
61
61
  const builder_1 = require("./builder");
62
62
  const router_1 = require("./router");
63
- // import { renderAsStream } from './renderer'; // REMOVIDO: Carregamento dinâmico agora
64
63
  const http_1 = require("./api/http");
65
64
  Object.defineProperty(exports, "VattsRequest", { enumerable: true, get: function () { return http_1.VattsRequest; } });
66
65
  Object.defineProperty(exports, "VattsResponse", { enumerable: true, get: function () { return http_1.VattsResponse; } });
@@ -236,33 +235,6 @@ Object.defineProperty(exports, "FrameworkAdapterFactory", { enumerable: true, ge
236
235
  var helpers_2 = require("./helpers");
237
236
  Object.defineProperty(exports, "app", { enumerable: true, get: function () { return helpers_2.app; } });
238
237
  // Função para verificar se o projeto é grande o suficiente para se beneficiar de chunks
239
- function isLargeProject(projectDir) {
240
- try {
241
- const srcDir = path_1.default.join(projectDir, 'src');
242
- if (!fs_1.default.existsSync(srcDir))
243
- return false;
244
- let totalFiles = 0;
245
- let totalSize = 0;
246
- function scanDirectory(dir) {
247
- const items = fs_1.default.readdirSync(dir, { withFileTypes: true });
248
- for (const item of items) {
249
- const fullPath = path_1.default.join(dir, item.name);
250
- if (item.isDirectory() && item.name !== 'node_modules' && item.name !== '.git') {
251
- scanDirectory(fullPath);
252
- }
253
- else if (item.isFile() && /\.(tsx?|jsx?|css|scss|less)$/i.test(item.name)) {
254
- totalFiles++;
255
- totalSize += fs_1.default.statSync(fullPath).size;
256
- }
257
- }
258
- }
259
- scanDirectory(srcDir);
260
- return totalFiles > 20 || totalSize > 500 * 1024;
261
- }
262
- catch (error) {
263
- return false;
264
- }
265
- }
266
238
  // Função para gerar o arquivo de entrada para o esbuild
267
239
  function createEntryFile(projectDir, routes, framework) {
268
240
  try {
@@ -556,11 +528,24 @@ function vatts(options) {
556
528
  const staticPath = path_1.default.join(dir, '.vatts');
557
529
  const requestPath = pathname.replace('/_vatts/', '');
558
530
  if (!isSuspiciousPathname(requestPath)) {
559
- const filePath = resolveWithin(staticPath, requestPath);
531
+ let filePath = resolveWithin(staticPath, requestPath);
532
+ let isGzipped = false;
533
+ // --- LÓGICA GZIP (Compatibilidade com Otimizador Go) ---
534
+ // Se o arquivo solicitado termina em .js e NÃO existe fisicamente (foi deletado pelo otimizador),
535
+ // tentamos encontrar a versão .js.gz
536
+ if (filePath && !fs_1.default.existsSync(filePath) && filePath.endsWith('.js')) {
537
+ const gzipPath = filePath + '.br';
538
+ if (fs_1.default.existsSync(gzipPath)) {
539
+ filePath = gzipPath;
540
+ isGzipped = true;
541
+ }
542
+ }
560
543
  // Verifica se existe E se é arquivo
561
544
  if (filePath && fs_1.default.existsSync(filePath) && fs_1.default.statSync(filePath).isFile()) {
562
545
  const stats = fs_1.default.statSync(filePath);
563
- const ext = path_1.default.extname(filePath).toLowerCase();
546
+ // Se for GZIP, usamos '.js' para definir o Content-Type correto (application/javascript),
547
+ // caso contrário, pegamos a extensão real do arquivo.
548
+ const ext = isGzipped ? '.js' : path_1.default.extname(filePath).toLowerCase();
564
549
  const contentTypes = {
565
550
  '.js': 'application/javascript',
566
551
  '.css': 'text/css',
@@ -578,14 +563,17 @@ function vatts(options) {
578
563
  genericRes.header('Expires', '0');
579
564
  }
580
565
  else {
581
- // Em produção, mantemos o cache agressivo (assumindo hash nos arquivos exceto entry points)
582
- // Dica: Se o main.js de prod também não tiver hash, use 'no-cache' nele também
566
+ // Em produção, mantemos o cache agressivo
583
567
  genericRes.header('Cache-Control', 'public, max-age=31536000, immutable');
584
568
  }
569
+ // --- HEADER VITAL PARA GZIP ---
570
+ if (isGzipped) {
571
+ genericRes.header('Content-Encoding', 'br');
572
+ }
585
573
  const lastModified = stats.mtime.toUTCString();
586
574
  genericRes.header('Last-Modified', lastModified);
587
575
  genericRes.header('Content-Type', contentTypes[ext] || 'text/plain');
588
- // Lógica 304 (Mantida igual, pois ajuda na performance mesmo em dev se o arquivo n mudou nada)
576
+ // Lógica 304
589
577
  const ifModifiedSince = req.headers['if-modified-since'];
590
578
  if (ifModifiedSince) {
591
579
  const requestDate = new Date(ifModifiedSince).getTime();
@@ -612,8 +600,7 @@ function vatts(options) {
612
600
  return; // Encerra aqui com sucesso
613
601
  }
614
602
  else {
615
- // CORREÇÃO 2: Se o arquivo não existe, retornamos 404 AQUI.
616
- // Isso impede que o código continue e caia na rota de renderização do HTML (SPA Fallback)
603
+ // CORREÇÃO 2: 404 para assets não encontrados
617
604
  if (adapter.type === 'express') {
618
605
  res.status(404).send('Vatts Asset Not Found');
619
606
  }
@@ -33,6 +33,9 @@ async function createReactConfig(entryPoint, outdir, isProduction, { prePlugins
33
33
  const replaceValues = {
34
34
  'process.env.NODE_ENV': JSON.stringify(isProduction ? 'production' : 'development'),
35
35
  'process.env.PORT': JSON.stringify(process.vatts?.port || 3000),
36
+ // [FIX] Define o hook do DevTools como um objeto vazio seguro caso não exista,
37
+ // evitando o ReferenceError em builds de produção ou ambientes restritos.
38
+ '__REACT_DEVTOOLS_GLOBAL_HOOK__': '({ isDisabled: true })',
36
39
  preventAssignment: true
37
40
  };
38
41
  const extensions = ['.mjs', '.js', '.json', '.node', '.jsx', '.tsx', '.ts'];
@@ -86,7 +89,9 @@ async function createReactConfig(entryPoint, outdir, isProduction, { prePlugins
86
89
  treeShaking: true,
87
90
  target: 'esnext',
88
91
  jsx: 'automatic',
89
- define: { __VERSION__: '"1.0.0"' },
92
+ define: {
93
+ __VERSION__: '"1.0.0"',
94
+ },
90
95
  loaders: esbuildLoaders
91
96
  })
92
97
  ],
@@ -151,8 +151,6 @@ function obfuscateData(data) {
151
151
  const hash = Buffer.from(Date.now().toString()).toString('base64').substring(0, 8);
152
152
  return `${hash}.${base64}`;
153
153
  }
154
- // Retorna as URLs dos scripts e CSS
155
- // Procura em .vatts/ e .vatts/assets/
156
154
  function getBuildAssets(req) {
157
155
  const projectDir = process.cwd();
158
156
  const distDir = path_1.default.join(projectDir, '.vatts');
@@ -173,8 +171,9 @@ function getBuildAssets(req) {
173
171
  const fullPath = path_1.default.join(directory, file);
174
172
  const stat = fs_1.default.statSync(fullPath);
175
173
  if (stat.isFile()) {
176
- if (file.endsWith('.js')) {
177
- scripts.push(`${urlPrefix}/${file}`);
174
+ // MODIFICADO: Aceita .js OU .js.br
175
+ if (file.endsWith('.js') || file.endsWith('.js.br')) {
176
+ scripts.push(`${urlPrefix}/${file.replace(".br", '')}`);
178
177
  }
179
178
  else if (file.endsWith('.css')) {
180
179
  styles.push(`${urlPrefix}/${file}`);
@@ -189,8 +188,9 @@ function getBuildAssets(req) {
189
188
  const manifest = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf8'));
190
189
  const manifestFiles = Object.values(manifest);
191
190
  scripts = manifestFiles
192
- .filter((f) => f.endsWith('.js'))
193
- .map((f) => `/_vatts/${f}`);
191
+ // MODIFICADO: Filtra .js E .js.br no manifesto
192
+ .filter((f) => f.endsWith('.js') || f.endsWith('.js.br'))
193
+ .map((f) => `/_vatts/${f.replace(".br", "")}`);
194
194
  styles = manifestFiles
195
195
  .filter((f) => f.endsWith('.css'))
196
196
  .map((f) => `/_vatts/${f}`);
package/dist/types.d.ts CHANGED
@@ -27,6 +27,7 @@ export interface VattsOptions {
27
27
  export interface VattsConfig {
28
28
  port: number;
29
29
  ssl?: {
30
+ backendPort: number;
30
31
  redirectPort: number;
31
32
  key: string;
32
33
  cert: string;
@@ -101,9 +101,9 @@ function extractComponentPreloads(componentPath) {
101
101
  tags.add(`<link rel="preload" as="style" href="${publicUrl}">`);
102
102
  tags.add(`<link rel="stylesheet" href="${publicUrl}">`);
103
103
  }
104
- else if (['.js'].includes(ext)) {
104
+ else if (['.js', '.js.br'].includes(ext)) {
105
105
  // Adicionado suporte para JS
106
- tags.add(`<link rel="preload" as="script" href="${publicUrl}">`);
106
+ tags.add(`<link rel="preload" as="script" href="${publicUrl.replace(".br", '')}">`);
107
107
  }
108
108
  else if (['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.avif'].includes(ext)) {
109
109
  tags.add(`<link rel="preload" as="image" href="${publicUrl}">`);
@@ -331,8 +331,8 @@ function getBuildAssets(req) {
331
331
  return;
332
332
  const fullPath = path_1.default.join(directory, file);
333
333
  if (fs_1.default.statSync(fullPath).isFile()) {
334
- if (file.endsWith('.js'))
335
- scripts.push(`${urlPrefix}/${file}`);
334
+ if (file.endsWith('.js') || file.endsWith(".js.br"))
335
+ scripts.push(`${urlPrefix}/${file.replace(".br", '')}`);
336
336
  else if (file.endsWith('.css'))
337
337
  styles.push(`${urlPrefix}/${file}`);
338
338
  }
@@ -343,7 +343,7 @@ function getBuildAssets(req) {
343
343
  if (fs_1.default.existsSync(manifestPath)) {
344
344
  const manifest = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf8'));
345
345
  const manifestFiles = Object.values(manifest);
346
- scripts = manifestFiles.filter((f) => f.endsWith('.js')).map((f) => `/_vatts/${f}`);
346
+ scripts = manifestFiles.filter((f) => f.endsWith('.js') || f.endsWith(".js.br")).map((f) => `/_vatts/${f.replace('.br', '')}`);
347
347
  styles = manifestFiles.filter((f) => f.endsWith('.css')).map((f) => `/_vatts/${f}`);
348
348
  }
349
349
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vatts",
3
- "version": "1.3.1-test.3",
3
+ "version": "1.4.1-test.2",
4
4
  "description": "Vatts.js 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
  "types": "dist/index.d.ts",
6
6
  "author": "mfraz",
@@ -110,6 +110,7 @@
110
110
  "chokidar": "^3.6.0",
111
111
  "commander": "^14.0.2",
112
112
  "esbuild": "^0.27.2",
113
+ "koffi": "^2.15.1",
113
114
  "rollup": "^4.56.0",
114
115
  "rollup-plugin-esbuild": "^6.2.1",
115
116
  "rollup-plugin-vue": "^6.0.0",
@@ -136,6 +137,6 @@
136
137
  "vue-tsc": "^3.2.3"
137
138
  },
138
139
  "scripts": {
139
- "build": "tsc && copyfiles -u 1 \"src/**/*.{vue,d.ts}\" dist"
140
+ "build": "tsc && copyfiles -u 1 \"src/**/*.{vue,d.ts}\" dist && copyfiles -f \"../../services/core-go/binaries/*.node\" dist/core-go"
140
141
  }
141
142
  }