@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.
Files changed (77) hide show
  1. package/bin/vista.js +171 -35
  2. package/dist/bin/build-rsc-flashpack.d.ts +4 -0
  3. package/dist/bin/build-rsc-flashpack.js +29 -0
  4. package/dist/bin/build-rsc.js +47 -16
  5. package/dist/bin/build.js +36 -13
  6. package/dist/bin/devtools-indicator-snippet.js +30 -0
  7. package/dist/bin/file-scanner.d.ts +1 -1
  8. package/dist/bin/file-scanner.js +8 -0
  9. package/dist/bin/flashpack-runner.d.ts +1 -0
  10. package/dist/bin/flashpack-runner.js +61 -0
  11. package/dist/bin/server-component-plugin.d.ts +6 -4
  12. package/dist/bin/server-component-plugin.js +22 -69
  13. package/dist/bin/webpack.config.d.ts +3 -0
  14. package/dist/bin/webpack.config.js +12 -3
  15. package/dist/build/manifest.d.ts +17 -3
  16. package/dist/build/manifest.js +99 -23
  17. package/dist/build/rsc/compiler.d.ts +2 -0
  18. package/dist/build/rsc/compiler.js +25 -5
  19. package/dist/build/rsc/react-client-reference-manifest.d.ts +22 -0
  20. package/dist/build/rsc/react-client-reference-manifest.js +219 -0
  21. package/dist/build/rsc/server-manifest.d.ts +23 -2
  22. package/dist/build/rsc/server-manifest.js +162 -24
  23. package/dist/build/standalone.d.ts +31 -0
  24. package/dist/build/standalone.js +334 -0
  25. package/dist/client/rsc-router.d.ts +31 -0
  26. package/dist/client/rsc-router.js +89 -0
  27. package/dist/config.d.ts +23 -0
  28. package/dist/config.js +106 -5
  29. package/dist/constants.d.ts +2 -0
  30. package/dist/constants.js +3 -1
  31. package/dist/flashpack/command.d.ts +8 -0
  32. package/dist/flashpack/command.js +134 -0
  33. package/dist/flashpack/runtime.d.ts +39 -0
  34. package/dist/flashpack/runtime.js +249 -0
  35. package/dist/server/app-router-runtime.d.ts +26 -0
  36. package/dist/server/app-router-runtime.js +321 -0
  37. package/dist/server/artifact-validator.js +21 -1
  38. package/dist/server/cache.d.ts +10 -0
  39. package/dist/server/cache.js +270 -0
  40. package/dist/server/client-boundary.js +20 -2
  41. package/dist/server/engine.js +236 -159
  42. package/dist/server/fetch-policy.d.ts +2 -0
  43. package/dist/server/fetch-policy.js +123 -0
  44. package/dist/server/index.d.ts +7 -0
  45. package/dist/server/index.js +131 -22
  46. package/dist/server/module-boundary-validator.d.ts +15 -0
  47. package/dist/server/module-boundary-validator.js +262 -0
  48. package/dist/server/module-compile-hook.d.ts +7 -0
  49. package/dist/server/module-compile-hook.js +662 -0
  50. package/dist/server/ppr.d.ts +18 -0
  51. package/dist/server/ppr.js +59 -0
  52. package/dist/server/request-context.d.ts +31 -0
  53. package/dist/server/request-context.js +95 -0
  54. package/dist/server/rsc-engine-flashpack.d.ts +4 -0
  55. package/dist/server/rsc-engine-flashpack.js +27 -0
  56. package/dist/server/rsc-engine.d.ts +2 -0
  57. package/dist/server/rsc-engine.js +589 -317
  58. package/dist/server/rsc-upstream.js +539 -233
  59. package/dist/server/runtime-actions.d.ts +5 -0
  60. package/dist/server/runtime-actions.js +80 -0
  61. package/dist/server/runtime-artifacts.d.ts +11 -0
  62. package/dist/server/runtime-artifacts.js +35 -0
  63. package/dist/server/segment-config.d.ts +37 -0
  64. package/dist/server/segment-config.js +205 -0
  65. package/dist/server/spawn-permissions.d.ts +2 -0
  66. package/dist/server/spawn-permissions.js +21 -0
  67. package/dist/server/static-cache.d.ts +15 -1
  68. package/dist/server/static-cache.js +83 -3
  69. package/dist/server/static-generator.js +254 -100
  70. package/dist/server/structure-validator.d.ts +1 -1
  71. package/dist/server/structure-validator.js +26 -5
  72. package/dist/server/structure-watch.js +1 -1
  73. package/dist/server/typed-api-runtime.d.ts +1 -0
  74. package/dist/server/typed-api-runtime.js +145 -25
  75. package/dist/server/vista-import-map.d.ts +1 -0
  76. package/dist/server/vista-import-map.js +123 -0
  77. package/package.json +13 -1
