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.
- package/dist/api/http3.d.ts +62 -0
- package/dist/api/http3.js +162 -0
- package/dist/api/optimizer.d.ts +35 -0
- package/dist/api/optimizer.js +104 -0
- package/dist/bin/vatts.js +1 -0
- package/dist/builder.js +79 -36
- package/dist/core-go/core-linux-arm64.node +0 -0
- package/dist/core-go/core-linux-x64.node +0 -0
- package/dist/core-go/core-win-x64.node +0 -0
- package/dist/core-go/dev-linux-arm64.node +0 -0
- package/dist/core-go/dev-linux-x64.node +0 -0
- package/dist/core-go/dev-win-x64.node +0 -0
- package/dist/helpers.d.ts +1 -2
- package/dist/helpers.js +99 -55
- package/dist/index.js +22 -35
- package/dist/react/react.build.js +6 -1
- package/dist/react/renderer-react.js +6 -6
- package/dist/types.d.ts +1 -0
- package/dist/vue/renderer.vue.js +5 -5
- package/package.json +3 -2
|
@@ -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
|
|
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
|
-
|
|
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 =
|
|
163
|
-
const referenceId = this.emitFile({ type: 'asset', name:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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:
|
|
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: {
|
|
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
|
-
|
|
177
|
-
|
|
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
|
-
.
|
|
193
|
-
.
|
|
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
package/dist/vue/renderer.vue.js
CHANGED
|
@@ -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
|
+
"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
|
}
|