vinext 0.1.7 → 0.1.8

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 (92) hide show
  1. package/dist/check.d.ts +18 -0
  2. package/dist/check.js +37 -17
  3. package/dist/cli.js +2 -2
  4. package/dist/config/next-config.d.ts +2 -1
  5. package/dist/config/next-config.js +3 -0
  6. package/dist/config/server-external-packages.d.ts +4 -0
  7. package/dist/config/server-external-packages.js +91 -0
  8. package/dist/deploy.d.ts +7 -0
  9. package/dist/deploy.js +25 -18
  10. package/dist/entries/app-rsc-entry.js +54 -10
  11. package/dist/entries/app-rsc-manifest.js +8 -0
  12. package/dist/entries/pages-client-entry.js +1 -0
  13. package/dist/entries/pages-server-entry.js +1 -0
  14. package/dist/index.js +46 -20
  15. package/dist/init.js +2 -1
  16. package/dist/plugins/middleware-server-only.d.ts +8 -6
  17. package/dist/plugins/middleware-server-only.js +8 -7
  18. package/dist/plugins/optimize-imports.js +1 -1
  19. package/dist/routing/app-route-graph.d.ts +6 -2
  20. package/dist/routing/app-route-graph.js +61 -13
  21. package/dist/routing/app-router.d.ts +5 -0
  22. package/dist/routing/app-router.js +5 -0
  23. package/dist/routing/file-matcher.d.ts +5 -0
  24. package/dist/routing/file-matcher.js +7 -1
  25. package/dist/server/app-browser-entry.js +42 -147
  26. package/dist/server/app-browser-history-controller.d.ts +2 -1
  27. package/dist/server/app-browser-history-controller.js +6 -2
  28. package/dist/server/app-browser-server-action-client.d.ts +32 -0
  29. package/dist/server/app-browser-server-action-client.js +128 -0
  30. package/dist/server/app-fallback-renderer.d.ts +3 -1
  31. package/dist/server/app-fallback-renderer.js +6 -2
  32. package/dist/server/app-page-boundary-render.d.ts +3 -0
  33. package/dist/server/app-page-boundary-render.js +14 -3
  34. package/dist/server/app-page-cache-finalizer.d.ts +1 -0
  35. package/dist/server/app-page-cache-finalizer.js +8 -2
  36. package/dist/server/app-page-dispatch.d.ts +33 -4
  37. package/dist/server/app-page-dispatch.js +62 -90
  38. package/dist/server/app-page-element-builder.d.ts +7 -1
  39. package/dist/server/app-page-element-builder.js +56 -19
  40. package/dist/server/app-page-head.d.ts +16 -1
  41. package/dist/server/app-page-head.js +44 -22
  42. package/dist/server/app-page-params.d.ts +2 -1
  43. package/dist/server/app-page-params.js +8 -1
  44. package/dist/server/app-page-ppr-runtime.d.ts +7 -0
  45. package/dist/server/app-page-ppr-runtime.js +70 -0
  46. package/dist/server/app-page-probe.d.ts +1 -0
  47. package/dist/server/app-page-probe.js +1 -1
  48. package/dist/server/app-page-render.d.ts +4 -1
  49. package/dist/server/app-page-render.js +8 -3
  50. package/dist/server/app-page-request.d.ts +8 -1
  51. package/dist/server/app-page-request.js +23 -11
  52. package/dist/server/app-page-route-wiring.d.ts +6 -1
  53. package/dist/server/app-page-route-wiring.js +30 -8
  54. package/dist/server/app-page-search-params-observation.d.ts +4 -2
  55. package/dist/server/app-page-search-params-observation.js +11 -7
  56. package/dist/server/app-ppr-fallback-shell-render.d.ts +4 -3
  57. package/dist/server/app-route-handler-dispatch.js +1 -0
  58. package/dist/server/app-route-handler-execution.js +2 -1
  59. package/dist/server/app-route-module-loader.d.ts +2 -0
  60. package/dist/server/app-route-module-loader.js +1 -0
  61. package/dist/server/app-router-entry.js +7 -6
  62. package/dist/server/app-rsc-errors.js +7 -1
  63. package/dist/server/app-rsc-handler.d.ts +2 -1
  64. package/dist/server/app-rsc-handler.js +6 -9
  65. package/dist/server/app-rsc-route-matching.d.ts +7 -0
  66. package/dist/server/app-rsc-route-matching.js +36 -3
  67. package/dist/server/app-segment-config.d.ts +1 -0
  68. package/dist/server/app-segment-config.js +32 -2
  69. package/dist/server/app-server-action-execution.d.ts +4 -0
  70. package/dist/server/app-server-action-execution.js +41 -10
  71. package/dist/server/app-ssr-entry.js +4 -1
  72. package/dist/server/app-static-generation.d.ts +1 -0
  73. package/dist/server/app-static-generation.js +1 -0
  74. package/dist/server/headers.d.ts +3 -1
  75. package/dist/server/headers.js +3 -1
  76. package/dist/server/prod-server.js +15 -6
  77. package/dist/server/worker-utils.d.ts +2 -1
  78. package/dist/server/worker-utils.js +7 -1
  79. package/dist/shims/error-boundary.d.ts +19 -1
  80. package/dist/shims/error-boundary.js +11 -1
  81. package/dist/shims/headers.d.ts +3 -1
  82. package/dist/shims/headers.js +16 -5
  83. package/dist/shims/link.js +16 -12
  84. package/dist/shims/metadata.d.ts +3 -2
  85. package/dist/shims/metadata.js +8 -4
  86. package/dist/shims/router.js +13 -2
  87. package/dist/typegen.js +6 -5
  88. package/dist/utils/path.d.ts +2 -1
  89. package/dist/utils/path.js +1 -1
  90. package/dist/utils/project.d.ts +4 -0
  91. package/dist/utils/project.js +5 -1
  92. package/package.json +7 -3
@@ -1,5 +1,5 @@
1
- import { normalizePathSeparators } from "../utils/path.js";
2
1
  import { decodeRouteSegment, isInvisibleSegment, sortRoutes } from "./utils.js";
2
+ import { normalizePathSeparators } from "../utils/path.js";
3
3
  import { findFileWithExts, scanWithExtensions } from "./file-matcher.js";
