vatts 2.1.2 → 2.1.3-canary.1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/react/renderer-react.js +11 -320
- package/dist/renderers/common.d.ts +57 -0
- package/dist/renderers/common.js +411 -0
- package/dist/vue/renderer.vue.js +12 -369
- package/package.json +1 -1
|
@@ -23,125 +23,10 @@ exports.render = render;
|
|
|
23
23
|
const react_1 = __importDefault(require("react"));
|
|
24
24
|
const server_1 = require("react-dom/server");
|
|
25
25
|
const router_1 = require("../router");
|
|
26
|
-
const fs_1 = __importDefault(require("fs"));
|
|
27
26
|
const path_1 = __importDefault(require("path"));
|
|
27
|
+
const common_1 = require("../renderers/common");
|
|
28
28
|
const BuildingPage_1 = __importDefault(require("../react/BuildingPage"));
|
|
29
29
|
const server_error_1 = __importDefault(require("../react/server-error"));
|
|
30
|
-
function stripScriptTags(html) {
|
|
31
|
-
if (!html)
|
|
32
|
-
return '';
|
|
33
|
-
return html.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, '');
|
|
34
|
-
}
|
|
35
|
-
function getRequestUrl(req) {
|
|
36
|
-
return req?.originalUrl || req?.url;
|
|
37
|
-
}
|
|
38
|
-
function toError(err) {
|
|
39
|
-
if (err instanceof Error)
|
|
40
|
-
return err;
|
|
41
|
-
if (typeof err === 'string')
|
|
42
|
-
return new Error(err);
|
|
43
|
-
try {
|
|
44
|
-
return new Error(JSON.stringify(err));
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
return new Error(String(err));
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
// --- Polyfill para Browser Env no Server ---
|
|
51
|
-
// Cria objetos globais falsos para evitar que bibliotecas client-side quebrem no SSR
|
|
52
|
-
function polyfillBrowserEnv() {
|
|
53
|
-
if (typeof window === 'undefined') {
|
|
54
|
-
const win = {
|
|
55
|
-
document: {
|
|
56
|
-
createElement: () => ({ style: {}, setAttribute: () => { }, classList: { add: () => { }, remove: () => { } } }),
|
|
57
|
-
getElementById: () => null,
|
|
58
|
-
getElementsByTagName: () => [],
|
|
59
|
-
querySelector: () => null,
|
|
60
|
-
querySelectorAll: () => [],
|
|
61
|
-
head: {},
|
|
62
|
-
body: { style: {} },
|
|
63
|
-
addEventListener: () => { },
|
|
64
|
-
removeEventListener: () => { },
|
|
65
|
-
cookie: '',
|
|
66
|
-
location: { href: '', origin: '' }
|
|
67
|
-
},
|
|
68
|
-
navigator: {
|
|
69
|
-
userAgent: 'Node.js/VattsSSR',
|
|
70
|
-
},
|
|
71
|
-
location: {
|
|
72
|
-
href: 'http://localhost',
|
|
73
|
-
origin: 'http://localhost',
|
|
74
|
-
pathname: '/',
|
|
75
|
-
search: '',
|
|
76
|
-
hash: '',
|
|
77
|
-
assign: () => { },
|
|
78
|
-
replace: () => { },
|
|
79
|
-
reload: () => { },
|
|
80
|
-
},
|
|
81
|
-
history: {
|
|
82
|
-
pushState: () => { },
|
|
83
|
-
replaceState: () => { },
|
|
84
|
-
},
|
|
85
|
-
screen: { width: 1920, height: 1080 },
|
|
86
|
-
addEventListener: () => { },
|
|
87
|
-
removeEventListener: () => { },
|
|
88
|
-
matchMedia: () => ({ matches: false, addListener: () => { }, removeListener: () => { } }),
|
|
89
|
-
requestAnimationFrame: (cb) => setTimeout(cb, 0),
|
|
90
|
-
cancelAnimationFrame: (id) => clearTimeout(id),
|
|
91
|
-
setTimeout: setTimeout,
|
|
92
|
-
clearTimeout: clearTimeout,
|
|
93
|
-
setInterval: setInterval,
|
|
94
|
-
clearInterval: clearInterval,
|
|
95
|
-
localStorage: {
|
|
96
|
-
getItem: () => null,
|
|
97
|
-
setItem: () => { },
|
|
98
|
-
removeItem: () => { },
|
|
99
|
-
clear: () => { },
|
|
100
|
-
},
|
|
101
|
-
sessionStorage: {
|
|
102
|
-
getItem: () => null,
|
|
103
|
-
setItem: () => { },
|
|
104
|
-
removeItem: () => { },
|
|
105
|
-
clear: () => { },
|
|
106
|
-
},
|
|
107
|
-
// MOCK SILENCIOSO: Substitui o console original por funções vazias no ambiente window fake
|
|
108
|
-
// para evitar que logs do client-side poluam o terminal do servidor durante o SSR.
|
|
109
|
-
console: {
|
|
110
|
-
log: () => { },
|
|
111
|
-
warn: () => { },
|
|
112
|
-
error: () => { },
|
|
113
|
-
info: () => { },
|
|
114
|
-
debug: () => { },
|
|
115
|
-
trace: () => { },
|
|
116
|
-
dir: () => { },
|
|
117
|
-
},
|
|
118
|
-
Image: class {
|
|
119
|
-
constructor() { }
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
const globalAny = global;
|
|
123
|
-
// Helper para definir globais de forma segura
|
|
124
|
-
// Node 21+ tem globais 'navigator', 'performance' etc que são getter-only e quebram se tentar sobrescrever
|
|
125
|
-
const setGlobal = (key, value) => {
|
|
126
|
-
try {
|
|
127
|
-
if (typeof globalAny[key] === 'undefined') {
|
|
128
|
-
globalAny[key] = value;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
catch (e) {
|
|
132
|
-
// Se falhar (propriedade read-only), ignoramos silenciosamente
|
|
133
|
-
}
|
|
134
|
-
};
|
|
135
|
-
setGlobal('window', win);
|
|
136
|
-
setGlobal('document', win.document);
|
|
137
|
-
setGlobal('navigator', win.navigator);
|
|
138
|
-
setGlobal('location', win.location);
|
|
139
|
-
setGlobal('localStorage', win.localStorage);
|
|
140
|
-
setGlobal('sessionStorage', win.sessionStorage);
|
|
141
|
-
setGlobal('requestAnimationFrame', win.requestAnimationFrame);
|
|
142
|
-
setGlobal('cancelAnimationFrame', win.cancelAnimationFrame);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
30
|
function buildShellHtml(options) {
|
|
146
31
|
const { lang, title, metaTagsHtml, stylesHtml, hotReloadScript, obfuscatedData, scriptsHtml } = options;
|
|
147
32
|
return `<!DOCTYPE html>
|
|
@@ -166,7 +51,7 @@ async function sendReactSsrFallback(options) {
|
|
|
166
51
|
.map((src) => `<script type="module" src="${src}"></script>`)
|
|
167
52
|
.join('\n');
|
|
168
53
|
// No DEV a gente remove scripts do head pra garantir que só aparece a página de erro.
|
|
169
|
-
const safeMetaTagsHtmlForDev = stripScriptTags(metaTagsHtml);
|
|
54
|
+
const safeMetaTagsHtmlForDev = (0, common_1.stripScriptTags)(metaTagsHtml);
|
|
170
55
|
if (res.headersSent) {
|
|
171
56
|
try {
|
|
172
57
|
res.end();
|
|
@@ -190,10 +75,10 @@ async function sendReactSsrFallback(options) {
|
|
|
190
75
|
}));
|
|
191
76
|
return;
|
|
192
77
|
}
|
|
193
|
-
const err = toError(error);
|
|
78
|
+
const err = (0, common_1.toError)(error);
|
|
194
79
|
return new Promise((resolve) => {
|
|
195
80
|
const { pipe } = (0, server_1.renderToPipeableStream)(react_1.default.createElement(ServerRoot, { lang: lang, title: title, metaTagsHtml: safeMetaTagsHtmlForDev, stylesHtml: stylesHtml, initialDataScript: `/* Data Injection */`, hotReloadScript: '', dataScript: react_1.default.createElement("script", { id: "__vatts_data__", type: "text/plain", "data-h": obfuscatedData }) },
|
|
196
|
-
react_1.default.createElement(server_error_1.default, { error: err, requestUrl: getRequestUrl(req), hint: "SSR failed to render this route. See the error below." })), {
|
|
81
|
+
react_1.default.createElement(server_error_1.default, { error: err, requestUrl: (0, common_1.getRequestUrl)(req), hint: "SSR failed to render this route. See the error below." })), {
|
|
197
82
|
onAllReady() {
|
|
198
83
|
pipe(res);
|
|
199
84
|
resolve();
|
|
@@ -209,200 +94,6 @@ async function sendReactSsrFallback(options) {
|
|
|
209
94
|
});
|
|
210
95
|
});
|
|
211
96
|
}
|
|
212
|
-
// --- Helpers de Servidor ---
|
|
213
|
-
// Função auxiliar para importar módulos ignorando CSS (mesma lógica do router.ts)
|
|
214
|
-
function requireWithoutStyles(modulePath) {
|
|
215
|
-
const extensions = ['.css', '.scss', '.sass', '.less', '.png', '.jpg', '.jpeg', '.gif', '.svg'];
|
|
216
|
-
const originalHandlers = {};
|
|
217
|
-
extensions.forEach(ext => {
|
|
218
|
-
originalHandlers[ext] = require.extensions[ext];
|
|
219
|
-
require.extensions[ext] = (m, filename) => {
|
|
220
|
-
m.exports = {};
|
|
221
|
-
};
|
|
222
|
-
});
|
|
223
|
-
try {
|
|
224
|
-
const resolved = require.resolve(modulePath);
|
|
225
|
-
if (require.cache[resolved])
|
|
226
|
-
delete require.cache[resolved];
|
|
227
|
-
return require(modulePath);
|
|
228
|
-
}
|
|
229
|
-
catch (e) {
|
|
230
|
-
return require(modulePath);
|
|
231
|
-
}
|
|
232
|
-
finally {
|
|
233
|
-
extensions.forEach(ext => {
|
|
234
|
-
if (originalHandlers[ext]) {
|
|
235
|
-
require.extensions[ext] = originalHandlers[ext];
|
|
236
|
-
}
|
|
237
|
-
else {
|
|
238
|
-
delete require.extensions[ext];
|
|
239
|
-
}
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
// --- Funções de Metadata e Scripts ---
|
|
244
|
-
function generateMetaTags(metadata) {
|
|
245
|
-
const tags = [];
|
|
246
|
-
tags.push(`<meta charset="${metadata.charset || 'UTF-8'}">`);
|
|
247
|
-
tags.push(`<meta name="viewport" content="${metadata.viewport || 'width=device-width, initial-scale=1.0'}">`);
|
|
248
|
-
if (metadata.description)
|
|
249
|
-
tags.push(`<meta name="description" content="${metadata.description}">`);
|
|
250
|
-
if (metadata.keywords) {
|
|
251
|
-
const keywordsStr = Array.isArray(metadata.keywords) ? metadata.keywords.join(', ') : metadata.keywords;
|
|
252
|
-
tags.push(`<meta name="keywords" content="${keywordsStr}">`);
|
|
253
|
-
}
|
|
254
|
-
if (metadata.author)
|
|
255
|
-
tags.push(`<meta name="author" content="${metadata.author}">`);
|
|
256
|
-
if (metadata.themeColor)
|
|
257
|
-
tags.push(`<meta name="theme-color" content="${metadata.themeColor}">`);
|
|
258
|
-
if (metadata.robots)
|
|
259
|
-
tags.push(`<meta name="robots" content="${metadata.robots}">`);
|
|
260
|
-
if (metadata.canonical)
|
|
261
|
-
tags.push(`<link rel="canonical" href="${metadata.canonical}">`);
|
|
262
|
-
if (metadata.favicon)
|
|
263
|
-
tags.push(`<link rel="icon" href="${metadata.favicon}">`);
|
|
264
|
-
// Apple & Manifest
|
|
265
|
-
if (metadata.appleTouchIcon)
|
|
266
|
-
tags.push(`<link rel="apple-touch-icon" href="${metadata.appleTouchIcon}">`);
|
|
267
|
-
if (metadata.manifest)
|
|
268
|
-
tags.push(`<link rel="manifest" href="${metadata.manifest}">`);
|
|
269
|
-
// Open Graph
|
|
270
|
-
if (metadata.openGraph) {
|
|
271
|
-
const og = metadata.openGraph;
|
|
272
|
-
if (og.title)
|
|
273
|
-
tags.push(`<meta property="og:title" content="${og.title}">`);
|
|
274
|
-
if (og.description)
|
|
275
|
-
tags.push(`<meta property="og:description" content="${og.description}">`);
|
|
276
|
-
if (og.type)
|
|
277
|
-
tags.push(`<meta property="og:type" content="${og.type}">`);
|
|
278
|
-
if (og.url)
|
|
279
|
-
tags.push(`<meta property="og:url" content="${og.url}">`);
|
|
280
|
-
if (og.siteName)
|
|
281
|
-
tags.push(`<meta property="og:site_name" content="${og.siteName}">`);
|
|
282
|
-
if (og.locale)
|
|
283
|
-
tags.push(`<meta property="og:locale" content="${og.locale}">`);
|
|
284
|
-
if (og.image) {
|
|
285
|
-
const imgUrl = typeof og.image === 'string' ? og.image : og.image.url;
|
|
286
|
-
tags.push(`<meta property="og:image" content="${imgUrl}">`);
|
|
287
|
-
if (typeof og.image !== 'string') {
|
|
288
|
-
if (og.image.width)
|
|
289
|
-
tags.push(`<meta property="og:image:width" content="${og.image.width}">`);
|
|
290
|
-
if (og.image.height)
|
|
291
|
-
tags.push(`<meta property="og:image:height" content="${og.image.height}">`);
|
|
292
|
-
if (og.image.alt)
|
|
293
|
-
tags.push(`<meta property="og:image:alt" content="${og.image.alt}">`);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
// Twitter Card
|
|
298
|
-
if (metadata.twitter) {
|
|
299
|
-
const tw = metadata.twitter;
|
|
300
|
-
if (tw.card)
|
|
301
|
-
tags.push(`<meta name="twitter:card" content="${tw.card}">`);
|
|
302
|
-
if (tw.site)
|
|
303
|
-
tags.push(`<meta name="twitter:site" content="${tw.site}">`);
|
|
304
|
-
if (tw.creator)
|
|
305
|
-
tags.push(`<meta name="twitter:creator" content="${tw.creator}">`);
|
|
306
|
-
if (tw.title)
|
|
307
|
-
tags.push(`<meta name="twitter:title" content="${tw.title}">`);
|
|
308
|
-
if (tw.description)
|
|
309
|
-
tags.push(`<meta name="twitter:description" content="${tw.description}">`);
|
|
310
|
-
if (tw.image)
|
|
311
|
-
tags.push(`<meta name="twitter:image" content="${tw.image}">`);
|
|
312
|
-
if (tw.imageAlt)
|
|
313
|
-
tags.push(`<meta name="twitter:image:alt" content="${tw.imageAlt}">`);
|
|
314
|
-
}
|
|
315
|
-
// Custom Meta Tags
|
|
316
|
-
if (metadata.other) {
|
|
317
|
-
for (const [key, value] of Object.entries(metadata.other)) {
|
|
318
|
-
tags.push(`<meta name="${key}" content="${value}">`);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
if (metadata.scripts) {
|
|
322
|
-
for (const [key, value] of Object.entries(metadata.scripts)) {
|
|
323
|
-
const rest = Object.entries(value).map((r) => {
|
|
324
|
-
return '' + r[0] + '="' + r[1] + '"';
|
|
325
|
-
});
|
|
326
|
-
tags.push(`<script src="${key}" ${rest.join(" ")}></script>`);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
return tags.join('\n');
|
|
330
|
-
}
|
|
331
|
-
function obfuscateData(data) {
|
|
332
|
-
const jsonStr = JSON.stringify(data);
|
|
333
|
-
const base64 = Buffer.from(jsonStr).toString('base64');
|
|
334
|
-
const hash = Buffer.from(Date.now().toString()).toString('base64').substring(0, 8);
|
|
335
|
-
return `${hash}.${base64}`;
|
|
336
|
-
}
|
|
337
|
-
function getBuildAssets(req) {
|
|
338
|
-
const projectDir = process.cwd();
|
|
339
|
-
const distDir = path_1.default.join(projectDir, '.vatts');
|
|
340
|
-
const assetsDir = path_1.default.join(distDir, 'assets');
|
|
341
|
-
const chunksDir = path_1.default.join(distDir, 'chunks');
|
|
342
|
-
if (!fs_1.default.existsSync(distDir))
|
|
343
|
-
return null;
|
|
344
|
-
let scripts = [];
|
|
345
|
-
let styles = [];
|
|
346
|
-
// Helper para processar arquivos de um diretório
|
|
347
|
-
const processDirectory = (directory, urlPrefix) => {
|
|
348
|
-
if (!fs_1.default.existsSync(directory))
|
|
349
|
-
return;
|
|
350
|
-
const files = fs_1.default.readdirSync(directory);
|
|
351
|
-
files.forEach(file => {
|
|
352
|
-
if (file.endsWith('.map'))
|
|
353
|
-
return; // Ignora sourcemaps
|
|
354
|
-
const fullPath = path_1.default.join(directory, file);
|
|
355
|
-
const stat = fs_1.default.statSync(fullPath);
|
|
356
|
-
if (stat.isFile()) {
|
|
357
|
-
// MODIFICADO: Aceita .js OU .js.bra
|
|
358
|
-
if (file.endsWith('.js') || file.endsWith('.js.br') || file.endsWith('.js.gz')) {
|
|
359
|
-
scripts.push(`${urlPrefix}/${file.replace(".br", '').replace(".gz", '')}`);
|
|
360
|
-
}
|
|
361
|
-
else if (file.endsWith('.css')) {
|
|
362
|
-
styles.push(`${urlPrefix}/${file}`);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
});
|
|
366
|
-
};
|
|
367
|
-
try {
|
|
368
|
-
// 1. Verificar Manifesto (se existir)
|
|
369
|
-
const manifestPath = path_1.default.join(distDir, 'manifest.json');
|
|
370
|
-
if (fs_1.default.existsSync(manifestPath)) {
|
|
371
|
-
const manifest = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf8'));
|
|
372
|
-
const manifestFiles = Object.values(manifest);
|
|
373
|
-
scripts = manifestFiles
|
|
374
|
-
// MODIFICADO: Filtra .js E .js.br no manifesto
|
|
375
|
-
.filter((f) => f.endsWith('.js') || f.endsWith('.js.br') || f.endsWith('.js.gz'))
|
|
376
|
-
.map((f) => `/_vatts/${f.replace(".br", "").replace(".gz", "")}`);
|
|
377
|
-
styles = manifestFiles
|
|
378
|
-
.filter((f) => f.endsWith('.css'))
|
|
379
|
-
.map((f) => `/_vatts/${f}`);
|
|
380
|
-
}
|
|
381
|
-
else {
|
|
382
|
-
// 2. Fallback: Scan manual de diretórios
|
|
383
|
-
// Scan na raiz .vatts/ (Geralmente entry points)
|
|
384
|
-
processDirectory(distDir, '/_vatts');
|
|
385
|
-
// Scan em .vatts/assets/ (Assets estáticos, chunks, CSS extraído)
|
|
386
|
-
processDirectory(assetsDir, '/_vatts/assets');
|
|
387
|
-
processDirectory(chunksDir, '/_vatts/chunks');
|
|
388
|
-
// Ordenação básica para garantir que o main carregue
|
|
389
|
-
scripts.sort((a, b) => {
|
|
390
|
-
if (a.includes('main'))
|
|
391
|
-
return -1;
|
|
392
|
-
if (b.includes('main'))
|
|
393
|
-
return 1;
|
|
394
|
-
return a.localeCompare(b);
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
if (scripts.length === 0)
|
|
398
|
-
return null;
|
|
399
|
-
return { scripts, styles };
|
|
400
|
-
}
|
|
401
|
-
catch (e) {
|
|
402
|
-
console.error("Error loading assets:", e);
|
|
403
|
-
return null;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
97
|
function ServerRoot({ lang, title, metaTagsHtml, stylesHtml, initialDataScript, hotReloadScript, dataScript, children }) {
|
|
407
98
|
// Concatena tudo que vai no head em uma única string
|
|
408
99
|
const headContent = `
|
|
@@ -422,7 +113,7 @@ function ServerRoot({ lang, title, metaTagsHtml, stylesHtml, initialDataScript,
|
|
|
422
113
|
async function render({ req, res, route, params, allRoutes }) {
|
|
423
114
|
// ATENÇÃO: Polyfill executado aqui para garantir que window/document existam
|
|
424
115
|
// antes de qualquer lógica de componente ser executada.
|
|
425
|
-
polyfillBrowserEnv();
|
|
116
|
+
(0, common_1.polyfillBrowserEnv)();
|
|
426
117
|
const { generateMetadata } = route;
|
|
427
118
|
const isProduction = !req.hwebDev;
|
|
428
119
|
const hotReloadManager = req.hotReloadManager;
|
|
@@ -432,7 +123,7 @@ async function render({ req, res, route, params, allRoutes }) {
|
|
|
432
123
|
let layoutInfo = null;
|
|
433
124
|
try {
|
|
434
125
|
// 1. Verificar Build - Se não tiver scripts, retorna tela de Loading
|
|
435
|
-
assets = getBuildAssets(
|
|
126
|
+
assets = (0, common_1.getBuildAssets)();
|
|
436
127
|
if (!assets || assets.scripts.length === 0) {
|
|
437
128
|
const { pipe } = (0, server_1.renderToPipeableStream)(react_1.default.createElement(BuildingPage_1.default, null), {
|
|
438
129
|
onShellReady() {
|
|
@@ -448,7 +139,7 @@ async function render({ req, res, route, params, allRoutes }) {
|
|
|
448
139
|
if (layoutInfo) {
|
|
449
140
|
try {
|
|
450
141
|
// Recarrega o componente de layout para ter acesso à função (o router só guarda metadata)
|
|
451
|
-
const layoutModule = requireWithoutStyles(path_1.default.resolve(process.cwd(), layoutInfo.componentPath));
|
|
142
|
+
const layoutModule = (0, common_1.requireWithoutStyles)(path_1.default.resolve(process.cwd(), layoutInfo.componentPath));
|
|
452
143
|
LayoutComponent = layoutModule.default;
|
|
453
144
|
}
|
|
454
145
|
catch (e) {
|
|
@@ -484,9 +175,9 @@ async function render({ req, res, route, params, allRoutes }) {
|
|
|
484
175
|
initialParams: params,
|
|
485
176
|
};
|
|
486
177
|
// Scripts e Estilos
|
|
487
|
-
const obfuscatedData = obfuscateData(initialData);
|
|
178
|
+
const obfuscatedData = (0, common_1.obfuscateData)(initialData);
|
|
488
179
|
const hotReloadScript = !isProduction && hotReloadManager ? hotReloadManager.getClientScript() : '';
|
|
489
|
-
const metaTagsHtml = generateMetaTags(metadata);
|
|
180
|
+
const metaTagsHtml = (0, common_1.generateMetaTags)(metadata);
|
|
490
181
|
const htmlLang = metadata.language || 'pt-BR';
|
|
491
182
|
// Gera tags de estilo para o head
|
|
492
183
|
const stylesHtml = assets.styles.map(styleUrl => `<link rel="stylesheet" href="${styleUrl}">`).join('\n');
|
|
@@ -559,7 +250,7 @@ async function render({ req, res, route, params, allRoutes }) {
|
|
|
559
250
|
title: metadata.title || 'Vatts.js',
|
|
560
251
|
metaTagsHtml: (() => {
|
|
561
252
|
try {
|
|
562
|
-
return generateMetaTags(metadata);
|
|
253
|
+
return (0, common_1.generateMetaTags)(metadata);
|
|
563
254
|
}
|
|
564
255
|
catch {
|
|
565
256
|
return '';
|
|
@@ -569,7 +260,7 @@ async function render({ req, res, route, params, allRoutes }) {
|
|
|
569
260
|
hotReloadScript: !isProduction && hotReloadManager ? hotReloadManager.getClientScript() : '',
|
|
570
261
|
obfuscatedData: (() => {
|
|
571
262
|
try {
|
|
572
|
-
return obfuscateData({
|
|
263
|
+
return (0, common_1.obfuscateData)({
|
|
573
264
|
routes: [],
|
|
574
265
|
initialComponentPath: route.componentPath,
|
|
575
266
|
initialParams: params,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { GenericRequest } from '../types/framework';
|
|
2
|
+
import { Metadata } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Common utility functions shared between React and Vue renderers
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Removes all script tags from HTML string
|
|
8
|
+
*/
|
|
9
|
+
export declare function stripScriptTags(html: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Extracts the request URL from generic request object
|
|
12
|
+
*/
|
|
13
|
+
export declare function getRequestUrl(req: GenericRequest): string | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Converts an unknown error to an Error instance
|
|
16
|
+
*/
|
|
17
|
+
export declare function toError(err: unknown): Error;
|
|
18
|
+
/**
|
|
19
|
+
* Creates a fake browser environment for server-side rendering
|
|
20
|
+
* This prevents client-side libraries from breaking when used in Node.js
|
|
21
|
+
*/
|
|
22
|
+
export declare function createBrowserEnvironmentPolyfill(): any;
|
|
23
|
+
/**
|
|
24
|
+
* Sets up the global browser environment polyfill for SSR
|
|
25
|
+
* Safely handles Node.js read-only properties
|
|
26
|
+
*/
|
|
27
|
+
export declare function polyfillBrowserEnv(): void;
|
|
28
|
+
/**
|
|
29
|
+
* Imports a module while ignoring CSS and other style imports
|
|
30
|
+
* Prevents style processing errors during SSR
|
|
31
|
+
*/
|
|
32
|
+
export declare function requireWithoutStyles<T>(modulePath: string): T;
|
|
33
|
+
/**
|
|
34
|
+
* Obfuscates data for client-side hydration
|
|
35
|
+
* Uses base64 encoding with a timestamp hash
|
|
36
|
+
*/
|
|
37
|
+
export declare function obfuscateData(data: any): string;
|
|
38
|
+
/**
|
|
39
|
+
* Generates HTML meta tags from metadata object
|
|
40
|
+
*/
|
|
41
|
+
export declare function generateMetaTags(metadata: Metadata): string;
|
|
42
|
+
/**
|
|
43
|
+
* Interface for build assets (scripts and styles)
|
|
44
|
+
*/
|
|
45
|
+
export interface BuildAssets {
|
|
46
|
+
scripts: string[];
|
|
47
|
+
styles: string[];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Retrieves compiled assets from .vatts build directory
|
|
51
|
+
*/
|
|
52
|
+
export declare function getBuildAssets(): BuildAssets | null;
|
|
53
|
+
/**
|
|
54
|
+
* Analyzes component source code to extract static asset imports
|
|
55
|
+
* and generate preload links for injection into the head
|
|
56
|
+
*/
|
|
57
|
+
export declare function extractComponentPreloads(componentPath: string): string[];
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* This file is part of the Vatts.js Project.
|
|
4
|
+
* Copyright (c) 2026 mfraz
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/
|
|
18
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
19
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
20
|
+
};
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.stripScriptTags = stripScriptTags;
|
|
23
|
+
exports.getRequestUrl = getRequestUrl;
|
|
24
|
+
exports.toError = toError;
|
|
25
|
+
exports.createBrowserEnvironmentPolyfill = createBrowserEnvironmentPolyfill;
|
|
26
|
+
exports.polyfillBrowserEnv = polyfillBrowserEnv;
|
|
27
|
+
exports.requireWithoutStyles = requireWithoutStyles;
|
|
28
|
+
exports.obfuscateData = obfuscateData;
|
|
29
|
+
exports.generateMetaTags = generateMetaTags;
|
|
30
|
+
exports.getBuildAssets = getBuildAssets;
|
|
31
|
+
exports.extractComponentPreloads = extractComponentPreloads;
|
|
32
|
+
const fs_1 = __importDefault(require("fs"));
|
|
33
|
+
const path_1 = __importDefault(require("path"));
|
|
34
|
+
/**
|
|
35
|
+
* Common utility functions shared between React and Vue renderers
|
|
36
|
+
*/
|
|
37
|
+
// --- String/HTML Utilities ---
|
|
38
|
+
/**
|
|
39
|
+
* Removes all script tags from HTML string
|
|
40
|
+
*/
|
|
41
|
+
function stripScriptTags(html) {
|
|
42
|
+
if (!html)
|
|
43
|
+
return '';
|
|
44
|
+
return html.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, '');
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Extracts the request URL from generic request object
|
|
48
|
+
*/
|
|
49
|
+
function getRequestUrl(req) {
|
|
50
|
+
return req?.originalUrl || req?.url;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Converts an unknown error to an Error instance
|
|
54
|
+
*/
|
|
55
|
+
function toError(err) {
|
|
56
|
+
if (err instanceof Error)
|
|
57
|
+
return err;
|
|
58
|
+
if (typeof err === 'string')
|
|
59
|
+
return new Error(err);
|
|
60
|
+
try {
|
|
61
|
+
return new Error(JSON.stringify(err));
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return new Error(String(err));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// --- Browser Environment Polyfills ---
|
|
68
|
+
/**
|
|
69
|
+
* Creates a fake browser environment for server-side rendering
|
|
70
|
+
* This prevents client-side libraries from breaking when used in Node.js
|
|
71
|
+
*/
|
|
72
|
+
function createBrowserEnvironmentPolyfill() {
|
|
73
|
+
return {
|
|
74
|
+
document: {
|
|
75
|
+
createElement: () => ({ style: {}, setAttribute: () => { }, classList: { add: () => { }, remove: () => { } } }),
|
|
76
|
+
getElementById: () => null,
|
|
77
|
+
getElementsByTagName: () => [],
|
|
78
|
+
querySelector: () => null,
|
|
79
|
+
querySelectorAll: () => [],
|
|
80
|
+
head: {},
|
|
81
|
+
body: { style: {} },
|
|
82
|
+
addEventListener: () => { },
|
|
83
|
+
removeEventListener: () => { },
|
|
84
|
+
cookie: '',
|
|
85
|
+
location: { href: '', origin: '' },
|
|
86
|
+
scrollTo: () => { },
|
|
87
|
+
},
|
|
88
|
+
navigator: {
|
|
89
|
+
userAgent: 'Node.js/VattsSSR',
|
|
90
|
+
},
|
|
91
|
+
location: {
|
|
92
|
+
href: 'http://localhost',
|
|
93
|
+
origin: 'http://localhost',
|
|
94
|
+
pathname: '/',
|
|
95
|
+
search: '',
|
|
96
|
+
hash: '',
|
|
97
|
+
assign: () => { },
|
|
98
|
+
replace: () => { },
|
|
99
|
+
reload: () => { },
|
|
100
|
+
},
|
|
101
|
+
history: {
|
|
102
|
+
pushState: () => { },
|
|
103
|
+
replaceState: () => { },
|
|
104
|
+
},
|
|
105
|
+
screen: { width: 1920, height: 1080 },
|
|
106
|
+
addEventListener: () => { },
|
|
107
|
+
removeEventListener: () => { },
|
|
108
|
+
matchMedia: () => ({ matches: false, addListener: () => { }, removeListener: () => { } }),
|
|
109
|
+
requestAnimationFrame: (cb) => setTimeout(cb, 0),
|
|
110
|
+
cancelAnimationFrame: (id) => clearTimeout(id),
|
|
111
|
+
setTimeout: setTimeout,
|
|
112
|
+
clearTimeout: clearTimeout,
|
|
113
|
+
setInterval: setInterval,
|
|
114
|
+
clearInterval: clearInterval,
|
|
115
|
+
localStorage: {
|
|
116
|
+
getItem: () => null,
|
|
117
|
+
setItem: () => { },
|
|
118
|
+
removeItem: () => { },
|
|
119
|
+
clear: () => { },
|
|
120
|
+
},
|
|
121
|
+
sessionStorage: {
|
|
122
|
+
getItem: () => null,
|
|
123
|
+
setItem: () => { },
|
|
124
|
+
removeItem: () => { },
|
|
125
|
+
clear: () => { },
|
|
126
|
+
},
|
|
127
|
+
console: {
|
|
128
|
+
log: () => { },
|
|
129
|
+
warn: () => { },
|
|
130
|
+
error: () => { },
|
|
131
|
+
info: () => { },
|
|
132
|
+
debug: () => { },
|
|
133
|
+
trace: () => { },
|
|
134
|
+
dir: () => { },
|
|
135
|
+
},
|
|
136
|
+
Image: class {
|
|
137
|
+
constructor() { }
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Sets up the global browser environment polyfill for SSR
|
|
143
|
+
* Safely handles Node.js read-only properties
|
|
144
|
+
*/
|
|
145
|
+
function polyfillBrowserEnv() {
|
|
146
|
+
if (typeof window === 'undefined') {
|
|
147
|
+
const win = createBrowserEnvironmentPolyfill();
|
|
148
|
+
const globalAny = global;
|
|
149
|
+
// Helper to safely set globals
|
|
150
|
+
// Node 21+ has read-only globals like 'navigator', 'performance' etc
|
|
151
|
+
const setGlobal = (key, value) => {
|
|
152
|
+
try {
|
|
153
|
+
if (typeof globalAny[key] === 'undefined') {
|
|
154
|
+
globalAny[key] = value;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch (e) {
|
|
158
|
+
// If it fails (read-only property), silently ignore
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
setGlobal('window', win);
|
|
162
|
+
setGlobal('document', win.document);
|
|
163
|
+
setGlobal('navigator', win.navigator);
|
|
164
|
+
setGlobal('location', win.location);
|
|
165
|
+
setGlobal('localStorage', win.localStorage);
|
|
166
|
+
setGlobal('sessionStorage', win.sessionStorage);
|
|
167
|
+
setGlobal('requestAnimationFrame', win.requestAnimationFrame);
|
|
168
|
+
setGlobal('cancelAnimationFrame', win.cancelAnimationFrame);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// --- Module Loading Utilities ---
|
|
172
|
+
/**
|
|
173
|
+
* Imports a module while ignoring CSS and other style imports
|
|
174
|
+
* Prevents style processing errors during SSR
|
|
175
|
+
*/
|
|
176
|
+
function requireWithoutStyles(modulePath) {
|
|
177
|
+
const extensions = ['.css', '.scss', '.sass', '.less', '.png', '.jpg', '.jpeg', '.gif', '.svg'];
|
|
178
|
+
const originalHandlers = {};
|
|
179
|
+
extensions.forEach(ext => {
|
|
180
|
+
originalHandlers[ext] = require.extensions[ext];
|
|
181
|
+
require.extensions[ext] = (m, filename) => {
|
|
182
|
+
m.exports = {};
|
|
183
|
+
};
|
|
184
|
+
});
|
|
185
|
+
try {
|
|
186
|
+
const resolved = require.resolve(modulePath);
|
|
187
|
+
if (require.cache[resolved])
|
|
188
|
+
delete require.cache[resolved];
|
|
189
|
+
return require(modulePath);
|
|
190
|
+
}
|
|
191
|
+
catch (e) {
|
|
192
|
+
return require(modulePath);
|
|
193
|
+
}
|
|
194
|
+
finally {
|
|
195
|
+
extensions.forEach(ext => {
|
|
196
|
+
if (originalHandlers[ext]) {
|
|
197
|
+
require.extensions[ext] = originalHandlers[ext];
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
delete require.extensions[ext];
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// --- Data Obfuscation ---
|
|
206
|
+
/**
|
|
207
|
+
* Obfuscates data for client-side hydration
|
|
208
|
+
* Uses base64 encoding with a timestamp hash
|
|
209
|
+
*/
|
|
210
|
+
function obfuscateData(data) {
|
|
211
|
+
const jsonStr = JSON.stringify(data);
|
|
212
|
+
const base64 = Buffer.from(jsonStr).toString('base64');
|
|
213
|
+
const hash = Buffer.from(Date.now().toString()).toString('base64').substring(0, 8);
|
|
214
|
+
return `${hash}.${base64}`;
|
|
215
|
+
}
|
|
216
|
+
// --- Metadata Generation ---
|
|
217
|
+
/**
|
|
218
|
+
* Generates HTML meta tags from metadata object
|
|
219
|
+
*/
|
|
220
|
+
function generateMetaTags(metadata) {
|
|
221
|
+
const tags = [];
|
|
222
|
+
tags.push(`<meta charset="${metadata.charset || 'UTF-8'}">`);
|
|
223
|
+
tags.push(`<meta name="viewport" content="${metadata.viewport || 'width=device-width, initial-scale=1.0'}">`);
|
|
224
|
+
if (metadata.description)
|
|
225
|
+
tags.push(`<meta name="description" content="${metadata.description}">`);
|
|
226
|
+
if (metadata.keywords) {
|
|
227
|
+
const keywordsStr = Array.isArray(metadata.keywords) ? metadata.keywords.join(', ') : metadata.keywords;
|
|
228
|
+
tags.push(`<meta name="keywords" content="${keywordsStr}">`);
|
|
229
|
+
}
|
|
230
|
+
if (metadata.author)
|
|
231
|
+
tags.push(`<meta name="author" content="${metadata.author}">`);
|
|
232
|
+
if (metadata.themeColor)
|
|
233
|
+
tags.push(`<meta name="theme-color" content="${metadata.themeColor}">`);
|
|
234
|
+
if (metadata.robots)
|
|
235
|
+
tags.push(`<meta name="robots" content="${metadata.robots}">`);
|
|
236
|
+
if (metadata.canonical)
|
|
237
|
+
tags.push(`<link rel="canonical" href="${metadata.canonical}">`);
|
|
238
|
+
if (metadata.favicon)
|
|
239
|
+
tags.push(`<link rel="icon" href="${metadata.favicon}">`);
|
|
240
|
+
// Apple & Manifest
|
|
241
|
+
if (metadata.appleTouchIcon)
|
|
242
|
+
tags.push(`<link rel="apple-touch-icon" href="${metadata.appleTouchIcon}">`);
|
|
243
|
+
if (metadata.manifest)
|
|
244
|
+
tags.push(`<link rel="manifest" href="${metadata.manifest}">`);
|
|
245
|
+
// Open Graph
|
|
246
|
+
if (metadata.openGraph) {
|
|
247
|
+
const og = metadata.openGraph;
|
|
248
|
+
if (og.title)
|
|
249
|
+
tags.push(`<meta property="og:title" content="${og.title}">`);
|
|
250
|
+
if (og.description)
|
|
251
|
+
tags.push(`<meta property="og:description" content="${og.description}">`);
|
|
252
|
+
if (og.type)
|
|
253
|
+
tags.push(`<meta property="og:type" content="${og.type}">`);
|
|
254
|
+
if (og.url)
|
|
255
|
+
tags.push(`<meta property="og:url" content="${og.url}">`);
|
|
256
|
+
if (og.siteName)
|
|
257
|
+
tags.push(`<meta property="og:site_name" content="${og.siteName}">`);
|
|
258
|
+
if (og.locale)
|
|
259
|
+
tags.push(`<meta property="og:locale" content="${og.locale}">`);
|
|
260
|
+
if (og.image) {
|
|
261
|
+
const imgUrl = typeof og.image === 'string' ? og.image : og.image.url;
|
|
262
|
+
tags.push(`<meta property="og:image" content="${imgUrl}">`);
|
|
263
|
+
if (typeof og.image !== 'string') {
|
|
264
|
+
if (og.image.width)
|
|
265
|
+
tags.push(`<meta property="og:image:width" content="${og.image.width}">`);
|
|
266
|
+
if (og.image.height)
|
|
267
|
+
tags.push(`<meta property="og:image:height" content="${og.image.height}">`);
|
|
268
|
+
if (og.image.alt)
|
|
269
|
+
tags.push(`<meta property="og:image:alt" content="${og.image.alt}">`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
// Twitter Card
|
|
274
|
+
if (metadata.twitter) {
|
|
275
|
+
const tw = metadata.twitter;
|
|
276
|
+
if (tw.card)
|
|
277
|
+
tags.push(`<meta name="twitter:card" content="${tw.card}">`);
|
|
278
|
+
if (tw.site)
|
|
279
|
+
tags.push(`<meta name="twitter:site" content="${tw.site}">`);
|
|
280
|
+
if (tw.creator)
|
|
281
|
+
tags.push(`<meta name="twitter:creator" content="${tw.creator}">`);
|
|
282
|
+
if (tw.title)
|
|
283
|
+
tags.push(`<meta name="twitter:title" content="${tw.title}">`);
|
|
284
|
+
if (tw.description)
|
|
285
|
+
tags.push(`<meta name="twitter:description" content="${tw.description}">`);
|
|
286
|
+
if (tw.image)
|
|
287
|
+
tags.push(`<meta name="twitter:image" content="${tw.image}">`);
|
|
288
|
+
if (tw.imageAlt)
|
|
289
|
+
tags.push(`<meta name="twitter:image:alt" content="${tw.imageAlt}">`);
|
|
290
|
+
}
|
|
291
|
+
// Custom Meta Tags
|
|
292
|
+
if (metadata.other) {
|
|
293
|
+
for (const [key, value] of Object.entries(metadata.other)) {
|
|
294
|
+
tags.push(`<meta name="${key}" content="${value}">`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (metadata.scripts) {
|
|
298
|
+
for (const [key, value] of Object.entries(metadata.scripts)) {
|
|
299
|
+
const rest = Object.entries(value).map((r) => {
|
|
300
|
+
return '' + r[0] + '="' + r[1] + '"';
|
|
301
|
+
});
|
|
302
|
+
tags.push(`<script src="${key}" ${rest.join(" ")}></script>`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return tags.join('\n');
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Retrieves compiled assets from .vatts build directory
|
|
309
|
+
*/
|
|
310
|
+
function getBuildAssets() {
|
|
311
|
+
const projectDir = process.cwd();
|
|
312
|
+
const distDir = path_1.default.join(projectDir, '.vatts');
|
|
313
|
+
const assetsDir = path_1.default.join(distDir, 'assets');
|
|
314
|
+
const chunksDir = path_1.default.join(distDir, 'chunks');
|
|
315
|
+
if (!fs_1.default.existsSync(distDir))
|
|
316
|
+
return null;
|
|
317
|
+
const scripts = [];
|
|
318
|
+
const styles = [];
|
|
319
|
+
// Helper to process directories
|
|
320
|
+
const processDirectory = (directory, urlPrefix) => {
|
|
321
|
+
if (!fs_1.default.existsSync(directory))
|
|
322
|
+
return;
|
|
323
|
+
const files = fs_1.default.readdirSync(directory);
|
|
324
|
+
files.forEach(file => {
|
|
325
|
+
if (file.endsWith('.map'))
|
|
326
|
+
return; // Skip sourcemaps
|
|
327
|
+
const url = `${urlPrefix}/${file.replace(".br", '').replace(".gz", '')}`;
|
|
328
|
+
// Support .js, .js.br, .js.gz
|
|
329
|
+
if (file.endsWith('.js') || file.endsWith('.js.br') || file.endsWith('.js.gz')) {
|
|
330
|
+
scripts.push(url);
|
|
331
|
+
}
|
|
332
|
+
else if (file.endsWith('.css')) {
|
|
333
|
+
styles.push(url);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
};
|
|
337
|
+
// Read assets from .vatts/
|
|
338
|
+
processDirectory(distDir, '/_vatts');
|
|
339
|
+
// Read assets from .vatts/assets
|
|
340
|
+
processDirectory(assetsDir, '/_vatts/assets');
|
|
341
|
+
// Read chunks from .vatts/chunks
|
|
342
|
+
processDirectory(chunksDir, '/_vatts/chunks');
|
|
343
|
+
return { scripts, styles };
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Analyzes component source code to extract static asset imports
|
|
347
|
+
* and generate preload links for injection into the head
|
|
348
|
+
*/
|
|
349
|
+
function extractComponentPreloads(componentPath) {
|
|
350
|
+
if (!componentPath || !fs_1.default.existsSync(componentPath))
|
|
351
|
+
return [];
|
|
352
|
+
const assetsDir = path_1.default.join(process.cwd(), '.vatts', 'assets');
|
|
353
|
+
let availableAssets = [];
|
|
354
|
+
try {
|
|
355
|
+
if (fs_1.default.existsSync(assetsDir)) {
|
|
356
|
+
availableAssets = fs_1.default.readdirSync(assetsDir);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
catch (e) {
|
|
360
|
+
// Silently fail if assets dir not found
|
|
361
|
+
}
|
|
362
|
+
const findHashedAsset = (filename) => {
|
|
363
|
+
if (availableAssets.includes(filename))
|
|
364
|
+
return filename;
|
|
365
|
+
const ext = path_1.default.extname(filename);
|
|
366
|
+
const base = path_1.default.basename(filename, ext);
|
|
367
|
+
const match = availableAssets.find(asset => asset.endsWith(ext) && asset.includes(base));
|
|
368
|
+
return match || null;
|
|
369
|
+
};
|
|
370
|
+
try {
|
|
371
|
+
const content = fs_1.default.readFileSync(componentPath, 'utf8');
|
|
372
|
+
const tags = new Set();
|
|
373
|
+
const processPath = (fullPath) => {
|
|
374
|
+
const filename = path_1.default.basename(fullPath);
|
|
375
|
+
const realFilename = findHashedAsset(filename);
|
|
376
|
+
if (!realFilename) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const ext = path_1.default.extname(realFilename).toLowerCase();
|
|
380
|
+
const publicUrl = `/_vatts/assets/${realFilename}`;
|
|
381
|
+
if (['.mp4', '.webm'].includes(ext)) {
|
|
382
|
+
tags.add(`<link rel="preload" as="video" href="${publicUrl}">`);
|
|
383
|
+
}
|
|
384
|
+
else if (['.css'].includes(ext)) {
|
|
385
|
+
tags.add(`<link rel="preload" as="style" href="${publicUrl}">`);
|
|
386
|
+
tags.add(`<link rel="stylesheet" href="${publicUrl}">`);
|
|
387
|
+
}
|
|
388
|
+
else if (['.js', '.js.br', '.js.gz'].includes(ext)) {
|
|
389
|
+
tags.add(`<link rel="preload" as="script" href="${publicUrl.replace(".br", '').replace(".gz", '')}">`);
|
|
390
|
+
}
|
|
391
|
+
else if (['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.avif'].includes(ext)) {
|
|
392
|
+
tags.add(`<link rel="preload" as="image" href="${publicUrl}">`);
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
const importRegex = /(?:import(?:\s+[^;'"]+\s+from)?\s+|require\(\s*)['"]([^'"]+\.(png|jpg|jpeg|gif|svg|webp|avif|mp4|webm|css|js))['"]/g;
|
|
396
|
+
let match;
|
|
397
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
398
|
+
processPath(match[1]);
|
|
399
|
+
}
|
|
400
|
+
const imgTagRegex = /<img\s+[^>]*src=['"]([^'"]+\.(png|jpg|jpeg|gif|svg|webp|avif))['"]/g;
|
|
401
|
+
while ((match = imgTagRegex.exec(content)) !== null) {
|
|
402
|
+
const src = match[1];
|
|
403
|
+
processPath(src);
|
|
404
|
+
}
|
|
405
|
+
return Array.from(tags);
|
|
406
|
+
}
|
|
407
|
+
catch (e) {
|
|
408
|
+
console.warn(`Failed to extract preloads for ${componentPath}:`, e);
|
|
409
|
+
return [];
|
|
410
|
+
}
|
|
411
|
+
}
|
package/dist/vue/renderer.vue.js
CHANGED
|
@@ -40,99 +40,11 @@ exports.renderVue = renderVue;
|
|
|
40
40
|
const router_1 = require("../router");
|
|
41
41
|
const fs_1 = __importDefault(require("fs"));
|
|
42
42
|
const path_1 = __importDefault(require("path"));
|
|
43
|
+
const common_1 = require("../renderers/common");
|
|
43
44
|
const vue = __importStar(require("vue"));
|
|
44
45
|
const vueServerRenderer = __importStar(require("@vue/server-renderer"));
|
|
45
46
|
const BuildingPage_vue_1 = __importDefault(require("./BuildingPage.vue"));
|
|
46
47
|
const server_error_vue_1 = __importDefault(require("./server-error.vue"));
|
|
47
|
-
function getRequestUrl(req) {
|
|
48
|
-
return req?.originalUrl || req?.url;
|
|
49
|
-
}
|
|
50
|
-
// --- Polyfill para Browser Env no Server ---
|
|
51
|
-
// Cria objetos globais falsos para evitar que bibliotecas client-side quebrem no SSR
|
|
52
|
-
// CORREÇÃO: Usa setGlobal para evitar erros em propriedades read-only do Node (ex: navigator)
|
|
53
|
-
function polyfillBrowserEnv() {
|
|
54
|
-
if (typeof window === 'undefined') {
|
|
55
|
-
const win = {
|
|
56
|
-
document: {
|
|
57
|
-
createElement: () => ({ style: {}, setAttribute: () => { }, classList: { add: () => { }, remove: () => { } } }),
|
|
58
|
-
getElementById: () => null,
|
|
59
|
-
getElementsByTagName: () => [],
|
|
60
|
-
querySelector: () => null,
|
|
61
|
-
querySelectorAll: () => [],
|
|
62
|
-
head: {},
|
|
63
|
-
body: { style: {} },
|
|
64
|
-
addEventListener: () => { },
|
|
65
|
-
removeEventListener: () => { },
|
|
66
|
-
cookie: '',
|
|
67
|
-
location: { href: '', origin: '' }
|
|
68
|
-
},
|
|
69
|
-
navigator: {
|
|
70
|
-
userAgent: 'Node.js/VattsSSR',
|
|
71
|
-
},
|
|
72
|
-
location: {
|
|
73
|
-
href: 'http://localhost',
|
|
74
|
-
origin: 'http://localhost',
|
|
75
|
-
pathname: '/',
|
|
76
|
-
search: '',
|
|
77
|
-
hash: '',
|
|
78
|
-
assign: () => { },
|
|
79
|
-
replace: () => { },
|
|
80
|
-
reload: () => { },
|
|
81
|
-
},
|
|
82
|
-
history: {
|
|
83
|
-
pushState: () => { },
|
|
84
|
-
replaceState: () => { },
|
|
85
|
-
},
|
|
86
|
-
screen: { width: 1920, height: 1080 },
|
|
87
|
-
addEventListener: () => { },
|
|
88
|
-
removeEventListener: () => { },
|
|
89
|
-
matchMedia: () => ({ matches: false, addListener: () => { }, removeListener: () => { } }),
|
|
90
|
-
requestAnimationFrame: (cb) => setTimeout(cb, 0),
|
|
91
|
-
cancelAnimationFrame: (id) => clearTimeout(id),
|
|
92
|
-
setTimeout: setTimeout,
|
|
93
|
-
clearTimeout: clearTimeout,
|
|
94
|
-
setInterval: setInterval,
|
|
95
|
-
clearInterval: clearInterval,
|
|
96
|
-
localStorage: {
|
|
97
|
-
getItem: () => null,
|
|
98
|
-
setItem: () => { },
|
|
99
|
-
removeItem: () => { },
|
|
100
|
-
clear: () => { },
|
|
101
|
-
},
|
|
102
|
-
sessionStorage: {
|
|
103
|
-
getItem: () => null,
|
|
104
|
-
setItem: () => { },
|
|
105
|
-
removeItem: () => { },
|
|
106
|
-
clear: () => { },
|
|
107
|
-
},
|
|
108
|
-
console: console,
|
|
109
|
-
Image: class {
|
|
110
|
-
constructor() { }
|
|
111
|
-
},
|
|
112
|
-
};
|
|
113
|
-
const globalAny = global;
|
|
114
|
-
// Helper para definir globais de forma segura
|
|
115
|
-
// Node 21+ tem globais 'navigator', 'performance' etc que são getter-only e quebram se tentar sobrescrever
|
|
116
|
-
const setGlobal = (key, value) => {
|
|
117
|
-
try {
|
|
118
|
-
if (typeof globalAny[key] === 'undefined') {
|
|
119
|
-
globalAny[key] = value;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
catch (e) {
|
|
123
|
-
// Se falhar (propriedade read-only), ignoramos silenciosamente
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
setGlobal('window', win);
|
|
127
|
-
setGlobal('document', win.document);
|
|
128
|
-
setGlobal('navigator', win.navigator);
|
|
129
|
-
setGlobal('location', win.location);
|
|
130
|
-
setGlobal('localStorage', win.localStorage);
|
|
131
|
-
setGlobal('sessionStorage', win.sessionStorage);
|
|
132
|
-
setGlobal('requestAnimationFrame', win.requestAnimationFrame);
|
|
133
|
-
setGlobal('cancelAnimationFrame', win.cancelAnimationFrame);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
48
|
function buildVueShellDocument(options) {
|
|
137
49
|
const { lang, title, metaTagsHtml, scriptPreloadsHtml, componentPreloadsHtml, stylesHtml, obfuscatedData, scriptsHtml, hotReloadScript, bodyInnerHtml, } = options;
|
|
138
50
|
return `<!DOCTYPE html>
|
|
@@ -153,135 +65,6 @@ function buildVueShellDocument(options) {
|
|
|
153
65
|
</body>
|
|
154
66
|
</html>`;
|
|
155
67
|
}
|
|
156
|
-
function stripScriptTags(html) {
|
|
157
|
-
if (!html)
|
|
158
|
-
return '';
|
|
159
|
-
return html.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, '');
|
|
160
|
-
}
|
|
161
|
-
// --- Helpers de Servidor (Duplicados para manter isolamento) ---
|
|
162
|
-
function requireWithoutStyles(modulePath) {
|
|
163
|
-
const extensions = ['.css', '.scss', '.sass', '.less', '.png', '.jpg', '.jpeg', '.gif', '.svg'];
|
|
164
|
-
const originalHandlers = {};
|
|
165
|
-
extensions.forEach(ext => {
|
|
166
|
-
originalHandlers[ext] = require.extensions[ext];
|
|
167
|
-
require.extensions[ext] = (m, filename) => {
|
|
168
|
-
m.exports = {};
|
|
169
|
-
};
|
|
170
|
-
});
|
|
171
|
-
try {
|
|
172
|
-
const resolved = require.resolve(modulePath);
|
|
173
|
-
if (require.cache[resolved])
|
|
174
|
-
delete require.cache[resolved];
|
|
175
|
-
return require(modulePath);
|
|
176
|
-
}
|
|
177
|
-
catch (e) {
|
|
178
|
-
return require(modulePath);
|
|
179
|
-
}
|
|
180
|
-
finally {
|
|
181
|
-
extensions.forEach(ext => {
|
|
182
|
-
if (originalHandlers[ext]) {
|
|
183
|
-
require.extensions[ext] = originalHandlers[ext];
|
|
184
|
-
}
|
|
185
|
-
else {
|
|
186
|
-
delete require.extensions[ext];
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
function obfuscateData(data) {
|
|
192
|
-
const jsonStr = JSON.stringify(data);
|
|
193
|
-
const base64 = Buffer.from(jsonStr).toString('base64');
|
|
194
|
-
const hash = Buffer.from(Date.now().toString()).toString('base64').substring(0, 8);
|
|
195
|
-
return `${hash}.${base64}`;
|
|
196
|
-
}
|
|
197
|
-
/**
|
|
198
|
-
* Analisa o código fonte do componente para encontrar imports estáticos de assets
|
|
199
|
-
* e gerar links de preload para injetar no head.
|
|
200
|
-
* * ATUALIZADO: Verifica se o arquivo existe em .vatts/assets (com hash) antes de gerar o link.
|
|
201
|
-
*/
|
|
202
|
-
function extractComponentPreloads(componentPath) {
|
|
203
|
-
if (!componentPath || !fs_1.default.existsSync(componentPath))
|
|
204
|
-
return [];
|
|
205
|
-
// Localização dos assets compilados para verificação de hash
|
|
206
|
-
const assetsDir = path_1.default.join(process.cwd(), '.vatts', 'assets');
|
|
207
|
-
let availableAssets = [];
|
|
208
|
-
try {
|
|
209
|
-
if (fs_1.default.existsSync(assetsDir)) {
|
|
210
|
-
availableAssets = fs_1.default.readdirSync(assetsDir);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
catch (e) {
|
|
214
|
-
// Silently fail if assets dir not found
|
|
215
|
-
}
|
|
216
|
-
// Função auxiliar para encontrar o arquivo real com hash
|
|
217
|
-
const findHashedAsset = (filename) => {
|
|
218
|
-
// 1. Se o arquivo existe exatamente como pedido
|
|
219
|
-
if (availableAssets.includes(filename))
|
|
220
|
-
return filename;
|
|
221
|
-
// 2. Procura por versão com hash (ex: style.css -> style.CjdCylXW.css ou da8dfae3-style.CjdCylXW.css)
|
|
222
|
-
const ext = path_1.default.extname(filename);
|
|
223
|
-
const base = path_1.default.basename(filename, ext); // "style"
|
|
224
|
-
// Filtra arquivos que terminam com a extensão correta e contêm o nome base
|
|
225
|
-
// Isso garante que pegamos o arquivo hasheado gerado pelo build
|
|
226
|
-
const match = availableAssets.find(asset => asset.endsWith(ext) && asset.includes(base));
|
|
227
|
-
return match || null;
|
|
228
|
-
};
|
|
229
|
-
try {
|
|
230
|
-
const content = fs_1.default.readFileSync(componentPath, 'utf8');
|
|
231
|
-
const tags = new Set();
|
|
232
|
-
const processPath = (fullPath) => {
|
|
233
|
-
const filename = path_1.default.basename(fullPath);
|
|
234
|
-
// VERIFICAÇÃO DE HASH:
|
|
235
|
-
// Tenta encontrar o nome real do arquivo na pasta de assets.
|
|
236
|
-
// Se não encontrar (retornar null), ignoramos o arquivo para não gerar link quebrado (404).
|
|
237
|
-
const realFilename = findHashedAsset(filename);
|
|
238
|
-
if (!realFilename) {
|
|
239
|
-
return; // Bloqueia a entrada se não tiver correspondente hash/físico
|
|
240
|
-
}
|
|
241
|
-
const ext = path_1.default.extname(realFilename).toLowerCase();
|
|
242
|
-
// Usa o nome real encontrado (com hash)
|
|
243
|
-
const publicUrl = `/_vatts/assets/${realFilename}`;
|
|
244
|
-
if (['.mp4', '.webm'].includes(ext)) {
|
|
245
|
-
tags.add(`<link rel="preload" as="video" href="${publicUrl}">`);
|
|
246
|
-
}
|
|
247
|
-
else if (['.css'].includes(ext)) {
|
|
248
|
-
tags.add(`<link rel="preload" as="style" href="${publicUrl}">`);
|
|
249
|
-
tags.add(`<link rel="stylesheet" href="${publicUrl}">`);
|
|
250
|
-
}
|
|
251
|
-
else if (['.js', '.js.br', '.js.gz'].includes(ext)) {
|
|
252
|
-
// Adicionado suporte para JS
|
|
253
|
-
tags.add(`<link rel="preload" as="script" href="${publicUrl.replace(".br", '').replace(".gz", '')}">`);
|
|
254
|
-
}
|
|
255
|
-
else if (['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.avif'].includes(ext)) {
|
|
256
|
-
tags.add(`<link rel="preload" as="image" href="${publicUrl}">`);
|
|
257
|
-
}
|
|
258
|
-
};
|
|
259
|
-
// 1. Imports (ESM) & Requires (CJS)
|
|
260
|
-
// Captura:
|
|
261
|
-
// - import foo from './foo.png' (Named import)
|
|
262
|
-
// - import './style.css' (Side-effect import)
|
|
263
|
-
// - require('./image.jpg') (CommonJS)
|
|
264
|
-
// Adicionado |js na lista de extensões
|
|
265
|
-
const importRegex = /(?:import(?:\s+[^;'"]+\s+from)?\s+|require\(\s*)['"]([^'"]+\.(png|jpg|jpeg|gif|svg|webp|avif|mp4|webm|css|js))['"]/g;
|
|
266
|
-
let match;
|
|
267
|
-
while ((match = importRegex.exec(content)) !== null) {
|
|
268
|
-
processPath(match[1]);
|
|
269
|
-
}
|
|
270
|
-
// 2. Imagens no Template (src="...")
|
|
271
|
-
// Captura caminhos relativos ou absolutos de imagens hardcoded no HTML do Vue
|
|
272
|
-
const imgTagRegex = /<img\s+[^>]*src=['"]([^'"]+\.(png|jpg|jpeg|gif|svg|webp|avif))['"]/g;
|
|
273
|
-
while ((match = imgTagRegex.exec(content)) !== null) {
|
|
274
|
-
const src = match[1];
|
|
275
|
-
// Para tags img src, também processamos para tentar achar a versão hash
|
|
276
|
-
processPath(src);
|
|
277
|
-
}
|
|
278
|
-
return Array.from(tags);
|
|
279
|
-
}
|
|
280
|
-
catch (e) {
|
|
281
|
-
console.warn(`Failed to extract preloads for ${componentPath}:`, e);
|
|
282
|
-
return [];
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
68
|
/**
|
|
286
69
|
* Garante que o componente Vue esteja carregado e compilado corretamente.
|
|
287
70
|
* Se o loader falhar em trazer o template (render function), compilamos manualmente aqui.
|
|
@@ -291,7 +74,7 @@ function ensureVueComponent(existingComponent, componentPath) {
|
|
|
291
74
|
// 1. Se não temos o componente objeto, tentamos carregar do disco
|
|
292
75
|
if (!component && componentPath) {
|
|
293
76
|
try {
|
|
294
|
-
const module = requireWithoutStyles(componentPath);
|
|
77
|
+
const module = (0, common_1.requireWithoutStyles)(componentPath);
|
|
295
78
|
component = module.default || module;
|
|
296
79
|
}
|
|
297
80
|
catch (e) {
|
|
@@ -374,150 +157,10 @@ function ensureVueComponent(existingComponent, componentPath) {
|
|
|
374
157
|
}
|
|
375
158
|
return component;
|
|
376
159
|
}
|
|
377
|
-
// --- Funções de Metadata ---
|
|
378
|
-
function generateMetaTags(metadata) {
|
|
379
|
-
const tags = [];
|
|
380
|
-
tags.push(`<meta charset="${metadata.charset || 'UTF-8'}">`);
|
|
381
|
-
tags.push(`<meta name="viewport" content="${metadata.viewport || 'width=device-width, initial-scale=1.0'}">`);
|
|
382
|
-
if (metadata.description)
|
|
383
|
-
tags.push(`<meta name="description" content="${metadata.description}">`);
|
|
384
|
-
if (metadata.keywords) {
|
|
385
|
-
const keywordsStr = Array.isArray(metadata.keywords) ? metadata.keywords.join(', ') : metadata.keywords;
|
|
386
|
-
tags.push(`<meta name="keywords" content="${keywordsStr}">`);
|
|
387
|
-
}
|
|
388
|
-
if (metadata.author)
|
|
389
|
-
tags.push(`<meta name="author" content="${metadata.author}">`);
|
|
390
|
-
if (metadata.themeColor)
|
|
391
|
-
tags.push(`<meta name="theme-color" content="${metadata.themeColor}">`);
|
|
392
|
-
if (metadata.robots)
|
|
393
|
-
tags.push(`<meta name="robots" content="${metadata.robots}">`);
|
|
394
|
-
if (metadata.canonical)
|
|
395
|
-
tags.push(`<link rel="canonical" href="${metadata.canonical}">`);
|
|
396
|
-
if (metadata.favicon)
|
|
397
|
-
tags.push(`<link rel="icon" href="${metadata.favicon}">`);
|
|
398
|
-
// Apple & Manifest
|
|
399
|
-
if (metadata.appleTouchIcon)
|
|
400
|
-
tags.push(`<link rel="apple-touch-icon" href="${metadata.appleTouchIcon}">`);
|
|
401
|
-
if (metadata.manifest)
|
|
402
|
-
tags.push(`<link rel="manifest" href="${metadata.manifest}">`);
|
|
403
|
-
// Open Graph
|
|
404
|
-
if (metadata.openGraph) {
|
|
405
|
-
const og = metadata.openGraph;
|
|
406
|
-
if (og.title)
|
|
407
|
-
tags.push(`<meta property="og:title" content="${og.title}">`);
|
|
408
|
-
if (og.description)
|
|
409
|
-
tags.push(`<meta property="og:description" content="${og.description}">`);
|
|
410
|
-
if (og.type)
|
|
411
|
-
tags.push(`<meta property="og:type" content="${og.type}">`);
|
|
412
|
-
if (og.url)
|
|
413
|
-
tags.push(`<meta property="og:url" content="${og.url}">`);
|
|
414
|
-
if (og.siteName)
|
|
415
|
-
tags.push(`<meta property="og:site_name" content="${og.siteName}">`);
|
|
416
|
-
if (og.locale)
|
|
417
|
-
tags.push(`<meta property="og:locale" content="${og.locale}">`);
|
|
418
|
-
if (og.image) {
|
|
419
|
-
const imgUrl = typeof og.image === 'string' ? og.image : og.image.url;
|
|
420
|
-
tags.push(`<meta property="og:image" content="${imgUrl}">`);
|
|
421
|
-
if (typeof og.image !== 'string') {
|
|
422
|
-
if (og.image.width)
|
|
423
|
-
tags.push(`<meta property="og:image:width" content="${og.image.width}">`);
|
|
424
|
-
if (og.image.height)
|
|
425
|
-
tags.push(`<meta property="og:image:height" content="${og.image.height}">`);
|
|
426
|
-
if (og.image.alt)
|
|
427
|
-
tags.push(`<meta property="og:image:alt" content="${og.image.alt}">`);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
// Twitter Card
|
|
432
|
-
if (metadata.twitter) {
|
|
433
|
-
const tw = metadata.twitter;
|
|
434
|
-
if (tw.card)
|
|
435
|
-
tags.push(`<meta name="twitter:card" content="${tw.card}">`);
|
|
436
|
-
if (tw.site)
|
|
437
|
-
tags.push(`<meta name="twitter:site" content="${tw.site}">`);
|
|
438
|
-
if (tw.creator)
|
|
439
|
-
tags.push(`<meta name="twitter:creator" content="${tw.creator}">`);
|
|
440
|
-
if (tw.title)
|
|
441
|
-
tags.push(`<meta name="twitter:title" content="${tw.title}">`);
|
|
442
|
-
if (tw.description)
|
|
443
|
-
tags.push(`<meta name="twitter:description" content="${tw.description}">`);
|
|
444
|
-
if (tw.image)
|
|
445
|
-
tags.push(`<meta name="twitter:image" content="${tw.image}">`);
|
|
446
|
-
if (tw.imageAlt)
|
|
447
|
-
tags.push(`<meta name="twitter:image:alt" content="${tw.imageAlt}">`);
|
|
448
|
-
}
|
|
449
|
-
// Custom Meta Tags
|
|
450
|
-
if (metadata.other) {
|
|
451
|
-
for (const [key, value] of Object.entries(metadata.other)) {
|
|
452
|
-
tags.push(`<meta name="${key}" content="${value}">`);
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
if (metadata.scripts) {
|
|
456
|
-
for (const [key, value] of Object.entries(metadata.scripts)) {
|
|
457
|
-
const rest = Object.entries(value).map((r) => {
|
|
458
|
-
return '' + r[0] + '="' + r[1] + '"';
|
|
459
|
-
});
|
|
460
|
-
tags.push(`<script src="${key}" ${rest.join(" ")}></script>`);
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
return tags.join('\n');
|
|
464
|
-
}
|
|
465
|
-
function getBuildAssets(req) {
|
|
466
|
-
const projectDir = process.cwd();
|
|
467
|
-
const distDir = path_1.default.join(projectDir, '.vatts');
|
|
468
|
-
const assetsDir = path_1.default.join(distDir, 'assets');
|
|
469
|
-
if (!fs_1.default.existsSync(distDir))
|
|
470
|
-
return null;
|
|
471
|
-
let scripts = [];
|
|
472
|
-
let styles = [];
|
|
473
|
-
const processDirectory = (directory, urlPrefix) => {
|
|
474
|
-
if (!fs_1.default.existsSync(directory))
|
|
475
|
-
return;
|
|
476
|
-
const files = fs_1.default.readdirSync(directory);
|
|
477
|
-
files.forEach(file => {
|
|
478
|
-
if (file.endsWith('.map'))
|
|
479
|
-
return;
|
|
480
|
-
const fullPath = path_1.default.join(directory, file);
|
|
481
|
-
if (fs_1.default.statSync(fullPath).isFile()) {
|
|
482
|
-
if (file.endsWith('.js') || file.endsWith(".js.br") || file.endsWith(".js.gz"))
|
|
483
|
-
scripts.push(`${urlPrefix}/${file.replace(".br", '').replace(".gz", '')}`);
|
|
484
|
-
else if (file.endsWith('.css'))
|
|
485
|
-
styles.push(`${urlPrefix}/${file}`);
|
|
486
|
-
}
|
|
487
|
-
});
|
|
488
|
-
};
|
|
489
|
-
try {
|
|
490
|
-
const manifestPath = path_1.default.join(distDir, 'manifest.json');
|
|
491
|
-
if (fs_1.default.existsSync(manifestPath)) {
|
|
492
|
-
const manifest = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf8'));
|
|
493
|
-
const manifestFiles = Object.values(manifest);
|
|
494
|
-
scripts = manifestFiles.filter((f) => f.endsWith('.js') || f.endsWith(".js.br")).map((f) => `/_vatts/${f.replace('.br', '')}`);
|
|
495
|
-
styles = manifestFiles.filter((f) => f.endsWith('.css')).map((f) => `/_vatts/${f}`);
|
|
496
|
-
}
|
|
497
|
-
else {
|
|
498
|
-
processDirectory(distDir, '/_vatts');
|
|
499
|
-
processDirectory(assetsDir, '/_vatts/assets');
|
|
500
|
-
scripts.sort((a, b) => {
|
|
501
|
-
if (a.includes('main'))
|
|
502
|
-
return -1;
|
|
503
|
-
if (b.includes('main'))
|
|
504
|
-
return 1;
|
|
505
|
-
return a.localeCompare(b);
|
|
506
|
-
});
|
|
507
|
-
}
|
|
508
|
-
if (scripts.length === 0)
|
|
509
|
-
return null;
|
|
510
|
-
return { scripts, styles };
|
|
511
|
-
}
|
|
512
|
-
catch (e) {
|
|
513
|
-
console.error("Error loading assets:", e);
|
|
514
|
-
return null;
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
160
|
async function renderVue({ req, res, route, params, allRoutes }) {
|
|
518
161
|
// ATENÇÃO: Polyfill executado aqui para garantir que window/document existam
|
|
519
162
|
// antes de qualquer lógica de componente ser executada.
|
|
520
|
-
polyfillBrowserEnv();
|
|
163
|
+
(0, common_1.polyfillBrowserEnv)();
|
|
521
164
|
if (!vue) {
|
|
522
165
|
res.statusCode = 500;
|
|
523
166
|
res.end('Vue dependencies not installed.');
|
|
@@ -549,14 +192,14 @@ async function renderVue({ req, res, route, params, allRoutes }) {
|
|
|
549
192
|
const hotReloadScript = includeScripts && !isProduction && hotReloadManager ? hotReloadManager.getClientScript() : '';
|
|
550
193
|
let metaTagsHtml = (() => {
|
|
551
194
|
try {
|
|
552
|
-
return generateMetaTags(metadata);
|
|
195
|
+
return (0, common_1.generateMetaTags)(metadata);
|
|
553
196
|
}
|
|
554
197
|
catch {
|
|
555
198
|
return '';
|
|
556
199
|
}
|
|
557
200
|
})();
|
|
558
201
|
if (!includeScripts) {
|
|
559
|
-
metaTagsHtml = stripScriptTags(metaTagsHtml);
|
|
202
|
+
metaTagsHtml = (0, common_1.stripScriptTags)(metaTagsHtml);
|
|
560
203
|
}
|
|
561
204
|
const htmlLang = metadata.language || 'pt-BR';
|
|
562
205
|
const title = metadata.title || 'Vatts.js';
|
|
@@ -566,7 +209,7 @@ async function renderVue({ req, res, route, params, allRoutes }) {
|
|
|
566
209
|
const componentPreloadsHtml = includeScripts
|
|
567
210
|
? (() => {
|
|
568
211
|
try {
|
|
569
|
-
const componentPreloads = extractComponentPreloads(route.componentPath ? path_1.default.resolve(process.cwd(), route.componentPath) : '');
|
|
212
|
+
const componentPreloads = (0, common_1.extractComponentPreloads)(route.componentPath ? path_1.default.resolve(process.cwd(), route.componentPath) : '');
|
|
570
213
|
return componentPreloads.join('\n');
|
|
571
214
|
}
|
|
572
215
|
catch {
|
|
@@ -580,7 +223,7 @@ async function renderVue({ req, res, route, params, allRoutes }) {
|
|
|
580
223
|
: '';
|
|
581
224
|
const obfuscatedData = (() => {
|
|
582
225
|
try {
|
|
583
|
-
return obfuscateData({
|
|
226
|
+
return (0, common_1.obfuscateData)({
|
|
584
227
|
routes: [],
|
|
585
228
|
initialComponentPath: route.componentPath,
|
|
586
229
|
initialParams: params,
|
|
@@ -615,7 +258,7 @@ async function renderVue({ req, res, route, params, allRoutes }) {
|
|
|
615
258
|
setup() {
|
|
616
259
|
return () => h(server_error_vue_1.default, {
|
|
617
260
|
error,
|
|
618
|
-
requestUrl: getRequestUrl(req),
|
|
261
|
+
requestUrl: (0, common_1.getRequestUrl)(req),
|
|
619
262
|
hint: 'SSR failed to render this route. See the error below.',
|
|
620
263
|
});
|
|
621
264
|
},
|
|
@@ -629,7 +272,7 @@ async function renderVue({ req, res, route, params, allRoutes }) {
|
|
|
629
272
|
}
|
|
630
273
|
};
|
|
631
274
|
try {
|
|
632
|
-
assets = getBuildAssets(
|
|
275
|
+
assets = (0, common_1.getBuildAssets)();
|
|
633
276
|
if (!assets || assets.scripts.length === 0) {
|
|
634
277
|
const RootComponent = {
|
|
635
278
|
setup() {
|
|
@@ -683,14 +326,14 @@ async function renderVue({ req, res, route, params, allRoutes }) {
|
|
|
683
326
|
initialComponentPath: route.componentPath,
|
|
684
327
|
initialParams: params,
|
|
685
328
|
};
|
|
686
|
-
const obfuscatedData = obfuscateData(initialData);
|
|
329
|
+
const obfuscatedData = (0, common_1.obfuscateData)(initialData);
|
|
687
330
|
const hotReloadScript = !isProduction && hotReloadManager ? hotReloadManager.getClientScript() : '';
|
|
688
|
-
const metaTagsHtml = generateMetaTags(metadata);
|
|
331
|
+
const metaTagsHtml = (0, common_1.generateMetaTags)(metadata);
|
|
689
332
|
const htmlLang = metadata.language || 'pt-BR';
|
|
690
333
|
// Otimização: Adiciona modulepreload para scripts principais do bundle
|
|
691
334
|
const scriptPreloadsHtml = assets.scripts.map(src => `<link rel="modulepreload" href="${src}">`).join('\n');
|
|
692
335
|
// Otimização: Intercepta assets do componente atual para preload
|
|
693
|
-
const componentPreloads = extractComponentPreloads(route.componentPath ? path_1.default.resolve(process.cwd(), route.componentPath) : '');
|
|
336
|
+
const componentPreloads = (0, common_1.extractComponentPreloads)(route.componentPath ? path_1.default.resolve(process.cwd(), route.componentPath) : '');
|
|
694
337
|
const componentPreloadsHtml = componentPreloads.join('\n');
|
|
695
338
|
const stylesHtml = assets.styles.map(styleUrl => `<link rel="stylesheet" href="${styleUrl}">`).join('\n');
|
|
696
339
|
const scriptsHtml = assets.scripts.map(src => `<script type="module" src="${src}"></script>`).join('\n');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vatts",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.3-canary.1.0.0",
|
|
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",
|