@@ -19,10 +19,40 @@ const fs_1 = __importDefault(require("fs"));
19
19
  const child_process_1 = require("child_process");
20
20
  const constants_1 = require("../constants");
21
21
  const static_cache_1 = require("./static-cache");
22
+ const module_compile_hook_1 = require("./module-compile-hook");
23
+ const request_context_1 = require("./request-context");
24
+ const app_router_runtime_1 = require("./app-router-runtime");
25
+ const fetch_policy_1 = require("./fetch-policy");
26
+ const spawn_permissions_1 = require("./spawn-permissions");
27
+ const config_1 = require("../config");
28
+ const ppr_1 = require("./ppr");
29
+ const vista_import_map_1 = require("./vista-import-map");
22
30
  const CjsModule = require('module');
23
31
  let staticRuntimeReady = false;
24
32
  let reactResolutionInstalled = false;
25
33
  let originalResolveFilename = null;
34
+ function resolveFromWorkspace(specifier, cwd) {
35
+ const searchRoots = [
36
+ cwd,
37
+ path_1.default.resolve(cwd, '..'),
38
+ path_1.default.resolve(cwd, '..', '..'),
39
+ path_1.default.resolve(cwd, '..', '..', 'rsc'),
40
+ path_1.default.resolve(cwd, '..', '..', '..'),
41
+ path_1.default.resolve(cwd, '..', '..', '..', 'rsc'),
42
+ ];
43
+ for (const root of searchRoots) {
44
+ try {
45
+ return require.resolve(specifier, { paths: [root] });
46
+ }
47
+ catch {
48
+ // continue
49
+ }
50
+ }
51
+ return require.resolve(specifier);
52
+ }
53
+ function resolveVistaInternalRequest(request) {
54
+ return (0, vista_import_map_1.resolveVistaSourceRequest)(request, path_1.default.resolve(__dirname, '..'));
55
+ }
26
56
  function installSingleReactResolution(cwd) {
27
57
  if (reactResolutionInstalled)
28
58
  return;
@@ -43,6 +73,9 @@ function installSingleReactResolution(cwd) {
43
73
  }
44
74
  originalResolveFilename = CjsModule._resolveFilename;
45
75
  CjsModule._resolveFilename = function (request, parent, isMain, options) {
76
+ const vistaResolvedPath = resolveVistaInternalRequest(request);
77
+ if (vistaResolvedPath)
78
+ return vistaResolvedPath;
46
79
  if (request === 'react')
47
80
  return reactPath;
48
81
  if (request === 'react-dom')
@@ -71,15 +104,24 @@ function installSingleReactResolution(cwd) {
71
104
  }
72
105
  function setupTypeScriptRuntime(cwd) {
73
106
  try {
74
- const swcPath = require.resolve('@swc-node/register', { paths: [cwd] });
75
- require(swcPath);
107
+ const swcRegisterPath = resolveFromWorkspace('@swc-node/register/register', cwd);
108
+ const typescriptPath = resolveFromWorkspace('typescript', cwd);
109
+ const { register } = require(swcRegisterPath);
110
+ const ts = require(typescriptPath);
111
+ register({
112
+ module: ts.ModuleKind.CommonJS,
113
+ jsx: ts.JsxEmit.ReactJSX,
114
+ moduleResolution: ts.ModuleResolutionKind.Node16,
115
+ esModuleInterop: true,
116
+ allowJs: true,
117
+ });
76
118
  return;
77
119
  }
78
120
  catch {
79
121
  // fallback
80
122
  }
81
123
  try {
82
- const tsNodePath = require.resolve('ts-node', { paths: [cwd] });
124
+ const tsNodePath = resolveFromWorkspace('ts-node', cwd);
83
125
  require(tsNodePath).register({
84
126
  transpileOnly: true,
85
127
  compilerOptions: {
@@ -87,6 +129,7 @@ function setupTypeScriptRuntime(cwd) {
87
129
  jsx: 'react-jsx',
88
130
  moduleResolution: 'node16',
89
131
  esModuleInterop: true,
132
+ allowJs: true,
90
133
  },
91
134
  });
92
135
  return;
@@ -95,8 +138,8 @@ function setupTypeScriptRuntime(cwd) {
95
138
  // fallback
96
139
  }
97
140
  try {
98
- require.resolve('tsx', { paths: [cwd] });
99
- require('tsx/cjs');
141
+ const tsxPath = resolveFromWorkspace('tsx/cjs', cwd);
142
+ require(tsxPath);
100
143
  }
101
144
  catch {
102
145
  // no transpiler available
@@ -111,8 +154,14 @@ function setupStaticGenerationRuntime(cwd) {
111
154
  m.exports = {};
112
155
  }
113
156
  };
157
+ const cacheComponentsConfig = (0, config_1.resolveCacheComponentsConfig)((0, config_1.loadConfig)(cwd));
114
158
  installSingleReactResolution(cwd);
115
159
  setupTypeScriptRuntime(cwd);
160
+ (0, module_compile_hook_1.installModuleCompileHook)({
161
+ cwd,
162
+ cacheComponentsEnabled: cacheComponentsConfig.enabled,
163
+ });
164
+ (0, fetch_policy_1.installSegmentFetchPolicyShim)();
116
165
  staticRuntimeReady = true;
117
166
  }
118
167
  // ---------------------------------------------------------------------------
@@ -178,97 +227,170 @@ function expandPattern(pattern, params) {
178
227
  * server bundle. For RSC mode, the actual Flight prerendering is
179
228
  * handled by the upstream process.
180
229
  */
181
- async function prerenderPage(urlPath, route, params, cwd) {
230
+ async function prerenderPage(urlPath, route, params, cwd, vistaDirRoot, appPprEnabled) {
182
231
  setupStaticGenerationRuntime(cwd);
183
- try {
184
- const React = require('react');
185
- const { renderToString } = require('react-dom/server');
186
- const isAsyncComponent = (component) => {
187
- return (typeof component === 'function' &&
188
- component.constructor &&
189
- component.constructor.name === 'AsyncFunction');
190
- };
191
- const renderComponent = async (component, props, child) => {
192
- if (isAsyncComponent(component)) {
193
- const asyncProps = child === undefined ? props : { ...props, children: child };
194
- return component(asyncProps);
195
- }
196
- if (child === undefined) {
197
- return React.createElement(component, props);
232
+ return (0, request_context_1.runWithRequestContext)({
233
+ cwd,
234
+ vistaDirRoot,
235
+ urlPath,
236
+ segmentConfig: route.segmentConfig,
237
+ }, async () => {
238
+ try {
239
+ const React = require('react');
240
+ const { renderToString } = require('react-dom/server');
241
+ const isAsyncComponent = (component) => {
242
+ return (typeof component === 'function' &&
243
+ component.constructor &&
244
+ component.constructor.name === 'AsyncFunction');
245
+ };
246
+ const renderComponent = async (component, props, child) => {
247
+ if (isAsyncComponent(component)) {
248
+ const asyncProps = child === undefined ? props : { ...props, children: child };
249
+ return component(asyncProps);
250
+ }
251
+ if (child === undefined) {
252
+ return React.createElement(component, props);
253
+ }
254
+ return React.createElement(component, props, child);
255
+ };
256
+ const renderStaticSubtree = async (input) => {
257
+ const appDir = path_1.default.join(cwd, 'app');
258
+ const RouteModule = require(input.entryFilePath);
259
+ const RouteComponent = RouteModule.default;
260
+ if (!RouteComponent) {
261
+ throw new Error(`Route module does not export default component: ${input.entryFilePath}`);
262
+ }
263
+ let subtree = await renderComponent(RouteComponent, {
264
+ params: input.params,
265
+ searchParams: input.searchParams,
266
+ });
267
+ const directoryChain = (0, app_router_runtime_1.resolveDirectoryChain)(input.subtreeRootDir, input.entryFilePath);
268
+ for (let i = directoryChain.length - 1; i >= 0; i--) {
269
+ const dir = directoryChain[i];
270
+ const layoutPath = (0, app_router_runtime_1.resolveConventionModule)(dir, 'root') ?? (0, app_router_runtime_1.resolveConventionModule)(dir, 'layout');
271
+ if (!layoutPath || path_1.default.resolve(layoutPath) === path_1.default.resolve(input.entryFilePath)) {
272
+ continue;
273
+ }
274
+ const LayoutModule = require(layoutPath);
275
+ const LayoutComponent = LayoutModule.default;
276
+ if (!LayoutComponent) {
277
+ continue;
278
+ }
279
+ const slotProps = {};
280
+ if (!input.disableParallelSlots) {
281
+ const slotMatches = (0, app_router_runtime_1.resolveParallelSlotMatches)({
282
+ appDir,
283
+ layoutPath,
284
+ pathname: input.pathname,
285
+ });
286
+ for (const slotMatch of slotMatches) {
287
+ slotProps[slotMatch.slotName] = await renderStaticSubtree({
288
+ subtreeRootDir: slotMatch.slotRootDir,
289
+ entryFilePath: slotMatch.filePath,
290
+ pathname: input.pathname,
291
+ params: {
292
+ ...input.params,
293
+ ...slotMatch.params,
294
+ },
295
+ searchParams: input.searchParams,
296
+ });
297
+ }
298
+ }
299
+ subtree = await renderComponent(LayoutComponent, {
300
+ params: input.params,
301
+ searchParams: input.searchParams,
302
+ ...slotProps,
303
+ }, subtree);
304
+ }
305
+ return subtree;
306
+ };
307
+ // Load page component from webpack-built server bundle
308
+ const pageModule = require(route.pagePath);
309
+ const PageComponent = pageModule.default;
310
+ if (!PageComponent) {
311
+ console.warn(`[vista:ssg] No default export in ${route.pagePath}`);
312
+ return null;
198
313
  }
199
- return React.createElement(component, props, child);
200
- };
201
- // Load page component from webpack-built server bundle
202
- const pageModule = require(route.pagePath);
203
- const PageComponent = pageModule.default;
204
- if (!PageComponent) {
205
- console.warn(`[vista:ssg] No default export in ${route.pagePath}`);
206
- return null;
207
- }
208
- let metadata = {};
209
- const searchParams = {};
210
- for (const layoutPath of route.layoutPaths) {
211
- try {
212
- const layoutModule = require(layoutPath);
213
- if (layoutModule?.metadata && typeof layoutModule.metadata === 'object') {
214
- metadata = { ...metadata, ...layoutModule.metadata };
314
+ let metadata = {};
315
+ const searchParams = {};
316
+ for (const layoutPath of route.layoutPaths) {
317
+ try {
318
+ const layoutModule = require(layoutPath);
319
+ if (layoutModule?.metadata && typeof layoutModule.metadata === 'object') {
320
+ metadata = { ...metadata, ...layoutModule.metadata };
321
+ }
322
+ }
323
+ catch {
324
+ // Ignore layout metadata failures for static generation.
215
325
  }
216
326
  }
217
- catch {
218
- // Ignore layout metadata failures for static generation.
327
+ if (pageModule.metadata && typeof pageModule.metadata === 'object') {
328
+ metadata = { ...metadata, ...pageModule.metadata };
219
329
  }
220
- }
221
- if (pageModule.metadata && typeof pageModule.metadata === 'object') {
222
- metadata = { ...metadata, ...pageModule.metadata };
223
- }
224
- if (typeof pageModule.generateMetadata === 'function') {
225
- try {
226
- const dynamicMeta = await pageModule.generateMetadata({ params: params || {}, searchParams }, metadata);
227
- if (dynamicMeta && typeof dynamicMeta === 'object') {
228
- metadata = { ...metadata, ...dynamicMeta };
330
+ if (typeof pageModule.generateMetadata === 'function') {
331
+ try {
332
+ const dynamicMeta = await pageModule.generateMetadata({ params: params || {}, searchParams }, metadata);
333
+ if (dynamicMeta && typeof dynamicMeta === 'object') {
334
+ metadata = { ...metadata, ...dynamicMeta };
335
+ }
336
+ }
337
+ catch (metadataError) {
338
+ console.warn(`[vista:ssg] generateMetadata failed for ${urlPath}:`, metadataError?.message || String(metadataError));
229
339
  }
230
340
  }
231
- catch (metadataError) {
232
- console.warn(`[vista:ssg] generateMetadata failed for ${urlPath}:`, metadataError?.message || String(metadataError));
233
- }
234
- }
235
- let metadataHtml = '';
236
- try {
237
- const { generateMetadataHtml } = require('../metadata/generate');
238
- metadataHtml = generateMetadataHtml(metadata);
239
- }
240
- catch {
241
- metadataHtml = '';
242
- }
243
- // Build the element, passing params as props
244
- let element = await renderComponent(PageComponent, { params: params || {} });
245
- // Wrap in layouts (outside-in)
246
- for (let i = route.layoutPaths.length - 1; i >= 0; i--) {
341
+ let metadataHtml = '';
247
342
  try {
248
- const layoutModule = require(route.layoutPaths[i]);
249
- const LayoutComponent = layoutModule.default;
250
- if (LayoutComponent) {
251
- element = await renderComponent(LayoutComponent, { params: params || {}, searchParams: {} }, element);
252
- }
343
+ const { generateMetadataHtml } = require('../metadata/generate');
344
+ metadataHtml = generateMetadataHtml(metadata);
253
345
  }
254
346
  catch {
255
- // Skip layout if it fails to load
347
+ metadataHtml = '';
348
+ }
349
+ const element = await renderStaticSubtree({
350
+ subtreeRootDir: path_1.default.join(cwd, 'app'),
351
+ entryFilePath: route.pagePath,
352
+ pathname: urlPath,
353
+ params: params || {},
354
+ searchParams,
355
+ });
356
+ const pprEnabled = (0, ppr_1.isRoutePPREligible)(route, appPprEnabled);
357
+ let shellHtml;
358
+ let pprInfo = undefined;
359
+ if (pprEnabled && route.loadingPath) {
360
+ try {
361
+ const shellElement = await renderStaticSubtree({
362
+ subtreeRootDir: path_1.default.join(cwd, 'app'),
363
+ entryFilePath: route.loadingPath,
364
+ pathname: urlPath,
365
+ params: params || {},
366
+ searchParams,
367
+ });
368
+ const renderedShellHtml = renderToString(shellElement);
369
+ shellHtml = (0, ppr_1.injectPprResumeBootstrap)(wrapInDocument(`${renderedShellHtml}\n<!--vista:ppr-shell-->`, urlPath, metadataHtml, cwd), urlPath);
370
+ pprInfo = (0, ppr_1.createPartialPrerenderInfo)(urlPath);
371
+ }
372
+ catch (shellError) {
373
+ console.warn(`[vista:ppr] Failed to generate shell for ${urlPath}:`, shellError?.message || String(shellError));
374
+ }
256
375
  }
376
+ // Render to HTML string
377
+ const html = renderToString(element);
378
+ return {
379
+ html: wrapInDocument(html, urlPath, metadataHtml, cwd),
380
+ shellHtml,
381
+ generatedAt: Date.now(),
382
+ revalidate: route.revalidate || 0,
383
+ routePattern: route.pattern,
384
+ params,
385
+ tags: (0, request_context_1.consumeTrackedTags)(),
386
+ ppr: pprInfo,
387
+ };
257
388
  }
258
- // Render to HTML string
259
- const html = renderToString(element);
260
- return {
261
- html: wrapInDocument(html, urlPath, metadataHtml, cwd),
262
- generatedAt: Date.now(),
263
- revalidate: route.revalidate || 0,
264
- routePattern: route.pattern,
265
- params,
266
- };
267
- }
268
- catch (err) {
269
- console.error(`[vista:ssg] Error pre-rendering ${urlPath}:`, err?.message || String(err));
270
- return null;
271
- }
389
+ catch (err) {
390
+ console.error(`[vista:ssg] Error pre-rendering ${urlPath}:`, err?.message || String(err));
391
+ return null;
392
+ }
393
+ });
272
394
  }
273
395
  /**
274
396
  * Wrap rendered HTML in a basic document shell.
@@ -381,16 +503,41 @@ async function startStaticFlightUpstream(cwd) {
381
503
  if (!Number.isInteger(port) || port < 1 || port > 65535) {
382
504
  return null;
383
505
  }
384
- const child = (0, child_process_1.spawn)(process.execPath, ['--conditions', 'react-server', upstreamScript, '--port', String(port)], {
385
- cwd,
386
- env: {
387
- ...process.env,
388
- NODE_ENV: process.env.NODE_ENV || 'production',
389
- RSC_UPSTREAM_PORT: String(port),
390
- },
391
- stdio: 'pipe',
392
- });
393
- await waitForUpstreamReady(child, 12000);
506
+ let child;
507
+ try {
508
+ child = (0, child_process_1.spawn)(process.execPath, ['--conditions', 'react-server', upstreamScript, '--port', String(port)], {
509
+ cwd,
510
+ env: {
511
+ ...process.env,
512
+ NODE_ENV: process.env.NODE_ENV || 'production',
513
+ RSC_UPSTREAM_PORT: String(port),
514
+ },
515
+ stdio: 'pipe',
516
+ });
517
+ }
518
+ catch (spawnError) {
519
+ if ((0, spawn_permissions_1.isPermissionDeniedSpawnError)(spawnError)) {
520
+ return null;
521
+ }
522
+ throw spawnError;
523
+ }
524
+ try {
525
+ await waitForUpstreamReady(child, 12000);
526
+ }
527
+ catch (startupError) {
528
+ if ((0, spawn_permissions_1.isPermissionDeniedSpawnError)(startupError)) {
529
+ try {
530
+ if (!child.killed) {
531
+ child.kill();
532
+ }
533
+ }
534
+ catch {
535
+ // ignore cleanup failures
536
+ }
537
+ return null;
538
+ }
539
+ throw startupError;
540
+ }
394
541
  const close = async () => {
395
542
  if (child.killed)
396
543
  return;
@@ -471,6 +618,8 @@ function wrapInDocument(bodyHtml, _urlPath, metadataHtml, cwd) {
471
618
  */
472
619
  async function generateStaticPages(options) {
473
620
  const { cwd, vistaDirRoot, manifest, isDev, buildId } = options;
621
+ const vistaConfig = (0, config_1.loadConfig)(cwd);
622
+ const appPprEnabled = (0, ppr_1.isAppPPREnabled)(vistaConfig);
474
623
  const result = {
475
624
  pagesGenerated: 0,
476
625
  generatedPaths: [],
@@ -479,7 +628,7 @@ async function generateStaticPages(options) {
479
628
  };
480
629
  // In dev mode, skip prerendering (pages are rendered on demand)
481
630
  if (isDev) {
482
- result.manifest = (0, static_cache_1.generatePrerenderManifest)(manifest.routes);
631
+ result.manifest = (0, static_cache_1.generatePrerenderManifest)(manifest.routes, undefined, { appPprEnabled });
483
632
  return result;
484
633
  }
485
634
  const staticRoutes = manifest.routes.filter((r) => r.renderMode === 'static' || r.renderMode === 'isr');
@@ -489,14 +638,19 @@ async function generateStaticPages(options) {
489
638
  flightUpstream = await startStaticFlightUpstream(cwd);
490
639
  }
491
640
  catch (flightError) {
492
- console.warn(`[vista:ssg] Flight payload pre-generation disabled: ${flightError.message}`);
641
+ if ((0, spawn_permissions_1.isPermissionDeniedSpawnError)(flightError)) {
642
+ console.log('[vista:ssg] Flight payload pre-generation skipped (spawn blocked by environment permissions)');
643
+ }
644
+ else {
645
+ console.warn(`[vista:ssg] Flight payload pre-generation disabled: ${(0, spawn_permissions_1.getErrorMessage)(flightError)}`);
646
+ }
493
647
  }
494
648
  try {
495
649
  for (const route of staticRoutes) {
496
650
  if (route.type === 'static') {
497
651
  // Simple static route — single URL
498
652
  const urlPath = route.pattern;
499
- const page = await prerenderPage(urlPath, route, undefined, cwd);
653
+ const page = await prerenderPage(urlPath, route, undefined, cwd, vistaDirRoot, appPprEnabled);
500
654
  if (page) {
501
655
  if (flightUpstream) {
502
656
  const flightData = await flightUpstream.fetchFlight(urlPath);
@@ -522,7 +676,7 @@ async function generateStaticPages(options) {
522
676
  }
523
677
  for (const params of paramSets) {
524
678
  const urlPath = expandPattern(route.pattern, params);
525
- const page = await prerenderPage(urlPath, route, params, cwd);
679
+ const page = await prerenderPage(urlPath, route, params, cwd, vistaDirRoot, appPprEnabled);
526
680
  if (page) {
527
681
  if (flightUpstream) {
528
682
  const flightData = await flightUpstream.fetchFlight(urlPath);
@@ -548,7 +702,7 @@ async function generateStaticPages(options) {
548
702
  }
549
703
  }
550
704
  // Generate prerender manifest
551
- result.manifest = (0, static_cache_1.generatePrerenderManifest)(manifest.routes);
705
+ result.manifest = (0, static_cache_1.generatePrerenderManifest)(manifest.routes, undefined, { appPprEnabled });
552
706
  // Write manifest to disk
553
707
  const manifestPath = path_1.default.join(vistaDirRoot, 'prerender-manifest.json');
554
708
  fs_1.default.writeFileSync(manifestPath, JSON.stringify(result.manifest, null, 2));
@@ -566,7 +720,7 @@ async function revalidatePath(urlPath, route, params, cwd, vistaDirRoot) {
566
720
  }
567
721
  (0, static_cache_1.markRevalidating)(urlPath);
568
722
  try {
569
- const page = await prerenderPage(urlPath, route, params, cwd);
723
+ const page = await prerenderPage(urlPath, route, params, cwd, vistaDirRoot, (0, ppr_1.isAppPPREnabled)((0, config_1.loadConfig)(cwd)));
570
724
  if (page) {
571
725
  (0, static_cache_1.setCachedPage)(urlPath, page);
572
726
  (0, static_cache_1.writeStaticPageToDisk)(vistaDirRoot, urlPath, page);
@@ -25,7 +25,7 @@ export interface StructureValidationResult {
25
25
  export interface RouteGraphNode {
26
26
  segment: string;
27
27
  pattern: string;
28
- kind: 'static' | 'dynamic' | 'catch-all' | 'optional-catch-all' | 'group' | 'reserved-internal';
28
+ kind: 'static' | 'dynamic' | 'catch-all' | 'optional-catch-all' | 'group' | 'parallel' | 'interception' | 'reserved-internal';
29
29
  hasPage: boolean;
30
30
  hasLayout: boolean;
31
31
  children: RouteGraphNode[];
@@ -20,7 +20,6 @@ const path_1 = __importDefault(require("path"));
20
20
  // ============================================================================
21
21
  const FILE_EXTENSIONS = ['.tsx', '.ts', '.jsx', '.js'];
22
22
  const RESERVED_INTERNAL_SEGMENTS = new Set(['[not-found]']);
23
- const VALID_SEGMENT_PATTERN = /^[a-zA-Z0-9_\-]+$|^\[\[\.\.\.[\w\-]+\]\]$|^\[[\w\-]+\]$|^\[\.\.\.[\w\-]+\]$|^\([\w\-]+\)$/;
24
23
  const CONVENTION_FILES = new Set([
25
24
  'page',
26
25
  'layout',
@@ -41,9 +40,28 @@ function fileExistsWithExtensions(dir, stem) {
41
40
  }
42
41
  return null;
43
42
  }
43
+ function isParallelSegment(name) {
44
+ return /^@[\w-]+$/.test(name);
45
+ }
46
+ function isInterceptionSegment(name) {
47
+ return /^(?:\(\.\)|\(\.\.\)|\(\.\.\)\(\.\.\)|\(\.\.\.\))(?:[\w-]+|\[[^\]]+\]|\[\[\.\.\.[^\]]+\]\])$/.test(name);
48
+ }
49
+ function isValidSegmentName(name) {
50
+ return (/^[a-zA-Z0-9_\-]+$/.test(name) ||
51
+ /^\[\[\.\.\.[\w\-]+\]\]$/.test(name) ||
52
+ /^\[[\w\-]+\]$/.test(name) ||
53
+ /^\[\.\.\.[\w\-]+\]$/.test(name) ||
54
+ /^\([\w\-]+\)$/.test(name) ||
55
+ isParallelSegment(name) ||
56
+ isInterceptionSegment(name));
57
+ }
44
58
  function classifySegment(name) {
45
59
  if (RESERVED_INTERNAL_SEGMENTS.has(name))
46
60
  return 'reserved-internal';
61
+ if (isParallelSegment(name))
62
+ return 'parallel';
63
+ if (isInterceptionSegment(name))
64
+ return 'interception';
47
65
  if (name.startsWith('(') && name.endsWith(')'))
48
66
  return 'group';
49
67
  if (name.startsWith('[[...') && name.endsWith(']]'))
@@ -67,7 +85,7 @@ function segmentToPattern(segment, kind) {
67
85
  const param = segment.slice(5, -2); // [[...slug]] -> slug
68
86
  return `:${param}*?`;
69
87
  }
70
- if (kind === 'group')
88
+ if (kind === 'group' || kind === 'parallel' || kind === 'interception')
71
89
  return '';
72
90
  return segment;
73
91
  }
@@ -105,6 +123,9 @@ function buildRouteGraph(dir, parentPattern = '') {
105
123
  }
106
124
  function collectPatterns(nodes, acc = new Map()) {
107
125
  for (const node of nodes) {
126
+ if (node.kind === 'parallel' || node.kind === 'interception') {
127
+ continue;
128
+ }
108
129
  if (node.hasPage && node.kind !== 'reserved-internal') {
109
130
  // Normalize dynamic params so /blog/:id and /blog/:slug collide
110
131
  const normalizedPattern = node.pattern.replace(/:[^/]+/g, ':_dynamic_');
@@ -198,13 +219,13 @@ function checkInvalidSegmentNames(dir, issues) {
198
219
  // Skip hidden directories and reserved
199
220
  if (name.startsWith('.') || name === 'node_modules')
200
221
  continue;
201
- if (!VALID_SEGMENT_PATTERN.test(name) && !RESERVED_INTERNAL_SEGMENTS.has(name)) {
222
+ if (!isValidSegmentName(name) && !RESERVED_INTERNAL_SEGMENTS.has(name)) {
202
223
  issues.push({
203
224
  code: 'INVALID_SEGMENT_NAME',
204
225
  severity: 'error',
205
- message: `Invalid route segment name: "${name}". Segments must be alphanumeric/dashes, [param], [...param], or (group).`,
226
+ message: `Invalid route segment name: "${name}". Segments must be alphanumeric/dashes, [param], [...param], (group), @slot, or interception syntax.`,
206
227
  filePath: path_1.default.join(dir, name),
207
- fix: `Rename "${name}" to a valid segment pattern (e.g., lowercase-dashed, [dynamic], (group)).`,
228
+ fix: `Rename "${name}" to a valid segment pattern (e.g., lowercase-dashed, [dynamic], (group), @modal, or (.)target).`,
208
229
  });
209
230
  }
210
231
  // Recurse into children
@@ -32,7 +32,7 @@ class StructureWatcher extends events_1.EventEmitter {
32
32
  constructor(options) {
33
33
  super();
34
34
  this.cwd = options.cwd;
35
- this.debounceMs = options.debounceMs ?? 120;
35
+ this.debounceMs = options.debounceMs ?? 60;
36
36
  this.notFoundRoute = options.notFoundRoute;
37
37
  }
38
38
  /**
@@ -1,6 +1,7 @@
1
1
  import type express from 'express';
2
2
  import type { ResolvedTypedApiConfig } from '../config';
3
3
  export declare function resolveLegacyApiRoutePath(cwd: string, requestPath: string): string | null;
4
+ export declare function resolveLegacyRouteHandlerPath(cwd: string, requestPath: string): string | null;
4
5
  export declare function runLegacyApiRoute(options: {
5
6
  req: express.Request;
6
7
  res: express.Response;