@vistagenic/vista 0.2.5 → 0.2.6
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/bin/build-rsc.js +134 -39
- package/dist/bin/build.js +39 -55
- package/dist/bin/dev-error-overlay-snippet.d.ts +5 -0
- package/dist/bin/dev-error-overlay-snippet.js +562 -0
- package/dist/bin/devtools-indicator-snippet.d.ts +5 -0
- package/dist/bin/devtools-indicator-snippet.js +368 -0
- package/dist/dev-error.d.ts +1 -2
- package/dist/dev-error.js +867 -346
- package/dist/server/engine.js +17 -4
- package/dist/server/rsc-engine.js +126 -5
- package/dist/server/static-generator.js +80 -12
- package/package.json +1 -1
package/dist/server/engine.js
CHANGED
|
@@ -276,10 +276,15 @@ function startServer(port = 3003, compiler) {
|
|
|
276
276
|
}, 100);
|
|
277
277
|
};
|
|
278
278
|
// Push compile errors to browser via SSE
|
|
279
|
-
const pushCompileError = (
|
|
279
|
+
const pushCompileError = (errorMessages) => {
|
|
280
|
+
const normalized = (Array.isArray(errorMessages) ? errorMessages : [])
|
|
281
|
+
.map((entry) => String(entry || '').trim())
|
|
282
|
+
.filter(Boolean);
|
|
283
|
+
const fallback = normalized.length > 0 ? normalized.join('\n\n') : 'Unknown build error.';
|
|
280
284
|
const errorData = JSON.stringify({
|
|
281
285
|
type: 'error',
|
|
282
|
-
message:
|
|
286
|
+
message: fallback,
|
|
287
|
+
errors: normalized.length > 0 ? normalized : [fallback],
|
|
283
288
|
});
|
|
284
289
|
sseClients.forEach((client) => {
|
|
285
290
|
client.write(`data: ${errorData}\n\n`);
|
|
@@ -296,9 +301,17 @@ function startServer(port = 3003, compiler) {
|
|
|
296
301
|
compiler.hooks.done.tap('VistaSSE', (stats) => {
|
|
297
302
|
if (stats.hasErrors()) {
|
|
298
303
|
const errors = stats.toJson().errors || [];
|
|
299
|
-
const
|
|
304
|
+
const errorMessages = errors
|
|
305
|
+
.map((e) => {
|
|
306
|
+
if (typeof e === 'string')
|
|
307
|
+
return e;
|
|
308
|
+
if (e && typeof e.message === 'string')
|
|
309
|
+
return e.message;
|
|
310
|
+
return String(e || '');
|
|
311
|
+
})
|
|
312
|
+
.filter((entry) => entry.trim().length > 0);
|
|
300
313
|
(0, logger_1.logEvent)('Build error detected, pushing to browser...');
|
|
301
|
-
pushCompileError(
|
|
314
|
+
pushCompileError(errorMessages);
|
|
302
315
|
}
|
|
303
316
|
else {
|
|
304
317
|
pushBuildSuccess();
|
|
@@ -296,6 +296,41 @@ function normalizeSSRManifest(manifest) {
|
|
|
296
296
|
}
|
|
297
297
|
const moduleMap = manifest.moduleMap;
|
|
298
298
|
const aliasEntries = [];
|
|
299
|
+
const pushAlias = (key, exportsMap) => {
|
|
300
|
+
if (!key)
|
|
301
|
+
return;
|
|
302
|
+
aliasEntries.push([key, exportsMap]);
|
|
303
|
+
// React Flight may request module IDs with/without a trailing '#'.
|
|
304
|
+
if (key.endsWith('#')) {
|
|
305
|
+
aliasEntries.push([key.slice(0, -1), exportsMap]);
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
aliasEntries.push([`${key}#`, exportsMap]);
|
|
309
|
+
}
|
|
310
|
+
// Normalize URI encoding variants for Windows paths with spaces, etc.
|
|
311
|
+
if (key.startsWith('file://')) {
|
|
312
|
+
try {
|
|
313
|
+
const decoded = decodeURI(key);
|
|
314
|
+
if (decoded !== key) {
|
|
315
|
+
aliasEntries.push([decoded, exportsMap]);
|
|
316
|
+
aliasEntries.push([decoded.endsWith('#') ? decoded.slice(0, -1) : `${decoded}#`, exportsMap]);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
// ignore decode failures
|
|
321
|
+
}
|
|
322
|
+
try {
|
|
323
|
+
const encoded = encodeURI(key);
|
|
324
|
+
if (encoded !== key) {
|
|
325
|
+
aliasEntries.push([encoded, exportsMap]);
|
|
326
|
+
aliasEntries.push([encoded.endsWith('#') ? encoded.slice(0, -1) : `${encoded}#`, exportsMap]);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
// ignore encode failures
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
};
|
|
299
334
|
for (const [moduleKey, exportsMap] of Object.entries(moduleMap)) {
|
|
300
335
|
const normalizedExports = {};
|
|
301
336
|
for (const [exportName, rawEntry] of Object.entries(exportsMap || {})) {
|
|
@@ -308,10 +343,10 @@ function normalizeSSRManifest(manifest) {
|
|
|
308
343
|
};
|
|
309
344
|
}
|
|
310
345
|
moduleMap[moduleKey] = normalizedExports;
|
|
311
|
-
|
|
346
|
+
pushAlias(moduleKey, normalizedExports);
|
|
312
347
|
for (const normalizedEntry of Object.values(normalizedExports)) {
|
|
313
348
|
const aliasKey = String(normalizedEntry.id);
|
|
314
|
-
|
|
349
|
+
pushAlias(aliasKey, normalizedExports);
|
|
315
350
|
}
|
|
316
351
|
}
|
|
317
352
|
for (const [aliasKey, exportsMap] of aliasEntries) {
|
|
@@ -688,6 +723,55 @@ function wrapInDocumentShell(bodyContent, metadataHtml, chunkFiles, rootMode) {
|
|
|
688
723
|
</body>
|
|
689
724
|
</html>`;
|
|
690
725
|
}
|
|
726
|
+
function normalizeWebpackErrors(stats) {
|
|
727
|
+
const errors = stats.toJson().errors || [];
|
|
728
|
+
const normalizedErrors = errors
|
|
729
|
+
.map((entry) => {
|
|
730
|
+
if (typeof entry === 'string')
|
|
731
|
+
return entry;
|
|
732
|
+
if (entry && typeof entry.message === 'string')
|
|
733
|
+
return entry.message;
|
|
734
|
+
return String(entry || '');
|
|
735
|
+
})
|
|
736
|
+
.filter((entry) => entry.trim().length > 0);
|
|
737
|
+
if (normalizedErrors.length > 0) {
|
|
738
|
+
return normalizedErrors;
|
|
739
|
+
}
|
|
740
|
+
return ['Unknown build error.'];
|
|
741
|
+
}
|
|
742
|
+
function renderCompilePendingHTML() {
|
|
743
|
+
return `<!DOCTYPE html>
|
|
744
|
+
<html lang="en">
|
|
745
|
+
<head>
|
|
746
|
+
<meta charset="utf-8" />
|
|
747
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
748
|
+
<title>Compiling...</title>
|
|
749
|
+
<style>
|
|
750
|
+
html,body{height:100%;margin:0}
|
|
751
|
+
body{
|
|
752
|
+
display:grid;
|
|
753
|
+
place-items:center;
|
|
754
|
+
font-family:ui-sans-serif,system-ui,-apple-system,'Segoe UI',sans-serif;
|
|
755
|
+
background:#09090b;
|
|
756
|
+
color:#f4f4f5;
|
|
757
|
+
}
|
|
758
|
+
.vista-dev-pending{
|
|
759
|
+
padding:20px 24px;
|
|
760
|
+
border-radius:14px;
|
|
761
|
+
border:1px solid rgba(255,255,255,0.15);
|
|
762
|
+
background:rgba(17,17,20,0.92);
|
|
763
|
+
box-shadow:0 18px 42px rgba(0,0,0,0.4);
|
|
764
|
+
font-size:14px;
|
|
765
|
+
letter-spacing:0.01em;
|
|
766
|
+
}
|
|
767
|
+
</style>
|
|
768
|
+
</head>
|
|
769
|
+
<body>
|
|
770
|
+
<div class="vista-dev-pending">Vista is compiling the client bundle. Retrying...</div>
|
|
771
|
+
<script>setTimeout(function(){window.location.reload();},700);</script>
|
|
772
|
+
</body>
|
|
773
|
+
</html>`;
|
|
774
|
+
}
|
|
691
775
|
function startRSCServer(options = {}) {
|
|
692
776
|
const app = (0, express_1.default)();
|
|
693
777
|
const cwd = process.cwd();
|
|
@@ -847,6 +931,8 @@ function startRSCServer(options = {}) {
|
|
|
847
931
|
// - Pushes compile errors/success from webpack client build
|
|
848
932
|
// ========================================================================
|
|
849
933
|
const sseReloadClients = new Set();
|
|
934
|
+
let clientCompileState = isDev && options.compiler ? 'compiling' : 'ready';
|
|
935
|
+
let clientCompileErrors = [];
|
|
850
936
|
if (isDev) {
|
|
851
937
|
app.get(constants_1.SSE_ENDPOINT, (req, res) => {
|
|
852
938
|
res.setHeader('Content-Type', 'text/event-stream');
|
|
@@ -935,15 +1021,27 @@ function startRSCServer(options = {}) {
|
|
|
935
1021
|
writeToDisk: true,
|
|
936
1022
|
}));
|
|
937
1023
|
// No webpack-hot-middleware — Vista uses SSE live-reload for RSC
|
|
1024
|
+
options.compiler.hooks.invalid.tap('VistaRSCCompileStateInvalid', () => {
|
|
1025
|
+
clientCompileState = 'compiling';
|
|
1026
|
+
clientCompileErrors = [];
|
|
1027
|
+
});
|
|
938
1028
|
// Push compile errors/success to SSE clients
|
|
939
1029
|
options.compiler.hooks.done.tap('VistaRSCLiveReload', (stats) => {
|
|
940
1030
|
if (stats.hasErrors()) {
|
|
941
|
-
const
|
|
942
|
-
const
|
|
943
|
-
|
|
1031
|
+
const normalizedErrors = normalizeWebpackErrors(stats);
|
|
1032
|
+
const fallback = normalizedErrors.join('\n\n');
|
|
1033
|
+
clientCompileState = 'error';
|
|
1034
|
+
clientCompileErrors = normalizedErrors;
|
|
1035
|
+
const payload = JSON.stringify({
|
|
1036
|
+
type: 'error',
|
|
1037
|
+
message: fallback,
|
|
1038
|
+
errors: normalizedErrors,
|
|
1039
|
+
});
|
|
944
1040
|
sseReloadClients.forEach((c) => c.write(`data: ${payload}\n\n`));
|
|
945
1041
|
}
|
|
946
1042
|
else {
|
|
1043
|
+
clientCompileState = 'ready';
|
|
1044
|
+
clientCompileErrors = [];
|
|
947
1045
|
const payload = JSON.stringify({ type: 'ok' });
|
|
948
1046
|
sseReloadClients.forEach((c) => c.write(`data: ${payload}\n\n`));
|
|
949
1047
|
}
|
|
@@ -1031,6 +1129,16 @@ function startRSCServer(options = {}) {
|
|
|
1031
1129
|
app.use(constants_1.URL_PREFIX, express_1.default.static(path_1.default.join(cwd, constants_1.BUILD_DIR)));
|
|
1032
1130
|
app.use(express_1.default.static(path_1.default.join(cwd, constants_1.BUILD_DIR)));
|
|
1033
1131
|
const proxyRSCRequest = async (req, res) => {
|
|
1132
|
+
if (isDev && options.compiler) {
|
|
1133
|
+
if (clientCompileState === 'compiling') {
|
|
1134
|
+
res.status(503).type('text/plain').send('[vista] Client bundle is compiling. Retry shortly.');
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
if (clientCompileState === 'error') {
|
|
1138
|
+
res.status(500).type('text/plain').send(clientCompileErrors.join('\n\n'));
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1034
1142
|
try {
|
|
1035
1143
|
const fetchOptions = {
|
|
1036
1144
|
method: req.method,
|
|
@@ -1122,6 +1230,19 @@ function startRSCServer(options = {}) {
|
|
|
1122
1230
|
.send((0, dev_error_1.renderErrorHTML)([errorInfo]));
|
|
1123
1231
|
return;
|
|
1124
1232
|
}
|
|
1233
|
+
if (isDev && options.compiler) {
|
|
1234
|
+
if (clientCompileState === 'compiling') {
|
|
1235
|
+
res.status(503).type('text/html').send(renderCompilePendingHTML());
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
if (clientCompileState === 'error') {
|
|
1239
|
+
const errorInfos = (clientCompileErrors.length > 0
|
|
1240
|
+
? clientCompileErrors
|
|
1241
|
+
: ['Unknown client build error.']).map((message) => ({ type: 'build', message }));
|
|
1242
|
+
res.status(500).type('text/html').send((0, dev_error_1.renderErrorHTML)(errorInfos));
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1125
1246
|
if (req.path.startsWith('/api/')) {
|
|
1126
1247
|
await handleApiRoute(req, res, cwd, isDev, typedApiConfig);
|
|
1127
1248
|
return;
|
|
@@ -16,6 +16,7 @@ exports.generateStaticPages = generateStaticPages;
|
|
|
16
16
|
exports.revalidatePath = revalidatePath;
|
|
17
17
|
const path_1 = __importDefault(require("path"));
|
|
18
18
|
const fs_1 = __importDefault(require("fs"));
|
|
19
|
+
const constants_1 = require("../constants");
|
|
19
20
|
const static_cache_1 = require("./static-cache");
|
|
20
21
|
const CjsModule = require('module');
|
|
21
22
|
let staticRuntimeReady = false;
|
|
@@ -203,6 +204,41 @@ async function prerenderPage(urlPath, route, params, cwd) {
|
|
|
203
204
|
console.warn(`[vista:ssg] No default export in ${route.pagePath}`);
|
|
204
205
|
return null;
|
|
205
206
|
}
|
|
207
|
+
let metadata = {};
|
|
208
|
+
const searchParams = {};
|
|
209
|
+
for (const layoutPath of route.layoutPaths) {
|
|
210
|
+
try {
|
|
211
|
+
const layoutModule = require(layoutPath);
|
|
212
|
+
if (layoutModule?.metadata && typeof layoutModule.metadata === 'object') {
|
|
213
|
+
metadata = { ...metadata, ...layoutModule.metadata };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
// Ignore layout metadata failures for static generation.
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (pageModule.metadata && typeof pageModule.metadata === 'object') {
|
|
221
|
+
metadata = { ...metadata, ...pageModule.metadata };
|
|
222
|
+
}
|
|
223
|
+
if (typeof pageModule.generateMetadata === 'function') {
|
|
224
|
+
try {
|
|
225
|
+
const dynamicMeta = await pageModule.generateMetadata({ params: params || {}, searchParams }, metadata);
|
|
226
|
+
if (dynamicMeta && typeof dynamicMeta === 'object') {
|
|
227
|
+
metadata = { ...metadata, ...dynamicMeta };
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
catch (metadataError) {
|
|
231
|
+
console.warn(`[vista:ssg] generateMetadata failed for ${urlPath}:`, metadataError?.message || String(metadataError));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
let metadataHtml = '';
|
|
235
|
+
try {
|
|
236
|
+
const { generateMetadataHtml } = require('../metadata/generate');
|
|
237
|
+
metadataHtml = generateMetadataHtml(metadata);
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
metadataHtml = '';
|
|
241
|
+
}
|
|
206
242
|
// Build the element, passing params as props
|
|
207
243
|
let element = await renderComponent(PageComponent, { params: params || {} });
|
|
208
244
|
// Wrap in layouts (outside-in)
|
|
@@ -221,7 +257,7 @@ async function prerenderPage(urlPath, route, params, cwd) {
|
|
|
221
257
|
// Render to HTML string
|
|
222
258
|
const html = renderToString(element);
|
|
223
259
|
return {
|
|
224
|
-
html: wrapInDocument(html, urlPath),
|
|
260
|
+
html: wrapInDocument(html, urlPath, metadataHtml, cwd),
|
|
225
261
|
generatedAt: Date.now(),
|
|
226
262
|
revalidate: route.revalidate || 0,
|
|
227
263
|
routePattern: route.pattern,
|
|
@@ -236,17 +272,49 @@ async function prerenderPage(urlPath, route, params, cwd) {
|
|
|
236
272
|
/**
|
|
237
273
|
* Wrap rendered HTML in a basic document shell.
|
|
238
274
|
*/
|
|
239
|
-
function
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
<
|
|
248
|
-
|
|
249
|
-
|
|
275
|
+
function injectBeforeClosingTag(html, tagName, injection) {
|
|
276
|
+
const closeTag = `</${tagName}>`;
|
|
277
|
+
if (html.includes(closeTag)) {
|
|
278
|
+
return html.replace(closeTag, `${injection}\n${closeTag}`);
|
|
279
|
+
}
|
|
280
|
+
return html;
|
|
281
|
+
}
|
|
282
|
+
function getCSSLinks(cwd) {
|
|
283
|
+
const links = ['<link rel="stylesheet" href="/styles.css" />'];
|
|
284
|
+
const chunksDir = path_1.default.join(cwd, constants_1.BUILD_DIR, 'static', 'chunks');
|
|
285
|
+
try {
|
|
286
|
+
if (fs_1.default.existsSync(chunksDir)) {
|
|
287
|
+
const files = fs_1.default.readdirSync(chunksDir).filter((entry) => entry.endsWith('.css'));
|
|
288
|
+
for (const file of files) {
|
|
289
|
+
links.push(`<link rel="stylesheet" href="${constants_1.STATIC_CHUNKS_PATH}${file}" />`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
// Ignore CSS discovery failures during static generation.
|
|
295
|
+
}
|
|
296
|
+
return links.join('\n ');
|
|
297
|
+
}
|
|
298
|
+
function wrapInDocument(bodyHtml, _urlPath, metadataHtml, cwd) {
|
|
299
|
+
const headInjection = `\n <meta charset="utf-8" />\n <meta name="viewport" content="width=device-width, initial-scale=1" />\n ${metadataHtml}\n ${getCSSLinks(cwd)}`;
|
|
300
|
+
const hasDocumentMarkup = /<html(?:\s|>)/i.test(bodyHtml) && /<\/html>/i.test(bodyHtml);
|
|
301
|
+
if (hasDocumentMarkup) {
|
|
302
|
+
const htmlStart = bodyHtml.search(/<html(?:\s|>)/i);
|
|
303
|
+
let html = htmlStart > 0 ? bodyHtml.slice(htmlStart) : bodyHtml;
|
|
304
|
+
if (!/^\s*<!doctype html>/i.test(html)) {
|
|
305
|
+
html = `<!DOCTYPE html>\n${html}`;
|
|
306
|
+
}
|
|
307
|
+
html = injectBeforeClosingTag(html, 'head', headInjection);
|
|
308
|
+
return html;
|
|
309
|
+
}
|
|
310
|
+
return `<!DOCTYPE html>
|
|
311
|
+
<html lang="en">
|
|
312
|
+
<head>
|
|
313
|
+
${headInjection}
|
|
314
|
+
</head>
|
|
315
|
+
<body>
|
|
316
|
+
<div id="root">${bodyHtml}</div>
|
|
317
|
+
</body>
|
|
250
318
|
</html>`;
|
|
251
319
|
}
|
|
252
320
|
// ---------------------------------------------------------------------------
|