vinext 0.1.1 → 0.1.2
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/README.md +2 -5
- package/dist/build/client-build-config.d.ts +7 -1
- package/dist/build/client-build-config.js +9 -1
- package/dist/check.js +4 -3
- package/dist/client/navigation-runtime.d.ts +3 -2
- package/dist/client/window-next.d.ts +6 -4
- package/dist/config/config-matchers.d.ts +11 -4
- package/dist/config/config-matchers.js +15 -2
- package/dist/config/next-config.d.ts +13 -0
- package/dist/config/next-config.js +2 -0
- package/dist/deploy.js +9 -2
- package/dist/entries/app-rsc-entry.js +7 -1
- package/dist/entries/pages-client-entry.js +1 -1
- package/dist/entries/pages-server-entry.js +7 -6
- package/dist/index.d.ts +0 -2
- package/dist/index.js +86 -78
- package/dist/plugins/dynamic-preload-metadata.d.ts +13 -0
- package/dist/plugins/dynamic-preload-metadata.js +415 -0
- package/dist/plugins/og-assets.js +2 -2
- package/dist/plugins/optimize-imports.d.ts +8 -4
- package/dist/plugins/optimize-imports.js +16 -12
- package/dist/plugins/sass.d.ts +53 -24
- package/dist/plugins/sass.js +249 -1
- package/dist/plugins/wasm-module-import.d.ts +15 -0
- package/dist/plugins/wasm-module-import.js +50 -0
- package/dist/routing/app-route-graph.d.ts +23 -1
- package/dist/routing/app-route-graph.js +47 -8
- package/dist/routing/file-matcher.js +1 -1
- package/dist/server/app-browser-entry.js +108 -213
- package/dist/server/app-browser-error.d.ts +4 -1
- package/dist/server/app-browser-error.js +7 -1
- package/dist/server/app-browser-history-controller.d.ts +104 -0
- package/dist/server/app-browser-history-controller.js +210 -0
- package/dist/server/app-browser-navigation-controller.d.ts +3 -2
- package/dist/server/app-browser-navigation-controller.js +10 -7
- package/dist/server/app-browser-rsc-redirect.d.ts +11 -2
- package/dist/server/app-browser-rsc-redirect.js +30 -8
- package/dist/server/app-browser-state.js +4 -7
- package/dist/server/app-browser-visible-commit.js +1 -1
- package/dist/server/app-fallback-renderer.d.ts +2 -1
- package/dist/server/app-fallback-renderer.js +3 -1
- package/dist/server/app-middleware.js +1 -0
- package/dist/server/app-optimistic-routing.js +22 -1
- package/dist/server/app-page-boundary-render.d.ts +2 -1
- package/dist/server/app-page-boundary-render.js +4 -2
- package/dist/server/app-page-cache.js +9 -7
- package/dist/server/app-page-dispatch.d.ts +8 -0
- package/dist/server/app-page-dispatch.js +18 -5
- package/dist/server/app-page-element-builder.d.ts +22 -2
- package/dist/server/app-page-element-builder.js +37 -8
- package/dist/server/app-page-execution.d.ts +1 -1
- package/dist/server/app-page-execution.js +32 -17
- package/dist/server/app-page-render.d.ts +1 -1
- package/dist/server/app-page-render.js +7 -14
- package/dist/server/app-page-request.d.ts +1 -0
- package/dist/server/app-page-request.js +3 -2
- package/dist/server/app-page-response.js +1 -1
- package/dist/server/app-page-route-wiring.d.ts +3 -1
- package/dist/server/app-page-route-wiring.js +8 -7
- package/dist/server/app-page-stream.d.ts +1 -6
- package/dist/server/app-page-stream.js +1 -4
- package/dist/server/app-route-handler-response.js +11 -10
- package/dist/server/app-route-handler-runtime.js +12 -1
- package/dist/server/app-rsc-handler.js +1 -1
- package/dist/server/app-rsc-response-finalizer.js +1 -1
- package/dist/server/app-server-action-execution.d.ts +11 -0
- package/dist/server/app-server-action-execution.js +5 -2
- package/dist/server/app-ssr-entry.js +2 -2
- package/dist/server/app-ssr-stream.js +9 -1
- package/dist/server/dev-lockfile.js +2 -1
- package/dist/server/dev-server.js +43 -12
- package/dist/server/headers.d.ts +8 -1
- package/dist/server/headers.js +8 -1
- package/dist/server/instrumentation-runtime.d.ts +6 -0
- package/dist/server/instrumentation-runtime.js +8 -0
- package/dist/server/isr-decision.d.ts +79 -0
- package/dist/server/isr-decision.js +70 -0
- package/dist/server/metadata-route-response.js +5 -3
- package/dist/server/middleware-runtime.d.ts +13 -0
- package/dist/server/middleware-runtime.js +11 -7
- package/dist/server/middleware.js +1 -0
- package/dist/server/navigation-planner.d.ts +62 -1
- package/dist/server/navigation-planner.js +188 -0
- package/dist/server/navigation-trace.d.ts +11 -1
- package/dist/server/navigation-trace.js +11 -1
- package/dist/server/normalize-path.d.ts +0 -8
- package/dist/server/normalize-path.js +3 -1
- package/dist/server/otel-tracer-extension.d.ts +45 -0
- package/dist/server/otel-tracer-extension.js +89 -0
- package/dist/server/pages-api-route.d.ts +14 -3
- package/dist/server/pages-api-route.js +6 -1
- package/dist/server/pages-asset-tags.d.ts +15 -4
- package/dist/server/pages-asset-tags.js +18 -12
- package/dist/server/pages-data-route.js +5 -1
- package/dist/server/pages-node-compat.d.ts +3 -11
- package/dist/server/pages-node-compat.js +174 -121
- package/dist/server/pages-page-data.d.ts +28 -0
- package/dist/server/pages-page-data.js +61 -17
- package/dist/server/pages-page-handler.d.ts +1 -0
- package/dist/server/pages-page-handler.js +22 -6
- package/dist/server/pages-page-response.d.ts +45 -1
- package/dist/server/pages-page-response.js +66 -5
- package/dist/server/pages-readiness.d.ts +1 -1
- package/dist/server/pages-request-pipeline.d.ts +15 -1
- package/dist/server/pages-request-pipeline.js +23 -2
- package/dist/server/prod-server.d.ts +39 -1
- package/dist/server/prod-server.js +98 -34
- package/dist/shims/cache-runtime.js +9 -2
- package/dist/shims/dynamic-preload-chunks.d.ts +8 -0
- package/dist/shims/dynamic-preload-chunks.js +77 -0
- package/dist/shims/dynamic.d.ts +4 -0
- package/dist/shims/dynamic.js +4 -2
- package/dist/shims/error-boundary.d.ts +4 -4
- package/dist/shims/error.js +37 -11
- package/dist/shims/fetch-cache.d.ts +9 -1
- package/dist/shims/fetch-cache.js +11 -1
- package/dist/shims/head.js +6 -1
- package/dist/shims/headers.d.ts +16 -2
- package/dist/shims/headers.js +37 -1
- package/dist/shims/image-config.js +7 -1
- package/dist/shims/internal/app-route-detection.d.ts +6 -3
- package/dist/shims/internal/app-route-detection.js +10 -6
- package/dist/shims/internal/app-router-context.d.ts +5 -0
- package/dist/shims/metadata.d.ts +6 -2
- package/dist/shims/metadata.js +32 -14
- package/dist/shims/navigation.d.ts +7 -16
- package/dist/shims/navigation.js +33 -16
- package/dist/shims/router.js +28 -1
- package/dist/shims/script-nonce-context.d.ts +1 -1
- package/dist/shims/script-nonce-context.js +11 -3
- package/dist/shims/server.d.ts +17 -1
- package/dist/shims/server.js +31 -6
- package/dist/shims/slot.js +1 -1
- package/dist/shims/unified-request-context.js +1 -0
- package/dist/typegen.js +1 -0
- package/dist/utils/client-build-manifest.js +15 -5
- package/dist/utils/client-runtime-metadata.d.ts +45 -0
- package/dist/utils/client-runtime-metadata.js +63 -0
- package/dist/utils/hash.d.ts +17 -1
- package/dist/utils/hash.js +36 -1
- package/dist/utils/lazy-chunks.d.ts +27 -1
- package/dist/utils/lazy-chunks.js +65 -1
- package/dist/utils/manifest-paths.d.ts +20 -2
- package/dist/utils/manifest-paths.js +38 -3
- package/dist/utils/path.d.ts +2 -1
- package/dist/utils/path.js +5 -1
- package/package.json +2 -2
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { BfcacheIdMap, HistoryTraversalIntent } from "./app-history-state.js";
|
|
2
|
+
import { AppRouterState } from "./app-browser-state.js";
|
|
3
|
+
import { HistoryUpdateMode } from "./app-browser-navigation-controller.js";
|
|
4
|
+
|
|
5
|
+
//#region src/server/app-browser-history-controller.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Visible router-state metadata at the instant a hash-only navigation commits.
|
|
8
|
+
* `null` means the browser router tree has not committed yet, so the controller
|
|
9
|
+
* falls back to reading the same facts off the live history entry.
|
|
10
|
+
*/
|
|
11
|
+
type VisibleNavigationMetadata = {
|
|
12
|
+
bfcacheIds: BfcacheIdMap | null;
|
|
13
|
+
previousNextUrl: string | null;
|
|
14
|
+
};
|
|
15
|
+
type AppBrowserHistoryControllerDeps = {
|
|
16
|
+
initialHistoryState: unknown;
|
|
17
|
+
maxHistoryStateSnapshots: number; /** Reads `window.history.state`. Injected so the controller stays unit-testable. */
|
|
18
|
+
readHistoryState: () => unknown; /** Reads `window.location.href`. Injected so the controller stays unit-testable. */
|
|
19
|
+
readCurrentHref: () => string; /** Wraps `pushHistoryStateWithoutNotify(state, "", href)`. */
|
|
20
|
+
pushHistoryState: (state: unknown, href: string) => void; /** Wraps `replaceHistoryStateWithoutNotify(state, "", href)`. */
|
|
21
|
+
replaceHistoryState: (state: unknown, href: string) => void;
|
|
22
|
+
readVisibleNavigationMetadata: () => VisibleNavigationMetadata | null;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Candidate visible state resolved from a restorable history snapshot, handed to
|
|
26
|
+
* the entry's approved-visible-restore callback. The controller resolves the
|
|
27
|
+
* candidate and owns the traversal-index commit; the entry owns the actual
|
|
28
|
+
* `AppBrowserNavigationController.restoreHistorySnapshotVisibleState()` call and
|
|
29
|
+
* the `ApprovedVisibleCommit` boundary.
|
|
30
|
+
*/
|
|
31
|
+
type RestorableSnapshotCandidate = {
|
|
32
|
+
state: AppRouterState;
|
|
33
|
+
beforeCommit: () => void;
|
|
34
|
+
};
|
|
35
|
+
type RestoreHistorySnapshotOptions = {
|
|
36
|
+
historyState: unknown;
|
|
37
|
+
stageClientParams: (params: Record<string, string | string[]>) => void;
|
|
38
|
+
approveVisibleRestore: (candidate: RestorableSnapshotCandidate) => boolean;
|
|
39
|
+
};
|
|
40
|
+
type CommitNavigationHistoryOptions = {
|
|
41
|
+
bfcacheIds: BfcacheIdMap;
|
|
42
|
+
href: string;
|
|
43
|
+
historyUpdateMode: HistoryUpdateMode | undefined;
|
|
44
|
+
previousNextUrl: string | null;
|
|
45
|
+
targetHistoryIndex?: number | null;
|
|
46
|
+
stageClientParams: () => void;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Owns App Router browser-history metadata and traversal bookkeeping behind a
|
|
50
|
+
* typed seam: traversal index allocation/commit, push/replace/traverse/hash-only
|
|
51
|
+
* history-state writes, BFCache epoch/snapshot invalidation through
|
|
52
|
+
* `RestorableClientStateController`, and restorable-snapshot candidate
|
|
53
|
+
* resolution.
|
|
54
|
+
*
|
|
55
|
+
* Ownership boundary: this is not a second router or visible-state authority. It
|
|
56
|
+
* resolves history facts and delegates visible restoration through an injected
|
|
57
|
+
* approved-commit callback. It never sets router state directly, never imports
|
|
58
|
+
* `applyApprovedVisibleCommit()`, and never bypasses the `ApprovedVisibleCommit`
|
|
59
|
+
* boundary owned by `AppBrowserNavigationController`.
|
|
60
|
+
*/
|
|
61
|
+
declare class AppBrowserHistoryController {
|
|
62
|
+
#private;
|
|
63
|
+
constructor(deps: AppBrowserHistoryControllerDeps);
|
|
64
|
+
get currentHistoryTraversalIndex(): number | null;
|
|
65
|
+
allocateNavigationHistoryTraversalIndex(historyUpdateMode: HistoryUpdateMode | undefined): number | null;
|
|
66
|
+
commitHistoryTraversalIndex(index: number | null): void;
|
|
67
|
+
commitTraversalIndexFromHistoryState(historyState: unknown): void;
|
|
68
|
+
resolveTraversalIntent(historyState: unknown): HistoryTraversalIntent;
|
|
69
|
+
readCurrentBfcacheVersionHistoryIds(historyState: unknown): BfcacheIdMap | null;
|
|
70
|
+
isCacheInvalidationGuarded(): boolean;
|
|
71
|
+
isCurrentBfcacheVersion(historyState: unknown): boolean;
|
|
72
|
+
beginCacheInvalidationGuard(): () => void;
|
|
73
|
+
invalidateRestorableClientState(): void;
|
|
74
|
+
rememberHistoryStateSnapshot(state: AppRouterState): void;
|
|
75
|
+
commitHashOnlyNavigation(href: string, historyUpdateMode: HistoryUpdateMode, scroll: boolean): void;
|
|
76
|
+
/**
|
|
77
|
+
* Writes the history entry for an approved push/replace/traverse commit and
|
|
78
|
+
* advances the traversal index. `stageClientParams` runs at the exact point it
|
|
79
|
+
* ran inline in the browser-entry commit effect so client-param staging stays
|
|
80
|
+
* ordered relative to the history write. Mirrors Next.js committing tree state
|
|
81
|
+
* into the history entry during the navigation commit.
|
|
82
|
+
*/
|
|
83
|
+
commitNavigationHistory(options: CommitNavigationHistoryOptions): void;
|
|
84
|
+
syncCurrentHistoryStatePreviousNextUrl(previousNextUrl: string | null, bfcacheIds?: BfcacheIdMap | null): void;
|
|
85
|
+
/** Initial history write performed before hydration starts. */
|
|
86
|
+
writeBootstrapHistoryMetadata(): void;
|
|
87
|
+
/** History write performed on the first committed (hydrated) render. */
|
|
88
|
+
writeHydratedHistoryMetadata(options: {
|
|
89
|
+
bfcacheIds: BfcacheIdMap;
|
|
90
|
+
previousNextUrl: string | null;
|
|
91
|
+
}): void;
|
|
92
|
+
/**
|
|
93
|
+
* Resolves a restorable snapshot candidate for the given history entry and
|
|
94
|
+
* commits the traversal index after, and only after, the injected
|
|
95
|
+
* approved-visible-restore callback succeeds. The traversal-index commit and
|
|
96
|
+
* client-param staging run inside `beforeCommit`, which the
|
|
97
|
+
* `AppBrowserNavigationController` invokes only once the `ApprovedVisibleCommit`
|
|
98
|
+
* is approved. Returns false when no snapshot is restorable or the restore is
|
|
99
|
+
* not approved.
|
|
100
|
+
*/
|
|
101
|
+
restoreHistorySnapshot(options: RestoreHistorySnapshotOptions): boolean;
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
104
|
+
export { AppBrowserHistoryController, RestorableSnapshotCandidate };
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { RestorableClientStateController, createHistoryStateWithNavigationMetadata, readHistoryStateBfcacheIds, readHistoryStatePreviousNextUrl, readHistoryStateTraversalIndex, resolveHistoryTraversalIntent } from "./app-history-state.js";
|
|
2
|
+
//#region src/server/app-browser-history-controller.ts
|
|
3
|
+
function stripVinextScrollState(state) {
|
|
4
|
+
if (!state || typeof state !== "object") return state;
|
|
5
|
+
const nextState = {};
|
|
6
|
+
for (const [key, value] of Object.entries(state)) {
|
|
7
|
+
if (key === "__vinext_scrollX" || key === "__vinext_scrollY") continue;
|
|
8
|
+
nextState[key] = value;
|
|
9
|
+
}
|
|
10
|
+
return Object.keys(nextState).length > 0 ? nextState : null;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Owns App Router browser-history metadata and traversal bookkeeping behind a
|
|
14
|
+
* typed seam: traversal index allocation/commit, push/replace/traverse/hash-only
|
|
15
|
+
* history-state writes, BFCache epoch/snapshot invalidation through
|
|
16
|
+
* `RestorableClientStateController`, and restorable-snapshot candidate
|
|
17
|
+
* resolution.
|
|
18
|
+
*
|
|
19
|
+
* Ownership boundary: this is not a second router or visible-state authority. It
|
|
20
|
+
* resolves history facts and delegates visible restoration through an injected
|
|
21
|
+
* approved-commit callback. It never sets router state directly, never imports
|
|
22
|
+
* `applyApprovedVisibleCommit()`, and never bypasses the `ApprovedVisibleCommit`
|
|
23
|
+
* boundary owned by `AppBrowserNavigationController`.
|
|
24
|
+
*/
|
|
25
|
+
var AppBrowserHistoryController = class {
|
|
26
|
+
#restorableClientState;
|
|
27
|
+
#readHistoryState;
|
|
28
|
+
#readCurrentHref;
|
|
29
|
+
#pushHistoryState;
|
|
30
|
+
#replaceHistoryState;
|
|
31
|
+
#readVisibleNavigationMetadata;
|
|
32
|
+
#currentHistoryTraversalIndex;
|
|
33
|
+
#nextHistoryTraversalIndex;
|
|
34
|
+
constructor(deps) {
|
|
35
|
+
this.#readHistoryState = deps.readHistoryState;
|
|
36
|
+
this.#readCurrentHref = deps.readCurrentHref;
|
|
37
|
+
this.#pushHistoryState = deps.pushHistoryState;
|
|
38
|
+
this.#replaceHistoryState = deps.replaceHistoryState;
|
|
39
|
+
this.#readVisibleNavigationMetadata = deps.readVisibleNavigationMetadata;
|
|
40
|
+
this.#restorableClientState = new RestorableClientStateController({
|
|
41
|
+
initialHistoryState: deps.initialHistoryState,
|
|
42
|
+
maxHistoryStateSnapshots: deps.maxHistoryStateSnapshots
|
|
43
|
+
});
|
|
44
|
+
this.#currentHistoryTraversalIndex = readHistoryStateTraversalIndex(deps.initialHistoryState) ?? 0;
|
|
45
|
+
this.#nextHistoryTraversalIndex = this.#currentHistoryTraversalIndex;
|
|
46
|
+
}
|
|
47
|
+
get currentHistoryTraversalIndex() {
|
|
48
|
+
return this.#currentHistoryTraversalIndex;
|
|
49
|
+
}
|
|
50
|
+
allocateNavigationHistoryTraversalIndex(historyUpdateMode) {
|
|
51
|
+
switch (historyUpdateMode) {
|
|
52
|
+
case "push": return this.#nextHistoryTraversalIndex + 1;
|
|
53
|
+
case "replace": return this.#currentHistoryTraversalIndex;
|
|
54
|
+
case void 0: return null;
|
|
55
|
+
default: throw new Error("[vinext] Unknown history update mode: " + String(historyUpdateMode));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
commitHistoryTraversalIndex(index) {
|
|
59
|
+
this.#currentHistoryTraversalIndex = index;
|
|
60
|
+
if (index !== null) this.#nextHistoryTraversalIndex = Math.max(this.#nextHistoryTraversalIndex, index);
|
|
61
|
+
}
|
|
62
|
+
commitTraversalIndexFromHistoryState(historyState) {
|
|
63
|
+
this.commitHistoryTraversalIndex(readHistoryStateTraversalIndex(historyState));
|
|
64
|
+
}
|
|
65
|
+
resolveTraversalIntent(historyState) {
|
|
66
|
+
return resolveHistoryTraversalIntent({
|
|
67
|
+
currentHistoryIndex: this.#currentHistoryTraversalIndex,
|
|
68
|
+
historyState
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
readCurrentBfcacheVersionHistoryIds(historyState) {
|
|
72
|
+
return this.#restorableClientState.readCurrentBfcacheVersionHistoryIds(historyState);
|
|
73
|
+
}
|
|
74
|
+
isCacheInvalidationGuarded() {
|
|
75
|
+
return this.#restorableClientState.isCacheInvalidationGuarded();
|
|
76
|
+
}
|
|
77
|
+
isCurrentBfcacheVersion(historyState) {
|
|
78
|
+
return this.#restorableClientState.isCurrentBfcacheVersion(historyState);
|
|
79
|
+
}
|
|
80
|
+
beginCacheInvalidationGuard() {
|
|
81
|
+
return this.#restorableClientState.beginCacheInvalidationGuard();
|
|
82
|
+
}
|
|
83
|
+
invalidateRestorableClientState() {
|
|
84
|
+
this.#restorableClientState.invalidateClientState();
|
|
85
|
+
}
|
|
86
|
+
rememberHistoryStateSnapshot(state) {
|
|
87
|
+
this.#restorableClientState.rememberHistoryStateSnapshot({
|
|
88
|
+
historyIndex: this.#currentHistoryTraversalIndex,
|
|
89
|
+
state
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
commitHashOnlyNavigation(href, historyUpdateMode, scroll) {
|
|
93
|
+
const navigationHistoryIndex = this.allocateNavigationHistoryTraversalIndex(historyUpdateMode);
|
|
94
|
+
const historyState = this.#readHistoryState();
|
|
95
|
+
const visible = this.#readVisibleNavigationMetadata();
|
|
96
|
+
const previousNextUrl = visible ? visible.previousNextUrl : readHistoryStatePreviousNextUrl(historyState);
|
|
97
|
+
const bfcacheIds = visible ? visible.bfcacheIds : this.#restorableClientState.readCurrentBfcacheVersionHistoryIds(historyState);
|
|
98
|
+
const nextHistoryState = createHistoryStateWithNavigationMetadata(this.#createHashOnlyNavigationBaseHistoryState(historyUpdateMode, scroll), {
|
|
99
|
+
bfcacheIds,
|
|
100
|
+
bfcacheVersion: bfcacheIds === null ? void 0 : this.#restorableClientState.currentBfcacheVersion,
|
|
101
|
+
previousNextUrl,
|
|
102
|
+
traversalIndex: navigationHistoryIndex
|
|
103
|
+
});
|
|
104
|
+
if (historyUpdateMode === "replace") this.#replaceHistoryState(nextHistoryState, href);
|
|
105
|
+
else this.#pushHistoryState(nextHistoryState, href);
|
|
106
|
+
this.commitHistoryTraversalIndex(navigationHistoryIndex);
|
|
107
|
+
}
|
|
108
|
+
#createHashOnlyNavigationBaseHistoryState(historyUpdateMode, scroll) {
|
|
109
|
+
if (historyUpdateMode !== "replace") return null;
|
|
110
|
+
const historyState = this.#readHistoryState();
|
|
111
|
+
return scroll ? stripVinextScrollState(historyState) : historyState;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Writes the history entry for an approved push/replace/traverse commit and
|
|
115
|
+
* advances the traversal index. `stageClientParams` runs at the exact point it
|
|
116
|
+
* ran inline in the browser-entry commit effect so client-param staging stays
|
|
117
|
+
* ordered relative to the history write. Mirrors Next.js committing tree state
|
|
118
|
+
* into the history entry during the navigation commit.
|
|
119
|
+
*/
|
|
120
|
+
commitNavigationHistory(options) {
|
|
121
|
+
const currentHref = this.#readCurrentHref();
|
|
122
|
+
const origin = new URL(currentHref).origin;
|
|
123
|
+
const targetHref = new URL(options.href, origin).href;
|
|
124
|
+
const preserveExistingState = options.historyUpdateMode === "replace";
|
|
125
|
+
const navigationHistoryIndex = options.targetHistoryIndex !== void 0 ? options.targetHistoryIndex : this.allocateNavigationHistoryTraversalIndex(options.historyUpdateMode);
|
|
126
|
+
const historyState = createHistoryStateWithNavigationMetadata(preserveExistingState ? this.#readHistoryState() : null, {
|
|
127
|
+
bfcacheIds: options.bfcacheIds,
|
|
128
|
+
bfcacheVersion: this.#restorableClientState.currentBfcacheVersion,
|
|
129
|
+
previousNextUrl: options.previousNextUrl,
|
|
130
|
+
traversalIndex: navigationHistoryIndex
|
|
131
|
+
});
|
|
132
|
+
let wroteHistoryState = false;
|
|
133
|
+
if (options.historyUpdateMode === "replace" && currentHref !== targetHref) {
|
|
134
|
+
options.stageClientParams();
|
|
135
|
+
this.#replaceHistoryState(historyState, options.href);
|
|
136
|
+
wroteHistoryState = true;
|
|
137
|
+
this.commitHistoryTraversalIndex(navigationHistoryIndex);
|
|
138
|
+
} else if (options.historyUpdateMode === "push" && currentHref !== targetHref) {
|
|
139
|
+
options.stageClientParams();
|
|
140
|
+
this.#pushHistoryState(historyState, options.href);
|
|
141
|
+
wroteHistoryState = true;
|
|
142
|
+
this.commitHistoryTraversalIndex(navigationHistoryIndex);
|
|
143
|
+
}
|
|
144
|
+
if (!wroteHistoryState) {
|
|
145
|
+
this.syncCurrentHistoryStatePreviousNextUrl(options.previousNextUrl, options.bfcacheIds);
|
|
146
|
+
options.stageClientParams();
|
|
147
|
+
if (options.targetHistoryIndex !== void 0) this.commitHistoryTraversalIndex(options.targetHistoryIndex);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
syncCurrentHistoryStatePreviousNextUrl(previousNextUrl, bfcacheIds) {
|
|
151
|
+
if (this.#isHistoryStateNavigationMetadataInSync(this.#readHistoryState(), previousNextUrl, bfcacheIds)) return;
|
|
152
|
+
const nextHistoryState = createHistoryStateWithNavigationMetadata(this.#readHistoryState(), {
|
|
153
|
+
bfcacheIds,
|
|
154
|
+
bfcacheVersion: bfcacheIds === void 0 ? void 0 : this.#restorableClientState.currentBfcacheVersion,
|
|
155
|
+
previousNextUrl
|
|
156
|
+
});
|
|
157
|
+
this.#replaceHistoryState(nextHistoryState, this.#readCurrentHref());
|
|
158
|
+
if (this.#isHistoryStateNavigationMetadataInSync(this.#readHistoryState(), previousNextUrl, bfcacheIds)) return;
|
|
159
|
+
this.#replaceHistoryState(nextHistoryState, this.#readCurrentHref());
|
|
160
|
+
}
|
|
161
|
+
#isHistoryStateNavigationMetadataInSync(state, previousNextUrl, bfcacheIds) {
|
|
162
|
+
return readHistoryStatePreviousNextUrl(state) === previousNextUrl && (bfcacheIds === void 0 || areBfcacheIdMapsEqual(readHistoryStateBfcacheIds(state), bfcacheIds) && this.#restorableClientState.isCurrentBfcacheVersion(state));
|
|
163
|
+
}
|
|
164
|
+
/** Initial history write performed before hydration starts. */
|
|
165
|
+
writeBootstrapHistoryMetadata() {
|
|
166
|
+
this.#replaceHistoryState(createHistoryStateWithNavigationMetadata(this.#readHistoryState(), {
|
|
167
|
+
previousNextUrl: null,
|
|
168
|
+
traversalIndex: this.#currentHistoryTraversalIndex
|
|
169
|
+
}), this.#readCurrentHref());
|
|
170
|
+
}
|
|
171
|
+
/** History write performed on the first committed (hydrated) render. */
|
|
172
|
+
writeHydratedHistoryMetadata(options) {
|
|
173
|
+
this.#replaceHistoryState(createHistoryStateWithNavigationMetadata(this.#readHistoryState(), {
|
|
174
|
+
bfcacheIds: options.bfcacheIds,
|
|
175
|
+
bfcacheVersion: this.#restorableClientState.currentBfcacheVersion,
|
|
176
|
+
previousNextUrl: options.previousNextUrl,
|
|
177
|
+
traversalIndex: this.#currentHistoryTraversalIndex
|
|
178
|
+
}), this.#readCurrentHref());
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Resolves a restorable snapshot candidate for the given history entry and
|
|
182
|
+
* commits the traversal index after, and only after, the injected
|
|
183
|
+
* approved-visible-restore callback succeeds. The traversal-index commit and
|
|
184
|
+
* client-param staging run inside `beforeCommit`, which the
|
|
185
|
+
* `AppBrowserNavigationController` invokes only once the `ApprovedVisibleCommit`
|
|
186
|
+
* is approved. Returns false when no snapshot is restorable or the restore is
|
|
187
|
+
* not approved.
|
|
188
|
+
*/
|
|
189
|
+
restoreHistorySnapshot(options) {
|
|
190
|
+
const decision = this.#restorableClientState.resolveHistoryStateSnapshotRestore(options.historyState);
|
|
191
|
+
if (decision.kind === "skip") return false;
|
|
192
|
+
return options.approveVisibleRestore({
|
|
193
|
+
state: decision.state,
|
|
194
|
+
beforeCommit: () => {
|
|
195
|
+
this.commitHistoryTraversalIndex(decision.targetHistoryIndex);
|
|
196
|
+
options.stageClientParams(decision.state.navigationSnapshot.params);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
function areBfcacheIdMapsEqual(a, b) {
|
|
202
|
+
if (a === b) return true;
|
|
203
|
+
if (a === null || b === null) return false;
|
|
204
|
+
const aEntries = Object.entries(a);
|
|
205
|
+
const bEntries = Object.entries(b);
|
|
206
|
+
if (aEntries.length !== bEntries.length) return false;
|
|
207
|
+
return aEntries.every(([key, value]) => b[key] === value);
|
|
208
|
+
}
|
|
209
|
+
//#endregion
|
|
210
|
+
export { AppBrowserHistoryController };
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { RouteManifest } from "../routing/app-route-graph.js";
|
|
2
2
|
import { AppRouterScrollIntent } from "../shims/app-router-scroll-state.js";
|
|
3
|
+
import { NavigationRuntimeVisibleCommitMode } from "../client/navigation-runtime.js";
|
|
3
4
|
import { ServerActionRevalidationKind } from "./app-browser-action-result.js";
|
|
4
5
|
import { AppElements } from "./app-elements-wire.js";
|
|
5
6
|
import { OperationLane } from "./navigation-planner.js";
|
|
6
|
-
import { ClientNavigationRenderSnapshot, commitClientNavigationState } from "../shims/navigation.js";
|
|
7
|
+
import { ClientNavigationRenderSnapshot, commitClientNavigationState, createSnapshotPathAndSearch } from "../shims/navigation.js";
|
|
7
8
|
import { AppNavigationPayloadOrigin, AppRouterState } from "./app-browser-state.js";
|
|
8
9
|
import { Dispatch, ReactNode } from "react";
|
|
9
10
|
|
|
@@ -75,6 +76,7 @@ type BrowserNavigationController = {
|
|
|
75
76
|
targetHistoryIndex?: number | null;
|
|
76
77
|
targetHref: string;
|
|
77
78
|
navId: number;
|
|
79
|
+
visibleCommitMode?: NavigationRuntimeVisibleCommitMode;
|
|
78
80
|
}): Promise<NavigationPayloadOutcome>;
|
|
79
81
|
commitSameUrlNavigatePayload(nextElements: Promise<AppElements>, navigationSnapshot: ClientNavigationRenderSnapshot, returnValue?: {
|
|
80
82
|
ok: boolean;
|
|
@@ -99,7 +101,6 @@ type BrowserNavigationController = {
|
|
|
99
101
|
}): ReactNode;
|
|
100
102
|
};
|
|
101
103
|
declare function clearHardNavigationLoopGuard(): void;
|
|
102
|
-
declare function createSnapshotPathAndSearch(snapshot: ClientNavigationRenderSnapshot): string;
|
|
103
104
|
declare function createBasePathStrippedPathAndSearch(url: URL, basePath: string): string;
|
|
104
105
|
declare function createAppBrowserNavigationController(deps?: BrowserNavigationControllerDeps): BrowserNavigationController;
|
|
105
106
|
//#endregion
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { stripBasePath } from "../utils/base-path.js";
|
|
2
2
|
import { claimAppRouterScrollIntentForCommit, consumeAppRouterScrollIntent } from "../shims/app-router-scroll-state.js";
|
|
3
|
-
import { activateNavigationSnapshot, clearPendingPathname, commitClientNavigationState } from "../shims/navigation.js";
|
|
3
|
+
import { activateNavigationSnapshot, clearPendingPathname, commitClientNavigationState, createSnapshotPathAndSearch } from "../shims/navigation.js";
|
|
4
4
|
import { shouldScheduleRefreshForDiscardedServerAction } from "./app-browser-action-result.js";
|
|
5
5
|
import { FRESH_APP_NAVIGATION_PAYLOAD_ORIGIN, createPendingNavigationCommit } from "./app-browser-state.js";
|
|
6
6
|
import { applyApprovedVisibleCommit, approveHmrVisibleCommit, approvePendingNavigationCommit, resolveAndClassifyNavigationCommit } from "./app-browser-visible-commit.js";
|
|
7
7
|
import { startTransition, useLayoutEffect } from "react";
|
|
8
|
+
import { flushSync } from "react-dom";
|
|
8
9
|
//#region src/server/app-browser-navigation-controller.ts
|
|
9
10
|
const HARD_NAVIGATION_LOOP_GUARD_KEY = "__vinext_hard_navigation_target__";
|
|
10
11
|
function normalizeBrowserHref(href) {
|
|
@@ -50,10 +51,6 @@ function performHardNavigationWithLoopGuard(href, mode = "assign") {
|
|
|
50
51
|
else window.location.assign(href);
|
|
51
52
|
return true;
|
|
52
53
|
}
|
|
53
|
-
function createSnapshotPathAndSearch(snapshot) {
|
|
54
|
-
const query = snapshot.searchParams.toString();
|
|
55
|
-
return query === "" ? snapshot.pathname : `${snapshot.pathname}?${query}`;
|
|
56
|
-
}
|
|
57
54
|
function createBasePathStrippedPathAndSearch(url, basePath) {
|
|
58
55
|
const pathname = stripBasePath(url.pathname, basePath);
|
|
59
56
|
const query = new URLSearchParams(url.search).toString();
|
|
@@ -221,12 +218,18 @@ function createAppBrowserNavigationController(deps = {}) {
|
|
|
221
218
|
}, [renderId]);
|
|
222
219
|
return children;
|
|
223
220
|
}
|
|
224
|
-
function dispatchApprovedVisibleCommit(commit, pendingRouterState) {
|
|
221
|
+
function dispatchApprovedVisibleCommit(commit, pendingRouterState, visibleCommitMode) {
|
|
225
222
|
const setter = getBrowserRouterStateSetter();
|
|
226
223
|
if (pendingRouterState) {
|
|
227
224
|
resolvePendingBrowserRouterState(pendingRouterState, commit);
|
|
228
225
|
return;
|
|
229
226
|
}
|
|
227
|
+
if (visibleCommitMode === "synchronous") {
|
|
228
|
+
flushSync(() => {
|
|
229
|
+
setter(applyApprovedVisibleCommit(getBrowserRouterState(), commit));
|
|
230
|
+
});
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
230
233
|
startTransition(() => {
|
|
231
234
|
setter(applyApprovedVisibleCommit(getBrowserRouterState(), commit));
|
|
232
235
|
});
|
|
@@ -349,7 +352,7 @@ function createAppBrowserNavigationController(deps = {}) {
|
|
|
349
352
|
claimAppRouterScrollIntentForCommit(options.scrollIntent, renderId);
|
|
350
353
|
activateNavigationSnapshot();
|
|
351
354
|
snapshotActivated = true;
|
|
352
|
-
dispatchApprovedVisibleCommit(approvedCommit, options.pendingRouterState);
|
|
355
|
+
dispatchApprovedVisibleCommit(approvedCommit, options.pendingRouterState, options.visibleCommitMode ?? "transition");
|
|
353
356
|
} catch (error) {
|
|
354
357
|
pendingNavigationPrePaintEffects.delete(renderId);
|
|
355
358
|
pendingNavigationCommits.delete(renderId);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
//#region src/server/app-browser-rsc-redirect.d.ts
|
|
2
|
-
declare const MAX_RSC_REDIRECT_DEPTH = 10;
|
|
3
2
|
type RscRedirectHistoryUpdateMode = "push" | "replace" | undefined;
|
|
4
3
|
type RscRedirectLifecycleDecision = {
|
|
4
|
+
href: string;
|
|
5
5
|
kind: "no-redirect";
|
|
6
6
|
} | {
|
|
7
7
|
href: string;
|
|
@@ -24,5 +24,14 @@ declare function resolveRscRedirectLifecycleHop(options: {
|
|
|
24
24
|
requestPreviousNextUrl: string | null;
|
|
25
25
|
responseUrl: string;
|
|
26
26
|
}): RscRedirectLifecycleDecision;
|
|
27
|
+
declare function resolveStreamedRscRedirectLifecycleHop(options: {
|
|
28
|
+
currentHref: string;
|
|
29
|
+
historyUpdateMode: Exclude<RscRedirectHistoryUpdateMode, undefined>;
|
|
30
|
+
maxRedirectDepth?: number;
|
|
31
|
+
origin: string;
|
|
32
|
+
redirectDepth: number;
|
|
33
|
+
requestPreviousNextUrl: string | null;
|
|
34
|
+
streamedRedirectTarget: string;
|
|
35
|
+
}): RscRedirectLifecycleDecision;
|
|
27
36
|
//#endregion
|
|
28
|
-
export {
|
|
37
|
+
export { resolveRscRedirectLifecycleHop, resolveStreamedRscRedirectLifecycleHop };
|
|
@@ -6,17 +6,23 @@ function toVisibleAppHref(href, origin) {
|
|
|
6
6
|
stripRscCacheBustingSearchParam(url);
|
|
7
7
|
return `${stripRscSuffix(url.pathname)}${url.search}${url.hash}`;
|
|
8
8
|
}
|
|
9
|
-
function
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
function toStreamedRedirectVisibleAppHref(href, origin) {
|
|
10
|
+
const url = new URL(href, origin);
|
|
11
|
+
return `${url.pathname}${url.search}${url.hash}`;
|
|
12
|
+
}
|
|
13
|
+
function resolveRedirectLifecycleHopFromTarget(options) {
|
|
14
|
+
if (options.targetUrl.origin !== options.origin) return {
|
|
15
|
+
href: options.targetUrl.href,
|
|
13
16
|
kind: "terminal-hard-navigation",
|
|
14
17
|
reason: "externalRedirect",
|
|
15
18
|
redirectDepth: options.redirectDepth
|
|
16
19
|
};
|
|
17
|
-
const redirectedHref =
|
|
18
|
-
if (redirectedHref === toVisibleAppHref(options.currentHref, options.origin)) return {
|
|
19
|
-
|
|
20
|
+
const redirectedHref = options.redirectedHref;
|
|
21
|
+
if (redirectedHref === toVisibleAppHref(options.currentHref, options.origin)) return {
|
|
22
|
+
href: redirectedHref,
|
|
23
|
+
kind: "no-redirect"
|
|
24
|
+
};
|
|
25
|
+
const maxRedirectDepth = options.maxRedirectDepth ?? MAX_RSC_REDIRECT_DEPTH;
|
|
20
26
|
if (options.redirectDepth >= maxRedirectDepth) return {
|
|
21
27
|
href: redirectedHref,
|
|
22
28
|
kind: "terminal-hard-navigation",
|
|
@@ -31,5 +37,21 @@ function resolveRscRedirectLifecycleHop(options) {
|
|
|
31
37
|
redirectDepth: options.redirectDepth + 1
|
|
32
38
|
};
|
|
33
39
|
}
|
|
40
|
+
function resolveRscRedirectLifecycleHop(options) {
|
|
41
|
+
const responseUrl = new URL(options.responseUrl, options.origin);
|
|
42
|
+
return resolveRedirectLifecycleHopFromTarget({
|
|
43
|
+
...options,
|
|
44
|
+
redirectedHref: resolveHardNavigationTargetFromRscResponse(responseUrl.href, options.currentHref, options.origin),
|
|
45
|
+
targetUrl: responseUrl
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function resolveStreamedRscRedirectLifecycleHop(options) {
|
|
49
|
+
const streamedRedirectUrl = new URL(options.streamedRedirectTarget, options.origin);
|
|
50
|
+
return resolveRedirectLifecycleHopFromTarget({
|
|
51
|
+
...options,
|
|
52
|
+
redirectedHref: toStreamedRedirectVisibleAppHref(options.streamedRedirectTarget, options.origin),
|
|
53
|
+
targetUrl: streamedRedirectUrl
|
|
54
|
+
});
|
|
55
|
+
}
|
|
34
56
|
//#endregion
|
|
35
|
-
export {
|
|
57
|
+
export { resolveRscRedirectLifecycleHop, resolveStreamedRscRedirectLifecycleHop };
|
|
@@ -7,9 +7,10 @@ import { getMountedSlotIds, getMountedSlotIdsHeader } from "./app-elements.js";
|
|
|
7
7
|
import "./app-bfcache-id.js";
|
|
8
8
|
import { createHistoryStateWithNavigationMetadata, createHistoryStateWithPreviousNextUrl, isBfcacheSegmentId, isHistoryStateBfcacheVersionCurrent, readHistoryStateBfcacheIds, readHistoryStateBfcacheVersion, readHistoryStatePreviousNextUrl, readHistoryStateTraversalIndex, resolveHistoryTraversalIntent } from "./app-history-state.js";
|
|
9
9
|
import { createRscRequestHeaders } from "./app-rsc-cache-busting.js";
|
|
10
|
-
import { createCacheEntryReuseProof } from "./cache-proof.js";
|
|
11
10
|
import { NavigationTraceReasonCodes, createNavigationLifecycleTraceFields, createNavigationTrace } from "./navigation-trace.js";
|
|
12
11
|
import { navigationPlanner, resolveDefaultOrUnmatchedSlotPersistenceForLayouts } from "./navigation-planner.js";
|
|
12
|
+
import { createSnapshotPathAndSearch } from "../shims/navigation.js";
|
|
13
|
+
import { createCacheEntryReuseProof } from "./cache-proof.js";
|
|
13
14
|
//#region src/server/app-browser-state.ts
|
|
14
15
|
const FRESH_APP_NAVIGATION_PAYLOAD_ORIGIN = { origin: "fresh" };
|
|
15
16
|
const VISITED_CACHE_APP_NAVIGATION_PAYLOAD_ORIGIN = { origin: "visited-cache" };
|
|
@@ -222,10 +223,6 @@ function createPendingNavigationTraceFields(options) {
|
|
|
222
223
|
...options.targetHref !== void 0 ? { targetHref: options.targetHref } : {}
|
|
223
224
|
};
|
|
224
225
|
}
|
|
225
|
-
function createNavigationSnapshotUrl(snapshot) {
|
|
226
|
-
const query = snapshot.searchParams.toString();
|
|
227
|
-
return query === "" ? snapshot.pathname : `${snapshot.pathname}?${query}`;
|
|
228
|
-
}
|
|
229
226
|
function createMountedParallelSlotSnapshots(elements) {
|
|
230
227
|
const snapshots = [];
|
|
231
228
|
for (const slotId of getMountedSlotIds(elements)) {
|
|
@@ -239,7 +236,7 @@ function createMountedParallelSlotSnapshots(elements) {
|
|
|
239
236
|
return snapshots;
|
|
240
237
|
}
|
|
241
238
|
function createVisibleRouteSnapshot(state) {
|
|
242
|
-
const displayUrl =
|
|
239
|
+
const displayUrl = createSnapshotPathAndSearch(state.navigationSnapshot);
|
|
243
240
|
const matchedUrl = normalizeNavigationSnapshotMatchedUrl(state.navigationSnapshot.pathname);
|
|
244
241
|
return {
|
|
245
242
|
displayUrl,
|
|
@@ -257,7 +254,7 @@ function createVisibleRouteSnapshot(state) {
|
|
|
257
254
|
};
|
|
258
255
|
}
|
|
259
256
|
function createPendingRouteSnapshot(pending) {
|
|
260
|
-
const displayUrl =
|
|
257
|
+
const displayUrl = createSnapshotPathAndSearch(pending.action.navigationSnapshot);
|
|
261
258
|
const matchedUrl = normalizeNavigationSnapshotMatchedUrl(pending.action.navigationSnapshot.pathname);
|
|
262
259
|
return {
|
|
263
260
|
displayUrl,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { normalizeAppElementsSlotBindings } from "./app-elements-wire.js";
|
|
2
2
|
import "./app-elements.js";
|
|
3
|
-
import { mergeElements } from "../shims/slot.js";
|
|
4
3
|
import { NavigationTraceReasonCodes, NavigationTraceTransactionCodes, createNavigationTrace, prependNavigationTraceEntry } from "./navigation-trace.js";
|
|
4
|
+
import { mergeElements } from "../shims/slot.js";
|
|
5
5
|
import { createPendingNavigationCommit, preserveBfcacheIdsForMergedElements, resolvePendingNavigationCommitDispositionDecision } from "./app-browser-state.js";
|
|
6
6
|
//#region src/server/app-browser-visible-commit.ts
|
|
7
7
|
const approvedVisibleCommitBrand = Symbol("ApprovedVisibleCommit");
|
|
@@ -52,7 +52,8 @@ type AppFallbackRendererOptions<TModule extends AppPageModule = AppPageModule> =
|
|
|
52
52
|
loadGlobalNotFoundModule?: (() => Promise<TModule | null | undefined>) | null;
|
|
53
53
|
makeThenableParams: (params: AppPageParams) => unknown;
|
|
54
54
|
metadataRoutes: MetadataFileRoute[]; /** Configured next.config `basePath`, threaded into file-based metadata href emission. */
|
|
55
|
-
basePath?: string;
|
|
55
|
+
basePath?: string; /** Configured next.config `trailingSlash`, threaded into canonical URL rendering. */
|
|
56
|
+
trailingSlash?: boolean;
|
|
56
57
|
resolveChildSegments: (routeSegments: readonly string[], treePosition: number, params: AppPageParams) => string[];
|
|
57
58
|
rootBoundaries: AppFallbackRendererRootBoundaries<TModule>;
|
|
58
59
|
rscRenderer: (element: ReactNode | AppElements, options: {
|
|
@@ -7,7 +7,7 @@ const EMPTY_MW_CTX = {
|
|
|
7
7
|
status: null
|
|
8
8
|
};
|
|
9
9
|
function createAppFallbackRenderer(options) {
|
|
10
|
-
const { basePath = "", clearRequestContext, createRscOnErrorHandler: buildRscOnErrorHandler, fontProviders, getNavigationContext, globalErrorModule, loadGlobalNotFoundModule, makeThenableParams, metadataRoutes, resolveChildSegments, rootBoundaries, rscRenderer, sanitizer, ssrLoader } = options;
|
|
10
|
+
const { basePath = "", clearRequestContext, createRscOnErrorHandler: buildRscOnErrorHandler, fontProviders, getNavigationContext, globalErrorModule, loadGlobalNotFoundModule, makeThenableParams, metadataRoutes, resolveChildSegments, rootBoundaries, rscRenderer, sanitizer, ssrLoader, trailingSlash } = options;
|
|
11
11
|
const { rootForbiddenModule, rootLayouts, rootNotFoundModule, rootUnauthorizedModule } = rootBoundaries;
|
|
12
12
|
const effectiveGlobalErrorModule = globalErrorModule ?? DEFAULT_GLOBAL_ERROR_MODULE;
|
|
13
13
|
const effectiveRootNotFoundModule = rootNotFoundModule ?? DEFAULT_NOT_FOUND_MODULE;
|
|
@@ -58,6 +58,7 @@ function createAppFallbackRenderer(options) {
|
|
|
58
58
|
}
|
|
59
59
|
return renderAppPageHttpAccessFallback({
|
|
60
60
|
basePath,
|
|
61
|
+
trailingSlash,
|
|
61
62
|
boundaryComponent: opts?.boundaryComponent ?? null,
|
|
62
63
|
boundaryModule: opts?.boundaryModule ?? null,
|
|
63
64
|
buildFontLinkHeader: fontProviders.buildFontLinkHeader,
|
|
@@ -96,6 +97,7 @@ function createAppFallbackRenderer(options) {
|
|
|
96
97
|
renderErrorBoundary(route, error, isRscRequest, request, matchedParams, scriptNonce, middlewareContext, callContext) {
|
|
97
98
|
return renderAppPageErrorBoundary({
|
|
98
99
|
basePath,
|
|
100
|
+
trailingSlash,
|
|
99
101
|
buildFontLinkHeader: fontProviders.buildFontLinkHeader,
|
|
100
102
|
clearRequestContext,
|
|
101
103
|
createRscOnErrorHandler(pathname, routePath) {
|
|
@@ -115,6 +115,7 @@ async function applyAppMiddleware(options) {
|
|
|
115
115
|
if (!forwarded.applied) {
|
|
116
116
|
const result = await executeMiddleware({
|
|
117
117
|
basePath: options.basePath,
|
|
118
|
+
hadBasePath: true,
|
|
118
119
|
i18nConfig: options.i18nConfig,
|
|
119
120
|
isDataRequest: options.isDataRequest,
|
|
120
121
|
isProxy: options.isProxy,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buildParams, decodeMatchedParams, splitPathnameForRouteMatch } from "../routing/utils.js";
|
|
2
2
|
import { stripBasePath } from "../utils/base-path.js";
|
|
3
|
+
import { matchRoutePattern } from "../routing/route-pattern.js";
|
|
3
4
|
import { isUnknownRecord } from "../utils/record.js";
|
|
4
5
|
import { AppElementsWire } from "./app-elements-wire.js";
|
|
5
6
|
import "./app-elements.js";
|
|
@@ -135,6 +136,20 @@ function matchOptimisticRouteManifestRoute(options) {
|
|
|
135
136
|
decodeMatchedParams(match.params);
|
|
136
137
|
return match;
|
|
137
138
|
}
|
|
139
|
+
function mergeParams(target, source) {
|
|
140
|
+
for (const [key, value] of Object.entries(source)) target[key] = value;
|
|
141
|
+
}
|
|
142
|
+
function resolveOptimisticNavigationParams(options) {
|
|
143
|
+
const navigationParams = { ...options.match.params };
|
|
144
|
+
for (const binding of options.routeManifest.segmentGraph.slotBindings.values()) {
|
|
145
|
+
if (binding.routeId !== options.match.route.id || binding.state !== "active") continue;
|
|
146
|
+
const patternParts = binding.slotPatternParts;
|
|
147
|
+
if (!patternParts) continue;
|
|
148
|
+
const matched = matchRoutePattern(options.urlParts, patternParts);
|
|
149
|
+
if (matched) mergeParams(navigationParams, matched);
|
|
150
|
+
}
|
|
151
|
+
return navigationParams;
|
|
152
|
+
}
|
|
138
153
|
function elementHasSuspenseFallback(value, depth = 0) {
|
|
139
154
|
if (depth > 100) return false;
|
|
140
155
|
if (Array.isArray(value)) return value.some((entry) => elementHasSuspenseFallback(entry, depth + 1));
|
|
@@ -183,6 +198,8 @@ function createOptimisticRouteElements(template) {
|
|
|
183
198
|
}
|
|
184
199
|
function resolveOptimisticNavigationPayload(options) {
|
|
185
200
|
if (options.interceptionContext !== null) return null;
|
|
201
|
+
const urlParts = hrefToRouteParts(options.href, options.basePath);
|
|
202
|
+
if (urlParts === null) return null;
|
|
186
203
|
const match = matchOptimisticRouteManifestRoute({
|
|
187
204
|
basePath: options.basePath,
|
|
188
205
|
href: options.href,
|
|
@@ -198,7 +215,11 @@ function resolveOptimisticNavigationPayload(options) {
|
|
|
198
215
|
if (template.mountedSlotsHeader !== options.mountedSlotsHeader) return null;
|
|
199
216
|
return {
|
|
200
217
|
elements: createOptimisticRouteElements(template),
|
|
201
|
-
params:
|
|
218
|
+
params: resolveOptimisticNavigationParams({
|
|
219
|
+
match,
|
|
220
|
+
routeManifest: options.routeManifest,
|
|
221
|
+
urlParts
|
|
222
|
+
}),
|
|
202
223
|
template
|
|
203
224
|
};
|
|
204
225
|
}
|
|
@@ -41,7 +41,8 @@ type AppPageBoundaryRenderCommonOptions<TModule extends AppPageModule = AppPageM
|
|
|
41
41
|
makeThenableParams: (params: AppPageParams) => unknown;
|
|
42
42
|
middlewareContext: AppPageMiddlewareContext;
|
|
43
43
|
metadataRoutes: MetadataFileRoute[]; /** Configured next.config `basePath`, threaded into file-based metadata href emission. */
|
|
44
|
-
basePath?: string;
|
|
44
|
+
basePath?: string; /** Configured next.config `trailingSlash`, threaded into canonical URL rendering. */
|
|
45
|
+
trailingSlash?: boolean;
|
|
45
46
|
renderToReadableStream: (element: ReactNode | AppElements, options: {
|
|
46
47
|
onError: AppPageBoundaryOnError;
|
|
47
48
|
}) => ReadableStream<Uint8Array>;
|
|
@@ -168,7 +168,8 @@ async function renderAppPageHttpAccessFallback(options) {
|
|
|
168
168
|
if (metadata) headElements.push(createElement(MetadataHead, {
|
|
169
169
|
key: "metadata",
|
|
170
170
|
metadata,
|
|
171
|
-
pathname
|
|
171
|
+
pathname,
|
|
172
|
+
trailingSlash: options.trailingSlash
|
|
172
173
|
}));
|
|
173
174
|
headElements.push(createElement(ViewportHead, {
|
|
174
175
|
key: "viewport",
|
|
@@ -231,7 +232,8 @@ async function renderAppPageErrorBoundary(options) {
|
|
|
231
232
|
if (metadata) headElements.push(createElement(MetadataHead, {
|
|
232
233
|
key: "metadata",
|
|
233
234
|
metadata,
|
|
234
|
-
pathname
|
|
235
|
+
pathname,
|
|
236
|
+
trailingSlash: options.trailingSlash
|
|
235
237
|
}));
|
|
236
238
|
headElements.push(createElement(ViewportHead, {
|
|
237
239
|
key: "viewport",
|