vatts 2.0.2 → 2.0.3-canary.1

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/vue/Link.vue CHANGED
@@ -1,39 +1,39 @@
1
- <!--
2
- This file is part of the Vatts.js Project.
3
- Copyright (c) 2026 mfraz
4
-
5
- Licensed under the Apache License, Version 2.0 (the "License");
6
- you may not use this file except in compliance with the License.
7
- You may obtain a copy of the License at
8
-
9
- http://www.apache.org/licenses/LICENSE-2.0
10
-
11
- Unless required by applicable law or agreed to in writing, software
12
- distributed under the License is distributed on an "AS IS" BASIS,
13
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- See the License for the specific language governing permissions and
15
- limitations under the License.
16
- -->
17
- <script setup>
18
- import { router } from '../client/clientRouter';
19
-
20
- const props = defineProps({
21
- href: {
22
- type: String,
23
- required: true
24
- }
25
- });
26
-
27
- const handleClick = async (e) => {
28
- e.preventDefault();
29
-
30
- // Usa o novo sistema de router
31
- await router.push(props.href);
32
- };
33
- </script>
34
-
35
- <template>
36
- <a :href="href" @click="handleClick">
37
- <slot></slot>
38
- </a>
1
+ <!--
2
+ This file is part of the Vatts.js Project.
3
+ Copyright (c) 2026 mfraz
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
16
+ -->
17
+ <script setup>
18
+ import { router } from '../client/clientRouter';
19
+
20
+ const props = defineProps({
21
+ href: {
22
+ type: String,
23
+ required: true
24
+ }
25
+ });
26
+
27
+ const handleClick = async (e) => {
28
+ e.preventDefault();
29
+
30
+ // Usa o novo sistema de router
31
+ await router.push(props.href);
32
+ };
33
+ </script>
34
+
35
+ <template>
36
+ <a :href="href" @click="handleClick">
37
+ <slot></slot>
38
+ </a>
39
39
  </template>
