@vistagenic/vista 0.2.12 → 0.2.13
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/bin/vista.js +171 -35
- package/dist/bin/build-rsc-flashpack.d.ts +4 -0
- package/dist/bin/build-rsc-flashpack.js +29 -0
- package/dist/bin/build-rsc.js +47 -16
- package/dist/bin/build.js +36 -13
- package/dist/bin/devtools-indicator-snippet.js +30 -0
- package/dist/bin/file-scanner.d.ts +1 -1
- package/dist/bin/file-scanner.js +8 -0
- package/dist/bin/flashpack-runner.d.ts +1 -0
- package/dist/bin/flashpack-runner.js +61 -0
- package/dist/bin/server-component-plugin.d.ts +6 -4
- package/dist/bin/server-component-plugin.js +22 -69
- package/dist/bin/webpack.config.d.ts +3 -0
- package/dist/bin/webpack.config.js +12 -3
- package/dist/build/manifest.d.ts +17 -3
- package/dist/build/manifest.js +99 -23
- package/dist/build/rsc/compiler.d.ts +2 -0
- package/dist/build/rsc/compiler.js +25 -5
- package/dist/build/rsc/react-client-reference-manifest.d.ts +22 -0
- package/dist/build/rsc/react-client-reference-manifest.js +219 -0
- package/dist/build/rsc/server-manifest.d.ts +23 -2
- package/dist/build/rsc/server-manifest.js +162 -24
- package/dist/build/standalone.d.ts +31 -0
- package/dist/build/standalone.js +334 -0
- package/dist/client/rsc-router.d.ts +31 -0
- package/dist/client/rsc-router.js +89 -0
- package/dist/config.d.ts +23 -0
- package/dist/config.js +106 -5
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +3 -1
- package/dist/flashpack/command.d.ts +8 -0
- package/dist/flashpack/command.js +134 -0
- package/dist/flashpack/runtime.d.ts +39 -0
- package/dist/flashpack/runtime.js +249 -0
- package/dist/server/app-router-runtime.d.ts +26 -0
- package/dist/server/app-router-runtime.js +321 -0
- package/dist/server/artifact-validator.js +21 -1
- package/dist/server/cache.d.ts +10 -0
- package/dist/server/cache.js +270 -0
- package/dist/server/client-boundary.js +20 -2
- package/dist/server/engine.js +236 -159
- package/dist/server/fetch-policy.d.ts +2 -0
- package/dist/server/fetch-policy.js +123 -0
- package/dist/server/index.d.ts +7 -0
- package/dist/server/index.js +131 -22
- package/dist/server/module-boundary-validator.d.ts +15 -0
- package/dist/server/module-boundary-validator.js +262 -0
- package/dist/server/module-compile-hook.d.ts +7 -0
- package/dist/server/module-compile-hook.js +662 -0
- package/dist/server/ppr.d.ts +18 -0
- package/dist/server/ppr.js +59 -0
- package/dist/server/request-context.d.ts +31 -0
- package/dist/server/request-context.js +95 -0
- package/dist/server/rsc-engine-flashpack.d.ts +4 -0
- package/dist/server/rsc-engine-flashpack.js +27 -0
- package/dist/server/rsc-engine.d.ts +2 -0
- package/dist/server/rsc-engine.js +589 -317
- package/dist/server/rsc-upstream.js +539 -233
- package/dist/server/runtime-actions.d.ts +5 -0
- package/dist/server/runtime-actions.js +80 -0
- package/dist/server/runtime-artifacts.d.ts +11 -0
- package/dist/server/runtime-artifacts.js +35 -0
- package/dist/server/segment-config.d.ts +37 -0
- package/dist/server/segment-config.js +205 -0
- package/dist/server/spawn-permissions.d.ts +2 -0
- package/dist/server/spawn-permissions.js +21 -0
- package/dist/server/static-cache.d.ts +15 -1
- package/dist/server/static-cache.js +83 -3
- package/dist/server/static-generator.js +254 -100
- package/dist/server/structure-validator.d.ts +1 -1
- package/dist/server/structure-validator.js +26 -5
- package/dist/server/structure-watch.js +1 -1
- package/dist/server/typed-api-runtime.d.ts +1 -0
- package/dist/server/typed-api-runtime.js +145 -25
- package/dist/server/vista-import-map.d.ts +1 -0
- package/dist/server/vista-import-map.js +123 -0
- package/package.json +13 -1
|
@@ -25,11 +25,13 @@ const child_process_1 = require("child_process");
|
|
|
25
25
|
const url_1 = require("url");
|
|
26
26
|
const middleware_runner_1 = require("./middleware-runner");
|
|
27
27
|
const image_optimizer_1 = require("./image-optimizer");
|
|
28
|
+
const ppr_1 = require("./ppr");
|
|
28
29
|
const registry_1 = require("../font/registry");
|
|
29
30
|
const logger_1 = require("./logger");
|
|
30
31
|
const not_found_page_1 = require("./not-found-page");
|
|
31
32
|
const static_cache_1 = require("./static-cache");
|
|
32
33
|
const static_generator_1 = require("./static-generator");
|
|
34
|
+
const react_client_reference_manifest_1 = require("../build/rsc/react-client-reference-manifest");
|
|
33
35
|
const constants_1 = require("../constants");
|
|
34
36
|
const CjsModule = require('module');
|
|
35
37
|
// ---------------------------------------------------------------------------
|
|
@@ -66,11 +68,18 @@ const config_1 = require("../config");
|
|
|
66
68
|
const dev_error_1 = require("../dev-error");
|
|
67
69
|
const artifact_validator_1 = require("./artifact-validator");
|
|
68
70
|
const root_resolver_1 = require("./root-resolver");
|
|
71
|
+
const runtime_artifacts_1 = require("./runtime-artifacts");
|
|
72
|
+
const spawn_permissions_1 = require("./spawn-permissions");
|
|
69
73
|
const structure_watch_1 = require("./structure-watch");
|
|
70
74
|
const structure_log_1 = require("./structure-log");
|
|
71
75
|
const error_boundary_1 = require("../components/error-boundary");
|
|
72
76
|
const route_suspense_1 = require("../components/route-suspense");
|
|
73
77
|
const typed_api_runtime_1 = require("./typed-api-runtime");
|
|
78
|
+
const module_compile_hook_1 = require("./module-compile-hook");
|
|
79
|
+
const request_context_1 = require("./request-context");
|
|
80
|
+
const app_router_runtime_1 = require("./app-router-runtime");
|
|
81
|
+
const fetch_policy_1 = require("./fetch-policy");
|
|
82
|
+
const vista_import_map_1 = require("./vista-import-map");
|
|
74
83
|
// Support CSS imports on server runtime
|
|
75
84
|
// - Regular .css: ignored (handled by PostCSS)
|
|
76
85
|
// - .module.css: return empty class mapping (webpack build handles real mappings)
|
|
@@ -84,7 +93,7 @@ require.extensions['.css'] = (m, filename) => {
|
|
|
84
93
|
* Includes the PostCSS globals and CSS Modules extracted stylesheet.
|
|
85
94
|
*/
|
|
86
95
|
function getCSSLinks(projectRoot) {
|
|
87
|
-
const root = projectRoot || process.cwd();
|
|
96
|
+
const root = projectRoot || process.env.VISTA_ARTIFACT_ROOT || process.cwd();
|
|
88
97
|
const links = ['<link rel="stylesheet" href="/styles.css" />'];
|
|
89
98
|
// Check for extracted CSS modules (from MiniCssExtractPlugin)
|
|
90
99
|
const chunksDir = path_1.default.join(root, constants_1.BUILD_DIR, 'static', 'chunks');
|
|
@@ -114,6 +123,47 @@ function resolvePort(raw, fallback) {
|
|
|
114
123
|
}
|
|
115
124
|
return value;
|
|
116
125
|
}
|
|
126
|
+
function removeStaticArtifacts(vistaDirRoot, urlPath) {
|
|
127
|
+
const staticDir = path_1.default.join(vistaDirRoot, 'static', 'pages');
|
|
128
|
+
const safePath = urlPath === '/' ? '/index' : urlPath;
|
|
129
|
+
const artifactPaths = ['.html', '.meta.json', '.rsc'].map((extension) => path_1.default.join(staticDir, `${safePath}${extension}`));
|
|
130
|
+
for (const absolutePath of artifactPaths) {
|
|
131
|
+
try {
|
|
132
|
+
if (fs_1.default.existsSync(absolutePath)) {
|
|
133
|
+
fs_1.default.unlinkSync(absolutePath);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
// ignore cache cleanup failures
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function parseRevalidationHeader(rawValue) {
|
|
142
|
+
if (!rawValue) {
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
const parsed = JSON.parse(rawValue);
|
|
147
|
+
return Array.isArray(parsed) ? parsed.map((entry) => String(entry || '').trim()).filter(Boolean) : [];
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function applyUpstreamRevalidations(upstream, vistaDirRoot) {
|
|
154
|
+
const revalidatedPaths = parseRevalidationHeader(upstream.headers.get('x-vista-revalidated-paths'));
|
|
155
|
+
for (const urlPath of revalidatedPaths) {
|
|
156
|
+
(0, static_cache_1.invalidateCachedPage)(urlPath);
|
|
157
|
+
removeStaticArtifacts(vistaDirRoot, urlPath);
|
|
158
|
+
}
|
|
159
|
+
const revalidatedTags = parseRevalidationHeader(upstream.headers.get('x-vista-revalidated-tags'));
|
|
160
|
+
for (const tag of revalidatedTags) {
|
|
161
|
+
const affectedPaths = (0, static_cache_1.invalidateCachedPagesByTag)(tag);
|
|
162
|
+
for (const urlPath of affectedPaths) {
|
|
163
|
+
removeStaticArtifacts(vistaDirRoot, urlPath);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
117
167
|
function normalizeModuleCachePath(filePath) {
|
|
118
168
|
return filePath.replace(/\\/g, '/').toLowerCase();
|
|
119
169
|
}
|
|
@@ -156,15 +206,24 @@ function resolveFromWorkspace(specifier, cwd) {
|
|
|
156
206
|
}
|
|
157
207
|
function setupTypeScriptRuntime(cwd) {
|
|
158
208
|
try {
|
|
159
|
-
const
|
|
160
|
-
|
|
209
|
+
const swcRegisterPath = resolveFromWorkspace('@swc-node/register/register', cwd);
|
|
210
|
+
const typescriptPath = resolveFromWorkspace('typescript', cwd);
|
|
211
|
+
const { register } = require(swcRegisterPath);
|
|
212
|
+
const ts = require(typescriptPath);
|
|
213
|
+
register({
|
|
214
|
+
module: ts.ModuleKind.CommonJS,
|
|
215
|
+
jsx: ts.JsxEmit.ReactJSX,
|
|
216
|
+
moduleResolution: ts.ModuleResolutionKind.Node16,
|
|
217
|
+
esModuleInterop: true,
|
|
218
|
+
allowJs: true,
|
|
219
|
+
});
|
|
161
220
|
return;
|
|
162
221
|
}
|
|
163
222
|
catch {
|
|
164
223
|
// fallback
|
|
165
224
|
}
|
|
166
225
|
try {
|
|
167
|
-
const tsNodePath =
|
|
226
|
+
const tsNodePath = resolveFromWorkspace('ts-node', cwd);
|
|
168
227
|
require(tsNodePath).register({
|
|
169
228
|
transpileOnly: true,
|
|
170
229
|
compilerOptions: {
|
|
@@ -172,6 +231,7 @@ function setupTypeScriptRuntime(cwd) {
|
|
|
172
231
|
jsx: 'react-jsx',
|
|
173
232
|
moduleResolution: 'node16',
|
|
174
233
|
esModuleInterop: true,
|
|
234
|
+
allowJs: true,
|
|
175
235
|
},
|
|
176
236
|
});
|
|
177
237
|
return;
|
|
@@ -180,8 +240,8 @@ function setupTypeScriptRuntime(cwd) {
|
|
|
180
240
|
// fallback
|
|
181
241
|
}
|
|
182
242
|
try {
|
|
183
|
-
|
|
184
|
-
require(
|
|
243
|
+
const tsxPath = resolveFromWorkspace('tsx/cjs', cwd);
|
|
244
|
+
require(tsxPath);
|
|
185
245
|
return;
|
|
186
246
|
}
|
|
187
247
|
catch (e) {
|
|
@@ -191,6 +251,9 @@ function setupTypeScriptRuntime(cwd) {
|
|
|
191
251
|
}
|
|
192
252
|
let reactResolutionInstalled = false;
|
|
193
253
|
let originalResolveFilename = null;
|
|
254
|
+
function resolveVistaInternalRequest(request) {
|
|
255
|
+
return (0, vista_import_map_1.resolveVistaSourceRequest)(request, path_1.default.resolve(__dirname, '..'));
|
|
256
|
+
}
|
|
194
257
|
function installSingleReactResolution(cwd) {
|
|
195
258
|
if (reactResolutionInstalled)
|
|
196
259
|
return;
|
|
@@ -211,6 +274,9 @@ function installSingleReactResolution(cwd) {
|
|
|
211
274
|
}
|
|
212
275
|
originalResolveFilename = CjsModule._resolveFilename;
|
|
213
276
|
CjsModule._resolveFilename = function (request, parent, isMain, options) {
|
|
277
|
+
const vistaResolvedPath = resolveVistaInternalRequest(request);
|
|
278
|
+
if (vistaResolvedPath)
|
|
279
|
+
return vistaResolvedPath;
|
|
214
280
|
if (request === 'react')
|
|
215
281
|
return reactPath;
|
|
216
282
|
if (request === 'react-dom')
|
|
@@ -357,7 +423,7 @@ function normalizeSSRManifest(manifest) {
|
|
|
357
423
|
return manifest;
|
|
358
424
|
}
|
|
359
425
|
function loadSSRManifestFromDisk(absolutePath) {
|
|
360
|
-
const manifest = JSON.parse(fs_1.default.readFileSync(absolutePath, 'utf-8'));
|
|
426
|
+
const manifest = (0, react_client_reference_manifest_1.normalizeReactServerConsumerManifest)(JSON.parse(fs_1.default.readFileSync(absolutePath, 'utf-8')));
|
|
361
427
|
return normalizeSSRManifest(manifest);
|
|
362
428
|
}
|
|
363
429
|
function matchPattern(pathname, pattern) {
|
|
@@ -422,80 +488,152 @@ function extractParams(pathname, route) {
|
|
|
422
488
|
}
|
|
423
489
|
return params;
|
|
424
490
|
}
|
|
425
|
-
async function
|
|
491
|
+
async function createRenderableRouteModuleElement(modulePath, context, options = {}) {
|
|
426
492
|
const { params, searchParams, req } = context;
|
|
427
|
-
|
|
428
|
-
|
|
493
|
+
const RouteModule = require(modulePath);
|
|
494
|
+
const RouteComponent = RouteModule.default;
|
|
495
|
+
if (!RouteComponent) {
|
|
496
|
+
throw new Error(`Route module does not export default component: ${modulePath}`);
|
|
429
497
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
if (!PageComponent) {
|
|
433
|
-
throw new Error(`Page module does not export default component: ${route.pagePath}`);
|
|
498
|
+
if (options.evaluateMetadata && typeof RouteModule.generateMetadata === 'function') {
|
|
499
|
+
await RouteModule.generateMetadata({ params, searchParams }, RouteModule.metadata ?? {});
|
|
434
500
|
}
|
|
435
|
-
const
|
|
436
|
-
? await
|
|
501
|
+
const routeProps = typeof RouteModule.getServerProps === 'function'
|
|
502
|
+
? await RouteModule.getServerProps({ query: req.query, params, req })
|
|
437
503
|
: {};
|
|
438
|
-
|
|
439
|
-
if (
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
console.error('[vista:rsc] Error in generateMetadata:', error);
|
|
449
|
-
}
|
|
504
|
+
const moduleStem = path_1.default.basename(modulePath).replace(/\.[jt]sx?$/, '');
|
|
505
|
+
if (moduleStem === 'default' || moduleStem === 'not-found') {
|
|
506
|
+
const eagerResult = await RouteComponent({
|
|
507
|
+
...routeProps,
|
|
508
|
+
params,
|
|
509
|
+
searchParams,
|
|
510
|
+
});
|
|
511
|
+
return react_1.default.isValidElement(eagerResult)
|
|
512
|
+
? eagerResult
|
|
513
|
+
: react_1.default.createElement(react_1.default.Fragment, null, eagerResult);
|
|
450
514
|
}
|
|
451
|
-
|
|
452
|
-
...
|
|
515
|
+
return react_1.default.createElement(RouteComponent, {
|
|
516
|
+
...routeProps,
|
|
453
517
|
params,
|
|
454
518
|
searchParams,
|
|
455
519
|
});
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
520
|
+
}
|
|
521
|
+
function applySegmentBoundaries(dir, element) {
|
|
522
|
+
const loadingPath = (0, app_router_runtime_1.resolveConventionModule)(dir, 'loading');
|
|
523
|
+
const errorPath = (0, app_router_runtime_1.resolveConventionModule)(dir, 'error');
|
|
524
|
+
const loadingComponent = loadingPath
|
|
525
|
+
? (() => {
|
|
526
|
+
try {
|
|
527
|
+
return require(loadingPath).default;
|
|
528
|
+
}
|
|
529
|
+
catch {
|
|
530
|
+
return undefined;
|
|
531
|
+
}
|
|
532
|
+
})()
|
|
533
|
+
: undefined;
|
|
534
|
+
const errorComponent = errorPath
|
|
535
|
+
? (() => {
|
|
536
|
+
try {
|
|
537
|
+
return require(errorPath).default;
|
|
538
|
+
}
|
|
539
|
+
catch {
|
|
540
|
+
return undefined;
|
|
541
|
+
}
|
|
542
|
+
})()
|
|
543
|
+
: undefined;
|
|
544
|
+
let wrappedElement = element;
|
|
545
|
+
if (loadingComponent) {
|
|
546
|
+
wrappedElement = react_1.default.createElement(route_suspense_1.RouteSuspense, {
|
|
547
|
+
loadingComponent,
|
|
548
|
+
children: wrappedElement,
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
if (errorComponent) {
|
|
552
|
+
wrappedElement = react_1.default.createElement(error_boundary_1.RouteErrorBoundary, {
|
|
553
|
+
fallbackComponent: errorComponent,
|
|
554
|
+
children: wrappedElement,
|
|
555
|
+
});
|
|
490
556
|
}
|
|
491
|
-
|
|
492
|
-
|
|
557
|
+
return wrappedElement;
|
|
558
|
+
}
|
|
559
|
+
async function renderAppSubtreeElement(input) {
|
|
560
|
+
const appDir = path_1.default.join(input.cwd, 'app');
|
|
561
|
+
let element = await createRenderableRouteModuleElement(input.entryFilePath, {
|
|
562
|
+
params: input.params,
|
|
563
|
+
searchParams: input.searchParams,
|
|
564
|
+
req: input.req,
|
|
565
|
+
}, {
|
|
566
|
+
evaluateMetadata: input.evaluateLeafMetadata,
|
|
567
|
+
});
|
|
568
|
+
const directoryChain = (0, app_router_runtime_1.resolveDirectoryChain)(input.subtreeRootDir, input.entryFilePath);
|
|
569
|
+
for (let i = directoryChain.length - 1; i >= 0; i--) {
|
|
570
|
+
const dir = directoryChain[i];
|
|
571
|
+
element = applySegmentBoundaries(dir, element);
|
|
572
|
+
const layoutPath = (0, app_router_runtime_1.resolveConventionModule)(dir, 'root') ?? (0, app_router_runtime_1.resolveConventionModule)(dir, 'layout');
|
|
573
|
+
if (!layoutPath || path_1.default.resolve(layoutPath) === path_1.default.resolve(input.entryFilePath)) {
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
493
576
|
const LayoutModule = require(layoutPath);
|
|
494
577
|
const LayoutComponent = LayoutModule.default;
|
|
495
|
-
if (!LayoutComponent)
|
|
578
|
+
if (!LayoutComponent) {
|
|
496
579
|
continue;
|
|
497
|
-
|
|
580
|
+
}
|
|
581
|
+
const slotProps = {};
|
|
582
|
+
if (!input.disableParallelSlots) {
|
|
583
|
+
const slotMatches = (0, app_router_runtime_1.resolveParallelSlotMatches)({
|
|
584
|
+
appDir,
|
|
585
|
+
layoutPath,
|
|
586
|
+
pathname: input.pathname,
|
|
587
|
+
});
|
|
588
|
+
for (const slotMatch of slotMatches) {
|
|
589
|
+
slotProps[slotMatch.slotName] = await renderAppSubtreeElement({
|
|
590
|
+
subtreeRootDir: slotMatch.slotRootDir,
|
|
591
|
+
entryFilePath: slotMatch.filePath,
|
|
592
|
+
pathname: input.pathname,
|
|
593
|
+
params: {
|
|
594
|
+
...input.params,
|
|
595
|
+
...slotMatch.params,
|
|
596
|
+
},
|
|
597
|
+
searchParams: input.searchParams,
|
|
598
|
+
req: input.req,
|
|
599
|
+
cwd: input.cwd,
|
|
600
|
+
evaluateLeafMetadata: true,
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
element = react_1.default.createElement(LayoutComponent, {
|
|
605
|
+
params: input.params,
|
|
606
|
+
searchParams: input.searchParams,
|
|
607
|
+
...slotProps,
|
|
608
|
+
}, element);
|
|
498
609
|
}
|
|
610
|
+
return element;
|
|
611
|
+
}
|
|
612
|
+
async function createRouteElement(route, context, isDev, rootLayout, runtimeRoot, options = {}) {
|
|
613
|
+
const { params, searchParams, req } = context;
|
|
614
|
+
if (isDev) {
|
|
615
|
+
clearProjectRequireCache(runtimeRoot);
|
|
616
|
+
}
|
|
617
|
+
const PageModule = require(route.pagePath);
|
|
618
|
+
let metadata = { ...(rootLayout.metadata || {}) };
|
|
619
|
+
if (PageModule.metadata) {
|
|
620
|
+
metadata = { ...metadata, ...PageModule.metadata };
|
|
621
|
+
}
|
|
622
|
+
if (typeof PageModule.generateMetadata === 'function') {
|
|
623
|
+
const dynamicMeta = await PageModule.generateMetadata({ params, searchParams }, metadata);
|
|
624
|
+
metadata = { ...metadata, ...dynamicMeta };
|
|
625
|
+
}
|
|
626
|
+
const element = await renderAppSubtreeElement({
|
|
627
|
+
subtreeRootDir: path_1.default.join(runtimeRoot, 'app'),
|
|
628
|
+
entryFilePath: route.pagePath,
|
|
629
|
+
pathname: req.path,
|
|
630
|
+
params,
|
|
631
|
+
searchParams,
|
|
632
|
+
req,
|
|
633
|
+
cwd: runtimeRoot,
|
|
634
|
+
evaluateLeafMetadata: false,
|
|
635
|
+
disableParallelSlots: options.disableParallelSlots,
|
|
636
|
+
});
|
|
499
637
|
return { element, metadata, rootMode: rootLayout.mode };
|
|
500
638
|
}
|
|
501
639
|
function injectBeforeClosingTag(html, tagName, injection) {
|
|
@@ -540,9 +678,19 @@ function createHtmlDocument(appHtml, metadataHtml, chunkFiles, rootMode = 'legac
|
|
|
540
678
|
</body>
|
|
541
679
|
</html>`;
|
|
542
680
|
}
|
|
543
|
-
|
|
681
|
+
function appendVaryHeader(existing, nextValue) {
|
|
682
|
+
const values = String(existing || '')
|
|
683
|
+
.split(',')
|
|
684
|
+
.map((entry) => entry.trim())
|
|
685
|
+
.filter(Boolean);
|
|
686
|
+
if (!values.includes(nextValue)) {
|
|
687
|
+
values.push(nextValue);
|
|
688
|
+
}
|
|
689
|
+
return values.join(', ');
|
|
690
|
+
}
|
|
691
|
+
async function handleApiRoute(req, res, runtimeRoot, isDev, typedApiConfig) {
|
|
544
692
|
try {
|
|
545
|
-
const legacyApiPath = (0, typed_api_runtime_1.resolveLegacyApiRoutePath)(
|
|
693
|
+
const legacyApiPath = (0, typed_api_runtime_1.resolveLegacyApiRoutePath)(runtimeRoot, req.path);
|
|
546
694
|
if (legacyApiPath) {
|
|
547
695
|
await (0, typed_api_runtime_1.runLegacyApiRoute)({
|
|
548
696
|
req,
|
|
@@ -555,7 +703,7 @@ async function handleApiRoute(req, res, cwd, isDev, typedApiConfig) {
|
|
|
555
703
|
const typedHandled = await (0, typed_api_runtime_1.runTypedApiRoute)({
|
|
556
704
|
req,
|
|
557
705
|
res,
|
|
558
|
-
cwd,
|
|
706
|
+
cwd: runtimeRoot,
|
|
559
707
|
isDev,
|
|
560
708
|
config: typedApiConfig,
|
|
561
709
|
});
|
|
@@ -569,17 +717,33 @@ async function handleApiRoute(req, res, cwd, isDev, typedApiConfig) {
|
|
|
569
717
|
res.status(500).json({ error: 'Internal Server Error' });
|
|
570
718
|
}
|
|
571
719
|
}
|
|
572
|
-
function spawnUpstream(cwd, upstreamPort) {
|
|
720
|
+
function spawnUpstream(cwd, runtimeRoot, upstreamPort) {
|
|
573
721
|
const upstreamScript = path_1.default.join(__dirname, 'rsc-upstream.js');
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
722
|
+
try {
|
|
723
|
+
return {
|
|
724
|
+
child: (0, child_process_1.spawn)(process.execPath, ['--conditions', 'react-server', upstreamScript, '--port', String(upstreamPort)], {
|
|
725
|
+
cwd,
|
|
726
|
+
env: {
|
|
727
|
+
...process.env,
|
|
728
|
+
NODE_ENV: process.env.NODE_ENV || 'development',
|
|
729
|
+
RSC_UPSTREAM_PORT: String(upstreamPort),
|
|
730
|
+
VISTA_ARTIFACT_ROOT: cwd,
|
|
731
|
+
VISTA_RUNTIME_ROOT: runtimeRoot,
|
|
732
|
+
},
|
|
733
|
+
stdio: 'pipe',
|
|
734
|
+
}),
|
|
735
|
+
unavailableReason: null,
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
catch (error) {
|
|
739
|
+
if ((0, spawn_permissions_1.isPermissionDeniedSpawnError)(error)) {
|
|
740
|
+
return {
|
|
741
|
+
child: null,
|
|
742
|
+
unavailableReason: `spawn blocked by environment permissions (${(0, spawn_permissions_1.getErrorMessage)(error)})`,
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
throw error;
|
|
746
|
+
}
|
|
583
747
|
}
|
|
584
748
|
// ---------------------------------------------------------------------------
|
|
585
749
|
// Flight-Based SSR Rendering Helpers
|
|
@@ -598,13 +762,6 @@ async function renderFlightToHTMLStream(upstreamOrigin, pathname, search, metada
|
|
|
598
762
|
if (!upstream.ok && upstream.status !== 404) {
|
|
599
763
|
throw new Error(`Upstream returned ${upstream.status}: ${await upstream.text()}`);
|
|
600
764
|
}
|
|
601
|
-
// Short-circuit 404: serve the styled standalone page directly
|
|
602
|
-
// (the Flight element is a bare <div> with no document shell, so streaming it
|
|
603
|
-
// would produce HTML without <html>/<body> tags → browser default margins)
|
|
604
|
-
if (upstream.status === 404) {
|
|
605
|
-
res.status(404).type('text/html').send((0, not_found_page_1.getStyledNotFoundHTML)());
|
|
606
|
-
return;
|
|
607
|
-
}
|
|
608
765
|
if (!upstream.body) {
|
|
609
766
|
throw new Error('Upstream returned empty body');
|
|
610
767
|
}
|
|
@@ -774,10 +931,16 @@ function renderCompilePendingHTML() {
|
|
|
774
931
|
}
|
|
775
932
|
function startRSCServer(options = {}) {
|
|
776
933
|
const app = (0, express_1.default)();
|
|
777
|
-
const cwd = process.cwd();
|
|
934
|
+
const cwd = path_1.default.resolve(options.projectRoot || process.env.VISTA_ARTIFACT_ROOT || process.cwd());
|
|
935
|
+
const runtimeRoot = (0, runtime_artifacts_1.resolveRuntimeProjectRoot)(cwd, options.runtimeRoot);
|
|
778
936
|
const isDev = process.env.NODE_ENV !== 'production';
|
|
779
|
-
const vistaConfig = (0, config_1.loadConfig)(
|
|
937
|
+
const vistaConfig = (0, config_1.loadConfig)(runtimeRoot);
|
|
938
|
+
const cacheComponentsConfig = (0, config_1.resolveCacheComponentsConfig)(vistaConfig);
|
|
939
|
+
const engineVariant = (0, config_1.resolveAndApplyEngineVariant)(vistaConfig);
|
|
780
940
|
const typedApiConfig = (0, config_1.resolveTypedApiConfig)(vistaConfig);
|
|
941
|
+
if (process.env.VISTA_DEBUG) {
|
|
942
|
+
(0, logger_1.logInfo)(`Engine variant: ${engineVariant}`);
|
|
943
|
+
}
|
|
781
944
|
// Clean stale hot-update files from previous runs
|
|
782
945
|
cleanHotUpdateFiles(cwd);
|
|
783
946
|
// Request logger — logs GET/POST with timing
|
|
@@ -785,8 +948,13 @@ function startRSCServer(options = {}) {
|
|
|
785
948
|
const port = resolvePort(String(options.port || vistaConfig.server?.port || 3003), 3003);
|
|
786
949
|
const upstreamPort = resolvePort(String(process.env.RSC_UPSTREAM_PORT || port + 1), port + 1);
|
|
787
950
|
const upstreamOrigin = `http://127.0.0.1:${upstreamPort}`;
|
|
788
|
-
installSingleReactResolution(
|
|
789
|
-
setupTypeScriptRuntime(
|
|
951
|
+
installSingleReactResolution(runtimeRoot);
|
|
952
|
+
setupTypeScriptRuntime(runtimeRoot);
|
|
953
|
+
(0, module_compile_hook_1.installModuleCompileHook)({
|
|
954
|
+
cwd: runtimeRoot,
|
|
955
|
+
cacheComponentsEnabled: cacheComponentsConfig.enabled,
|
|
956
|
+
});
|
|
957
|
+
(0, fetch_policy_1.installSegmentFetchPolicyShim)();
|
|
790
958
|
installSSRWebpackShim();
|
|
791
959
|
const serverManifestPath = path_1.default.join(cwd, constants_1.BUILD_DIR, 'server', 'server-manifest.json');
|
|
792
960
|
if (!fs_1.default.existsSync(serverManifestPath)) {
|
|
@@ -794,7 +962,9 @@ function startRSCServer(options = {}) {
|
|
|
794
962
|
process.exit(1);
|
|
795
963
|
}
|
|
796
964
|
try {
|
|
797
|
-
(
|
|
965
|
+
if (!isDev || !options.compiler) {
|
|
966
|
+
(0, artifact_validator_1.assertVistaArtifacts)(cwd, 'rsc');
|
|
967
|
+
}
|
|
798
968
|
}
|
|
799
969
|
catch (error) {
|
|
800
970
|
console.error(error.message);
|
|
@@ -853,32 +1023,40 @@ function startRSCServer(options = {}) {
|
|
|
853
1023
|
(0, logger_1.logInfo)(`Loaded ${loadedStaticPages} pre-rendered page(s) from cache`);
|
|
854
1024
|
}
|
|
855
1025
|
}
|
|
856
|
-
const upstreamChild = spawnUpstream(cwd, upstreamPort);
|
|
857
|
-
upstreamChild.stdout.setEncoding('utf8');
|
|
858
|
-
upstreamChild.stderr.setEncoding('utf8');
|
|
859
|
-
// Always capture stderr so we can log crash reasons
|
|
860
1026
|
let upstreamStderr = '';
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
1027
|
+
const upstreamLaunch = spawnUpstream(cwd, runtimeRoot, upstreamPort);
|
|
1028
|
+
const upstreamChild = upstreamLaunch.child;
|
|
1029
|
+
let upstreamUnavailableReason = upstreamLaunch.unavailableReason;
|
|
1030
|
+
if (upstreamChild) {
|
|
1031
|
+
upstreamChild.stdout.setEncoding('utf8');
|
|
1032
|
+
upstreamChild.stderr.setEncoding('utf8');
|
|
1033
|
+
// Always capture stderr so we can log crash reasons
|
|
1034
|
+
if (process.env.VISTA_DEBUG) {
|
|
1035
|
+
upstreamChild.stdout.on('data', (chunk) => process.stdout.write(chunk));
|
|
1036
|
+
upstreamChild.stderr.on('data', (chunk) => {
|
|
1037
|
+
upstreamStderr += chunk;
|
|
1038
|
+
process.stderr.write(chunk);
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
else {
|
|
1042
|
+
upstreamChild.stdout.on('data', () => { }); // drain
|
|
1043
|
+
upstreamChild.stderr.on('data', (chunk) => {
|
|
1044
|
+
upstreamStderr += chunk;
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
upstreamChild.on('exit', (code, signal) => {
|
|
1048
|
+
if (code !== 0 || (code === null && !shutdownCalled)) {
|
|
1049
|
+
upstreamUnavailableReason = `process exited unexpectedly (code=${code ?? 'unknown'}, signal=${signal ?? 'null'})`;
|
|
1050
|
+
(0, logger_1.logError)(`Upstream exited unexpectedly (code=${code}, signal=${signal ?? 'null'})`);
|
|
1051
|
+
if (upstreamStderr.trim()) {
|
|
1052
|
+
(0, logger_1.logError)(`Upstream stderr:\n${upstreamStderr.trim()}`);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
866
1055
|
});
|
|
867
1056
|
}
|
|
868
|
-
else {
|
|
869
|
-
|
|
870
|
-
upstreamChild.stderr.on('data', (chunk) => {
|
|
871
|
-
upstreamStderr += chunk;
|
|
872
|
-
});
|
|
1057
|
+
else if (upstreamUnavailableReason) {
|
|
1058
|
+
(0, logger_1.logError)(`[vista:rsc] ${upstreamUnavailableReason}`);
|
|
873
1059
|
}
|
|
874
|
-
upstreamChild.on('exit', (code, signal) => {
|
|
875
|
-
if (code !== 0 && code !== null) {
|
|
876
|
-
(0, logger_1.logError)(`Upstream exited unexpectedly (code=${code}, signal=${signal ?? 'null'})`);
|
|
877
|
-
if (upstreamStderr.trim()) {
|
|
878
|
-
(0, logger_1.logError)(`Upstream stderr:\n${upstreamStderr.trim()}`);
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
});
|
|
882
1060
|
// Graceful shutdown — populated after all resources are created
|
|
883
1061
|
let shutdownCalled = false;
|
|
884
1062
|
let httpServer = null;
|
|
@@ -888,7 +1066,7 @@ function startRSCServer(options = {}) {
|
|
|
888
1066
|
return;
|
|
889
1067
|
shutdownCalled = true;
|
|
890
1068
|
// 1. Kill upstream RSC child
|
|
891
|
-
if (!upstreamChild.killed) {
|
|
1069
|
+
if (upstreamChild && !upstreamChild.killed) {
|
|
892
1070
|
upstreamChild.kill('SIGTERM');
|
|
893
1071
|
setTimeout(() => {
|
|
894
1072
|
if (!upstreamChild.killed)
|
|
@@ -971,7 +1149,7 @@ function startRSCServer(options = {}) {
|
|
|
971
1149
|
reloadTimer = setTimeout(() => {
|
|
972
1150
|
(0, logger_1.logEvent)('Source changed, reloading...');
|
|
973
1151
|
pushSSE('reload');
|
|
974
|
-
},
|
|
1152
|
+
}, 70);
|
|
975
1153
|
};
|
|
976
1154
|
try {
|
|
977
1155
|
const chokidar = require('chokidar');
|
|
@@ -1124,12 +1302,13 @@ function startRSCServer(options = {}) {
|
|
|
1124
1302
|
res.status(404).type('text/css').send('/* CSS not found */');
|
|
1125
1303
|
});
|
|
1126
1304
|
// Image optimization endpoint
|
|
1127
|
-
const imageHandler = (0, image_optimizer_1.createImageHandler)(
|
|
1305
|
+
const imageHandler = (0, image_optimizer_1.createImageHandler)(runtimeRoot, isDev);
|
|
1128
1306
|
app.get(constants_1.IMAGE_ENDPOINT, imageHandler);
|
|
1129
|
-
app.use(express_1.default.static(path_1.default.join(
|
|
1307
|
+
app.use(express_1.default.static(path_1.default.join(runtimeRoot, 'public')));
|
|
1130
1308
|
app.use(`${constants_1.URL_PREFIX}/static`, express_1.default.static(path_1.default.join(cwd, constants_1.BUILD_DIR, 'static')));
|
|
1131
1309
|
app.use(constants_1.URL_PREFIX, express_1.default.static(path_1.default.join(cwd, constants_1.BUILD_DIR)));
|
|
1132
1310
|
app.use(express_1.default.static(path_1.default.join(cwd, constants_1.BUILD_DIR)));
|
|
1311
|
+
const getUpstreamUnavailableMessage = () => `RSC upstream unavailable (${upstreamOrigin}/rsc): ${upstreamUnavailableReason || 'upstream process is not running'}`;
|
|
1133
1312
|
const proxyRSCRequest = async (req, res) => {
|
|
1134
1313
|
if (isDev && options.compiler) {
|
|
1135
1314
|
if (clientCompileState === 'compiling') {
|
|
@@ -1141,6 +1320,10 @@ function startRSCServer(options = {}) {
|
|
|
1141
1320
|
return;
|
|
1142
1321
|
}
|
|
1143
1322
|
}
|
|
1323
|
+
if (upstreamUnavailableReason) {
|
|
1324
|
+
res.status(503).type('text/plain').send(getUpstreamUnavailableMessage());
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1144
1327
|
try {
|
|
1145
1328
|
const fetchOptions = {
|
|
1146
1329
|
method: req.method,
|
|
@@ -1164,6 +1347,7 @@ function startRSCServer(options = {}) {
|
|
|
1164
1347
|
fetchOptions.body = Buffer.concat(chunks);
|
|
1165
1348
|
}
|
|
1166
1349
|
const upstream = await withTimeout(`${upstreamOrigin}${req.originalUrl}`, fetchOptions);
|
|
1350
|
+
applyUpstreamRevalidations(upstream, vistaDirRoot);
|
|
1167
1351
|
res.status(upstream.status);
|
|
1168
1352
|
const contentType = upstream.headers.get('content-type');
|
|
1169
1353
|
if (contentType)
|
|
@@ -1176,10 +1360,7 @@ function startRSCServer(options = {}) {
|
|
|
1176
1360
|
stream_1.Readable.fromWeb(upstream.body).pipe(res);
|
|
1177
1361
|
}
|
|
1178
1362
|
catch (error) {
|
|
1179
|
-
res
|
|
1180
|
-
.status(503)
|
|
1181
|
-
.type('text/plain')
|
|
1182
|
-
.send(`RSC upstream unavailable (${upstreamOrigin}/rsc): ${error.message}`);
|
|
1363
|
+
res.status(503).type('text/plain').send(getUpstreamUnavailableMessage());
|
|
1183
1364
|
}
|
|
1184
1365
|
};
|
|
1185
1366
|
app.get('/rsc*', proxyRSCRequest);
|
|
@@ -1199,7 +1380,7 @@ function startRSCServer(options = {}) {
|
|
|
1199
1380
|
req.path.startsWith('/_rsc')) {
|
|
1200
1381
|
return next();
|
|
1201
1382
|
}
|
|
1202
|
-
const result = await (0, middleware_runner_1.runMiddleware)(req,
|
|
1383
|
+
const result = await (0, middleware_runner_1.runMiddleware)(req, runtimeRoot, isDev);
|
|
1203
1384
|
const finalized = (0, middleware_runner_1.applyMiddlewareResult)(result, req, res);
|
|
1204
1385
|
if (finalized)
|
|
1205
1386
|
return; // response already sent (redirect / short-circuit)
|
|
@@ -1214,206 +1395,241 @@ function startRSCServer(options = {}) {
|
|
|
1214
1395
|
req.path.startsWith('/_rsc')) {
|
|
1215
1396
|
return next();
|
|
1216
1397
|
}
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
.
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1398
|
+
await (0, request_context_1.runWithRequestContext)({
|
|
1399
|
+
req,
|
|
1400
|
+
res,
|
|
1401
|
+
cwd: runtimeRoot,
|
|
1402
|
+
vistaDirRoot,
|
|
1403
|
+
urlPath: req.path,
|
|
1404
|
+
}, async () => {
|
|
1405
|
+
// ======================================================================
|
|
1406
|
+
// Structure validation gate (strict-block in dev)
|
|
1407
|
+
// ======================================================================
|
|
1408
|
+
if (isDev &&
|
|
1409
|
+
structureConfig.enabled &&
|
|
1410
|
+
structureConfig.mode === 'strict' &&
|
|
1411
|
+
currentStructureState?.state === 'error') {
|
|
1412
|
+
const overlayMessage = (0, structure_log_1.formatIssuesForOverlay)(currentStructureState, structureConfig.includeWarningsInOverlay);
|
|
1413
|
+
const errorInfo = {
|
|
1414
|
+
type: 'build',
|
|
1415
|
+
message: `Structure Validation Failed\n\n${overlayMessage}`,
|
|
1416
|
+
};
|
|
1417
|
+
res
|
|
1418
|
+
.status(500)
|
|
1419
|
+
.type('text/html')
|
|
1420
|
+
.send((0, dev_error_1.renderErrorHTML)([errorInfo]));
|
|
1238
1421
|
return;
|
|
1239
1422
|
}
|
|
1240
|
-
if (
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1423
|
+
if (isDev && options.compiler) {
|
|
1424
|
+
if (clientCompileState === 'compiling') {
|
|
1425
|
+
res.status(503).type('text/html').send(renderCompilePendingHTML());
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
if (clientCompileState === 'error') {
|
|
1429
|
+
const errorInfos = (clientCompileErrors.length > 0
|
|
1430
|
+
? clientCompileErrors
|
|
1431
|
+
: ['Unknown client build error.']).map((message) => ({ type: 'build', message }));
|
|
1432
|
+
res.status(500).type('text/html').send((0, dev_error_1.renderErrorHTML)(errorInfos));
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1246
1435
|
}
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
// cached version immediately and kick off background revalidation.
|
|
1258
|
-
// ==================================================================
|
|
1259
|
-
if (!isDev) {
|
|
1260
|
-
const cached = (0, static_cache_1.getCachedPage)(req.path);
|
|
1261
|
-
if (cached.page) {
|
|
1262
|
-
if (cached.stale) {
|
|
1263
|
-
// ISR: serve stale page immediately, revalidate in background
|
|
1264
|
-
const route = matchRoute(req.path, serverManifest.routes);
|
|
1265
|
-
if (route && !(0, static_cache_1.isRevalidating)(req.path)) {
|
|
1266
|
-
const urlPath = req.path;
|
|
1267
|
-
// Fire-and-forget background revalidation
|
|
1268
|
-
(0, static_generator_1.revalidatePath)(urlPath, route, undefined, cwd, vistaDirRoot).catch((err) => {
|
|
1269
|
-
console.error('[vista:isr] Background revalidation error:', err);
|
|
1270
|
-
});
|
|
1271
|
-
}
|
|
1436
|
+
const routeHandlerPath = (0, typed_api_runtime_1.resolveLegacyRouteHandlerPath)(runtimeRoot, req.path);
|
|
1437
|
+
if (routeHandlerPath) {
|
|
1438
|
+
try {
|
|
1439
|
+
await (0, typed_api_runtime_1.runLegacyApiRoute)({
|
|
1440
|
+
req,
|
|
1441
|
+
res,
|
|
1442
|
+
apiPath: routeHandlerPath,
|
|
1443
|
+
isDev,
|
|
1444
|
+
});
|
|
1445
|
+
return;
|
|
1272
1446
|
}
|
|
1273
|
-
|
|
1274
|
-
.
|
|
1275
|
-
.
|
|
1276
|
-
|
|
1277
|
-
|
|
1447
|
+
catch (error) {
|
|
1448
|
+
console.error('[vista:rsc] Route handler error:', error);
|
|
1449
|
+
res.status(500).json({ error: 'Internal Server Error' });
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
if (req.path.startsWith('/api/')) {
|
|
1454
|
+
await handleApiRoute(req, res, runtimeRoot, isDev, typedApiConfig);
|
|
1278
1455
|
return;
|
|
1279
1456
|
}
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
if (
|
|
1292
|
-
|
|
1293
|
-
|
|
1457
|
+
const currentRoute = matchRoute(req.path, serverManifest.routes);
|
|
1458
|
+
(0, request_context_1.setCurrentSegmentConfig)(currentRoute?.segmentConfig);
|
|
1459
|
+
// ==================================================================
|
|
1460
|
+
// Static / ISR Cache Check
|
|
1461
|
+
// ==================================================================
|
|
1462
|
+
// Before dynamic rendering, check if we have a pre-rendered page.
|
|
1463
|
+
// For ISR pages whose revalidate window has expired, serve the stale
|
|
1464
|
+
// cached version immediately and kick off background revalidation.
|
|
1465
|
+
// ==================================================================
|
|
1466
|
+
if (!isDev) {
|
|
1467
|
+
const cached = (0, static_cache_1.getCachedPage)(req.path);
|
|
1468
|
+
if (cached.page) {
|
|
1469
|
+
if (cached.stale) {
|
|
1470
|
+
// ISR: serve stale page immediately, revalidate in background
|
|
1471
|
+
const route = currentRoute;
|
|
1472
|
+
if (route && !(0, static_cache_1.isRevalidating)(req.path)) {
|
|
1473
|
+
const urlPath = req.path;
|
|
1474
|
+
// Fire-and-forget background revalidation
|
|
1475
|
+
(0, static_generator_1.revalidatePath)(urlPath, route, undefined, runtimeRoot, vistaDirRoot).catch((err) => {
|
|
1476
|
+
console.error('[vista:isr] Background revalidation error:', err);
|
|
1477
|
+
});
|
|
1478
|
+
}
|
|
1294
1479
|
}
|
|
1295
|
-
|
|
1296
|
-
|
|
1480
|
+
const pprRequestMode = (0, ppr_1.resolvePprRequestMode)({
|
|
1481
|
+
headerValue: req.headers['x-vista-prerender'],
|
|
1482
|
+
queryValue: req.query?.__vista_prerender,
|
|
1483
|
+
});
|
|
1484
|
+
const servePprShell = pprRequestMode === 'shell' && Boolean(cached.page.shellHtml);
|
|
1485
|
+
const responseHtml = servePprShell ? cached.page.shellHtml : cached.page.html;
|
|
1486
|
+
res
|
|
1487
|
+
.status(200)
|
|
1488
|
+
.type('text/html')
|
|
1489
|
+
.setHeader('X-Vista-Cache', cached.stale ? 'STALE' : 'HIT');
|
|
1490
|
+
if (cached.page.ppr?.enabled) {
|
|
1491
|
+
const responseMode = pprRequestMode === 'resume' ? 'RESUME' : servePprShell ? 'SHELL' : 'PPR';
|
|
1492
|
+
res.setHeader('X-Vista-Prerender', responseMode);
|
|
1493
|
+
res.setHeader('X-Vista-Prerender-Resume', cached.page.ppr.resumePath);
|
|
1494
|
+
res.setHeader('X-Vista-Prerender-Strategy', cached.page.ppr.strategy);
|
|
1495
|
+
res.setHeader('Vary', appendVaryHeader(res.getHeader('Vary'), 'x-vista-prerender'));
|
|
1297
1496
|
}
|
|
1497
|
+
res.setHeader('X-Vista-Route-Runtime', currentRoute?.segmentConfig.runtime ?? 'nodejs');
|
|
1498
|
+
res.send(responseHtml);
|
|
1499
|
+
return;
|
|
1298
1500
|
}
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1501
|
+
}
|
|
1502
|
+
if (upstreamUnavailableReason) {
|
|
1503
|
+
res.status(503).type('text/plain').send(getUpstreamUnavailableMessage());
|
|
1504
|
+
return;
|
|
1505
|
+
}
|
|
1506
|
+
// ==================================================================
|
|
1507
|
+
// Flight-Based SSR Path
|
|
1508
|
+
// ==================================================================
|
|
1509
|
+
// If the Flight SSR client + SSR manifest are available, render pages
|
|
1510
|
+
// by fetching the Flight stream from upstream and using
|
|
1511
|
+
// renderToPipeableStream for streaming HTML with proper hydration.
|
|
1512
|
+
// Falls back to legacy renderToString if Flight SSR is unavailable.
|
|
1513
|
+
// ==================================================================
|
|
1514
|
+
if (useFlightSSR) {
|
|
1515
|
+
try {
|
|
1516
|
+
if (isDev && resolvedSSRManifestPath && fs_1.default.existsSync(resolvedSSRManifestPath)) {
|
|
1315
1517
|
try {
|
|
1518
|
+
ssrManifest = loadSSRManifestFromDisk(resolvedSSRManifestPath);
|
|
1519
|
+
}
|
|
1520
|
+
catch {
|
|
1521
|
+
// Manifest may be mid-write during compilation; keep the last good in-memory copy.
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
// Metadata extraction: still done locally so we have <head> content
|
|
1525
|
+
const rootLayout = (0, root_resolver_1.resolveRootLayout)(runtimeRoot, isDev);
|
|
1526
|
+
const route = currentRoute;
|
|
1527
|
+
let metadataHtml = '';
|
|
1528
|
+
if (route) {
|
|
1529
|
+
if (isDev) {
|
|
1530
|
+
clearProjectRequireCache(runtimeRoot);
|
|
1531
|
+
}
|
|
1532
|
+
const PageModule = require(route.pagePath);
|
|
1533
|
+
let metadata = { ...(rootLayout.metadata || {}) };
|
|
1534
|
+
if (PageModule.metadata) {
|
|
1535
|
+
metadata = { ...metadata, ...PageModule.metadata };
|
|
1536
|
+
}
|
|
1537
|
+
if (typeof PageModule.generateMetadata === 'function') {
|
|
1538
|
+
const params = extractParams(req.path, route);
|
|
1539
|
+
const searchParams = Object.fromEntries(new URLSearchParams(req.query).entries());
|
|
1316
1540
|
const dynamicMeta = await PageModule.generateMetadata({ params, searchParams }, metadata);
|
|
1317
1541
|
metadata = { ...metadata, ...dynamicMeta };
|
|
1318
1542
|
}
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
}
|
|
1543
|
+
const { generateMetadataHtml } = require('../metadata/generate');
|
|
1544
|
+
metadataHtml = metadata ? generateMetadataHtml(metadata) : '';
|
|
1322
1545
|
}
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
}
|
|
1326
|
-
// Render the page via Flight stream → SSR
|
|
1327
|
-
await renderFlightToHTMLStream(upstreamOrigin, req.path, req.query ? new URLSearchParams(req.query).toString() : '', metadataHtml, findChunkFiles(cwd, isDev), rootLayout.mode, flightSSRClient, ssrManifest, res, isDev);
|
|
1328
|
-
return;
|
|
1329
|
-
}
|
|
1330
|
-
catch (flightError) {
|
|
1331
|
-
console.error('[vista:rsc] Flight SSR failed:', flightError.message);
|
|
1332
|
-
// If headers haven't been sent yet, show the error overlay directly.
|
|
1333
|
-
// This is much better than falling through to legacy renderToString,
|
|
1334
|
-
// which will likely hit the same error (e.g. useState in a server component).
|
|
1335
|
-
if (isDev && !res.headersSent) {
|
|
1336
|
-
const errorInfo = {
|
|
1337
|
-
type: 'runtime',
|
|
1338
|
-
message: flightError.message || 'Flight SSR Error',
|
|
1339
|
-
stack: flightError.stack,
|
|
1340
|
-
};
|
|
1341
|
-
res.status(500).send((0, dev_error_1.renderErrorHTML)([errorInfo]));
|
|
1546
|
+
// Render the page via Flight stream → SSR
|
|
1547
|
+
await renderFlightToHTMLStream(upstreamOrigin, req.path, req.query ? new URLSearchParams(req.query).toString() : '', metadataHtml, findChunkFiles(cwd, isDev), rootLayout.mode, flightSSRClient, ssrManifest, res, isDev);
|
|
1342
1548
|
return;
|
|
1343
1549
|
}
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1550
|
+
catch (flightError) {
|
|
1551
|
+
if (flightError?.name === 'NotFoundError' && !res.headersSent) {
|
|
1552
|
+
try {
|
|
1553
|
+
const rootLayout = (0, root_resolver_1.resolveRootLayout)(runtimeRoot, isDev);
|
|
1554
|
+
const route = currentRoute;
|
|
1555
|
+
if (route) {
|
|
1556
|
+
const segmentNotFoundPath = (0, app_router_runtime_1.resolveNearestSegmentNotFoundPath)(path_1.default.join(runtimeRoot, 'app'), route.routeDir);
|
|
1557
|
+
if (segmentNotFoundPath) {
|
|
1558
|
+
const params = extractParams(req.path, route);
|
|
1559
|
+
const searchParams = Object.fromEntries(new URLSearchParams(req.query).entries());
|
|
1560
|
+
const notFoundResult = await createRouteElement({
|
|
1561
|
+
...route,
|
|
1562
|
+
pagePath: segmentNotFoundPath,
|
|
1563
|
+
}, { params, searchParams, req }, isDev, rootLayout, runtimeRoot, { disableParallelSlots: true });
|
|
1564
|
+
const html = (0, server_1.renderToString)(notFoundResult.element);
|
|
1565
|
+
const { generateMetadataHtml } = require('../metadata/generate');
|
|
1566
|
+
const metadataHtml = notFoundResult.metadata
|
|
1567
|
+
? generateMetadataHtml(notFoundResult.metadata)
|
|
1568
|
+
: '';
|
|
1569
|
+
res
|
|
1570
|
+
.status(404)
|
|
1571
|
+
.type('text/html')
|
|
1572
|
+
.send(createHtmlDocument(html, metadataHtml, findChunkFiles(cwd, isDev), notFoundResult.rootMode));
|
|
1573
|
+
return;
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
catch (notFoundError) {
|
|
1578
|
+
console.error('[vista:rsc] Failed to render segment not-found fallback:', notFoundError);
|
|
1579
|
+
}
|
|
1354
1580
|
}
|
|
1355
|
-
|
|
1356
|
-
|
|
1581
|
+
console.error('[vista:rsc] Flight SSR failed:', flightError.message);
|
|
1582
|
+
// If headers haven't been sent yet, show the error overlay directly.
|
|
1583
|
+
// This is much better than falling through to legacy renderToString,
|
|
1584
|
+
// which will likely hit the same error (e.g. useState in a server component).
|
|
1585
|
+
if (isDev && !res.headersSent) {
|
|
1586
|
+
const errorInfo = {
|
|
1587
|
+
type: 'runtime',
|
|
1588
|
+
message: flightError.message || 'Flight SSR Error',
|
|
1589
|
+
stack: flightError.stack,
|
|
1590
|
+
};
|
|
1591
|
+
res.status(500).send((0, dev_error_1.renderErrorHTML)([errorInfo]));
|
|
1592
|
+
return;
|
|
1593
|
+
}
|
|
1594
|
+
// If headers were already sent (stream was partially flushed),
|
|
1595
|
+
// we can't change the status code, but we can inject an error
|
|
1596
|
+
// overlay script at the end of the stream.
|
|
1597
|
+
if (isDev && res.headersSent) {
|
|
1598
|
+
try {
|
|
1599
|
+
const errMsg = (flightError.message || 'Flight SSR Error')
|
|
1600
|
+
.replace(/'/g, "\\'")
|
|
1601
|
+
.replace(/\n/g, '\\n');
|
|
1602
|
+
res.write(`<script>document.body.innerHTML='';document.body.style.background='#1a1a2e';document.body.style.color='#ff6b6b';document.body.style.fontFamily='monospace';document.body.style.padding='40px';document.body.innerHTML='<h2 style="color:#ff6b6b">\\u26a0 Server Error</h2><pre style="white-space:pre-wrap;color:#ffa07a">${errMsg}</pre>';</script>`);
|
|
1603
|
+
res.end();
|
|
1604
|
+
}
|
|
1605
|
+
catch {
|
|
1606
|
+
res.end();
|
|
1607
|
+
}
|
|
1608
|
+
return;
|
|
1357
1609
|
}
|
|
1358
|
-
return;
|
|
1359
1610
|
}
|
|
1360
1611
|
}
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
res
|
|
1375
|
-
.status(503)
|
|
1376
|
-
.type('text/plain')
|
|
1377
|
-
.send(`RSC upstream unavailable (${upstreamOrigin}/rsc): ${error.message}`);
|
|
1378
|
-
return;
|
|
1379
|
-
}
|
|
1380
|
-
try {
|
|
1381
|
-
const rootLayout = (0, root_resolver_1.resolveRootLayout)(cwd, isDev);
|
|
1382
|
-
const route = matchRoute(req.path, serverManifest.routes);
|
|
1383
|
-
if (!route) {
|
|
1384
|
-
const resolvedNotFound = (0, root_resolver_1.resolveNotFoundComponent)(cwd, rootLayout, isDev);
|
|
1385
|
-
if (resolvedNotFound) {
|
|
1386
|
-
const notFoundElement = react_1.default.createElement(resolvedNotFound.component, {
|
|
1387
|
-
params: {},
|
|
1388
|
-
searchParams: {},
|
|
1389
|
-
});
|
|
1390
|
-
const wrapped = react_1.default.createElement(rootLayout.component, { params: {}, searchParams: {} }, notFoundElement);
|
|
1391
|
-
const html = (0, server_1.renderToString)(wrapped);
|
|
1392
|
-
res
|
|
1393
|
-
.status(404)
|
|
1394
|
-
.type('text/html')
|
|
1395
|
-
.send(createHtmlDocument(html, '', findChunkFiles(cwd, isDev), rootLayout.mode));
|
|
1396
|
-
return;
|
|
1397
|
-
}
|
|
1398
|
-
res.status(404).type('text/html').send((0, not_found_page_1.getStyledNotFoundHTML)());
|
|
1612
|
+
// ==================================================================
|
|
1613
|
+
// Legacy Fallback: Direct renderToString
|
|
1614
|
+
// ==================================================================
|
|
1615
|
+
// Used when Flight SSR is unavailable or fails. This path does NOT
|
|
1616
|
+
// go through the Flight protocol — it requires page modules directly
|
|
1617
|
+
// and renders them with renderToString (synchronous, no streaming).
|
|
1618
|
+
// ==================================================================
|
|
1619
|
+
try {
|
|
1620
|
+
// Check upstream availability for the legacy path
|
|
1621
|
+
await withTimeout(`${upstreamOrigin}/rsc/`, { headers: { Accept: 'text/x-component' } }, 3000);
|
|
1622
|
+
}
|
|
1623
|
+
catch (error) {
|
|
1624
|
+
res.status(503).type('text/plain').send(getUpstreamUnavailableMessage());
|
|
1399
1625
|
return;
|
|
1400
1626
|
}
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
res
|
|
1408
|
-
.status(200)
|
|
1409
|
-
.type('text/html')
|
|
1410
|
-
.send(createHtmlDocument(appHtml, metadataHtml, findChunkFiles(cwd, isDev), rootMode));
|
|
1411
|
-
}
|
|
1412
|
-
catch (error) {
|
|
1413
|
-
if (error?.name === 'NotFoundError') {
|
|
1414
|
-
try {
|
|
1415
|
-
const rootLayout = (0, root_resolver_1.resolveRootLayout)(cwd, isDev);
|
|
1416
|
-
const resolvedNotFound = (0, root_resolver_1.resolveNotFoundComponent)(cwd, rootLayout, isDev);
|
|
1627
|
+
try {
|
|
1628
|
+
const rootLayout = (0, root_resolver_1.resolveRootLayout)(runtimeRoot, isDev);
|
|
1629
|
+
const route = currentRoute;
|
|
1630
|
+
(0, request_context_1.setCurrentSegmentConfig)(route?.segmentConfig);
|
|
1631
|
+
if (!route) {
|
|
1632
|
+
const resolvedNotFound = (0, root_resolver_1.resolveNotFoundComponent)(runtimeRoot, rootLayout, isDev);
|
|
1417
1633
|
if (resolvedNotFound) {
|
|
1418
1634
|
const notFoundElement = react_1.default.createElement(resolvedNotFound.component, {
|
|
1419
1635
|
params: {},
|
|
@@ -1430,23 +1646,79 @@ function startRSCServer(options = {}) {
|
|
|
1430
1646
|
res.status(404).type('text/html').send((0, not_found_page_1.getStyledNotFoundHTML)());
|
|
1431
1647
|
return;
|
|
1432
1648
|
}
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
};
|
|
1444
|
-
res.status(500).send((0, dev_error_1.renderErrorHTML)([errorInfo]));
|
|
1649
|
+
const params = extractParams(req.path, route);
|
|
1650
|
+
const searchParams = Object.fromEntries(new URLSearchParams(req.query).entries());
|
|
1651
|
+
const { element, metadata, rootMode } = await createRouteElement(route, { params, searchParams, req }, isDev, rootLayout, runtimeRoot);
|
|
1652
|
+
const appHtml = (0, server_1.renderToString)(element);
|
|
1653
|
+
const { generateMetadataHtml } = require('../metadata/generate');
|
|
1654
|
+
const metadataHtml = metadata ? generateMetadataHtml(metadata) : '';
|
|
1655
|
+
res
|
|
1656
|
+
.status(200)
|
|
1657
|
+
.type('text/html')
|
|
1658
|
+
.send(createHtmlDocument(appHtml, metadataHtml, findChunkFiles(cwd, isDev), rootMode));
|
|
1445
1659
|
}
|
|
1446
|
-
|
|
1447
|
-
|
|
1660
|
+
catch (error) {
|
|
1661
|
+
if (error?.name === 'NotFoundError') {
|
|
1662
|
+
try {
|
|
1663
|
+
const rootLayout = (0, root_resolver_1.resolveRootLayout)(runtimeRoot, isDev);
|
|
1664
|
+
const route = currentRoute;
|
|
1665
|
+
(0, request_context_1.setCurrentSegmentConfig)(route?.segmentConfig);
|
|
1666
|
+
if (route) {
|
|
1667
|
+
const segmentNotFoundPath = (0, app_router_runtime_1.resolveNearestSegmentNotFoundPath)(path_1.default.join(runtimeRoot, 'app'), route.routeDir);
|
|
1668
|
+
if (segmentNotFoundPath) {
|
|
1669
|
+
const params = extractParams(req.path, route);
|
|
1670
|
+
const searchParams = Object.fromEntries(new URLSearchParams(req.query).entries());
|
|
1671
|
+
const notFoundResult = await createRouteElement({
|
|
1672
|
+
...route,
|
|
1673
|
+
pagePath: segmentNotFoundPath,
|
|
1674
|
+
}, { params, searchParams, req }, isDev, rootLayout, runtimeRoot, { disableParallelSlots: true });
|
|
1675
|
+
const html = (0, server_1.renderToString)(notFoundResult.element);
|
|
1676
|
+
const { generateMetadataHtml } = require('../metadata/generate');
|
|
1677
|
+
const metadataHtml = notFoundResult.metadata
|
|
1678
|
+
? generateMetadataHtml(notFoundResult.metadata)
|
|
1679
|
+
: '';
|
|
1680
|
+
res
|
|
1681
|
+
.status(404)
|
|
1682
|
+
.type('text/html')
|
|
1683
|
+
.send(createHtmlDocument(html, metadataHtml, findChunkFiles(cwd, isDev), notFoundResult.rootMode));
|
|
1684
|
+
return;
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
const resolvedNotFound = (0, root_resolver_1.resolveNotFoundComponent)(runtimeRoot, rootLayout, isDev);
|
|
1688
|
+
if (resolvedNotFound) {
|
|
1689
|
+
const notFoundElement = react_1.default.createElement(resolvedNotFound.component, {
|
|
1690
|
+
params: {},
|
|
1691
|
+
searchParams: {},
|
|
1692
|
+
});
|
|
1693
|
+
const wrapped = react_1.default.createElement(rootLayout.component, { params: {}, searchParams: {} }, notFoundElement);
|
|
1694
|
+
const html = (0, server_1.renderToString)(wrapped);
|
|
1695
|
+
res
|
|
1696
|
+
.status(404)
|
|
1697
|
+
.type('text/html')
|
|
1698
|
+
.send(createHtmlDocument(html, '', findChunkFiles(cwd, isDev), rootLayout.mode));
|
|
1699
|
+
return;
|
|
1700
|
+
}
|
|
1701
|
+
res.status(404).type('text/html').send((0, not_found_page_1.getStyledNotFoundHTML)());
|
|
1702
|
+
return;
|
|
1703
|
+
}
|
|
1704
|
+
catch (notFoundError) {
|
|
1705
|
+
console.error('[vista:rsc] Failed to render NotFoundError fallback:', notFoundError);
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
console.error('[vista:rsc] Render error:', error);
|
|
1709
|
+
if (isDev) {
|
|
1710
|
+
const errorInfo = {
|
|
1711
|
+
type: 'runtime',
|
|
1712
|
+
message: error.message || 'Unknown Server Error',
|
|
1713
|
+
stack: error.stack,
|
|
1714
|
+
};
|
|
1715
|
+
res.status(500).send((0, dev_error_1.renderErrorHTML)([errorInfo]));
|
|
1716
|
+
}
|
|
1717
|
+
else {
|
|
1718
|
+
res.status(500).send('<h1>Internal Server Error</h1>');
|
|
1719
|
+
}
|
|
1448
1720
|
}
|
|
1449
|
-
}
|
|
1721
|
+
});
|
|
1450
1722
|
});
|
|
1451
1723
|
const server = app.listen(port, () => {
|
|
1452
1724
|
(0, logger_1.printServerReady)({ port, mode: 'rsc', rscFlight: useFlightSSR });
|