@rangojs/router 0.18.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/types/browser/event-controller.d.ts +3 -3
- package/dist/types/browser/history-state.d.ts +2 -0
- package/dist/types/browser/navigation-transaction.d.ts +20 -2
- package/dist/types/browser/partial-update.d.ts +16 -1
- package/dist/types/browser/prefetch/cache.d.ts +6 -6
- package/dist/types/browser/react/Link.d.ts +19 -9
- package/dist/types/browser/react/context.d.ts +2 -2
- package/dist/types/browser/react/location-state-shared.d.ts +95 -56
- package/dist/types/browser/react/location-state.d.ts +1 -1
- package/dist/types/browser/transition-when.d.ts +80 -0
- package/dist/types/browser/types.d.ts +104 -9
- package/dist/types/build/generate-manifest.d.ts +8 -0
- package/dist/types/cache/background-task.d.ts +24 -2
- package/dist/types/cache/cache-exec-scope.d.ts +31 -0
- package/dist/types/cache/cache-key-utils.d.ts +44 -13
- package/dist/types/cache/cache-policy.d.ts +1 -1
- package/dist/types/cache/cache-runtime.d.ts +7 -0
- package/dist/types/cache/cache-scope.d.ts +117 -35
- package/dist/types/cache/cache-tag.d.ts +24 -4
- package/dist/types/cache/cf/cf-cache-store.d.ts +55 -44
- package/dist/types/cache/cf/cf-shell-frame.d.ts +1 -1
- package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +0 -15
- package/dist/types/cache/document-cache.d.ts +10 -0
- package/dist/types/cache/handle-snapshot.d.ts +40 -8
- package/dist/types/cache/invalidation-order.d.ts +23 -0
- package/dist/types/cache/request-tag-mask.d.ts +54 -0
- package/dist/types/cache/shell-snapshot.d.ts +88 -141
- package/dist/types/cache/tag-invalidation.d.ts +38 -14
- package/dist/types/cache/types.d.ts +68 -61
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +23 -16
- package/dist/types/client-urls/navigation.d.ts +28 -2
- package/dist/types/client-urls/server-projection.d.ts +5 -2
- package/dist/types/client-urls/types.d.ts +5 -5
- package/dist/types/client.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.rsc.d.ts +1 -1
- package/dist/types/route-definition/helpers-types.d.ts +11 -11
- package/dist/types/route-name.d.ts +7 -0
- package/dist/types/router/error-handling.d.ts +1 -1
- package/dist/types/router/intercept-resolution.d.ts +2 -0
- package/dist/types/router/match-context.d.ts +5 -0
- package/dist/types/router/match-middleware/background-revalidation.d.ts +0 -4
- package/dist/types/router/router-options.d.ts +4 -1
- package/dist/types/router/segment-resolution/loader-cache.d.ts +18 -1
- package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -4
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +14 -40
- package/dist/types/router/segment-resolution/mask-nested.d.ts +11 -3
- package/dist/types/router/segment-resolution/view-transition-default.d.ts +7 -5
- package/dist/types/router/transition-when.d.ts +24 -10
- package/dist/types/rsc/attach-transition-when.d.ts +18 -0
- package/dist/types/rsc/full-payload.d.ts +14 -7
- package/dist/types/rsc/response-cache-serve.d.ts +15 -12
- package/dist/types/rsc/shell-build-manifest.d.ts +4 -3
- package/dist/types/rsc/shell-capture-constants.d.ts +77 -20
- package/dist/types/rsc/shell-capture.d.ts +78 -81
- package/dist/types/rsc/shell-serve.d.ts +19 -48
- package/dist/types/rsc/types.d.ts +2 -0
- package/dist/types/segment-system.d.ts +7 -0
- package/dist/types/server/context.d.ts +89 -61
- package/dist/types/server/cookie-store.d.ts +30 -18
- package/dist/types/server/handle-store.d.ts +52 -1
- package/dist/types/server/request-context.d.ts +66 -45
- package/dist/types/server/request-headers.d.ts +44 -0
- package/dist/types/server.d.ts +1 -0
- package/dist/types/testing/e2e/index.d.ts +1 -1
- package/dist/types/testing/index.d.ts +3 -2
- package/dist/types/testing/internal/action-id.d.ts +7 -0
- package/dist/types/testing/location-state-key.d.ts +24 -0
- package/dist/types/testing/render-route.d.ts +17 -1
- package/dist/types/testing/run-transition-when.d.ts +44 -69
- package/dist/types/testing/serve-shell-request.d.ts +7 -6
- package/dist/types/testing/shell-status.d.ts +34 -22
- package/dist/types/transition-when-ref.d.ts +79 -0
- package/dist/types/types/cache-types.d.ts +15 -3
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/types/segments.d.ts +86 -71
- package/dist/types/urls/path-helper-types.d.ts +3 -4
- package/dist/types/urls/pattern-types.d.ts +3 -4
- package/dist/types/vite/plugins/transition-when-hoist.d.ts +45 -0
- package/dist/vite/index.js +960 -97
- package/package.json +1 -1
- package/skills/cache-guide/SKILL.md +76 -49
- package/skills/caching/SKILL.md +167 -69
- package/skills/client-urls/SKILL.md +1 -1
- package/skills/comparison/references/framework-comparison.md +8 -5
- package/skills/document-cache/SKILL.md +24 -0
- package/skills/hooks/SKILL.md +3 -1
- package/skills/hooks/navigation.md +23 -2
- package/skills/hooks/state.md +98 -6
- package/skills/intercept/SKILL.md +29 -12
- package/skills/loader/SKILL.md +52 -21
- package/skills/middleware/SKILL.md +18 -0
- package/skills/migrate-nextjs/SKILL.md +3 -3
- package/skills/observability/SKILL.md +6 -5
- package/skills/parallel/SKILL.md +1 -1
- package/skills/ppr/SKILL.md +129 -77
- package/skills/prerender/SKILL.md +3 -3
- package/skills/rango/SKILL.md +25 -6
- package/skills/route/SKILL.md +2 -0
- package/skills/testing/SKILL.md +27 -25
- package/skills/testing/cache-prerender.md +9 -9
- package/skills/testing/client-components.md +45 -3
- package/skills/testing/loader.md +3 -2
- package/skills/testing/middleware.md +1 -0
- package/skills/testing/predicates.md +49 -49
- package/skills/testing/render-handler.md +1 -0
- package/skills/testing/serve-shell-request.md +14 -0
- package/skills/testing/server-actions.md +9 -3
- package/skills/theme/SKILL.md +8 -4
- package/skills/typesafety/env-and-bindings.md +8 -0
- package/skills/use-cache/SKILL.md +64 -19
- package/skills/view-transitions/SKILL.md +150 -29
- package/src/__augment-tests__/augmented.check.ts +23 -0
- package/src/browser/event-controller.ts +4 -4
- package/src/browser/history-state.ts +52 -1
- package/src/browser/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +77 -18
- package/src/browser/navigation-client.ts +4 -3
- package/src/browser/navigation-store.ts +43 -1
- package/src/browser/navigation-transaction.ts +31 -11
- package/src/browser/partial-update.ts +146 -35
- package/src/browser/prefetch/cache.ts +6 -6
- package/src/browser/prefetch/fetch.ts +2 -2
- package/src/browser/react/Link.tsx +38 -33
- package/src/browser/react/NavigationProvider.tsx +2 -2
- package/src/browser/react/context.ts +2 -2
- package/src/browser/react/location-state-shared.ts +241 -49
- package/src/browser/react/location-state.ts +5 -2
- package/src/browser/rsc-router.tsx +4 -0
- package/src/browser/server-action-bridge.ts +84 -15
- package/src/browser/transition-when.ts +205 -0
- package/src/browser/types.ts +124 -10
- package/src/build/generate-manifest.ts +77 -1
- package/src/cache/background-task.ts +55 -2
- package/src/cache/cache-exec-scope.ts +48 -1
- package/src/cache/cache-key-utils.ts +56 -16
- package/src/cache/cache-policy.ts +9 -4
- package/src/cache/cache-runtime.ts +197 -144
- package/src/cache/cache-scope.ts +304 -112
- package/src/cache/cache-tag.ts +105 -13
- package/src/cache/cf/cf-cache-store.ts +152 -76
- package/src/cache/cf/cf-shell-frame.ts +2 -2
- package/src/cache/cf/cf-tag-marker-memo.ts +11 -100
- package/src/cache/document-cache.ts +53 -7
- package/src/cache/handle-snapshot.ts +52 -9
- package/src/cache/invalidation-order.ts +84 -0
- package/src/cache/request-tag-mask.ts +147 -0
- package/src/cache/shell-snapshot.ts +130 -348
- package/src/cache/tag-invalidation.ts +57 -51
- package/src/cache/types.ts +71 -67
- package/src/cache/vercel/vercel-cache-store.ts +61 -58
- package/src/client-urls/client-root.tsx +8 -4
- package/src/client-urls/client-urls.ts +8 -6
- package/src/client-urls/navigation.ts +55 -4
- package/src/client-urls/server-projection.ts +31 -10
- package/src/client-urls/types.ts +12 -5
- package/src/client.tsx +2 -0
- package/src/host/cookie-handler.ts +2 -1
- package/src/index.rsc.ts +3 -0
- package/src/index.ts +3 -0
- package/src/loader-redirect.tsx +7 -3
- package/src/prerender/build-shell-capture.ts +155 -146
- package/src/route-definition/dsl-helpers.ts +2 -0
- package/src/route-definition/helpers-types.ts +11 -11
- package/src/route-name.ts +12 -0
- package/src/router/content-negotiation.ts +4 -1
- package/src/router/error-handling.ts +5 -2
- package/src/router/handler-context.ts +10 -7
- package/src/router/intercept-resolution.ts +11 -1
- package/src/router/loader-resolution.ts +28 -3
- package/src/router/logging.ts +5 -3
- package/src/router/match-api.ts +62 -34
- package/src/router/match-context.ts +5 -0
- package/src/router/match-handlers.ts +9 -29
- package/src/router/match-middleware/background-revalidation.ts +3 -5
- package/src/router/match-middleware/cache-lookup.ts +35 -19
- package/src/router/match-middleware/cache-store.ts +15 -9
- package/src/router/match-result.ts +1 -0
- package/src/router/middleware.ts +2 -5
- package/src/router/navigation-snapshot.ts +6 -5
- package/src/router/prerender-match.ts +4 -0
- package/src/router/request-classification.ts +7 -4
- package/src/router/router-options.ts +4 -1
- package/src/router/segment-resolution/fresh.ts +0 -4
- package/src/router/segment-resolution/loader-cache.ts +150 -25
- package/src/router/segment-resolution/loader-mask.ts +3 -4
- package/src/router/segment-resolution/loader-snapshot.ts +15 -78
- package/src/router/segment-resolution/mask-nested.ts +47 -22
- package/src/router/segment-resolution/revalidation.ts +0 -3
- package/src/router/segment-resolution/view-transition-default.ts +6 -21
- package/src/router/telemetry.ts +6 -3
- package/src/router/transition-when.ts +34 -52
- package/src/router.ts +2 -1
- package/src/rsc/attach-transition-when.ts +47 -0
- package/src/rsc/full-payload.ts +30 -10
- package/src/rsc/handler.ts +12 -10
- package/src/rsc/loader-fetch.ts +2 -1
- package/src/rsc/manifest-init.ts +4 -0
- package/src/rsc/origin-guard.ts +8 -5
- package/src/rsc/progressive-enhancement.ts +7 -30
- package/src/rsc/response-cache-serve.ts +68 -80
- package/src/rsc/response-route-handler.ts +2 -2
- package/src/rsc/rsc-rendering.ts +206 -252
- package/src/rsc/server-action.ts +8 -20
- package/src/rsc/shell-build-manifest.ts +22 -20
- package/src/rsc/shell-capture-constants.ts +136 -20
- package/src/rsc/shell-capture.ts +352 -589
- package/src/rsc/shell-serve.ts +73 -109
- package/src/rsc/ssr-setup.ts +3 -2
- package/src/rsc/types.ts +2 -0
- package/src/segment-system.tsx +40 -7
- package/src/server/context.ts +189 -124
- package/src/server/cookie-store.ts +153 -127
- package/src/server/handle-store.ts +245 -27
- package/src/server/request-context.ts +123 -84
- package/src/server/request-headers.ts +134 -0
- package/src/server.ts +4 -0
- package/src/testing/dispatch.ts +4 -7
- package/src/testing/e2e/index.ts +1 -0
- package/src/testing/index.ts +5 -0
- package/src/testing/internal/action-id.ts +25 -0
- package/src/testing/internal/context.ts +1 -1
- package/src/testing/location-state-key.ts +45 -0
- package/src/testing/render-handler.ts +1 -1
- package/src/testing/render-route.tsx +109 -16
- package/src/testing/run-client-revalidate.ts +3 -19
- package/src/testing/run-loader.ts +5 -1
- package/src/testing/run-transition-when.ts +109 -172
- package/src/testing/serve-shell-request.ts +10 -7
- package/src/testing/shell-status.ts +60 -56
- package/src/transition-when-ref.ts +220 -0
- package/src/types/cache-types.ts +15 -3
- package/src/types/index.ts +3 -0
- package/src/types/segments.ts +94 -91
- package/src/urls/path-helper-types.ts +3 -4
- package/src/urls/pattern-types.ts +3 -4
- package/src/vite/discovery/discover-routers.ts +3 -0
- package/src/vite/discovery/shell-prerender-phase.ts +0 -2
- package/src/vite/discovery/virtual-module-codegen.ts +16 -6
- package/src/vite/plugins/transition-when-hoist.ts +1064 -0
- package/src/vite/rango.ts +2 -0
- package/src/vite/router-discovery.ts +75 -5
- package/dist/types/rsc/transition-gate.d.ts +0 -23
- package/src/rsc/transition-gate.ts +0 -86
package/src/rsc/shell-serve.ts
CHANGED
|
@@ -18,26 +18,32 @@
|
|
|
18
18
|
|
|
19
19
|
import React from "react";
|
|
20
20
|
import { isPprEntry, type EntryData } from "../server/context.js";
|
|
21
|
-
import { sortedSearchString } from "../cache/cache-key-utils.js";
|
|
22
|
-
import type { SearchParamsFilter } from "../cache/search-params-filter.js";
|
|
23
21
|
import { base64ToBytes } from "../cache/cf/cf-base64.js";
|
|
24
|
-
import { getNonceAttribute } from "../theme/theme-script.js";
|
|
25
22
|
import type {
|
|
26
23
|
DocumentShellCacheEntry,
|
|
27
24
|
ShellCacheEntry,
|
|
28
25
|
ShellDocumentRead,
|
|
26
|
+
ShellEntryHead,
|
|
29
27
|
ShellReadStats,
|
|
30
28
|
ShellSnapshotFailure,
|
|
31
29
|
ShellSnapshotRecord,
|
|
32
30
|
SegmentCacheStore,
|
|
33
31
|
} from "../cache/types.js";
|
|
34
|
-
import {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
import {
|
|
33
|
+
SHELL_CAPTURE_MAX_WAIT_MS,
|
|
34
|
+
hasWarnedOnce,
|
|
35
|
+
resetShellWarningsForTests,
|
|
36
|
+
warnOnce,
|
|
37
|
+
} from "./shell-capture-constants.js";
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
export {
|
|
40
|
+
PPR_REPLAY_STATUS_HEADER,
|
|
41
|
+
SHELL_STATUS_HEADER,
|
|
42
|
+
buildShellKey,
|
|
43
|
+
navigationShellKey,
|
|
44
|
+
partitionShellKey,
|
|
45
|
+
shellSearchSeed,
|
|
46
|
+
} from "./shell-capture-constants.js";
|
|
41
47
|
|
|
42
48
|
/**
|
|
43
49
|
* Default shell ttl (seconds) for `ppr: true` and for a PartialPrerenderProps
|
|
@@ -120,29 +126,6 @@ export function resolvePprConfig(
|
|
|
120
126
|
};
|
|
121
127
|
}
|
|
122
128
|
|
|
123
|
-
/**
|
|
124
|
-
* Shell cache key: host + pathname + sorted search + a `:shell` namespace suffix
|
|
125
|
-
* (so it can never collide with a document-cache key; the store further isolates
|
|
126
|
-
* the shell family internally).
|
|
127
|
-
*
|
|
128
|
-
* The key includes the request HOST: in a multi-tenant host-router deployment
|
|
129
|
-
* (one worker, one shared KV/runtime-cache store) a host-less key would serve
|
|
130
|
-
* tenant A's captured shell to tenant B's users.
|
|
131
|
-
*
|
|
132
|
-
* `filter` is the request's compiled `cache.searchParams` config
|
|
133
|
-
* (ctx._searchParamsFilter): excluded params collapse onto one shell slot.
|
|
134
|
-
* Callers on the serve/capture path MUST pass it -- key drift between capture
|
|
135
|
-
* and lookup makes every shell request a permanent miss.
|
|
136
|
-
*/
|
|
137
|
-
export function buildShellKey(url: URL, filter?: SearchParamsFilter): string {
|
|
138
|
-
return `${url.host}${url.pathname}${shellSearchSeed(url, filter)}:shell`;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export {
|
|
142
|
-
navigationShellKey,
|
|
143
|
-
partitionShellKey,
|
|
144
|
-
} from "./shell-capture-constants.js";
|
|
145
|
-
|
|
146
129
|
/**
|
|
147
130
|
* The forced-MISS query marker. A document HIT that cannot finish its tail
|
|
148
131
|
* reloads the page with it (shellReloadScript); the serve gate renders a
|
|
@@ -179,38 +162,24 @@ export function withoutShellMissMarker(request: Request): Request | undefined {
|
|
|
179
162
|
* did (a server that ignores the marker) it would return without touching
|
|
180
163
|
* the page. window.stop() runs only when it reloads: it keeps the half-sent
|
|
181
164
|
* Flight stream from being closed by DOMContentLoaded, which would throw
|
|
182
|
-
* React's "Connection closed" (#412) before the reload lands.
|
|
183
|
-
* request's CSP nonce; a ppr route with an active nonce is axis 1
|
|
184
|
-
* (rsc-rendering.ts shellServePlan), so on a HIT it is normally absent.
|
|
165
|
+
* React's "Connection closed" (#412) before the reload lands.
|
|
185
166
|
*/
|
|
186
|
-
export function shellReloadScript(
|
|
167
|
+
export function shellReloadScript(): string {
|
|
187
168
|
return inlineShellScript(
|
|
188
169
|
"(function(){var u=new URL(location.href);" +
|
|
189
170
|
`if(u.searchParams.has(${JSON.stringify(SHELL_MISS_PARAM)}))return;` +
|
|
190
171
|
`u.searchParams.set(${JSON.stringify(SHELL_MISS_PARAM)},"miss");` +
|
|
191
172
|
"try{window.stop()}catch(e){}location.replace(u.href)})()",
|
|
192
|
-
nonce,
|
|
193
173
|
);
|
|
194
174
|
}
|
|
195
175
|
|
|
196
|
-
/** An inline script a shell HIT's tail appends, carrying the CSP nonce if any. */
|
|
197
|
-
export function inlineShellScript(body: string, nonce?: string): string {
|
|
198
|
-
return `<script${getNonceAttribute(nonce)}>${body}</script>`;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
176
|
/**
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
* / ShellCaptureOptions.search / ShellResumeOptions.search). Search is part
|
|
206
|
-
* of shell identity, so static-part `useSearchParams` reads render exactly
|
|
207
|
-
* what the key names; deriving seed and key from this one helper is what
|
|
208
|
-
* keeps capture, resume, and lookup byte-agreed (drift = replay mismatch or
|
|
209
|
-
* permanent MISS).
|
|
177
|
+
* An inline script a shell HIT's tail appends. It carries no CSP nonce: a
|
|
178
|
+
* request with an active per-request nonce never reaches a HIT
|
|
179
|
+
* (rsc-rendering.ts shellServePlan serves it on axis 1).
|
|
210
180
|
*/
|
|
211
|
-
export function
|
|
212
|
-
|
|
213
|
-
return sorted ? `?${sorted}` : "";
|
|
181
|
+
export function inlineShellScript(body: string): string {
|
|
182
|
+
return `<script>${body}</script>`;
|
|
214
183
|
}
|
|
215
184
|
|
|
216
185
|
/**
|
|
@@ -219,12 +188,10 @@ export function shellSearchSeed(url: URL, filter?: SearchParamsFilter): string {
|
|
|
219
188
|
* one exact tree, so resuming it under a different React OR a different app
|
|
220
189
|
* build tree-mismatches inside resume() — after the 200 + prelude committed,
|
|
221
190
|
* with no recovery. Either mismatch is a miss: the recapture overwrites the
|
|
222
|
-
* same key (self-healing) and the entry otherwise ages out via TTL.
|
|
223
|
-
* with no buildVersion (stored before the field existed) is a miss for the
|
|
224
|
-
* same reason — its build is unknown, so it cannot be proven resumable.
|
|
191
|
+
* same key (self-healing) and the entry otherwise ages out via TTL.
|
|
225
192
|
*/
|
|
226
193
|
export function isValidShellHit(
|
|
227
|
-
entry: ShellCacheEntry,
|
|
194
|
+
entry: Pick<ShellCacheEntry, "reactVersion" | "buildVersion">,
|
|
228
195
|
buildVersion: string,
|
|
229
196
|
): boolean {
|
|
230
197
|
return (
|
|
@@ -233,9 +200,9 @@ export function isValidShellHit(
|
|
|
233
200
|
}
|
|
234
201
|
|
|
235
202
|
/** The postponed blob is stored and parses (null is the DATA variant). */
|
|
236
|
-
function hasParseablePostponed(
|
|
237
|
-
entry:
|
|
238
|
-
): entry is
|
|
203
|
+
function hasParseablePostponed<T extends ShellEntryHead>(
|
|
204
|
+
entry: T,
|
|
205
|
+
): entry is T & { postponed: string | null } {
|
|
239
206
|
if (entry.postponed === undefined) return false;
|
|
240
207
|
try {
|
|
241
208
|
if (entry.postponed !== null) JSON.parse(entry.postponed);
|
|
@@ -266,7 +233,7 @@ export function hasIntactShellPayload(
|
|
|
266
233
|
|
|
267
234
|
/** A document shell ready to serve: its prelude decoded exactly once. */
|
|
268
235
|
export interface ShellDocument {
|
|
269
|
-
entry:
|
|
236
|
+
entry: ShellEntryHead;
|
|
270
237
|
prelude: Uint8Array;
|
|
271
238
|
/** React's postponed state, parse-checked (null = DATA variant). */
|
|
272
239
|
postponed: string | null;
|
|
@@ -275,10 +242,7 @@ export interface ShellDocument {
|
|
|
275
242
|
* after a prelude-first read (SegmentCacheStore.readShellDocument), which
|
|
276
243
|
* only the tail awaits.
|
|
277
244
|
*/
|
|
278
|
-
snapshot:
|
|
279
|
-
| ShellSnapshotRecord[]
|
|
280
|
-
| undefined
|
|
281
|
-
| Promise<ShellSnapshotRecord[] | undefined>;
|
|
245
|
+
snapshot: ShellSnapshotRecord[] | Promise<ShellSnapshotRecord[] | undefined>;
|
|
282
246
|
/** Why a prelude-first read's snapshot is missing (ShellDocumentRead). */
|
|
283
247
|
snapshotFailure?: Promise<ShellSnapshotFailure | undefined>;
|
|
284
248
|
/** The prelude-first read's stats, when perf metrics are on. */
|
|
@@ -297,8 +261,16 @@ export interface ShellDocument {
|
|
|
297
261
|
* A prelude-first `read` (SegmentCacheStore.readShellDocument) already carries
|
|
298
262
|
* the raw bytes and a pending snapshot, so only its postponed blob is checked.
|
|
299
263
|
*/
|
|
264
|
+
export function openShellDocument(entry: ShellCacheEntry): ShellDocument | null;
|
|
300
265
|
export function openShellDocument(
|
|
301
|
-
entry:
|
|
266
|
+
entry: ShellEntryHead,
|
|
267
|
+
read: Pick<
|
|
268
|
+
ShellDocumentRead,
|
|
269
|
+
"prelude" | "snapshot" | "snapshotFailure" | "stats"
|
|
270
|
+
>,
|
|
271
|
+
): ShellDocument | null;
|
|
272
|
+
export function openShellDocument(
|
|
273
|
+
entry: ShellEntryHead | ShellCacheEntry,
|
|
302
274
|
read?: Pick<
|
|
303
275
|
ShellDocumentRead,
|
|
304
276
|
"prelude" | "snapshot" | "snapshotFailure" | "stats"
|
|
@@ -315,18 +287,20 @@ export function openShellDocument(
|
|
|
315
287
|
...(read.stats && { stats: read.stats }),
|
|
316
288
|
};
|
|
317
289
|
}
|
|
318
|
-
|
|
290
|
+
// The read-less overload takes a whole entry (getShell, a build shell).
|
|
291
|
+
const whole = entry as ShellCacheEntry;
|
|
292
|
+
if (!hasIntactShellPayload(whole)) return null;
|
|
319
293
|
let prelude: Uint8Array;
|
|
320
294
|
try {
|
|
321
|
-
prelude = base64ToBytes(
|
|
295
|
+
prelude = base64ToBytes(whole.prelude);
|
|
322
296
|
} catch {
|
|
323
297
|
return null;
|
|
324
298
|
}
|
|
325
299
|
return {
|
|
326
|
-
entry,
|
|
327
|
-
postponed:
|
|
300
|
+
entry: whole,
|
|
301
|
+
postponed: whole.postponed,
|
|
328
302
|
prelude,
|
|
329
|
-
snapshot:
|
|
303
|
+
snapshot: whole.snapshot,
|
|
330
304
|
};
|
|
331
305
|
}
|
|
332
306
|
|
|
@@ -369,7 +343,7 @@ export interface ShellTailTiming {
|
|
|
369
343
|
snapshotReadMs?: number;
|
|
370
344
|
/** Snapshot JSON.parse CPU (reads 0 on a deployed worker: see bytes). */
|
|
371
345
|
snapshotParseMs?: number;
|
|
372
|
-
/** Snapshot records by family, e.g. `segment:1/
|
|
346
|
+
/** Snapshot records by family, e.g. `segment:1/loader:2` (no commas: it rides a Server-Timing desc). */
|
|
373
347
|
snapshotRecords?: string;
|
|
374
348
|
/** Records the capture pruned from the snapshot, same format (ShellCacheEntry.prunedRecords). */
|
|
375
349
|
snapshotPruned?: string;
|
|
@@ -393,7 +367,7 @@ export interface ShellTailTiming {
|
|
|
393
367
|
|
|
394
368
|
/**
|
|
395
369
|
* Compact single-line form for the console log and the Server-Timing mirror's
|
|
396
|
-
* `desc`: alphanumerics and `=`, `-`, `:`, `/` only (records=segment:1 pruned=
|
|
370
|
+
* `desc`: alphanumerics and `=`, `-`, `:`, `/` only (records=segment:1/loader:2 pruned=loader:1),
|
|
397
371
|
* so no quoted-string escaping is needed. Offsets are from the commit; the
|
|
398
372
|
* `-cpu` fields are CPU-only durations.
|
|
399
373
|
*/
|
|
@@ -472,9 +446,6 @@ export function takeShellTailTimingForServerTiming(
|
|
|
472
446
|
return timing;
|
|
473
447
|
}
|
|
474
448
|
|
|
475
|
-
/** Keys already warned about a missing shell store family (once per key). */
|
|
476
|
-
const warnedMissingStore = new Set<string>();
|
|
477
|
-
|
|
478
449
|
/**
|
|
479
450
|
* Warn once per key that a route declared `ppr` but the app-level cache store
|
|
480
451
|
* does not implement the shell family (getShell/putShell), so the route stays on
|
|
@@ -482,10 +453,11 @@ const warnedMissingStore = new Set<string>();
|
|
|
482
453
|
* honored deserves a diagnostic.
|
|
483
454
|
*/
|
|
484
455
|
export function warnShellStoreMissingOnce(key: string): void {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
456
|
+
warnOnce(
|
|
457
|
+
"store-missing",
|
|
458
|
+
key,
|
|
459
|
+
() =>
|
|
460
|
+
`[rango] Route for "${key}" declares the ppr path option, but the app-level ` +
|
|
489
461
|
"cache store does not implement the shell family (getShell/putShell), so " +
|
|
490
462
|
"the route is served on axis 1 without a shell. Use MemorySegmentCacheStore, " +
|
|
491
463
|
"CFCacheStore, or VercelCacheStore (or add the family to your custom store) " +
|
|
@@ -493,9 +465,6 @@ export function warnShellStoreMissingOnce(key: string): void {
|
|
|
493
465
|
);
|
|
494
466
|
}
|
|
495
467
|
|
|
496
|
-
/** Keys already warned about an active per-request nonce (once per key). */
|
|
497
|
-
const warnedNonceActive = new Set<string>();
|
|
498
|
-
|
|
499
468
|
/**
|
|
500
469
|
* Warn once per key that a route declared `ppr` but a per-request CSP nonce is
|
|
501
470
|
* active for the request, so the route stays on axis 1 (a shared shell would
|
|
@@ -507,10 +476,11 @@ const warnedNonceActive = new Set<string>();
|
|
|
507
476
|
* the missing-store warning above (an undeclared route stays silent).
|
|
508
477
|
*/
|
|
509
478
|
export function warnPprNonceActiveOnce(key: string): void {
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
479
|
+
warnOnce(
|
|
480
|
+
"nonce-active",
|
|
481
|
+
key,
|
|
482
|
+
() =>
|
|
483
|
+
`[rango] Route for "${key}" declares the ppr path option, but a per-request ` +
|
|
514
484
|
"CSP nonce is active for this request (from createRouter({ nonce }) or a " +
|
|
515
485
|
"ctx.set(nonce, …) token write in middleware), so the route is served on " +
|
|
516
486
|
"axis 1 without a shell. A shell is shared per host+URL; baking one " +
|
|
@@ -519,9 +489,6 @@ export function warnPprNonceActiveOnce(key: string): void {
|
|
|
519
489
|
);
|
|
520
490
|
}
|
|
521
491
|
|
|
522
|
-
/** Routes already warned about a ppr window the route cache() caps. */
|
|
523
|
-
const warnedPprWindowCapped = new Set<string>();
|
|
524
|
-
|
|
525
492
|
/**
|
|
526
493
|
* Dev only (the caller gates on NODE_ENV): warn once per route that the
|
|
527
494
|
* route's cache() entry reduces an explicit `ppr.ttl` or `ppr.swr`. `shell`
|
|
@@ -537,7 +504,7 @@ export function warnPprWindowCappedOnce(
|
|
|
537
504
|
shell: { ttl: number; swr: number },
|
|
538
505
|
cache: { ttl: number; swr: number },
|
|
539
506
|
): void {
|
|
540
|
-
if (
|
|
507
|
+
if (hasWarnedOnce("ppr-window-capped", routeName)) return;
|
|
541
508
|
const reduced: string[] = [];
|
|
542
509
|
for (const field of ["ttl", "swr"] as const) {
|
|
543
510
|
const own = ppr[field];
|
|
@@ -546,9 +513,11 @@ export function warnPprWindowCappedOnce(
|
|
|
546
513
|
}
|
|
547
514
|
}
|
|
548
515
|
if (reduced.length === 0) return;
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
516
|
+
warnOnce(
|
|
517
|
+
"ppr-window-capped",
|
|
518
|
+
routeName,
|
|
519
|
+
() =>
|
|
520
|
+
`[rango] Route "${routeName}": its shell is stored with ttl ${shell.ttl} ` +
|
|
552
521
|
`and swr ${shell.swr}, below its ${reduced.join(" and ")}. A shell ` +
|
|
553
522
|
`never outlives the route cache() entry (ttl ${cache.ttl}, swr ` +
|
|
554
523
|
`${cache.swr}) it was captured from, so a document request and a ` +
|
|
@@ -558,9 +527,6 @@ export function warnPprWindowCappedOnce(
|
|
|
558
527
|
);
|
|
559
528
|
}
|
|
560
529
|
|
|
561
|
-
/** Routes (or paths) already warned that a partition skips their build shell. */
|
|
562
|
-
const partitionBuildShellWarned = new Set<string>();
|
|
563
|
-
|
|
564
530
|
/**
|
|
565
531
|
* Paths a partitioned request found without a build shell. Build shells are
|
|
566
532
|
* per path (a param route prerenders some of its paths), so a negative is
|
|
@@ -578,7 +544,7 @@ export function partitionBuildShellCheckDone(
|
|
|
578
544
|
routeName: string | undefined,
|
|
579
545
|
): boolean {
|
|
580
546
|
return (
|
|
581
|
-
|
|
547
|
+
hasWarnedOnce("partition-build-shell", routeName ?? pathname) ||
|
|
582
548
|
pathsWithoutBuildShell.has(pathname)
|
|
583
549
|
);
|
|
584
550
|
}
|
|
@@ -606,11 +572,11 @@ export function notePartitionBuildShellCheck(
|
|
|
606
572
|
return;
|
|
607
573
|
}
|
|
608
574
|
// Concurrent first requests can both probe; one of them warns.
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
575
|
+
warnOnce(
|
|
576
|
+
"partition-build-shell",
|
|
577
|
+
routeName ?? pathname,
|
|
578
|
+
() =>
|
|
579
|
+
`[rango] Route ${routeName ? `"${routeName}" ` : ""}("${pathname}") has a ` +
|
|
614
580
|
"build-time shell, but its request partition (a cache({ key }) enclosing the route, " +
|
|
615
581
|
"or the store's keyGenerator) keys its shell per partition, so the build " +
|
|
616
582
|
"shell is not served and each partition captures its own at runtime. A " +
|
|
@@ -619,14 +585,12 @@ export function notePartitionBuildShellCheck(
|
|
|
619
585
|
}
|
|
620
586
|
|
|
621
587
|
/**
|
|
622
|
-
* @internal Reset the serve path's once-per-key warnings
|
|
623
|
-
*
|
|
588
|
+
* @internal Reset the serve path's once-per-key warnings (the shell path's
|
|
589
|
+
* one registry), its build-shell probe memo and buffered tail timings. Tests
|
|
590
|
+
* only (testing/serve-shell-request.ts resetShellTestState).
|
|
624
591
|
*/
|
|
625
592
|
export function resetShellServeStateForTests(): void {
|
|
626
|
-
|
|
627
|
-
warnedNonceActive.clear();
|
|
628
|
-
partitionBuildShellWarned.clear();
|
|
593
|
+
resetShellWarningsForTests();
|
|
629
594
|
pathsWithoutBuildShell.clear();
|
|
630
|
-
warnedPprWindowCapped.clear();
|
|
631
595
|
lastTailTimingsForServerTiming.clear();
|
|
632
596
|
}
|
package/src/rsc/ssr-setup.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* matching, so they can run in parallel with segment resolution.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { requestHeaders } from "../server/request-headers.js";
|
|
9
10
|
import type { HandlerContext } from "./handler-context.js";
|
|
10
11
|
import type { SSRModule, SSRRenderOptions } from "./types.js";
|
|
11
12
|
import type { SSRStreamMode } from "../router/router-options.js";
|
|
@@ -183,7 +184,7 @@ export function getSSRSetup<TEnv>(
|
|
|
183
184
|
*/
|
|
184
185
|
function acceptsFlightExplicitly(request: Request, url: URL): boolean {
|
|
185
186
|
if (url.searchParams.has("__html")) return false;
|
|
186
|
-
const accept = request.
|
|
187
|
+
const accept = requestHeaders(request).get("accept");
|
|
187
188
|
if (accept === null || !accept.includes(RSC_WIRE_MIME)) return false;
|
|
188
189
|
return prefersFlightRepresentation(parseAcceptTypes(accept));
|
|
189
190
|
}
|
|
@@ -206,7 +207,7 @@ export function mayNeedSSR(request: Request, url: URL): boolean {
|
|
|
206
207
|
if (
|
|
207
208
|
url.searchParams.has("_rsc_partial") ||
|
|
208
209
|
url.searchParams.has("_rsc_action") ||
|
|
209
|
-
request.
|
|
210
|
+
requestHeaders(request).has("rsc-action") ||
|
|
210
211
|
url.searchParams.has("_rsc_loader") ||
|
|
211
212
|
url.searchParams.has("__rsc")
|
|
212
213
|
) {
|
package/src/rsc/types.ts
CHANGED
|
@@ -34,6 +34,8 @@ export interface RscPayload {
|
|
|
34
34
|
resolvedIds?: string[];
|
|
35
35
|
/** Merged route params from the matched route */
|
|
36
36
|
params?: Record<string, string>;
|
|
37
|
+
/** Matched route name, when named (transition({ when }) route names). */
|
|
38
|
+
routeName?: string;
|
|
37
39
|
slots?: Record<string, SlotState>;
|
|
38
40
|
/**
|
|
39
41
|
* Intercept TARGET route names reachable from this location as a
|
package/src/segment-system.tsx
CHANGED
|
@@ -125,6 +125,14 @@ export interface RenderSegmentsOptions {
|
|
|
125
125
|
/** Seeded descendant client-route pending state for testing. */
|
|
126
126
|
outletPending?: boolean;
|
|
127
127
|
|
|
128
|
+
/**
|
|
129
|
+
* This commit's transition({ when }) decision was false
|
|
130
|
+
* (browser/transition-when.ts): the navigation is urgent, so no segment
|
|
131
|
+
* animates. Every router <ViewTransition> stays in the tree with each class
|
|
132
|
+
* "none", so the element type never changes; keys never read this (#995).
|
|
133
|
+
*/
|
|
134
|
+
transitionGatedOff?: boolean;
|
|
135
|
+
|
|
128
136
|
/**
|
|
129
137
|
* Intercept segments to inject into the tree.
|
|
130
138
|
* These are parallel segments from intercept routes that need to be
|
|
@@ -143,13 +151,31 @@ export interface RenderSegmentsOptions {
|
|
|
143
151
|
rootLayout?: ComponentType<RootLayoutProps>;
|
|
144
152
|
}
|
|
145
153
|
|
|
154
|
+
/**
|
|
155
|
+
* On a gated-off commit every segment keeps its <ViewTransition> so the
|
|
156
|
+
* element type never changes with a per-navigation `transition({ when })`
|
|
157
|
+
* decision (#995), but animates nothing: "none" for every class, whatever the
|
|
158
|
+
* transition type.
|
|
159
|
+
*/
|
|
160
|
+
const GATED_OFF_CLASSES = {
|
|
161
|
+
enter: "none",
|
|
162
|
+
exit: "none",
|
|
163
|
+
update: "none",
|
|
164
|
+
share: "none",
|
|
165
|
+
default: "none",
|
|
166
|
+
} as const;
|
|
167
|
+
|
|
146
168
|
function createViewTransitionBoundary(
|
|
147
169
|
transition: NonNullable<ResolvedSegment["transition"]>,
|
|
148
170
|
children: ReactNode,
|
|
149
171
|
): ReactNode {
|
|
150
|
-
// `viewTransition`
|
|
151
|
-
// <ViewTransition>
|
|
152
|
-
const {
|
|
172
|
+
// `viewTransition` (boundary opt-out) and `when` (the browser-run gate) are
|
|
173
|
+
// router fields, not React <ViewTransition> props — strip them.
|
|
174
|
+
const {
|
|
175
|
+
viewTransition: _viewTransition,
|
|
176
|
+
when: _when,
|
|
177
|
+
...vtProps
|
|
178
|
+
} = transition;
|
|
153
179
|
return createElement(ReactViewTransition, {
|
|
154
180
|
...vtProps,
|
|
155
181
|
// The commit after an optimistic clientUrls() presentation repaints the
|
|
@@ -306,6 +332,7 @@ export async function renderSegments(
|
|
|
306
332
|
forceAwait,
|
|
307
333
|
outletPending = false,
|
|
308
334
|
rootLayout: RootLayout,
|
|
335
|
+
transitionGatedOff,
|
|
309
336
|
} = options || {};
|
|
310
337
|
|
|
311
338
|
const segDebug = INTERNAL_RANGO_DEBUG;
|
|
@@ -358,8 +385,11 @@ export async function renderSegments(
|
|
|
358
385
|
// same-route navigation reconciles (holds content) instead of remounting. The
|
|
359
386
|
// value is a static property of the route's position in the tree, so it is the
|
|
360
387
|
// same on every render of that route (SSR, navigation, action) — the keys
|
|
361
|
-
// never drift.
|
|
362
|
-
//
|
|
388
|
+
// never drift. It reads the static `transition` config only, never the
|
|
389
|
+
// per-navigation `when` decision (transitionGatedOff): a server-side `when`
|
|
390
|
+
// used to delete `transition`, which flipped this value and remounted the
|
|
391
|
+
// route twice (#995). Cross-route navigation still remounts: different routes
|
|
392
|
+
// have different segment ids regardless of transition scope.
|
|
363
393
|
const inTransitionScope = normalizedSegments.some(
|
|
364
394
|
(s) =>
|
|
365
395
|
s.transition != null &&
|
|
@@ -522,10 +552,13 @@ export async function renderSegments(
|
|
|
522
552
|
transition &&
|
|
523
553
|
transition.viewTransition !== false
|
|
524
554
|
) {
|
|
555
|
+
const boundary = transitionGatedOff
|
|
556
|
+
? { ...transition, ...GATED_OFF_CLASSES }
|
|
557
|
+
: transition;
|
|
525
558
|
if (node.segment.type === "layout") {
|
|
526
|
-
outletContent = wrapDefaultOutletContent(outletContent,
|
|
559
|
+
outletContent = wrapDefaultOutletContent(outletContent, boundary);
|
|
527
560
|
} else {
|
|
528
|
-
nodeContent = createViewTransitionBoundary(
|
|
561
|
+
nodeContent = createViewTransitionBoundary(boundary, nodeContent);
|
|
529
562
|
}
|
|
530
563
|
}
|
|
531
564
|
|