@vistagenic/vista 0.2.4 → 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/build/rsc/server-manifest.js +2 -2
- 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 +97 -14
- package/package.json +2 -3
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;
|
|
@@ -181,6 +182,21 @@ async function prerenderPage(urlPath, route, params, cwd) {
|
|
|
181
182
|
try {
|
|
182
183
|
const React = require('react');
|
|
183
184
|
const { renderToString } = require('react-dom/server');
|
|
185
|
+
const isAsyncComponent = (component) => {
|
|
186
|
+
return (typeof component === 'function' &&
|
|
187
|
+
component.constructor &&
|
|
188
|
+
component.constructor.name === 'AsyncFunction');
|
|
189
|
+
};
|
|
190
|
+
const renderComponent = async (component, props, child) => {
|
|
191
|
+
if (isAsyncComponent(component)) {
|
|
192
|
+
const asyncProps = child === undefined ? props : { ...props, children: child };
|
|
193
|
+
return component(asyncProps);
|
|
194
|
+
}
|
|
195
|
+
if (child === undefined) {
|
|
196
|
+
return React.createElement(component, props);
|
|
197
|
+
}
|
|
198
|
+
return React.createElement(component, props, child);
|
|
199
|
+
};
|
|
184
200
|
// Load page component from webpack-built server bundle
|
|
185
201
|
const pageModule = require(route.pagePath);
|
|
186
202
|
const PageComponent = pageModule.default;
|
|
@@ -188,15 +204,50 @@ async function prerenderPage(urlPath, route, params, cwd) {
|
|
|
188
204
|
console.warn(`[vista:ssg] No default export in ${route.pagePath}`);
|
|
189
205
|
return null;
|
|
190
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
|
+
}
|
|
191
242
|
// Build the element, passing params as props
|
|
192
|
-
let element =
|
|
243
|
+
let element = await renderComponent(PageComponent, { params: params || {} });
|
|
193
244
|
// Wrap in layouts (outside-in)
|
|
194
245
|
for (let i = route.layoutPaths.length - 1; i >= 0; i--) {
|
|
195
246
|
try {
|
|
196
247
|
const layoutModule = require(route.layoutPaths[i]);
|
|
197
248
|
const LayoutComponent = layoutModule.default;
|
|
198
249
|
if (LayoutComponent) {
|
|
199
|
-
element =
|
|
250
|
+
element = await renderComponent(LayoutComponent, { params: params || {}, searchParams: {} }, element);
|
|
200
251
|
}
|
|
201
252
|
}
|
|
202
253
|
catch {
|
|
@@ -206,7 +257,7 @@ async function prerenderPage(urlPath, route, params, cwd) {
|
|
|
206
257
|
// Render to HTML string
|
|
207
258
|
const html = renderToString(element);
|
|
208
259
|
return {
|
|
209
|
-
html: wrapInDocument(html, urlPath),
|
|
260
|
+
html: wrapInDocument(html, urlPath, metadataHtml, cwd),
|
|
210
261
|
generatedAt: Date.now(),
|
|
211
262
|
revalidate: route.revalidate || 0,
|
|
212
263
|
routePattern: route.pattern,
|
|
@@ -221,17 +272,49 @@ async function prerenderPage(urlPath, route, params, cwd) {
|
|
|
221
272
|
/**
|
|
222
273
|
* Wrap rendered HTML in a basic document shell.
|
|
223
274
|
*/
|
|
224
|
-
function
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
<
|
|
233
|
-
|
|
234
|
-
|
|
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>
|
|
235
318
|
</html>`;
|
|
236
319
|
}
|
|
237
320
|
// ---------------------------------------------------------------------------
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vistagenic/vista",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "The React Framework for Visionaries - Rust-powered SSR with Server Components",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -132,6 +132,5 @@
|
|
|
132
132
|
"@types/webpack": "^5.28.5",
|
|
133
133
|
"@types/webpack-hot-middleware": "^2.25.9",
|
|
134
134
|
"typescript": "^5.7.2"
|
|
135
|
-
}
|
|
136
|
-
"gitHead": "9fac2fe1ed23e51248ba8bcecbd623b9e3a3b520"
|
|
135
|
+
}
|
|
137
136
|
}
|