@@ -91,13 +91,13 @@ function initializeClient() {
91
91
  catch (error) {
92
92
  console.error('[Vatts] Critical Error rendering application:', error);
93
93
  if (typeof document !== 'undefined') {
94
- document.body.innerHTML = `
95
- <div style="font-family: monospace; padding: 20px; color: #ff4444; background: #1a1a1a; min-height: 100vh;">
96
- <h1>Vatts Client Error (Vue)</h1>
97
- <p>A critical error occurred while initializing the application.</p>
98
- <pre style="background: #000; padding: 15px; border-radius: 5px; overflow: auto;">${error?.message || error}</pre>
99
- <pre style="color: #666; font-size: 12px; margin-top: 10px;">${error?.stack || ''}</pre>
100
- </div>
94
+ document.body.innerHTML = `
95
+ <div style="font-family: monospace; padding: 20px; color: #ff4444; background: #1a1a1a; min-height: 100vh;">
96
+ <h1>Vatts Client Error (Vue)</h1>
97
+ <p>A critical error occurred while initializing the application.</p>
98
+ <pre style="background: #000; padding: 15px; border-radius: 5px; overflow: auto;">${error?.message || error}</pre>
99
+ <pre style="color: #666; font-size: 12px; margin-top: 10px;">${error?.stack || ''}</pre>
100
+ </div>
101
101
  `;
102
102
  }
103
103
  }
@@ -1,107 +1,107 @@
1
- <!--
2
- * This file is part of the Vatts.js Project.
3
- * Copyright (c) 2026 mfraz
4
- *
5
- * Licensed under the Apache License, Version 2.0 (the "License");
6
- * you may not use this file except in compliance with the License.
7
- * You may obtain a copy of the License at
8
- *
9
- * http://www.apache.org/licenses/LICENSE-2.0
10
- *
11
- * Unless required by applicable law or agreed to in writing, software
12
- * distributed under the License is distributed on an "AS IS" BASIS,
13
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- * See the License for the specific language governing permissions and
15
- * limitations under the License.
16
- -->
17
-
18
- <script setup>
19
- import { computed } from 'vue';
20
-
21
- const props = defineProps({
22
- src: {
23
- type: String,
24
- required: true
25
- },
26
- width: [Number, String],
27
- height: [Number, String],
28
- quality: {
29
- type: Number,
30
- default: 75
31
- },
32
- priority: {
33
- type: Boolean,
34
- default: false
35
- },
36
- alt: {
37
- type: String,
38
- default: ""
39
- }
40
- });
41
-
42
- const getBaseUrl = () => {
43
- if (typeof window === "undefined") return null;
44
- return window.location.origin;
45
- };
46
-
47
- const optimizedSrc = computed(() => {
48
- const baseUrl = getBaseUrl();
49
- const { src, quality, width, height } = props;
50
-
51
- // Se a imagem for Base64 (pequena) ou externa (http), não otimizamos via backend local
52
- const isOptimizable = src && typeof src === 'string' && !src.startsWith('data:') &&
53
- ((baseUrl && src.startsWith(baseUrl)) || !src.startsWith('http'));
54
-
55
- if (!isOptimizable) return src;
56
-
57
- let path = src;
58
- if (baseUrl && path.startsWith(baseUrl)) {
59
- path = path.slice(baseUrl.length) || '/';
60
- }
61
-
62
- const params = new URLSearchParams();
63
- params.set('url', path);
64
-
65
- // Tratamento para remover "px" se o usuário passar string
66
- if (width) {
67
- const w = String(width).replace('px', '');
68
- if (!isNaN(Number(w))) params.set('w', w);
69
- }
70
-
71
- if (height) {
72
- const h = String(height).replace('px', '');
73
- if (!isNaN(Number(h))) params.set('h', h);
74
- }
75
-
76
- if (quality) params.set('q', quality.toString());
77
-
78
- return `/_vatts/image?${params.toString()}`;
79
- });
80
-
81
- const baseStyle = computed(() => {
82
- return {
83
- width: props.width ? (typeof props.width === 'number' ? `${props.width}px` : props.width) : 'auto',
84
- height: props.height ? (typeof props.height === 'number' ? `${props.height}px` : props.height) : 'auto',
85
- };
86
- });
87
-
88
- // Remove "px" para os atributos nativos width/height da tag img
89
- const cleanDimension = (val) => {
90
- if (typeof val === 'string') return val.replace('px', '');
91
- return val;
92
- };
93
- </script>
94
-
95
- <template>
96
- <img
97
- v-bind="$attrs"
98
- :src="optimizedSrc"
99
- :alt="alt"
100
- :loading="priority ? 'eager' : 'lazy'"
101
- :decoding="priority ? 'sync' : 'async'"
102
- :width="cleanDimension(width)"
103
- :height="cleanDimension(height)"
104
- :style="baseStyle"
105
- class="vatts-image"
106
- />
1
+ <!--
2
+ * This file is part of the Vatts.js Project.
3
+ * Copyright (c) 2026 mfraz
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ -->
17
+
18
+ <script setup>
19
+ import { computed } from 'vue';
20
+
21
+ const props = defineProps({
22
+ src: {
23
+ type: String,
24
+ required: true
25
+ },
26
+ width: [Number, String],
27
+ height: [Number, String],
28
+ quality: {
29
+ type: Number,
30
+ default: 75
31
+ },
32
+ priority: {
33
+ type: Boolean,
34
+ default: false
35
+ },
36
+ alt: {
37
+ type: String,
38
+ default: ""
39
+ }
40
+ });
41
+
42
+ const getBaseUrl = () => {
43
+ if (typeof window === "undefined") return null;
44
+ return window.location.origin;
45
+ };
46
+
47
+ const optimizedSrc = computed(() => {
48
+ const baseUrl = getBaseUrl();
49
+ const { src, quality, width, height } = props;
50
+
51
+ // Se a imagem for Base64 (pequena) ou externa (http), não otimizamos via backend local
52
+ const isOptimizable = src && typeof src === 'string' && !src.startsWith('data:') &&
53
+ ((baseUrl && src.startsWith(baseUrl)) || !src.startsWith('http'));
54
+
55
+ if (!isOptimizable) return src;
56
+
57
+ let path = src;
58
+ if (baseUrl && path.startsWith(baseUrl)) {
59
+ path = path.slice(baseUrl.length) || '/';
60
+ }
61
+
62
+ const params = new URLSearchParams();
63
+ params.set('url', path);
64
+
65
+ // Tratamento para remover "px" se o usuário passar string
66
+ if (width) {
67
+ const w = String(width).replace('px', '');
68
+ if (!isNaN(Number(w))) params.set('w', w);
69
+ }
70
+
71
+ if (height) {
72
+ const h = String(height).replace('px', '');
73
+ if (!isNaN(Number(h))) params.set('h', h);
74
+ }
75
+
76
+ if (quality) params.set('q', quality.toString());
77
+
78
+ return `/_vatts/image?${params.toString()}`;
79
+ });
80
+
81
+ const baseStyle = computed(() => {
82
+ return {
83
+ width: props.width ? (typeof props.width === 'number' ? `${props.width}px` : props.width) : 'auto',
84
+ height: props.height ? (typeof props.height === 'number' ? `${props.height}px` : props.height) : 'auto',
85
+ };
86
+ });
87
+
88
+ // Remove "px" para os atributos nativos width/height da tag img
89
+ const cleanDimension = (val) => {
90
+ if (typeof val === 'string') return val.replace('px', '');
91
+ return val;
92
+ };
93
+ </script>
94
+
95
+ <template>
96
+ <img
97
+ v-bind="$attrs"
98
+ :src="optimizedSrc"
99
+ :alt="alt"
100
+ :loading="priority ? 'eager' : 'lazy'"
101
+ :decoding="priority ? 'sync' : 'async'"
102
+ :width="cleanDimension(width)"
103
+ :height="cleanDimension(height)"
104
+ :style="baseStyle"
105
+ class="vatts-image"
106
+ />
107
107
  </template>
@@ -43,6 +43,35 @@ const path_1 = __importDefault(require("path"));
43
43
  const vue = __importStar(require("vue"));
44
44
  const vueServerRenderer = __importStar(require("@vue/server-renderer"));
45
45
  const BuildingPage_vue_1 = __importDefault(require("./BuildingPage.vue"));
46
+ const server_error_vue_1 = __importDefault(require("./server-error.vue"));
47
+ function getRequestUrl(req) {
48
+ return req?.originalUrl || req?.url;
49
+ }
50
+ function buildVueShellDocument(options) {
51
+ const { lang, title, metaTagsHtml, scriptPreloadsHtml, componentPreloadsHtml, stylesHtml, obfuscatedData, scriptsHtml, hotReloadScript, bodyInnerHtml, } = options;
52
+ return `<!DOCTYPE html>
53
+ <html lang="${lang}">
54
+ <head>
55
+ <meta charset="utf-8" />
56
+ <title>${title}</title>
57
+ ${metaTagsHtml}
58
+ ${scriptPreloadsHtml}
59
+ ${componentPreloadsHtml}
60
+ ${stylesHtml}
61
+ </head>
62
+ <body>
63
+ <script id="__vatts_data__" type="text/plain" data-h="${obfuscatedData}"></script>
64
+ <div id="root">${bodyInnerHtml || ''}</div>
65
+ ${scriptsHtml}
66
+ ${hotReloadScript ? `<div style="display:none">${hotReloadScript}</div>` : ''}
67
+ </body>
68
+ </html>`;
69
+ }
70
+ function stripScriptTags(html) {
71
+ if (!html)
72
+ return '';
73
+ return html.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, '');
74
+ }
46
75
  // --- Helpers de Servidor (Duplicados para manter isolamento) ---
47
76
  function requireWithoutStyles(modulePath) {
48
77
  const extensions = ['.css', '.scss', '.sass', '.less', '.png', '.jpg', '.jpeg', '.gif', '.svg'];
@@ -82,18 +111,50 @@ function obfuscateData(data) {
82
111
  /**
83
112
  * Analisa o código fonte do componente para encontrar imports estáticos de assets
84
113
  * e gerar links de preload para injetar no head.
114
+ * * ATUALIZADO: Verifica se o arquivo existe em .vatts/assets (com hash) antes de gerar o link.
85
115
  */
86
116
  function extractComponentPreloads(componentPath) {
87
117
  if (!componentPath || !fs_1.default.existsSync(componentPath))
88
118
  return [];
119
+ // Localização dos assets compilados para verificação de hash
120
+ const assetsDir = path_1.default.join(process.cwd(), '.vatts', 'assets');
121
+ let availableAssets = [];
122
+ try {
123
+ if (fs_1.default.existsSync(assetsDir)) {
124
+ availableAssets = fs_1.default.readdirSync(assetsDir);
125
+ }
126
+ }
127
+ catch (e) {
128
+ // Silently fail if assets dir not found
129
+ }
130
+ // Função auxiliar para encontrar o arquivo real com hash
131
+ const findHashedAsset = (filename) => {
132
+ // 1. Se o arquivo existe exatamente como pedido
133
+ if (availableAssets.includes(filename))
134
+ return filename;
135
+ // 2. Procura por versão com hash (ex: style.css -> style.CjdCylXW.css ou da8dfae3-style.CjdCylXW.css)
136
+ const ext = path_1.default.extname(filename);
137
+ const base = path_1.default.basename(filename, ext); // "style"
138
+ // Filtra arquivos que terminam com a extensão correta e contêm o nome base
139
+ // Isso garante que pegamos o arquivo hasheado gerado pelo build
140
+ const match = availableAssets.find(asset => asset.endsWith(ext) && asset.includes(base));
141
+ return match || null;
142
+ };
89
143
  try {
90
144
  const content = fs_1.default.readFileSync(componentPath, 'utf8');
91
145
  const tags = new Set();
92
146
  const processPath = (fullPath) => {
93
147
  const filename = path_1.default.basename(fullPath);
94
- const ext = path_1.default.extname(filename).toLowerCase();
95
- // Assume estrutura flattened do Vatts em /_vatts/assets/ onde os arquivos finais residem
96
- const publicUrl = `/_vatts/assets/${filename}`;
148
+ // VERIFICAÇÃO DE HASH:
149
+ // Tenta encontrar o nome real do arquivo na pasta de assets.
150
+ // Se não encontrar (retornar null), ignoramos o arquivo para não gerar link quebrado (404).
151
+ const realFilename = findHashedAsset(filename);
152
+ if (!realFilename) {
153
+ return; // Bloqueia a entrada se não tiver correspondente hash/físico
154
+ }
155
+ const ext = path_1.default.extname(realFilename).toLowerCase();
156
+ // Usa o nome real encontrado (com hash)
157
+ const publicUrl = `/_vatts/assets/${realFilename}`;
97
158
  if (['.mp4', '.webm'].includes(ext)) {
98
159
  tags.add(`<link rel="preload" as="video" href="${publicUrl}">`);
99
160
  }
@@ -125,7 +186,8 @@ function extractComponentPreloads(componentPath) {
125
186
  const imgTagRegex = /<img\s+[^>]*src=['"]([^'"]+\.(png|jpg|jpeg|gif|svg|webp|avif))['"]/g;
126
187
  while ((match = imgTagRegex.exec(content)) !== null) {
127
188
  const src = match[1];
128
- tags.add(`<link rel="preload" as="image" href="${src}">`);
189
+ // Para tags img src, também processamos para tentar achar a versão hash
190
+ processPath(src);
129
191
  }
130
192
  return Array.from(tags);
131
193
  }
@@ -378,12 +440,107 @@ async function renderVue({ req, res, route, params, allRoutes }) {
378
440
  return;
379
441
  }
380
442
  const { createSSRApp, h } = vue;
381
- const { renderToNodeStream } = vueServerRenderer;
443
+ const { renderToNodeStream, renderToString } = vueServerRenderer;
382
444
  const { generateMetadata } = route;
383
445
  const isProduction = !req.hwebDev;
384
446
  const hotReloadManager = req.hotReloadManager;
447
+ let assets = null;
448
+ let metadata = { title: 'Vatts App' };
449
+ let layoutInfo = null;
450
+ const sendShell = async (options) => {
451
+ const includeScripts = options.includeScripts !== false;
452
+ if (!assets) {
453
+ if (!res.headersSent) {
454
+ res.statusCode = options.statusCode;
455
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
456
+ res.end(isProduction ? '' : '<h1>SSR Error</h1>');
457
+ }
458
+ return;
459
+ }
460
+ const hotReloadScript = includeScripts && !isProduction && hotReloadManager ? hotReloadManager.getClientScript() : '';
461
+ let metaTagsHtml = (() => {
462
+ try {
463
+ return generateMetaTags(metadata);
464
+ }
465
+ catch {
466
+ return '';
467
+ }
468
+ })();
469
+ if (!includeScripts) {
470
+ metaTagsHtml = stripScriptTags(metaTagsHtml);
471
+ }
472
+ const htmlLang = metadata.language || 'pt-BR';
473
+ const title = metadata.title || 'Vatts.js';
474
+ const scriptPreloadsHtml = includeScripts
475
+ ? assets.scripts.map((src) => `<link rel="modulepreload" href="${src}">`).join('\n')
476
+ : '';
477
+ const componentPreloadsHtml = includeScripts
478
+ ? (() => {
479
+ try {
480
+ const componentPreloads = extractComponentPreloads(route.componentPath ? path_1.default.resolve(process.cwd(), route.componentPath) : '');
481
+ return componentPreloads.join('\n');
482
+ }
483
+ catch {
484
+ return '';
485
+ }
486
+ })()
487
+ : '';
488
+ const stylesHtml = assets.styles.map((styleUrl) => `<link rel="stylesheet" href="${styleUrl}">`).join('\n');
489
+ const scriptsHtml = includeScripts
490
+ ? assets.scripts.map((src) => `<script type="module" src="${src}"></script>`).join('\n')
491
+ : '';
492
+ const obfuscatedData = (() => {
493
+ try {
494
+ return obfuscateData({
495
+ routes: [],
496
+ initialComponentPath: route.componentPath,
497
+ initialParams: params,
498
+ });
499
+ }
500
+ catch {
501
+ return '';
502
+ }
503
+ })();
504
+ res.statusCode = options.statusCode;
505
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
506
+ res.end(buildVueShellDocument({
507
+ lang: htmlLang,
508
+ title,
509
+ metaTagsHtml,
510
+ scriptPreloadsHtml,
511
+ componentPreloadsHtml,
512
+ stylesHtml,
513
+ obfuscatedData,
514
+ scriptsHtml,
515
+ hotReloadScript,
516
+ bodyInnerHtml: options.bodyInnerHtml,
517
+ }));
518
+ };
519
+ const sendSsrError = async (error) => {
520
+ if (isProduction) {
521
+ await sendShell({ bodyInnerHtml: '', statusCode: 200, includeScripts: true });
522
+ return;
523
+ }
524
+ try {
525
+ const ErrorRoot = {
526
+ setup() {
527
+ return () => h(server_error_vue_1.default, {
528
+ error,
529
+ requestUrl: getRequestUrl(req),
530
+ hint: 'O SSR falhou ao renderizar essa rota. Veja o erro abaixo.',
531
+ });
532
+ },
533
+ };
534
+ const errorApp = createSSRApp(ErrorRoot);
535
+ const errorHtml = await renderToString(errorApp);
536
+ await sendShell({ bodyInnerHtml: errorHtml, statusCode: 500, includeScripts: false });
537
+ }
538
+ catch {
539
+ await sendShell({ bodyInnerHtml: '<h1>SSR Error</h1>', statusCode: 500, includeScripts: false });
540
+ }
541
+ };
385
542
  try {
386
- const assets = getBuildAssets(req);
543
+ assets = getBuildAssets(req);
387
544
  if (!assets || assets.scripts.length === 0) {
388
545
  const RootComponent = {
389
546
  setup() {
@@ -404,13 +561,12 @@ async function renderVue({ req, res, route, params, allRoutes }) {
404
561
  return;
405
562
  }
406
563
  // 1. Layout (Carrega e Corrige se necessário)
407
- const layoutInfo = (0, router_1.getLayout)();
564
+ layoutInfo = (0, router_1.getLayout)();
408
565
  let LayoutComponent = null;
409
566
  if (layoutInfo) {
410
567
  LayoutComponent = ensureVueComponent(null, path_1.default.resolve(process.cwd(), layoutInfo.componentPath));
411
568
  }
412
569
  // 2. Metadata
413
- let metadata = { title: 'Vatts App' };
414
570
  if (layoutInfo && layoutInfo.metadata) {
415
571
  metadata = { ...metadata, ...layoutInfo.metadata };
416
572
  }
@@ -465,47 +621,35 @@ async function renderVue({ req, res, route, params, allRoutes }) {
465
621
  }
466
622
  };
467
623
  const app = createSSRApp(RootComponent);
468
- // 5. Stream
469
- const stream = renderToNodeStream(app);
470
- res.setHeader('Content-Type', 'text/html');
471
- res.write(`<!DOCTYPE html>
472
- <html lang="${htmlLang}">
473
- <head>
474
- <meta charset="utf-8" />
475
- <title>${metadata.title || 'Vatts.js'}</title>
476
- ${metaTagsHtml}
477
- ${scriptPreloadsHtml}
478
- ${componentPreloadsHtml}
479
- ${stylesHtml}
480
- </head>
481
- <body>
482
- <script id="__vatts_data__" type="text/plain" data-h="${obfuscatedData}"></script>
483
- <div id="root">`);
484
- stream.pipe(res, { end: false });
485
- stream.on('end', () => {
486
- res.write(`</div>
487
- ${scriptsHtml}
488
- ${hotReloadScript ? `<div style="display:none">${hotReloadScript}</div>` : ''}
489
- </body>
490
- </html>`);
491
- res.end();
492
- });
493
- stream.on('error', (err) => {
494
- console.error('Vue Streaming Error:', err);
495
- if (!res.headersSent) {
496
- res.statusCode = 500;
497
- res.end('Internal Server Error');
498
- }
499
- else {
500
- res.end();
624
+ // 5. Render (usa renderToString para evitar HTML parcial e permitir fallback)
625
+ try {
626
+ const bodyInnerHtml = await renderToString(app);
627
+ res.statusCode = 200;
628
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
629
+ res.end(buildVueShellDocument({
630
+ lang: htmlLang,
631
+ title: metadata.title || 'Vatts.js',
632
+ metaTagsHtml,
633
+ scriptPreloadsHtml,
634
+ componentPreloadsHtml,
635
+ stylesHtml,
636
+ obfuscatedData,
637
+ scriptsHtml,
638
+ hotReloadScript,
639
+ bodyInnerHtml,
640
+ }));
641
+ }
642
+ catch (err) {
643
+ if (!isProduction) {
644
+ console.error('Vue SSR Error:', err);
501
645
  }
502
- });
646
+ await sendSsrError(err);
647
+ }
503
648
  }
504
649
  catch (err) {
505
- console.error("Critical Vue Render Error:", err);
506
- if (!res.headersSent) {
507
- res.statusCode = 500;
508
- res.end('Internal Server Error');
650
+ if (!isProduction) {
651
+ console.error("Critical Vue Render Error:", err);
509
652
  }
653
+ await sendSsrError(err);
510
654
  }
511
655
  }