4
4
  import { validateRoutePatterns } from "./route-validation.js";
5
5
  import { compareStrings } from "../utils/compare.js";
@@ -360,8 +360,8 @@ async function buildAppRouteGraph(appDir, matcher) {
360
360
  const routePatterns = new Set(routes.map((route) => route.pattern));
361
361
  const ghostParentRoutes = [];
362
362
  for await (const file of scanWithExtensions("**/layout", appDir, scanMatcher.extensions, excludeDir)) {
363
- const dir = path.dirname(file);
364
- const routeDir = dir === "." ? appDir : path.join(appDir, dir);
363
+ const dir = path.posix.dirname(file);
364
+ const routeDir = dir === "." ? appDir : path.posix.join(appDir, dir);
365
365
  if (!hasParallelSlotDirectory(routeDir)) continue;
366
366
  if (discoverParallelSlots(routeDir, appDir, scanMatcher).length === 0) continue;
367
367
  const route = directoryToAppRoute(dir, appDir, scanMatcher, null, null);
@@ -434,11 +434,16 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
434
434
  const applySlotSubPages = (route, slotPages, rawSegments) => {
435
435
  route.parallelSlots = route.parallelSlots.map((slot) => {
436
436
  const subPage = slotPages.get(slot.key);
437
- if (subPage !== void 0) return {
438
- ...slot,
439
- pagePath: subPage,
440
- routeSegments: rawSegments
441
- };
437
+ if (subPage !== void 0) {
438
+ const configLayoutPaths = findSlotConfigLayoutPaths(slot.ownerDir, subPage, matcher);
439
+ return {
440
+ ...slot,
441
+ pagePath: subPage,
442
+ configLayoutPaths,
443
+ configLayoutTreePositions: findSlotConfigLayoutTreePositions(slot.ownerDir, configLayoutPaths),
444
+ routeSegments: rawSegments
445
+ };
446
+ }
442
447
  return slot;
443
448
  });
444
449
  };
@@ -495,9 +500,12 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
495
500
  if (Array.from(routesByPattern.values()).some((r) => patternsStructurallyEquivalent(r.patternParts, syntheticParts))) continue;
496
501
  const subSlots = parentRoute.parallelSlots.map((slot) => {
497
502
  const subPage = slotPages.get(slot.key);
503
+ const configLayoutPaths = findSlotConfigLayoutPaths(slot.ownerDir, subPage ?? null, matcher);
498
504
  return {
499
505
  ...slot,
500
506
  pagePath: subPage || null,
507
+ configLayoutPaths,
508
+ configLayoutTreePositions: findSlotConfigLayoutTreePositions(slot.ownerDir, configLayoutPaths),
501
509
  routeSegments: subPage ? rawSegments : null
502
510
  };
503
511
  });
@@ -574,6 +582,23 @@ function findSlotSubPages(slotDir, matcher) {
574
582
  perMatcher.set(slotDir, results);
575
583
  return results;
576
584
  }
585
+ function findSlotConfigLayoutPaths(slotDir, pagePath, matcher) {
586
+ if (!pagePath) return [];
587
+ const layouts = [];
588
+ let dir = path.dirname(pagePath);
589
+ while (dir !== slotDir && dir.startsWith(`${slotDir}${path.sep}`)) {
590
+ const layoutPath = findFile(dir, "layout", matcher);
591
+ if (layoutPath) layouts.unshift(layoutPath);
592
+ dir = path.dirname(dir);
593
+ }
594
+ return layouts;
595
+ }
596
+ function findSlotConfigLayoutTreePositions(slotDir, layoutPaths) {
597
+ return layoutPaths.map((layoutPath) => {
598
+ const relativeDir = path.relative(slotDir, path.dirname(layoutPath));
599
+ return relativeDir ? relativeDir.split(path.sep).filter(Boolean).length : 0;
600
+ });
601
+ }
577
602
  /**
578
603
  * Find a sibling catch-all page directly under `dir`, i.e. a `[...slug]` or
579
604
  * `[[...slug]]` directory that contains a `page` file. Returns the absolute
@@ -613,8 +638,14 @@ function fileToAppRoute(file, appDir, type, matcher) {
613
638
  }
614
639
  return directoryToAppRoute(dir, appDir, matcher, type === "page" ? path.join(appDir, file) : null, type === "route" ? path.join(appDir, file) : null);
615
640
  }
641
+ /**
642
+ * `dir` and `appDir` must both be forward-slash. `dir` is split on
643
+ * `path.posix.sep` and joined onto `appDir` with `path.posix.join`, and `appDir`
644
+ * is threaded to the layout/slot/boundary discovery below, which build paths the
645
+ * same way.
646
+ */
616
647
  function directoryToAppRoute(dir, appDir, matcher, pagePath, routePath) {
617
- const segments = dir === "." ? [] : dir.split(path.sep);
648
+ const segments = dir === "." ? [] : dir.split(path.posix.sep);
618
649
  const params = [];
619
650
  let isDynamic = false;
620
651
  const convertedRoute = convertSegmentsToRouteParts(segments);
@@ -631,7 +662,7 @@ function directoryToAppRoute(dir, appDir, matcher, pagePath, routePath) {
631
662
  const errorEntries = discoverSegmentErrors(segments, appDir, matcher);
632
663
  const errorPaths = errorEntries.map((entry) => entry.path);
633
664
  const errorTreePositions = errorEntries.map((entry) => entry.treePosition);
634
- const routeDir = dir === "." ? appDir : path.join(appDir, dir);
665
+ const routeDir = dir === "." ? appDir : path.posix.join(appDir, dir);
635
666
  const loadingPath = findFile(routeDir, "loading", matcher);
636
667
  const errorPath = findFile(routeDir, "error", matcher);
637
668
  const notFoundPath = discoverBoundaryFile(segments, appDir, "not-found", matcher);
@@ -863,6 +894,10 @@ function discoverBoundaryFilePerLayout(layouts, fileName, matcher) {
863
894
  * Walk from appDir through each segment to the route's directory. At each level
864
895
  * that has @slot dirs, collect them. Slots at the route's own directory level
865
896
  * use page.tsx; slots at ancestor levels use default.tsx only.
897
+ *
898
+ * `appDir` and `routeDir` must be forward-slash — `currentDir` descends from
899
+ * `appDir` via `path.posix.join`, and the `dir === routeDir` active-level test
900
+ * below only matches when both share the canonical separator.
866
901
  */
867
902
  function discoverInheritedParallelSlots(segments, appDir, routeDir, matcher) {
868
903
  const slotMap = /* @__PURE__ */ new Map();
@@ -875,7 +910,7 @@ function discoverInheritedParallelSlots(segments, appDir, routeDir, matcher) {
875
910
  segmentIndex: 0
876
911
  });
877
912
  for (let i = 0; i < segments.length; i++) {
878
- currentDir = path.join(currentDir, segments[i]);
913
+ currentDir = path.posix.join(currentDir, segments[i]);
879
914
  if (findFile(currentDir, "layout", matcher)) layoutIdx++;
880
915
  dirsToCheck.push({
881
916
  dir: currentDir,
@@ -902,9 +937,12 @@ function discoverInheritedParallelSlots(segments, appDir, routeDir, matcher) {
902
937
  slotPatternParts = [...ownerUrl?.urlSegments ?? [], ...mirror.slotUrlSegments];
903
938
  slotParamNames = [...ownerUrl?.params ?? [], ...mirror.slotParamNames];
904
939
  }
940
+ const configLayoutPaths = findSlotConfigLayoutPaths(slot.ownerDir, mirror?.pagePath ?? null, matcher);
905
941
  const inheritedSlot = {
906
942
  ...slot,
907
943
  pagePath: mirror?.pagePath ?? null,
944
+ configLayoutPaths,
945
+ configLayoutTreePositions: findSlotConfigLayoutTreePositions(slot.ownerDir, configLayoutPaths),
908
946
  layoutIndex: slotLayoutIdx,
909
947
  routeSegments: mirror?.segments ?? null,
910
948
  slotPatternParts,
@@ -1035,6 +1073,9 @@ function patternsStructurallyEquivalent(a, b) {
1035
1073
  *
1036
1074
  * Returns the absolute page path, or null if no root-level page is found.
1037
1075
  *
1076
+ * `slotDir` must be forward-slash: the `path.posix.join` descent stays a
1077
+ * canonical id only when the base already is.
1078
+ *
1038
1079
  * Only descends into route-group directories (those whose name starts with `(`
1039
1080
  * and ends with `)`). Dynamic segments, regular named dirs, and `@slot` dirs
1040
1081
  * are not transparent and are therefore not searched.
@@ -1051,7 +1092,7 @@ function findSlotRootPage(slotDir, matcher) {
1051
1092
  for (const entry of entries) {
1052
1093
  if (!entry.isDirectory()) continue;
1053
1094
  if (!entry.name.startsWith("(") || !entry.name.endsWith(")")) continue;
1054
- const found = findSlotRootPage(path.join(slotDir, entry.name), matcher);
1095
+ const found = findSlotRootPage(path.posix.join(slotDir, entry.name), matcher);
1055
1096
  if (found) return found;
1056
1097
  }
1057
1098
  return null;
@@ -1068,13 +1109,14 @@ function discoverParallelSlots(dir, appDir, matcher) {
1068
1109
  if (!entry.isDirectory() || !entry.name.startsWith("@")) continue;
1069
1110
  if (entry.name === "@children") continue;
1070
1111
  const slotName = entry.name.slice(1);
1071
- const slotDir = path.join(dir, entry.name);
1112
+ const slotDir = path.posix.join(dir, entry.name);
1072
1113
  const pagePath = findSlotRootPage(slotDir, matcher);
1073
1114
  const defaultPath = findFile(slotDir, "default", matcher);
1074
1115
  const interceptingRoutes = discoverInterceptingRoutes(slotDir, dir, appDir, matcher);
1075
1116
  if (!pagePath && !defaultPath && interceptingRoutes.length === 0) continue;
1076
1117
  const ownerSegments = path.relative(appDir, dir).split(path.sep).filter((segment) => segment.length > 0);
1077
1118
  const ownerTreePath = createAppRouteGraphTreePath(ownerSegments, ownerSegments.length);
1119
+ const configLayoutPaths = findSlotConfigLayoutPaths(slotDir, pagePath, matcher);
1078
1120
  slots.push({
1079
1121
  id: createAppRouteGraphSlotId(slotName, ownerTreePath),
1080
1122
  key: `${slotName}@${path.relative(appDir, slotDir).replace(/\\/g, "/")}`,
@@ -1085,6 +1127,8 @@ function discoverParallelSlots(dir, appDir, matcher) {
1085
1127
  pagePath,
1086
1128
  defaultPath,
1087
1129
  layoutPath: findFile(slotDir, "layout", matcher),
1130
+ configLayoutPaths,
1131
+ configLayoutTreePositions: findSlotConfigLayoutTreePositions(slotDir, configLayoutPaths),
1088
1132
  loadingPath: findFile(slotDir, "loading", matcher),
1089
1133
  errorPath: findFile(slotDir, "error", matcher),
1090
1134
  interceptingRoutes,
@@ -1272,8 +1316,12 @@ function collectInterceptingPages(currentDir, interceptRoot, convention, interce
1272
1316
  if (targetPattern) {
1273
1317
  const sourceMatchPattern = computeInterceptSourceMatchPattern(interceptParentDir, appDir);
1274
1318
  results.push({
1319
+ branchSegments: [interceptSegment, ...normalizePathSeparators(path.relative(interceptRoot, path.dirname(page))).split("/").filter(Boolean)],
1275
1320
  convention,
1276
1321
  layoutPaths: [...layoutPaths],
1322
+ layoutSegments: layoutPaths.map((layoutPath) => {
1323
+ return [interceptSegment, ...path.relative(interceptRoot, path.dirname(layoutPath)).split(path.sep).filter(Boolean)];
1324
+ }),
1277
1325
  targetPattern: targetPattern.pattern,
1278
1326
  sourceMatchPattern,
1279
1327
  pagePath: page,
@@ -12,11 +12,16 @@ declare function invalidateAppRouteCache(): void;
12
12
  * TODO(#726): Layer 4 should consume this read model directly once the
13
13
  * navigation planner owns route graph facts.
14
14
  *
15
+ * `appDir` must be forward-slash — callers normalize it at their entry, and it
16
+ * flows into `buildAppRouteGraph`, which builds every path with `path.posix.*`.
17
+ *
15
18
  * @internal
16
19
  */
17
20
  declare function appRouteGraph(appDir: string, pageExtensions?: readonly string[], matcher?: ValidFileMatcher): Promise<AppRouteGraph>;
18
21
  /**
19
22
  * Scan the app/ directory and return a list of routes.
23
+ *
24
+ * `appDir` must be forward-slash — it is forwarded to `appRouteGraph`.
20
25
  */
21
26
  declare function appRouter(appDir: string, pageExtensions?: readonly string[], matcher?: ValidFileMatcher): Promise<AppRouteGraphRoute[]>;
22
27
  /**
@@ -30,6 +30,9 @@ function invalidateAppRouteCache() {
30
30
  * TODO(#726): Layer 4 should consume this read model directly once the
31
31
  * navigation planner owns route graph facts.
32
32
  *
33
+ * `appDir` must be forward-slash — callers normalize it at their entry, and it
34
+ * flows into `buildAppRouteGraph`, which builds every path with `path.posix.*`.
35
+ *
33
36
  * @internal
34
37
  */
35
38
  async function appRouteGraph(appDir, pageExtensions, matcher) {
@@ -44,6 +47,8 @@ async function appRouteGraph(appDir, pageExtensions, matcher) {
44
47
  }
45
48
  /**
46
49
  * Scan the app/ directory and return a list of routes.
50
+ *
51
+ * `appDir` must be forward-slash — it is forwarded to `appRouteGraph`.
47
52
  */
48
53
  async function appRouter(appDir, pageExtensions, matcher) {
49
54
  return (await appRouteGraph(appDir, pageExtensions, matcher)).routes;
@@ -54,6 +54,11 @@ declare function buildViteResolveExtensions(pageExtensions?: readonly string[] |
54
54
  declare function normalizeViteResolveExtensions(extensions: readonly string[]): string[];
55
55
  /**
56
56
  * Use function-form exclude for Node < 22.14 compatibility.
57
+ *
58
+ * Yields forward-slash relative paths: node's glob emits native (backslash)
59
+ * separators on Windows, so each match is normalized — this is the entry point
60
+ * that lets downstream consumers treat the scanned paths as canonical
61
+ * forward-slash ids.
57
62
  */
58
63
  declare function scanWithExtensions(stem: string, cwd: string, extensions: readonly string[], exclude?: (name: string) => boolean): AsyncGenerator<string>;
59
64
  //#endregion
@@ -1,4 +1,5 @@
1
1
  import { escapeRegExp } from "../utils/regex.js";
2
+ import { normalizePathSeparators } from "../utils/path.js";
2
3
  import { existsSync } from "node:fs";
3
4
  import path from "node:path";
4
5
  import { glob } from "node:fs/promises";
@@ -135,13 +136,18 @@ function normalizeViteResolveExtensions(extensions) {
135
136
  }
136
137
  /**
137
138
  * Use function-form exclude for Node < 22.14 compatibility.
139
+ *
140
+ * Yields forward-slash relative paths: node's glob emits native (backslash)
141
+ * separators on Windows, so each match is normalized — this is the entry point
142
+ * that lets downstream consumers treat the scanned paths as canonical
143
+ * forward-slash ids.
138
144
  */
139
145
  async function* scanWithExtensions(stem, cwd, extensions, exclude) {
140
146
  const pattern = buildExtensionGlob(stem, extensions);
141
147
  for await (const file of glob(pattern, {
142
148
  cwd,
143
149
  ...exclude ? { exclude } : {}
144
- })) yield file;
150
+ })) yield normalizePathSeparators(file);
145
151
  }
146
152
  //#endregion
147
153
  export { buildViteResolveExtensions, createValidFileMatcher, findFileWithExtensions, findFileWithExts, normalizePageExtensions, normalizeViteResolveExtensions, scanWithExtensions };
@@ -1,5 +1,4 @@
1
- import { ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_PARAMS_HEADER, VINEXT_RSC_REDIRECT_HEADER } from "./headers.js";
2
- import { DANGEROUS_URL_BLOCK_MESSAGE, isDangerousScheme } from "../shims/url-safety.js";
1
+ import { VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_PARAMS_HEADER, VINEXT_RSC_REDIRECT_HEADER } from "./headers.js";
3
2
  import { AppElementsWire } from "./app-elements-wire.js";
4
3
  import { APP_RSC_RENDER_MODE_REFRESH_PRESERVE_UI } from "./app-rsc-render-mode.js";
5
4
  import { getMountedSlotIdsHeader, resolveVisitedResponseInterceptionContext } from "./app-elements.js";
@@ -14,7 +13,7 @@ import { DevRecoveryBoundary, GlobalErrorBoundary, RedirectBoundary } from "../s
14
13
  import { beginAppRouterScrollIntent, consumeAppRouterScrollIntent } from "../shims/app-router-scroll-state.js";
15
14
  import { AppRouterScrollCommitProvider } from "../shims/app-router-scroll.js";
16
15
  import { BfcacheStateKeyMapContext, ElementsContext, Slot } from "../shims/slot.js";
17
- import { VINEXT_RSC_COMPATIBILITY_ID_HEADER, VINEXT_RSC_CONTENT_TYPE, createRscRequestHeaders, createRscRequestUrl, createServerActionRequestUrl, getVinextRscCompatibilityId } from "./app-rsc-cache-busting.js";
16
+ import { VINEXT_RSC_COMPATIBILITY_ID_HEADER, VINEXT_RSC_CONTENT_TYPE, createRscRequestHeaders, createRscRequestUrl, getVinextRscCompatibilityId } from "./app-rsc-cache-busting.js";
18
17
  import { getNavigationRuntime, registerNavigationRuntimeBootstrap, registerNavigationRuntimeFunctions } from "../client/navigation-runtime.js";
19
18
  import { notifyAppRouterTransitionStart } from "../client/instrumentation-client-state.js";
20
19
  import "../client/instrumentation-client.js";
@@ -25,26 +24,23 @@ import { blockDangerousStreamedRscRedirect } from "./app-browser-rsc-redirect.js
25
24
  import { navigationPlanner } from "./navigation-planner.js";
26
25
  import { __basePath, appRouterInstance, commitClientNavigationState, consumePrefetchResponseForNavigation, createCachedRscResponseSnapshot, createClientNavigationRenderSnapshot, createSnapshotPathAndSearch, getClientNavigationRenderContext, getPrefetchCache, hasPrefetchCacheEntryForNavigation, invalidatePrefetchCache, pushHistoryStateWithoutNotify, replaceClientParamsWithoutNotify, replaceHistoryStateWithoutNotify, resolvePrefetchCacheEntryMountedSlotsHeader, restoreRscResponse, saveScrollPosition, setClientParams, setMountedSlotsHeader, setPendingPathname, useRouter } from "../shims/navigation.js";
27
26
  import { createBfcacheSegmentStateKeyMap, createInitialBfcacheIdMap } from "./app-bfcache-identity.js";
28
- import { createDiscardedServerActionRefreshScheduler, createServerActionInitiationSnapshot, createServerActionResultFacts, isServerActionResult, normalizeServerActionThrownValue, parseServerActionRevalidationHeader, readInvalidServerActionResponseError, shouldClearClientNavigationCachesForServerActionResult } from "./app-browser-action-result.js";
27
+ import { createDiscardedServerActionRefreshScheduler, createServerActionInitiationSnapshot } from "./app-browser-action-result.js";
29
28
  import { createClientReuseManifestHeaderFromVisibleAppState } from "./app-browser-client-reuse-manifest.js";
30
29
  import { chunksToReadableStream, createProgressiveRscStream, getVinextBrowserGlobal } from "./app-browser-stream.js";
31
- import { FRESH_APP_NAVIGATION_PAYLOAD_ORIGIN, VISITED_CACHE_APP_NAVIGATION_PAYLOAD_ORIGIN, isCacheRestorableAppPayloadMetadata, resolveInterceptionContextFromPreviousNextUrl, resolveServerActionRequestState } from "./app-browser-state.js";
30
+ import { FRESH_APP_NAVIGATION_PAYLOAD_ORIGIN, VISITED_CACHE_APP_NAVIGATION_PAYLOAD_ORIGIN, isCacheRestorableAppPayloadMetadata, resolveInterceptionContextFromPreviousNextUrl } from "./app-browser-state.js";
32
31
  import { clearHardNavigationLoopGuard, createAppBrowserNavigationController, createBasePathStrippedPathAndSearch } from "./app-browser-navigation-controller.js";
33
- import { applyServerActionResultDecision } from "./app-browser-server-action-navigation.js";
34
32
  import { consumeInitialFormState, createVinextHydrateRootOptions, hydrateRootInTransition } from "./app-browser-hydration.js";
35
33
  import { AppBrowserHistoryController } from "./app-browser-history-controller.js";
36
34
  import { createVisitedResponseCacheEntry, isVisitedResponseCacheEntryFresh } from "./app-visited-response-cache.js";
37
35
  import { createPopstateRestoreHandler, restoreSynchronousPopstateScrollPosition } from "./app-browser-popstate.js";
38
36
  import { createDevOnCaughtError, createOnUncaughtError, createProdOnCaughtError, prodOnRecoverableError } from "./app-browser-error.js";
39
- import { dismissOverlay } from "./dev-error-overlay-store.js";
40
- import { devOnCaughtError, installDevErrorOverlay, installViteHmrErrorHandler, reportInitialDevServerErrors } from "./dev-error-overlay.js";
41
- import { throwOnServerActionNotFound } from "./server-action-not-found.js";
42
37
  import { createOptimisticRouteTemplate, getOptimisticPrefetchSourceKey, getOptimisticRouteTemplateKey, resolveOptimisticNavigationPayload } from "./app-optimistic-routing.js";
43
38
  import { removeStylesheetLinksCoveredByInlineCss } from "./app-inline-css-client.js";
44
39
  import { createElement, startTransition, use, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
45
40
  import { flushSync } from "react-dom";
46
- import { createFromFetch, createFromReadableStream, createTemporaryReferenceSet, encodeReply, setServerCallback } from "@vitejs/plugin-rsc/browser";
41
+ import { createFromFetch, createFromReadableStream, setServerCallback } from "@vitejs/plugin-rsc/browser";
47
42
  import { createRoot, hydrateRoot } from "react-dom/client";
43
+ import { hasServerActions, loadServerActionClient } from "virtual:vinext-app-capabilities";
48
44
  //#region src/server/app-browser-entry.ts
49
45
  function toActionType(kind) {
50
46
  return kind === "traverse" ? "traverse" : "navigate";
@@ -94,10 +90,14 @@ const browserNavigationController = createAppBrowserNavigationController({
94
90
  getRouteManifest: getBrowserRouteManifest,
95
91
  syncHistoryStatePreviousNextUrl: (previousNextUrl, bfcacheIds) => historyController.syncCurrentHistoryStatePreviousNextUrl(previousNextUrl, bfcacheIds)
96
92
  });
97
- const discardedServerActionRefreshScheduler = createDiscardedServerActionRefreshScheduler({ runRefresh() {
93
+ const discardedServerActionRefreshScheduler = hasServerActions ? createDiscardedServerActionRefreshScheduler({ runRefresh() {
98
94
  clearClientNavigationCaches();
99
95
  getNavigationRuntime()?.functions.navigate?.(window.location.href, 0, "refresh", void 0, void 0, true);
100
- } });
96
+ } }) : {
97
+ markNavigationSettled() {},
98
+ markNavigationStart() {},
99
+ schedule() {}
100
+ };
101
101
  const NavigationCommitSignal = browserNavigationController.NavigationCommitSignal;
102
102
  const ACTION_HTTP_FALLBACK_ROBOTS_META_ATTR = "data-vinext-action-http-fallback";
103
103
  function syncServerActionHttpFallbackHead(status) {
@@ -303,52 +303,6 @@ async function renderNavigationPayload(payload, navigationSnapshot, targetHref,
303
303
  visibleCommitMode
304
304
  });
305
305
  }
306
- function resolveActionRedirectTarget(response) {
307
- const actionRedirect = response.headers.get(ACTION_REDIRECT_HEADER);
308
- if (!actionRedirect) return null;
309
- if (isDangerousScheme(actionRedirect)) {
310
- console.error(DANGEROUS_URL_BLOCK_MESSAGE);
311
- return null;
312
- }
313
- try {
314
- let redirectUrl;
315
- if (actionRedirect.startsWith("/") || /^[a-z]+:/i.test(actionRedirect)) redirectUrl = new URL(actionRedirect, window.location.href);
316
- else {
317
- const baseParsed = new URL(window.location.href);
318
- let baseDir = baseParsed.pathname;
319
- if (!baseDir.endsWith("/")) baseDir = baseDir + "/";
320
- redirectUrl = new URL(actionRedirect, `${baseParsed.origin}${baseDir}${baseParsed.search}`);
321
- }
322
- if (redirectUrl.origin !== window.location.origin) {
323
- browserNavigationController.performHardNavigation(actionRedirect);
324
- return null;
325
- }
326
- const statusHeader = response.headers.get(ACTION_REDIRECT_STATUS_HEADER);
327
- const status = statusHeader ? parseInt(statusHeader, 10) : 307;
328
- return {
329
- href: redirectUrl.href,
330
- type: response.headers.get("x-action-redirect-type") ?? "push",
331
- status
332
- };
333
- } catch {
334
- browserNavigationController.performHardNavigation(actionRedirect);
335
- return null;
336
- }
337
- }
338
- var ServerActionRedirectError = class extends Error {
339
- digest;
340
- handled = true;
341
- constructor(target) {
342
- super("NEXT_REDIRECT");
343
- const redirectUrl = new URL(target.href, window.location.href);
344
- const redirectHref = redirectUrl.pathname + redirectUrl.search + redirectUrl.hash;
345
- const redirectType = target.type === "push" ? "push" : "replace";
346
- this.digest = `NEXT_REDIRECT;${redirectType};${encodeURIComponent(redirectHref)};${target.status};`;
347
- }
348
- };
349
- function createServerActionRedirectError(target) {
350
- return new ServerActionRedirectError(target);
351
- }
352
306
  async function commitSameUrlNavigatePayload(nextElements, actionInitiation, returnValue, revalidation = "none") {
353
307
  const navigationSnapshot = createClientNavigationRenderSnapshot(actionInitiation.href, actionInitiation.routerState.navigationSnapshot.params);
354
308
  return browserNavigationController.commitSameUrlNavigatePayload(nextElements, navigationSnapshot, returnValue, actionInitiation.routerState, {
@@ -709,112 +663,53 @@ function applyRuntimeRscBootstrap(rsc) {
709
663
  if (rsc.nav) restoreHydrationNavigationContext(rsc.nav.pathname, rsc.nav.searchParams, params);
710
664
  }
711
665
  function registerServerActionCallback() {
712
- const serverActionCallback = async (id, args) => {
713
- syncServerActionHttpFallbackHead(null);
714
- const temporaryReferences = createTemporaryReferenceSet();
666
+ setServerCallback((id, args) => {
667
+ const releaseCacheInvalidationGuard = historyController.beginCacheInvalidationGuard();
715
668
  const actionInitiation = createActionInitiationSnapshot();
716
- historyController.syncCurrentHistoryStatePreviousNextUrl(actionInitiation.routerState.previousNextUrl, actionInitiation.routerState.bfcacheIds);
717
- const body = await encodeReply(args, { temporaryReferences });
718
- const { headers } = resolveServerActionRequestState({
719
- actionId: id,
669
+ return loadServerActionClient().then(({ invokeClientServerAction }) => invokeClientServerAction(id, args, actionInitiation, {
720
670
  basePath: __basePath,
721
- elements: actionInitiation.routerState.elements,
722
- previousNextUrl: actionInitiation.routerState.previousNextUrl
723
- });
724
- const fetchResponse = await fetch(createServerActionRequestUrl(actionInitiation.path), {
725
- method: "POST",
726
- headers,
727
- body
728
- });
729
- throwOnServerActionNotFound(fetchResponse, id);
730
- const hasActionRedirect = fetchResponse.headers.has(ACTION_REDIRECT_HEADER);
731
- const actionRedirectTarget = resolveActionRedirectTarget(fetchResponse);
732
- if (hasActionRedirect && !actionRedirectTarget) return;
733
- const actionResultFacts = createServerActionResultFacts({
734
- actionRedirectHref: actionRedirectTarget?.href ?? null,
735
- actionRedirectType: actionRedirectTarget?.type ?? null,
736
- clientCompatibilityId: CLIENT_RSC_COMPATIBILITY_ID,
737
- compatibilityIdHeader: fetchResponse.headers.get(VINEXT_RSC_COMPATIBILITY_ID_HEADER),
738
- contentTypeHeader: fetchResponse.headers.get("content-type"),
739
- currentHref: actionInitiation.href,
740
- origin: window.location.origin,
741
- responseUrl: fetchResponse.url
742
- });
743
- const fetchResponseIsRsc = actionResultFacts.isRscContentType;
744
- if (applyServerActionResultDecision(navigationPlanner.classifyServerActionResult(actionResultFacts), clearClientNavigationCaches, (url, historyMode) => browserNavigationController.performHardNavigation(url, historyMode))) return;
745
- const revalidation = parseServerActionRevalidationHeader(fetchResponse.headers);
746
- if (revalidation !== "none") clearClientNavigationCaches();
747
- const invalidResponseError = await readInvalidServerActionResponseError(fetchResponse.clone(), actionRedirectTarget !== null);
748
- if (invalidResponseError) throw invalidResponseError;
749
- if (actionRedirectTarget && !fetchResponseIsRsc) {
750
- browserNavigationController.performHardNavigation(actionRedirectTarget.href);
751
- return;
752
- }
753
- const flightResponse = fetchResponse.status === 303 ? new Response(fetchResponse.body, {
754
- headers: fetchResponse.headers,
755
- status: 200,
756
- statusText: "OK"
757
- }) : fetchResponse;
758
- const result = await createFromFetch(Promise.resolve(flightResponse), { temporaryReferences });
759
- if (revalidation === "none" && shouldClearClientNavigationCachesForServerActionResult(result, revalidation)) clearClientNavigationCaches();
760
- if (actionRedirectTarget) {
761
- if (isServerActionResult(result) && result.root !== void 0) {
762
- const decoded = AppElementsWire.decode(result.root);
763
- const hashIdx = actionRedirectTarget.href.indexOf("#");
764
- const actionScrollIntent = beginAppRouterScrollIntent((hashIdx !== -1 ? actionRedirectTarget.href.slice(hashIdx) : "") || null);
765
- if (actionRedirectTarget.type === "push") saveScrollPosition();
766
- renderNavigationPayload(Promise.resolve(decoded), createClientNavigationRenderSnapshot(actionRedirectTarget.href, actionInitiation.routerState.navigationSnapshot.params), actionRedirectTarget.href, actionInitiation.navigationId, actionRedirectTarget.type === "push" ? "push" : "replace", {}, null, null, FRESH_APP_NAVIGATION_PAYLOAD_ORIGIN, actionRedirectTarget.type === "push" ? "navigate" : "replace", "server-action", null, actionScrollIntent).catch(() => {
767
- browserNavigationController.performHardNavigation(actionRedirectTarget.href);
671
+ clearClientNavigationCaches,
672
+ clientRscCompatibilityId: CLIENT_RSC_COMPATIBILITY_ID,
673
+ commitSameUrlNavigatePayload,
674
+ navigationPlanner,
675
+ performHardNavigation: (url, historyMode) => browserNavigationController.performHardNavigation(url, historyMode),
676
+ renderRedirectPayload(elements, target, actionInitiation) {
677
+ const hashIdx = target.href.indexOf("#");
678
+ const actionScrollIntent = beginAppRouterScrollIntent((hashIdx !== -1 ? target.href.slice(hashIdx) : "") || null);
679
+ if (target.type === "push") saveScrollPosition();
680
+ renderNavigationPayload(Promise.resolve(elements), createClientNavigationRenderSnapshot(target.href, actionInitiation.routerState.navigationSnapshot.params), target.href, actionInitiation.navigationId, target.type === "push" ? "push" : "replace", {}, null, null, FRESH_APP_NAVIGATION_PAYLOAD_ORIGIN, target.type === "push" ? "navigate" : "replace", "server-action", null, actionScrollIntent).catch(() => {
681
+ browserNavigationController.performHardNavigation(target.href);
768
682
  });
769
- throw createServerActionRedirectError(actionRedirectTarget);
770
- }
771
- browserNavigationController.performHardNavigation(actionRedirectTarget.href);
772
- return;
773
- }
774
- syncServerActionHttpFallbackHead(isServerActionResult(result) && result.root !== void 0 ? null : fetchResponse.status);
775
- if (isServerActionResult(result)) {
776
- if (result.root !== void 0) {
777
- const returnValue = result.returnValue && !result.returnValue.ok ? {
778
- ok: false,
779
- data: normalizeServerActionThrownValue(result.returnValue.data, fetchResponse.status)
780
- } : result.returnValue;
781
- return commitSameUrlNavigatePayload(Promise.resolve(AppElementsWire.decode(result.root)), actionInitiation, returnValue, revalidation);
782
- }
783
- if (result.returnValue) {
784
- if (!result.returnValue.ok) throw normalizeServerActionThrownValue(result.returnValue.data, fetchResponse.status);
785
- return result.returnValue.data;
786
- }
787
- return;
788
- }
789
- return commitSameUrlNavigatePayload(Promise.resolve(AppElementsWire.decode(result)), actionInitiation, void 0, revalidation);
790
- };
791
- setServerCallback((id, args) => {
792
- const releaseCacheInvalidationGuard = historyController.beginCacheInvalidationGuard();
793
- return Promise.resolve().then(() => serverActionCallback(id, args)).finally(releaseCacheInvalidationGuard);
683
+ },
684
+ syncCurrentHistoryState: (previousNextUrl, bfcacheIds) => historyController.syncCurrentHistoryStatePreviousNextUrl(previousNextUrl, bfcacheIds),
685
+ syncServerActionHttpFallbackHead
686
+ })).finally(releaseCacheInvalidationGuard);
794
687
  });
795
688
  }
796
689
  async function main() {
797
690
  if (!claimInitialAppRouterBootstrap()) return;
798
- registerServerActionCallback();
691
+ if (hasServerActions) registerServerActionCallback();
799
692
  installAppNavigationFailureListeners();
693
+ let devErrorOverlay = null;
800
694
  if (import.meta.env.DEV) {
801
- installDevErrorOverlay();
802
- installViteHmrErrorHandler(import.meta.hot);
803
- reportInitialDevServerErrors();
695
+ devErrorOverlay = await import("./dev-error-overlay.js");
696
+ devErrorOverlay.installDevErrorOverlay();
697
+ devErrorOverlay.installViteHmrErrorHandler(import.meta.hot);
698
+ devErrorOverlay.reportInitialDevServerErrors();
804
699
  }
805
700
  const rscStream = await readInitialRscStream();
806
701
  if (rscStream === null) return;
807
- bootstrapHydration(rscStream);
702
+ bootstrapHydration(rscStream, devErrorOverlay);
808
703
  }
809
- function bootstrapHydration(rscStream) {
704
+ function bootstrapHydration(rscStream, devErrorOverlay) {
810
705
  const root = decodeAppElementsPromise(createFromReadableStream(rscStream));
811
706
  const initialNavigationSnapshot = createClientNavigationRenderSnapshot(window.location.href, latestClientParams);
812
707
  historyController.writeBootstrapHistoryMetadata();
813
708
  const onUncaughtError = createOnUncaughtError();
814
709
  const formState = consumeInitialFormState(getVinextBrowserGlobal());
815
- const hydrateRootOptions = import.meta.env.DEV ? createVinextHydrateRootOptions({
710
+ const hydrateRootOptions = import.meta.env.DEV && devErrorOverlay ? createVinextHydrateRootOptions({
816
711
  formState,
817
- onCaughtError: createDevOnCaughtError(devOnCaughtError, onUncaughtError),
712
+ onCaughtError: createDevOnCaughtError(devErrorOverlay.devOnCaughtError, onUncaughtError),
818
713
  onUncaughtError
819
714
  }) : createVinextHydrateRootOptions({
820
715
  formState,
@@ -1141,7 +1036,7 @@ function bootstrapHydration(rscStream) {
1141
1036
  restorePopstateScrollPosition
1142
1037
  }, event.state);
1143
1038
  });
1144
- if (import.meta.hot) {
1039
+ if (import.meta.env.DEV && import.meta.hot) {
1145
1040
  const applyRscHmrUpdate = async (updateId) => {
1146
1041
  if (updateId !== latestRscHmrUpdateId) return;
1147
1042
  if (document.documentElement.id === "__next_error__") {
@@ -1157,7 +1052,7 @@ function bootstrapHydration(rscStream) {
1157
1052
  if (!browserNavigationController.hasBrowserRouterState()) return;
1158
1053
  clearClientNavigationCaches();
1159
1054
  const navigationSnapshot = createClientNavigationRenderSnapshot(window.location.href, latestClientParams);
1160
- dismissOverlay();
1055
+ devErrorOverlay?.dismissOverlay();
1161
1056
  const hmrHeaders = createRscRequestHeaders();
1162
1057
  await browserNavigationController.hmrReplaceTree(decodeAppElementsPromise(createFromFetch(fetch(await createRscRequestUrl(window.location.pathname + window.location.search, hmrHeaders), { headers: hmrHeaders }))), navigationSnapshot);
1163
1058
  };
@@ -45,6 +45,7 @@ type CommitNavigationHistoryOptions = {
45
45
  targetHistoryIndex?: number | null;
46
46
  stageClientParams: () => void;
47
47
  };
48
+ declare function createCanonicalBrowserHistoryHref(href: string): string;
48
49
  /**
49
50
  * Owns App Router browser-history metadata and traversal bookkeeping behind a
50
51
  * typed seam: traversal index allocation/commit, push/replace/traverse/hash-only
@@ -101,4 +102,4 @@ declare class AppBrowserHistoryController {
101
102
  restoreHistorySnapshot(options: RestoreHistorySnapshotOptions): boolean;
102
103
  }
103
104
  //#endregion
104
- export { AppBrowserHistoryController, RestorableSnapshotCandidate };
105
+ export { AppBrowserHistoryController, RestorableSnapshotCandidate, createCanonicalBrowserHistoryHref };
@@ -1,5 +1,9 @@
1
1
  import { RestorableClientStateController, createHistoryStateWithNavigationMetadata, readHistoryStateBfcacheIds, readHistoryStatePreviousNextUrl, readHistoryStateTraversalIndex, resolveHistoryTraversalIntent } from "./app-history-state.js";
2
2
  //#region src/server/app-browser-history-controller.ts
3
+ function createCanonicalBrowserHistoryHref(href) {
4
+ const url = new URL(href);
5
+ return `${url.pathname}${url.search}${url.hash}`;
6
+ }
3
7
  function stripVinextScrollState(state) {
4
8
  if (!state || typeof state !== "object") return state;
5
9
  const nextState = {};
@@ -166,7 +170,7 @@ var AppBrowserHistoryController = class {
166
170
  this.#replaceHistoryState(createHistoryStateWithNavigationMetadata(this.#readHistoryState(), {
167
171
  previousNextUrl: null,
168
172
  traversalIndex: this.#currentHistoryTraversalIndex
169
- }), this.#readCurrentHref());
173
+ }), createCanonicalBrowserHistoryHref(this.#readCurrentHref()));
170
174
  }
171
175
  /** History write performed on the first committed (hydrated) render. */
172
176
  writeHydratedHistoryMetadata(options) {
@@ -207,4 +211,4 @@ function areBfcacheIdMapsEqual(a, b) {
207
211
  return aEntries.every(([key, value]) => b[key] === value);
208
212
  }
209
213
  //#endregion
210
- export { AppBrowserHistoryController };
214
+ export { AppBrowserHistoryController, createCanonicalBrowserHistoryHref };
@@ -0,0 +1,32 @@
1
+ import { AppElements, AppWireElements } from "./app-elements-wire.js";
2
+ import { navigationPlanner } from "./navigation-planner.js";
3
+ import { AppBrowserServerActionResult, ServerActionRevalidationKind } from "./app-browser-action-result.js";
4
+ import { AppRouterState } from "./app-browser-state.js";
5
+
6
+ //#region src/server/app-browser-server-action-client.d.ts
7
+ type ServerActionResult = AppBrowserServerActionResult<AppWireElements>;
8
+ type ClientServerActionInitiation = {
9
+ href: string;
10
+ navigationId: number;
11
+ path: string;
12
+ routerState: AppRouterState;
13
+ };
14
+ type ActionRedirectTarget = {
15
+ href: string;
16
+ type: string;
17
+ status: number;
18
+ };
19
+ type ClientServerActionDeps = {
20
+ basePath: string;
21
+ clearClientNavigationCaches(): void;
22
+ clientRscCompatibilityId: string | null;
23
+ commitSameUrlNavigatePayload(elements: Promise<AppElements>, actionInitiation: ClientServerActionInitiation, returnValue: ServerActionResult["returnValue"] | undefined, revalidation: ServerActionRevalidationKind): Promise<unknown>;
24
+ navigationPlanner: typeof navigationPlanner;
25
+ performHardNavigation(url: string, historyMode?: "assign" | "replace"): void;
26
+ renderRedirectPayload(elements: AppElements, target: ActionRedirectTarget, actionInitiation: ClientServerActionInitiation): void;
27
+ syncCurrentHistoryState(previousNextUrl: string | null, bfcacheIds: Readonly<Record<string, string>>): void;
28
+ syncServerActionHttpFallbackHead(status: number | null): void;
29
+ };
30
+ declare function invokeClientServerAction(id: string, args: unknown[], actionInitiation: ClientServerActionInitiation, deps: ClientServerActionDeps): Promise<unknown>;
31
+ //#endregion
32
+ export { ClientServerActionDeps, ClientServerActionInitiation, invokeClientServerAction };