vatts 1.0.2-alpha.2 → 1.0.2-alpha.4

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/renderer.js CHANGED
@@ -3,57 +3,65 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.renderAsStream = renderAsStream;
6
7
  exports.render = render;
8
+ const jsx_runtime_1 = require("react/jsx-runtime");
9
+ const server_1 = require("react-dom/server");
7
10
  const router_1 = require("./router");
8
11
  const fs_1 = __importDefault(require("fs"));
9
12
  const path_1 = __importDefault(require("path"));
10
- // Função para gerar todas as meta tags
13
+ // --- Helpers de Servidor ---
14
+ // Função auxiliar para importar módulos ignorando CSS (mesma lógica do router.ts)
15
+ function requireWithoutStyles(modulePath) {
16
+ const extensions = ['.css', '.scss', '.sass', '.less', '.png', '.jpg', '.jpeg', '.gif', '.svg'];
17
+ const originalHandlers = {};
18
+ extensions.forEach(ext => {
19
+ originalHandlers[ext] = require.extensions[ext];
20
+ require.extensions[ext] = (m, filename) => {
21
+ m.exports = {};
22
+ };
23
+ });
24
+ try {
25
+ const resolved = require.resolve(modulePath);
26
+ if (require.cache[resolved])
27
+ delete require.cache[resolved];
28
+ return require(modulePath);
29
+ }
30
+ catch (e) {
31
+ return require(modulePath);
32
+ }
33
+ finally {
34
+ extensions.forEach(ext => {
35
+ if (originalHandlers[ext]) {
36
+ require.extensions[ext] = originalHandlers[ext];
37
+ }
38
+ else {
39
+ delete require.extensions[ext];
40
+ }
41
+ });
42
+ }
43
+ }
44
+ // --- Funções de Metadata e Scripts ---
11
45
  function generateMetaTags(metadata) {
12
46
  const tags = [];
13
- // Charset
14
47
  tags.push(`<meta charset="${metadata.charset || 'UTF-8'}">`);
15
- // Viewport
16
48
  tags.push(`<meta name="viewport" content="${metadata.viewport || 'width=device-width, initial-scale=1.0'}">`);
17
- // Description
18
- if (metadata.description) {
49
+ if (metadata.description)
19
50
  tags.push(`<meta name="description" content="${metadata.description}">`);
20
- }
21
- // Keywords
22
51
  if (metadata.keywords) {
23
- const keywordsStr = Array.isArray(metadata.keywords)
24
- ? metadata.keywords.join(', ')
25
- : metadata.keywords;
52
+ const keywordsStr = Array.isArray(metadata.keywords) ? metadata.keywords.join(', ') : metadata.keywords;
26
53
  tags.push(`<meta name="keywords" content="${keywordsStr}">`);
27
54
  }
28
- // Author
29
- if (metadata.author) {
55
+ if (metadata.author)
30
56
  tags.push(`<meta name="author" content="${metadata.author}">`);
31
- }
32
- // Theme color
33
- if (metadata.themeColor) {
57
+ if (metadata.themeColor)
34
58
  tags.push(`<meta name="theme-color" content="${metadata.themeColor}">`);
35
- }
36
- // Robots
37
- if (metadata.robots) {
59
+ if (metadata.robots)
38
60
  tags.push(`<meta name="robots" content="${metadata.robots}">`);
39
- }
40
- // Canonical
41
- if (metadata.canonical) {
61
+ if (metadata.canonical)
42
62
  tags.push(`<link rel="canonical" href="${metadata.canonical}">`);
43
- }
44
- // Favicon
45
- if (metadata.favicon) {
63
+ if (metadata.favicon)
46
64
  tags.push(`<link rel="icon" href="${metadata.favicon}">`);
47
- }
48
- // Apple Touch Icon
49
- if (metadata.appleTouchIcon) {
50
- tags.push(`<link rel="apple-touch-icon" href="${metadata.appleTouchIcon}">`);
51
- }
52
- // Manifest
53
- if (metadata.manifest) {
54
- tags.push(`<link rel="manifest" href="${metadata.manifest}">`);
55
- }
56
- // Open Graph
57
65
  if (metadata.openGraph) {
58
66
  const og = metadata.openGraph;
59
67
  if (og.title)
@@ -64,44 +72,11 @@ function generateMetaTags(metadata) {
64
72
  tags.push(`<meta property="og:type" content="${og.type}">`);
65
73
  if (og.url)
66
74
  tags.push(`<meta property="og:url" content="${og.url}">`);
67
- if (og.siteName)
68
- tags.push(`<meta property="og:site_name" content="${og.siteName}">`);
69
- if (og.locale)
70
- tags.push(`<meta property="og:locale" content="${og.locale}">`);
71
75
  if (og.image) {
72
- if (typeof og.image === 'string') {
73
- tags.push(`<meta property="og:image" content="${og.image}">`);
74
- }
75
- else {
76
- tags.push(`<meta property="og:image" content="${og.image.url}">`);
77
- if (og.image.width)
78
- tags.push(`<meta property="og:image:width" content="${og.image.width}">`);
79
- if (og.image.height)
80
- tags.push(`<meta property="og:image:height" content="${og.image.height}">`);
81
- if (og.image.alt)
82
- tags.push(`<meta property="og:image:alt" content="${og.image.alt}">`);
83
- }
76
+ const imgUrl = typeof og.image === 'string' ? og.image : og.image.url;
77
+ tags.push(`<meta property="og:image" content="${imgUrl}">`);
84
78
  }
85
79
  }
86
- // Twitter Card
87
- if (metadata.twitter) {
88
- const tw = metadata.twitter;
89
- if (tw.card)
90
- tags.push(`<meta name="twitter:card" content="${tw.card}">`);
91
- if (tw.site)
92
- tags.push(`<meta name="twitter:site" content="${tw.site}">`);
93
- if (tw.creator)
94
- tags.push(`<meta name="twitter:creator" content="${tw.creator}">`);
95
- if (tw.title)
96
- tags.push(`<meta name="twitter:title" content="${tw.title}">`);
97
- if (tw.description)
98
- tags.push(`<meta name="twitter:description" content="${tw.description}">`);
99
- if (tw.image)
100
- tags.push(`<meta name="twitter:image" content="${tw.image}">`);
101
- if (tw.imageAlt)
102
- tags.push(`<meta name="twitter:image:alt" content="${tw.imageAlt}">`);
103
- }
104
- // Custom meta tags
105
80
  if (metadata.other) {
106
81
  for (const [key, value] of Object.entries(metadata.other)) {
107
82
  tags.push(`<meta name="${key}" content="${value}">`);
@@ -109,39 +84,83 @@ function generateMetaTags(metadata) {
109
84
  }
110
85
  return tags.join('\n');
111
86
  }
112
- // Função para ofuscar dados (não é criptografia, apenas ofuscação)
113
87
  function obfuscateData(data) {
114
- // 1. Serializa para JSON minificado
115
88
  const jsonStr = JSON.stringify(data);
116
- // 2. Converte para base64
117
89
  const base64 = Buffer.from(jsonStr).toString('base64');
118
- // 3. Adiciona um hash fake no início para parecer um token
119
90
  const hash = Buffer.from(Date.now().toString()).toString('base64').substring(0, 8);
120
91
  return `${hash}.${base64}`;
121
92
  }
122
- // Função para criar script ofuscado
123
- function createInitialDataScript(data) {
124
- const obfuscated = obfuscateData(data);
125
- // Usa um atributo data-* ao invés de JSON visível
126
- return `<script id="__vatts_data__" type="text/plain" data-h="${obfuscated}"></script>`;
93
+ // Retorna as URLs dos scripts para o bootstrapModules do React
94
+ // Retorna null se não encontrar scripts (indica que o build ainda não terminou)
95
+ function getJavaScriptUrls(req) {
96
+ const projectDir = process.cwd();
97
+ const distDir = path_1.default.join(projectDir, '.vatts');
98
+ if (!fs_1.default.existsSync(distDir))
99
+ return null;
100
+ try {
101
+ const manifestPath = path_1.default.join(distDir, 'manifest.json');
102
+ if (fs_1.default.existsSync(manifestPath)) {
103
+ const manifest = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf8'));
104
+ const files = Object.values(manifest)
105
+ .filter((file) => file.endsWith('.js'))
106
+ .map((file) => `/_vatts/${file}`);
107
+ return files.length > 0 ? files : null;
108
+ }
109
+ else {
110
+ const jsFiles = fs_1.default.readdirSync(distDir)
111
+ .filter(file => file.endsWith('.js') && !file.endsWith('.map'))
112
+ .sort((a, b) => {
113
+ if (a.includes('main'))
114
+ return -1;
115
+ if (b.includes('main'))
116
+ return 1;
117
+ return a.localeCompare(b);
118
+ });
119
+ return jsFiles.length > 0 ? jsFiles.map(file => `/_vatts/${file}`) : null;
120
+ }
121
+ }
122
+ catch {
123
+ return null;
124
+ }
125
+ }
126
+ function ServerRoot({ lang, title, metaTagsHtml, initialDataScript, hotReloadScript, dataScript, children }) {
127
+ return ((0, jsx_runtime_1.jsxs)("html", { lang: lang, children: [(0, jsx_runtime_1.jsxs)("head", { children: [(0, jsx_runtime_1.jsx)("title", { children: title }), (0, jsx_runtime_1.jsx)("script", { dangerouslySetInnerHTML: { __html: initialDataScript } }), (0, jsx_runtime_1.jsx)("span", { dangerouslySetInnerHTML: { __html: metaTagsHtml } })] }), (0, jsx_runtime_1.jsxs)("body", { children: [dataScript, (0, jsx_runtime_1.jsx)("div", { id: "root", children: children }), hotReloadScript && (0, jsx_runtime_1.jsx)("span", { dangerouslySetInnerHTML: { __html: hotReloadScript } })] })] }));
127
128
  }
128
- async function render({ req, route, params, allRoutes }) {
129
+ async function renderAsStream({ req, res, route, params, allRoutes }) {
129
130
  const { generateMetadata } = route;
130
- // Pega a opção dev e hot reload manager do req
131
131
  const isProduction = !req.hwebDev;
132
132
  const hotReloadManager = req.hotReloadManager;
133
- // Pega o layout se existir
134
- const layout = (0, router_1.getLayout)();
135
- let metadata = { title: 'App hweb' };
136
- // Primeiro usa o metadata do layout se existir
137
- if (layout && layout.metadata) {
138
- metadata = { ...metadata, ...layout.metadata };
133
+ // 1. Verificar Build - Se não tiver scripts, retorna tela de Loading
134
+ const bootstrapScripts = getJavaScriptUrls(req);
135
+ if (!bootstrapScripts) {
136
+ res.setHeader('Content-Type', 'text/html');
137
+ // Usamos .end() diretamente para garantir que a resposta seja enviada sem stream
138
+ res.end(getBuildingHTML());
139
+ return;
140
+ }
141
+ // 2. Preparar Layout
142
+ const layoutInfo = (0, router_1.getLayout)();
143
+ let LayoutComponent = null;
144
+ if (layoutInfo) {
145
+ try {
146
+ // Recarrega o componente de layout para ter acesso à função (o router só guarda metadata)
147
+ const layoutModule = requireWithoutStyles(path_1.default.resolve(process.cwd(), layoutInfo.componentPath));
148
+ LayoutComponent = layoutModule.default;
149
+ }
150
+ catch (e) {
151
+ console.error("Error loading layout component for SSR:", e);
152
+ }
153
+ }
154
+ // 3. Preparar Metadata
155
+ let metadata = { title: 'Vatts App' };
156
+ if (layoutInfo && layoutInfo.metadata) {
157
+ metadata = { ...metadata, ...layoutInfo.metadata };
139
158
  }
140
- // Depois sobrescreve com metadata específico da rota se existir
141
159
  if (generateMetadata) {
142
160
  const routeMetadata = await Promise.resolve(generateMetadata(params, req));
143
161
  metadata = { ...metadata, ...routeMetadata };
144
162
  }
163
+ // 4. Preparar Dados Iniciais
145
164
  const results = await Promise.all(allRoutes.map(async (r) => {
146
165
  let routeMeta = {};
147
166
  if (r.generateMetadata) {
@@ -153,99 +172,61 @@ async function render({ req, route, params, allRoutes }) {
153
172
  metadata: routeMeta,
154
173
  };
155
174
  }));
156
- // Prepara os dados para injetar na janela do navegador
157
175
  const initialData = {
158
176
  routes: results,
159
177
  initialComponentPath: route.componentPath,
160
178
  initialParams: params,
161
179
  };
162
- // Cria script JSON limpo
163
- const initialDataScript = createInitialDataScript(initialData);
164
- // Script de hot reload apenas em desenvolvimento
165
- const hotReloadScript = !isProduction && hotReloadManager
166
- ? hotReloadManager.getClientScript()
167
- : '';
168
- // Gera todas as meta tags
169
- const metaTags = generateMetaTags(metadata);
170
- // Determina quais arquivos JavaScript carregar
171
- const jsFiles = getJavaScriptFiles(req);
180
+ // Scripts
181
+ const obfuscatedData = obfuscateData(initialData);
182
+ const hotReloadScript = !isProduction && hotReloadManager ? hotReloadManager.getClientScript() : '';
183
+ const metaTagsHtml = generateMetaTags(metadata);
172
184
  const htmlLang = metadata.language || 'pt-BR';
173
- // HTML base sem SSR - apenas o container e scripts para client-side rendering
174
- return `<!DOCTYPE html>
175
- <html lang="${htmlLang}">
176
- <head>
177
- ${metaTags}
178
- <title>${metadata.title || 'Vatts.js'}</title>
179
- </head>
180
- <body>
181
- <div id="root"></div>
182
- ${initialDataScript}
183
- ${jsFiles}
184
- ${hotReloadScript}
185
- </body>
186
- </html>`;
187
- }
188
- // Função para determinar quais arquivos JavaScript carregar
189
- function getJavaScriptFiles(req) {
190
- const projectDir = process.cwd();
191
- const distDir = path_1.default.join(projectDir, '.vatts');
192
- // Verifica se o diretório de build existe
193
- if (!fs_1.default.existsSync(distDir)) {
194
- // Diretório não existe - build ainda não foi executado
195
- return getBuildingHTML();
185
+ // 5. Componente da Página Atual
186
+ const PageComponent = route.component;
187
+ // Monta a árvore da aplicação
188
+ let AppTree = (0, jsx_runtime_1.jsx)(PageComponent, { params: params });
189
+ if (LayoutComponent) {
190
+ AppTree = (0, jsx_runtime_1.jsx)(LayoutComponent, { children: AppTree });
196
191
  }
197
- try {
198
- // Verifica se existe um manifesto de chunks (gerado pelo ESBuild com splitting)
199
- const manifestPath = path_1.default.join(distDir, 'manifest.json');
200
- if (fs_1.default.existsSync(manifestPath)) {
201
- // Modo chunks - carrega todos os arquivos necessários
202
- const manifest = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf8'));
203
- const scripts = Object.values(manifest)
204
- .filter((file) => file.endsWith('.js'))
205
- .map((file) => `<script type="module" src="/_vatts/${file}"></script>`)
206
- .join('');
207
- // Se não há arquivos JS no manifesto, build em andamento
208
- if (!scripts) {
209
- return getBuildingHTML();
192
+ // 6. Streaming
193
+ return new Promise((resolve, reject) => {
194
+ let didError = false;
195
+ const { pipe } = (0, server_1.renderToPipeableStream)((0, jsx_runtime_1.jsx)(ServerRoot, { lang: htmlLang, title: metadata.title || 'Vatts.js', metaTagsHtml: metaTagsHtml,
196
+ // Recriando o script de dados exatamente como o client espera
197
+ initialDataScript: `/* Data Injection */`, hotReloadScript: hotReloadScript, dataScript: (0, jsx_runtime_1.jsx)("script", { id: "__vatts_data__", type: "text/plain", "data-h": obfuscatedData }), children: AppTree }), {
198
+ // Usar bootstrapModules para scripts tipo módulo (ESM)
199
+ bootstrapModules: bootstrapScripts,
200
+ onShellReady() {
201
+ res.setHeader('Content-Type', 'text/html');
202
+ pipe(res);
203
+ resolve();
204
+ },
205
+ onShellError(error) {
206
+ console.error('Streaming Shell Error:', error);
207
+ res.statusCode = 500;
208
+ res.setHeader('Content-Type', 'text/html');
209
+ res.end('<h1>Internal Server Error</h1>');
210
+ resolve();
211
+ },
212
+ onError(error) {
213
+ didError = true;
214
+ console.error('Streaming Error:', error);
210
215
  }
211
- return scripts;
212
- }
213
- else {
214
- // Verifica se existem múltiplos arquivos JS (chunks sem manifesto)
215
- const jsFiles = fs_1.default.readdirSync(distDir)
216
- .filter(file => file.endsWith('.js') && !file.endsWith('.map'))
217
- .sort((a, b) => {
218
- // Ordena para carregar arquivos principais primeiro
219
- if (a.includes('main'))
220
- return -1;
221
- if (b.includes('main'))
222
- return 1;
223
- if (a.includes('vendor') || a.includes('react'))
224
- return -1;
225
- if (b.includes('vendor') || b.includes('react'))
226
- return 1;
227
- return a.localeCompare(b);
228
- });
229
- // @ts-ignore
230
- if (jsFiles.length >= 1) {
231
- // Modo chunks sem manifesto
232
- return jsFiles
233
- .map(file => `<script type="module" src="/_vatts/${file}"></script>`)
234
- .join('');
235
- }
236
- else {
237
- // Nenhum arquivo JS encontrado - build em andamento ou erro
238
- return getBuildingHTML();
239
- }
240
- }
241
- }
242
- catch (error) {
243
- // Erro ao ler diretório - build em andamento ou erro
244
- return getBuildingHTML();
245
- }
216
+ });
217
+ });
218
+ }
219
+ // Mantemos a função antiga para compatibilidade
220
+ async function render(options) {
221
+ return "";
246
222
  }
247
223
  // Função para retornar HTML de "Build em andamento" com auto-refresh
248
224
  function getBuildingHTML() {
225
+ let version = "1.0.0";
226
+ try {
227
+ version = require("../package.json").version;
228
+ }
229
+ catch (e) { }
249
230
  return `
250
231
  <!DOCTYPE html>
251
232
  <html lang="en">
@@ -489,7 +470,7 @@ function getBuildingHTML() {
489
470
  <span>Building...</span>
490
471
  <div class="status-active">
491
472
  <div class="dot"></div>
492
- v${require("../package.json").version}
473
+ v${version}
493
474
  </div>
494
475
  </div>
495
476
  </div>
package/dist/types.d.ts CHANGED
@@ -26,6 +26,7 @@ export interface VattsOptions {
26
26
  cert: string;
27
27
  ca?: string;
28
28
  };
29
+ envFiles?: string[];
29
30
  }
30
31
  /**
31
32
  * Interface para as configurações avançadas do servidor Vatts.js.
@@ -137,6 +138,10 @@ export interface VattsConfig {
137
138
  * Exemplo: { 'X-Custom-Header': 'value', 'X-Powered-By': 'Vatts.js' }
138
139
  */
139
140
  customHeaders?: Record<string, string>;
141
+ /**
142
+ * Arquivos .env adicionais para carregar. O .env padrão é sempre carregado.
143
+ */
144
+ envFiles?: string[];
140
145
  }
141
146
  /**
142
147
  * Tipo da função de configuração que pode ser exportada no vatts.config.js
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vatts",
3
- "version": "1.0.2-alpha.2",
3
+ "version": "1.0.2-alpha.4",
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": "itsmuzin",