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/LICENSE +12 -12
- package/README.md +63 -63
- package/dist/api/native-server.js +25 -4
- package/dist/builder.js +39 -39
- package/dist/core-go/core-linux-arm64.node +0 -0
- package/dist/core-go/core-linux-x64.node +0 -0
- package/dist/core-go/core-win-x64.node +0 -0
- package/dist/global/global.d.ts +176 -176
- package/dist/helpers.js +17 -7
- package/dist/hotReload.js +205 -205
- package/dist/loaders.js +15 -15
- package/dist/react/BuildingPage.js +201 -201
- package/dist/react/DefaultNotFound.js +15 -15
- package/dist/react/DevIndicator.js +101 -101
- package/dist/react/entry.client.js +7 -7
- package/dist/react/renderer-react.js +172 -33
- package/dist/react/server-error.d.ts +8 -0
- package/dist/react/server-error.js +64 -0
- package/dist/vue/App.vue +191 -191
- package/dist/vue/BuildingPage.vue +280 -280
- package/dist/vue/DefaultNotFound.vue +328 -328
- package/dist/vue/DevIndicator.vue +225 -225
- package/dist/vue/ErrorModal.vue +316 -316
- package/dist/vue/Link.vue +38 -38
- package/dist/vue/entry.client.js +7 -7
- package/dist/vue/image/Image.vue +106 -106
- package/dist/vue/renderer.vue.js +190 -46
- package/dist/vue/server-error.vue +119 -0
- package/package.json +1 -1
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>
|
package/dist/vue/entry.client.js
CHANGED
|
@@ -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
|
}
|
package/dist/vue/image/Image.vue
CHANGED
|
@@ -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>
|
package/dist/vue/renderer.vue.js
CHANGED
|
@@ -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
|
-
|
|
95
|
-
//
|
|
96
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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
|
-
|
|
506
|
-
|
|
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
|
}
|