@rangojs/router 0.16.0 → 0.17.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/README.md +254 -89
- package/dist/testing/vitest.js +252 -0
- package/dist/types/browser/navigation-transaction.d.ts +6 -0
- package/dist/types/browser/partial-update.d.ts +11 -0
- package/dist/types/browser/react/ScrollRestoration.d.ts +6 -6
- package/dist/types/browser/rsc-router.d.ts +1 -1
- package/dist/types/browser/scroll-restoration.d.ts +7 -0
- package/dist/types/cache/cache-exec-scope.d.ts +18 -1
- package/dist/types/cache/cache-scope.d.ts +34 -5
- package/dist/types/cache/handle-capture.d.ts +41 -3
- package/dist/types/cache/handle-snapshot.d.ts +53 -13
- package/dist/types/cache/segment-codec.d.ts +10 -3
- package/dist/types/cache/taint.d.ts +1 -1
- package/dist/types/cache/types.d.ts +15 -3
- package/dist/types/client.d.ts +4 -4
- package/dist/types/client.rsc.d.ts +4 -5
- package/dist/types/components/DefaultDocument.d.ts +5 -2
- package/dist/types/context-var.d.ts +6 -5
- package/dist/types/escape-script.d.ts +1 -1
- package/dist/types/handles/MetaTags.d.ts +5 -4
- package/dist/types/handles/Scripts.d.ts +8 -7
- package/dist/types/handles/meta.d.ts +2 -2
- package/dist/types/handles/script.d.ts +17 -16
- package/dist/types/href-client.d.ts +1 -1
- package/dist/types/html.d.ts +12 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.rsc.d.ts +3 -2
- package/dist/types/loader-store.d.ts +7 -4
- package/dist/types/reverse.d.ts +36 -4
- package/dist/types/route-definition/dsl-helpers.d.ts +3 -3
- package/dist/types/route-definition/helpers-types.d.ts +33 -9
- package/dist/types/route-definition/resolve-handler-use.d.ts +23 -1
- package/dist/types/route-types.d.ts +1 -1
- package/dist/types/router/debug-manifest.d.ts +3 -2
- package/dist/types/router/error-handling.d.ts +20 -0
- package/dist/types/router/handler-context.d.ts +6 -0
- package/dist/types/router/match-middleware/background-revalidation.d.ts +46 -5
- package/dist/types/router/middleware-types.d.ts +3 -2
- package/dist/types/router/router-context.d.ts +3 -1
- package/dist/types/router/router-interfaces.d.ts +24 -2
- package/dist/types/router/router-options.d.ts +8 -5
- package/dist/types/router/segment-resolution/fresh.d.ts +10 -0
- package/dist/types/router/segment-resolution/loader-cache.d.ts +37 -1
- package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +3 -3
- package/dist/types/router/segment-resolution/mask-nested.d.ts +7 -7
- package/dist/types/router/segment-resolution/revalidation.d.ts +6 -2
- package/dist/types/router/segment-wrappers.d.ts +1 -1
- package/dist/types/rsc/response-route-handler.d.ts +1 -1
- package/dist/types/rsc/rsc-rendering.d.ts +1 -2
- package/dist/types/rsc/shell-capture-constants.d.ts +8 -0
- package/dist/types/rsc/shell-capture.d.ts +5 -4
- package/dist/types/rsc/ssr-setup.d.ts +6 -6
- package/dist/types/rsc/types.d.ts +10 -0
- package/dist/types/server/context.d.ts +33 -3
- package/dist/types/server/handle-store.d.ts +26 -2
- package/dist/types/server/request-context.d.ts +24 -21
- package/dist/types/ssr/index.d.ts +15 -0
- package/dist/types/static-handler.d.ts +4 -1
- package/dist/types/testing/generated-routes.d.ts +2 -2
- package/dist/types/testing/internal/context.d.ts +1 -1
- package/dist/types/testing/render-route.d.ts +32 -6
- package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +28 -5
- package/dist/types/testing/vitest.d.ts +35 -1
- package/dist/types/theme/ThemeScript.d.ts +3 -3
- package/dist/types/types/cache-types.d.ts +14 -10
- package/dist/types/types/handler-context.d.ts +14 -0
- package/dist/types/types/loader-types.d.ts +8 -6
- package/dist/types/urls/response-types.d.ts +3 -12
- package/dist/types/use-loader.d.ts +12 -3
- package/dist/types/vite/plugins/use-cache-transform.d.ts +33 -2
- package/dist/types/vite/rango.d.ts +1 -1
- package/dist/vite/index.js +1381 -1346
- package/package.json +2 -2
- package/skills/api-client/SKILL.md +22 -10
- package/skills/breadcrumbs/SKILL.md +65 -22
- package/skills/bundle-analysis/SKILL.md +22 -22
- package/skills/cache-guide/SKILL.md +207 -97
- package/skills/caching/SKILL.md +295 -93
- package/skills/catalog.json +16 -16
- package/skills/client-urls/SKILL.md +53 -31
- package/skills/cloudflare/SKILL.md +72 -20
- package/skills/cloudflare/references/d1-and-local-dev.md +15 -2
- package/skills/cloudflare/references/streaming-and-deploy.md +10 -3
- package/skills/cloudflare/references/webhooks-and-crypto.md +9 -2
- package/skills/comparison/SKILL.md +5 -0
- package/skills/comparison/references/framework-comparison.md +39 -33
- package/skills/composability/SKILL.md +38 -29
- package/skills/css/SKILL.md +10 -13
- package/skills/debug-manifest/SKILL.md +74 -31
- package/skills/defer-hydration/SKILL.md +2 -1
- package/skills/deployment-caching/SKILL.md +6 -1
- package/skills/document-cache/SKILL.md +92 -28
- package/skills/fonts/SKILL.md +16 -8
- package/skills/handler-use/SKILL.md +53 -17
- package/skills/hooks/SKILL.md +28 -20
- package/skills/hooks/data.md +23 -3
- package/skills/hooks/handle-and-actions.md +31 -16
- package/skills/hooks/navigation.md +39 -10
- package/skills/hooks/outlets.md +13 -6
- package/skills/hooks/state.md +37 -15
- package/skills/hooks/urls.md +7 -5
- package/skills/host-router/SKILL.md +64 -12
- package/skills/i18n/SKILL.md +60 -34
- package/skills/intercept/SKILL.md +161 -26
- package/skills/layout/SKILL.md +106 -50
- package/skills/links/SKILL.md +51 -19
- package/skills/loader/SKILL.md +242 -106
- package/skills/middleware/SKILL.md +119 -23
- package/skills/migrate-nextjs/SKILL.md +100 -45
- package/skills/migrate-nextjs/backend-host-swap.md +5 -2
- package/skills/migrate-react-router/SKILL.md +49 -38
- package/skills/migrate-react-router/component-migration.md +25 -6
- package/skills/migrate-react-router/data-and-actions.md +18 -5
- package/skills/migrate-react-router/route-mapping.md +12 -6
- package/skills/mime-routes/SKILL.md +12 -7
- package/skills/observability/SKILL.md +28 -14
- package/skills/parallel/SKILL.md +75 -39
- package/skills/ppr/SKILL.md +345 -150
- package/skills/prerender/SKILL.md +189 -100
- package/skills/rango/SKILL.md +65 -34
- package/skills/react-compiler/SKILL.md +6 -5
- package/skills/response-routes/SKILL.md +133 -25
- package/skills/route/SKILL.md +165 -60
- package/skills/router-setup/SKILL.md +418 -240
- package/skills/scripts/SKILL.md +31 -20
- package/skills/server-actions/SKILL.md +220 -59
- package/skills/shell-manifest/SKILL.md +12 -7
- package/skills/streams-and-websockets/SKILL.md +32 -14
- package/skills/tailwind/SKILL.md +37 -33
- package/skills/testing/SKILL.md +68 -32
- package/skills/testing/cache-prerender.md +55 -29
- package/skills/testing/client-components.md +59 -6
- package/skills/testing/e2e-parity.md +12 -2
- package/skills/testing/flight.md +19 -11
- package/skills/testing/handles.md +8 -5
- package/skills/testing/loader.md +8 -5
- package/skills/testing/middleware.md +8 -3
- package/skills/testing/predicates.md +120 -0
- package/skills/testing/render-handler.md +70 -22
- package/skills/testing/response-routes.md +11 -4
- package/skills/testing/reverse-and-types.md +20 -14
- package/skills/testing/server-actions.md +12 -3
- package/skills/testing/server-tree.md +9 -0
- package/skills/testing/setup.md +9 -5
- package/skills/theme/SKILL.md +106 -29
- package/skills/typesafety/SKILL.md +20 -9
- package/skills/typesafety/env-and-bindings.md +126 -47
- package/skills/typesafety/generated-files-and-cli.md +26 -14
- package/skills/typesafety/params-and-search.md +44 -19
- package/skills/typesafety/route-types.md +50 -14
- package/skills/use-cache/SKILL.md +200 -82
- package/skills/vercel/SKILL.md +21 -11
- package/skills/view-transitions/SKILL.md +41 -20
- package/src/__augment-tests__/augmented.check.ts +26 -1
- package/src/browser/index.ts +2 -2
- package/src/browser/navigation-bridge.ts +3 -4
- package/src/browser/navigation-transaction.ts +22 -2
- package/src/browser/partial-update.ts +35 -25
- package/src/browser/react/ScrollRestoration.tsx +6 -6
- package/src/browser/rsc-router.tsx +1 -1
- package/src/browser/scroll-restoration.ts +53 -6
- package/src/cache/cache-exec-scope.ts +37 -4
- package/src/cache/cache-key-utils.ts +1 -6
- package/src/cache/cache-runtime.ts +230 -61
- package/src/cache/cache-scope.ts +76 -12
- package/src/cache/document-cache.ts +15 -0
- package/src/cache/handle-capture.ts +90 -10
- package/src/cache/handle-snapshot.ts +132 -28
- package/src/cache/segment-codec.ts +26 -7
- package/src/cache/tag-invalidation.ts +4 -3
- package/src/cache/taint.ts +1 -1
- package/src/cache/types.ts +16 -3
- package/src/client.rsc.tsx +10 -9
- package/src/client.tsx +5 -4
- package/src/components/DefaultDocument.tsx +7 -2
- package/src/context-var.ts +6 -5
- package/src/escape-script.ts +1 -1
- package/src/handles/MetaTags.tsx +13 -11
- package/src/handles/Scripts.tsx +11 -10
- package/src/handles/meta.ts +2 -2
- package/src/handles/script.ts +17 -16
- package/src/href-client.ts +1 -1
- package/src/html.ts +12 -0
- package/src/index.rsc.ts +5 -0
- package/src/index.ts +1 -0
- package/src/loader-store.ts +7 -4
- package/src/prerender/build-shell-capture.ts +8 -11
- package/src/reverse.ts +44 -4
- package/src/route-definition/dsl-helpers.ts +117 -30
- package/src/route-definition/helpers-types.ts +33 -9
- package/src/route-definition/resolve-handler-use.ts +121 -6
- package/src/route-types.ts +0 -3
- package/src/router/debug-manifest.ts +10 -2
- package/src/router/error-handling.ts +51 -15
- package/src/router/handler-context.ts +34 -25
- package/src/router/intercept-resolution.ts +125 -39
- package/src/router/loader-resolution.ts +56 -11
- package/src/router/match-api.ts +12 -17
- package/src/router/match-middleware/background-revalidation.ts +161 -96
- package/src/router/match-middleware/cache-lookup.ts +93 -10
- package/src/router/match-middleware/cache-store.ts +17 -90
- package/src/router/middleware-types.ts +3 -5
- package/src/router/middleware.ts +52 -9
- package/src/router/prerender-match.ts +53 -19
- package/src/router/route-snapshot.ts +11 -1
- package/src/router/router-context.ts +2 -0
- package/src/router/router-interfaces.ts +25 -2
- package/src/router/router-options.ts +8 -5
- package/src/router/segment-resolution/fresh.ts +44 -12
- package/src/router/segment-resolution/loader-cache.ts +205 -45
- package/src/router/segment-resolution/loader-mask.ts +12 -22
- package/src/router/segment-resolution/loader-snapshot.ts +6 -6
- package/src/router/segment-resolution/mask-nested.ts +7 -7
- package/src/router/segment-resolution/revalidation.ts +34 -6
- package/src/router/segment-wrappers.ts +4 -0
- package/src/rsc/render-pipeline.ts +3 -0
- package/src/rsc/response-route-handler.ts +11 -3
- package/src/rsc/rsc-rendering.ts +8 -45
- package/src/rsc/shell-capture-constants.ts +13 -0
- package/src/rsc/shell-capture.ts +111 -90
- package/src/rsc/ssr-setup.ts +14 -8
- package/src/rsc/types.ts +11 -0
- package/src/server/context.ts +88 -11
- package/src/server/cookie-store.ts +5 -4
- package/src/server/handle-store.ts +232 -7
- package/src/server/request-context.ts +35 -30
- package/src/ssr/index.tsx +40 -7
- package/src/static-handler.ts +4 -1
- package/src/testing/flight-runtime.d.ts +21 -0
- package/src/testing/generated-routes.ts +2 -2
- package/src/testing/internal/context.ts +1 -1
- package/src/testing/internal/flight-client-globals.ts +7 -5
- package/src/testing/render-route.tsx +85 -24
- package/src/testing/run-loader.ts +5 -1
- package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
- package/src/testing/vitest.ts +62 -1
- package/src/theme/ThemeScript.tsx +3 -3
- package/src/types/cache-types.ts +14 -10
- package/src/types/handler-context.ts +14 -0
- package/src/types/loader-types.ts +8 -6
- package/src/urls/response-types.ts +3 -23
- package/src/use-loader.tsx +12 -3
- package/src/vite/plugins/use-cache-transform.ts +118 -74
- package/src/vite/rango.ts +7 -3
|
@@ -129,6 +129,19 @@ function shouldCacheResponse(response: Response): CacheDirectives | null {
|
|
|
129
129
|
return parseCacheControl(cacheControl);
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Throw the first error the drained render reported through Flight or Fizz
|
|
134
|
+
* onError. A component that throws after the 200 committed does not fail the
|
|
135
|
+
* stream: it completes with an error row or an errored Suspense boundary, and
|
|
136
|
+
* every hit would serve it (issue #915). The caller's catch reports the skipped
|
|
137
|
+
* write as cache-write. Read only after the body is fully drained, when both
|
|
138
|
+
* renders have finished.
|
|
139
|
+
*/
|
|
140
|
+
function throwIfRenderErrored(requestCtx: RequestContext | undefined): void {
|
|
141
|
+
const errors = requestCtx?._renderErrors;
|
|
142
|
+
if (errors && errors.length > 0) throw errors[0];
|
|
143
|
+
}
|
|
144
|
+
|
|
132
145
|
// ============================================================================
|
|
133
146
|
// Response Helpers
|
|
134
147
|
// ============================================================================
|
|
@@ -404,6 +417,7 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
404
417
|
// drain the fresh render fully before snapshotting tags
|
|
405
418
|
// (same render-complete barrier as the miss path).
|
|
406
419
|
const body = await new Response(fresh.body).arrayBuffer();
|
|
420
|
+
throwIfRenderErrored(requestCtx);
|
|
407
421
|
await store.putResponse!(
|
|
408
422
|
cacheKey,
|
|
409
423
|
new Response(body, fresh),
|
|
@@ -468,6 +482,7 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
468
482
|
// unaffected) is the render-complete barrier that keeps the cached
|
|
469
483
|
// body and its tag set consistent.
|
|
470
484
|
const body = await new Response(cacheStream).arrayBuffer();
|
|
485
|
+
throwIfRenderErrored(requestCtx);
|
|
471
486
|
await store.putResponse!(
|
|
472
487
|
cacheKey,
|
|
473
488
|
new Response(body, originalResponse),
|
|
@@ -6,10 +6,82 @@
|
|
|
6
6
|
* pulling in @vitejs/plugin-rsc/rsc dependencies.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { getCurrentLoaderBodyId } from "../server/context.js";
|
|
9
10
|
import type { HandleStore } from "../server/handle-store.js";
|
|
10
11
|
import type { SegmentHandleData } from "./types.js";
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Scoping for one capture of a cached unit (a loader's own cache(),
|
|
15
|
+
* loader-cache.ts; a "use cache" function, cache-runtime.ts) that runs
|
|
16
|
+
* concurrently with live pushes into the same store.
|
|
17
|
+
*/
|
|
18
|
+
export interface HandleCaptureOptions {
|
|
19
|
+
/** Push-time predicate; a rejected push is not recorded by this capture. */
|
|
20
|
+
accept?: () => boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Recorded pushes do not reach the store. A stale hit's background
|
|
23
|
+
* revalidation: the foreground already replayed the entry's pushes, so the
|
|
24
|
+
* fresh body's pushes belong only to the refreshed entry.
|
|
25
|
+
*/
|
|
26
|
+
divert?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Record key for an accepted push, in place of its segment id. A loader's
|
|
29
|
+
* own cache() (loader-cache.ts recordOwnerKey) and "use cache"
|
|
30
|
+
* (useCacheRecordKey) group pushes by the loader body that made them.
|
|
31
|
+
*/
|
|
32
|
+
key?: () => string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// The loader whose recorded push a "use cache" HIT is replaying
|
|
36
|
+
// (replayLoaderPush), for an enclosing "use cache" MISS's record key.
|
|
37
|
+
let replayingOwner: string | undefined;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* HandleStore.pushReplayed, attributed to `loaderId` for the record key of an
|
|
41
|
+
* enclosing "use cache" MISS: an outer cached function records an inner HIT's
|
|
42
|
+
* replayed loader pushes under that loader, not as its own.
|
|
43
|
+
*/
|
|
44
|
+
export function replayLoaderPush(
|
|
45
|
+
handleStore: HandleStore,
|
|
46
|
+
handleName: string,
|
|
47
|
+
segmentId: string,
|
|
48
|
+
value: unknown,
|
|
49
|
+
loaderId: string,
|
|
50
|
+
): void {
|
|
51
|
+
const prev = replayingOwner;
|
|
52
|
+
replayingOwner = loaderId;
|
|
53
|
+
try {
|
|
54
|
+
handleStore.pushReplayed(handleName, segmentId, value, loaderId);
|
|
55
|
+
} finally {
|
|
56
|
+
replayingOwner = prev;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Record key for a "use cache" capture: run-length groups `${seq}:${owner}`,
|
|
62
|
+
* so the HIT (handle-snapshot.ts appendHandles) delivers each loader's pushes
|
|
63
|
+
* at most once per request. The owner is the loader whose body, entered
|
|
64
|
+
* inside this execution via ctx.use, made the push; empty for the function's
|
|
65
|
+
* own pushes and those of cached functions it calls. Groups keep push order
|
|
66
|
+
* across owners. Create at call time: the caller's loader body is the
|
|
67
|
+
* function's own scope.
|
|
68
|
+
*/
|
|
69
|
+
export function useCacheRecordKey(): () => string {
|
|
70
|
+
const callerBody = getCurrentLoaderBodyId();
|
|
71
|
+
let seq = 0;
|
|
72
|
+
let last: string | undefined;
|
|
73
|
+
return () => {
|
|
74
|
+
const body = replayingOwner ?? getCurrentLoaderBodyId();
|
|
75
|
+
const owner = body === callerBody ? "" : (body ?? "");
|
|
76
|
+
if (owner !== last) {
|
|
77
|
+
last = owner;
|
|
78
|
+
seq++;
|
|
79
|
+
}
|
|
80
|
+
return `${seq}:${owner}`;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface HandleCapture extends HandleCaptureOptions {
|
|
13
85
|
data: Record<string, SegmentHandleData>;
|
|
14
86
|
}
|
|
15
87
|
|
|
@@ -42,33 +114,41 @@ function ensureInterceptorInstalled(handleStore: HandleStore): void {
|
|
|
42
114
|
segmentId: string,
|
|
43
115
|
value: unknown,
|
|
44
116
|
) => {
|
|
117
|
+
let diverted = false;
|
|
45
118
|
for (const capture of captures) {
|
|
46
|
-
if (!capture.
|
|
47
|
-
|
|
119
|
+
if (capture.accept && !capture.accept()) continue;
|
|
120
|
+
if (capture.divert) diverted = true;
|
|
121
|
+
const key = capture.key ? capture.key() : segmentId;
|
|
122
|
+
if (!capture.data[key]) {
|
|
123
|
+
capture.data[key] = {};
|
|
48
124
|
}
|
|
49
|
-
if (!capture.data[
|
|
50
|
-
capture.data[
|
|
125
|
+
if (!capture.data[key][handleName]) {
|
|
126
|
+
capture.data[key][handleName] = [];
|
|
51
127
|
}
|
|
52
|
-
capture.data[
|
|
128
|
+
capture.data[key][handleName].push(value);
|
|
53
129
|
}
|
|
54
|
-
originalPush(handleName, segmentId, value);
|
|
130
|
+
if (!diverted) originalPush(handleName, segmentId, value);
|
|
55
131
|
};
|
|
56
132
|
}
|
|
57
133
|
|
|
58
134
|
/**
|
|
59
|
-
* Start capturing handle pushes for a cached function
|
|
135
|
+
* Start capturing handle pushes for a cached function or cached loader
|
|
136
|
+
* execution (`options`: see HandleCaptureOptions).
|
|
60
137
|
*
|
|
61
138
|
* Concurrency-safe: multiple overlapping captures on the same
|
|
62
139
|
* HandleStore are independent. Each capture registers a token in a
|
|
63
140
|
* Set; stopping removes it. No ordering requirement (LIFO not needed).
|
|
64
141
|
*/
|
|
65
|
-
export function startHandleCapture(
|
|
142
|
+
export function startHandleCapture(
|
|
143
|
+
handleStore: HandleStore,
|
|
144
|
+
options?: HandleCaptureOptions,
|
|
145
|
+
): {
|
|
66
146
|
capture: HandleCapture;
|
|
67
147
|
stop: () => void;
|
|
68
148
|
} {
|
|
69
149
|
ensureInterceptorInstalled(handleStore);
|
|
70
150
|
|
|
71
|
-
const capture: HandleCapture = { data: {} };
|
|
151
|
+
const capture: HandleCapture = { data: {}, ...options };
|
|
72
152
|
const captures = activeCapturesMap.get(handleStore)!;
|
|
73
153
|
captures.add(capture);
|
|
74
154
|
|
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
* during segment resolution and stored alongside cached segments.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { replayLoaderPush } from "./handle-capture.js";
|
|
9
10
|
import type { ResolvedSegment } from "../types.js";
|
|
10
11
|
import type { HandleStore } from "../server/handle-store.js";
|
|
11
|
-
import type { SegmentHandleData } from "./types.js";
|
|
12
|
+
import type { HandleOwners, SegmentHandleData } from "./types.js";
|
|
12
13
|
// segment-codec eagerly pulls @vitejs/plugin-rsc (a virtual: module unresolvable
|
|
13
14
|
// in plain node/vitest). It is imported LAZILY inside the two async encode/decode
|
|
14
15
|
// helpers below so that modules which import handle-snapshot only for the
|
|
@@ -47,19 +48,32 @@ function withTimeout<T>(p: Promise<T>, ms: number, onTimeout: T): Promise<T> {
|
|
|
47
48
|
]);
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
/**
|
|
52
|
+
* `onError` reaches the Flight encode (see serializeResult). A cache writer
|
|
53
|
+
* passes the collector it gives the value's encode, so a handle value that
|
|
54
|
+
* fails to encode (a rejected pushed promise) refuses the whole entry like a
|
|
55
|
+
* failed value does; prerender (prerender-match.ts) passes one too and
|
|
56
|
+
* hands the error to its prerender.onError policy.
|
|
57
|
+
*/
|
|
58
|
+
export async function encodeHandles(
|
|
59
|
+
handles: HandleRecord,
|
|
60
|
+
onError?: (error: unknown) => void,
|
|
61
|
+
): Promise<string> {
|
|
51
62
|
if (!hasHandleData(handles)) return "";
|
|
52
|
-
return encodeHandleValue(handles);
|
|
63
|
+
return encodeHandleValue(handles, onError);
|
|
53
64
|
}
|
|
54
65
|
|
|
55
66
|
export function decodeHandles(encoded: string): Promise<HandleRecord | null> {
|
|
56
67
|
return decodeHandleValue<HandleRecord>(encoded);
|
|
57
68
|
}
|
|
58
69
|
|
|
59
|
-
export async function encodeHandleValue(
|
|
70
|
+
export async function encodeHandleValue(
|
|
71
|
+
value: unknown,
|
|
72
|
+
onError?: (error: unknown) => void,
|
|
73
|
+
): Promise<string> {
|
|
60
74
|
const { serializeResult } = await import("./segment-codec.js");
|
|
61
75
|
const encoded = await withTimeout(
|
|
62
|
-
serializeResult(value),
|
|
76
|
+
serializeResult(value, onError),
|
|
63
77
|
HANDLE_ENCODE_TIMEOUT_MS,
|
|
64
78
|
null,
|
|
65
79
|
);
|
|
@@ -84,49 +98,139 @@ export async function decodeHandleValue<T>(encoded: string): Promise<T | null> {
|
|
|
84
98
|
* Capture handle data for a set of segments from the handle store.
|
|
85
99
|
* Used when caching segments to preserve their handle data.
|
|
86
100
|
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
101
|
+
* DSL-loader pushes stay out of the CACHE WRITE: a HIT runs loaders exactly
|
|
102
|
+
* as an uncached render of the same request would (cache-lookup.ts
|
|
103
|
+
* resolveFreshLoadersAndYield), so replaying a recorded copy would duplicate
|
|
104
|
+
* the live push. Handler pushes, including
|
|
105
|
+
* handler-invoked ctx.use(Loader) bodies (skipped with their handler on a
|
|
106
|
+
* HIT), are kept. The store tags loader pushes by array position at push
|
|
107
|
+
* time, so primitive values are covered. A PPR shell capture untags the
|
|
108
|
+
* settled bake-lane pushes its record keeps (shell-capture.ts) and gives
|
|
109
|
+
* them an owner (captureHandleOwners). Only this call site passes
|
|
110
|
+
* excludeLoaderPushes; every other getDataForSegment consumer (the
|
|
111
|
+
* render-barrier snapshot, prerender) sees every push.
|
|
94
112
|
*/
|
|
95
113
|
export function captureHandles(
|
|
96
114
|
segments: ResolvedSegment[],
|
|
97
115
|
handleStore: HandleStore,
|
|
98
|
-
exclude?: WeakSet<object>,
|
|
99
116
|
): Record<string, SegmentHandleData> {
|
|
100
117
|
const handles: Record<string, SegmentHandleData> = {};
|
|
101
118
|
for (const seg of segments) {
|
|
102
|
-
|
|
103
|
-
if (!exclude) {
|
|
104
|
-
handles[seg.id] = data;
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
const filtered: SegmentHandleData = {};
|
|
108
|
-
for (const [handleName, values] of Object.entries(data)) {
|
|
109
|
-
const kept = values.filter(
|
|
110
|
-
(v) => typeof v !== "object" || v === null || !exclude.has(v),
|
|
111
|
-
);
|
|
112
|
-
if (kept.length > 0) filtered[handleName] = kept;
|
|
113
|
-
}
|
|
114
|
-
handles[seg.id] = filtered;
|
|
119
|
+
handles[seg.id] = handleStore.getDataForSegment(seg.id, true);
|
|
115
120
|
}
|
|
116
121
|
return handles;
|
|
117
122
|
}
|
|
118
123
|
|
|
124
|
+
/** The owners of captureHandles' values; undefined when none is owned. */
|
|
125
|
+
export function captureHandleOwners(
|
|
126
|
+
segments: ResolvedSegment[],
|
|
127
|
+
handleStore: HandleStore,
|
|
128
|
+
): HandleOwners | undefined {
|
|
129
|
+
let owners: HandleOwners | undefined;
|
|
130
|
+
for (const seg of segments) {
|
|
131
|
+
const segOwners = handleStore.getRecordOwners(seg.id);
|
|
132
|
+
if (segOwners) (owners ??= {})[seg.id] = segOwners;
|
|
133
|
+
}
|
|
134
|
+
return owners;
|
|
135
|
+
}
|
|
136
|
+
|
|
119
137
|
/**
|
|
120
138
|
* Restore handle data from a cached snapshot into the handle store.
|
|
121
|
-
* Used when serving cached segments to replay their handle data
|
|
139
|
+
* Used when serving cached segments to replay their handle data: a route
|
|
140
|
+
* cache() record owns its segments' arrays, so it REPLACES them.
|
|
141
|
+
*
|
|
142
|
+
* A segment with `owners` is replaced with empty arrays and re-pushed in
|
|
143
|
+
* recorded order, an owned value through pushReplayed: the owning loader
|
|
144
|
+
* re-runs on the HIT (loaders stay live), and its live pushes replace the
|
|
145
|
+
* recorded ones in place instead of appending a second copy. `claim`
|
|
146
|
+
* (setupLoaderAccess _claimLoaderPushes) is asked once per owner, so the
|
|
147
|
+
* loader's own cache() HIT does not replay its pushes a second time
|
|
148
|
+
* (loader-cache.ts replayLoaderHandles); an owner it refuses is skipped. A
|
|
149
|
+
* record without owners restores as a plain replay.
|
|
122
150
|
*/
|
|
123
151
|
export function restoreHandles(
|
|
124
152
|
handles: Record<string, SegmentHandleData>,
|
|
125
153
|
handleStore: HandleStore,
|
|
154
|
+
owners?: HandleOwners,
|
|
155
|
+
claim?: (loaderId: string) => boolean,
|
|
126
156
|
): void {
|
|
157
|
+
let delivers: Map<string, boolean> | undefined;
|
|
127
158
|
for (const [segId, segHandles] of Object.entries(handles)) {
|
|
128
|
-
if (Object.keys(segHandles).length
|
|
159
|
+
if (Object.keys(segHandles).length === 0) continue;
|
|
160
|
+
const segOwners = owners?.[segId];
|
|
161
|
+
if (!segOwners) {
|
|
129
162
|
handleStore.replaySegmentData(segId, segHandles);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
const cleared: SegmentHandleData = {};
|
|
166
|
+
for (const handleName in segHandles) cleared[handleName] = [];
|
|
167
|
+
handleStore.replaySegmentData(segId, cleared);
|
|
168
|
+
for (const [handleName, values] of Object.entries(segHandles)) {
|
|
169
|
+
const valueOwners = segOwners[handleName];
|
|
170
|
+
for (let i = 0; i < values.length; i++) {
|
|
171
|
+
const owner = valueOwners?.[i];
|
|
172
|
+
if (!owner) {
|
|
173
|
+
handleStore.push(handleName, segId, values[i]);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
delivers ??= new Map();
|
|
177
|
+
let deliver = delivers.get(owner);
|
|
178
|
+
if (deliver === undefined) {
|
|
179
|
+
deliver = claim ? claim(owner) : true;
|
|
180
|
+
delivers.set(owner, deliver);
|
|
181
|
+
}
|
|
182
|
+
if (deliver) {
|
|
183
|
+
handleStore.pushReplayed(handleName, segId, values[i], owner);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Append recorded handle pushes to the store, in recorded order, via push()
|
|
192
|
+
* so active captures and loader-scope tagging see them like live pushes. For
|
|
193
|
+
* a cached unit that shares its segments with live pushes ("use cache");
|
|
194
|
+
* restoreHandles would wipe those. `segmentId` redirects every value to one
|
|
195
|
+
* segment (the caller's).
|
|
196
|
+
*
|
|
197
|
+
* Owner-keyed groups (useCacheRecordKey, `${seq}:${loaderId}`) are a loader's
|
|
198
|
+
* pushes, which reach the page once per request: `claim` (setupLoaderAccess
|
|
199
|
+
* _claimLoaderPushes) skips a loader that already ran or was replayed in this
|
|
200
|
+
* request, and a claimed loader that runs later replaces its replayed values
|
|
201
|
+
* (HandleStore.pushReplayed). `${seq}:` groups are the function's own pushes.
|
|
202
|
+
* A key without ":" is a segment id (records written before owner keys) and
|
|
203
|
+
* replays unchanged. A loader's own cache() replays through loader-cache.ts
|
|
204
|
+
* replayLoaderHandles.
|
|
205
|
+
*/
|
|
206
|
+
export function appendHandles(
|
|
207
|
+
handles: Record<string, SegmentHandleData>,
|
|
208
|
+
handleStore: HandleStore,
|
|
209
|
+
segmentId?: string,
|
|
210
|
+
claim?: (loaderId: string) => boolean,
|
|
211
|
+
): void {
|
|
212
|
+
let claims: Map<string, boolean> | undefined;
|
|
213
|
+
for (const [key, segHandles] of Object.entries(handles)) {
|
|
214
|
+
const colon = key.indexOf(":");
|
|
215
|
+
const owner = colon < 0 ? "" : key.slice(colon + 1);
|
|
216
|
+
if (owner) {
|
|
217
|
+
claims ??= new Map();
|
|
218
|
+
let deliver = claims.get(owner);
|
|
219
|
+
if (deliver === undefined) {
|
|
220
|
+
deliver = claim ? claim(owner) : true;
|
|
221
|
+
claims.set(owner, deliver);
|
|
222
|
+
}
|
|
223
|
+
if (!deliver) continue;
|
|
224
|
+
}
|
|
225
|
+
const target = segmentId ?? key;
|
|
226
|
+
for (const [handleName, values] of Object.entries(segHandles)) {
|
|
227
|
+
for (const value of values) {
|
|
228
|
+
if (owner) {
|
|
229
|
+
replayLoaderPush(handleStore, handleName, target, value, owner);
|
|
230
|
+
} else {
|
|
231
|
+
handleStore.push(handleName, target, value);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
130
234
|
}
|
|
131
235
|
}
|
|
132
236
|
}
|
|
@@ -69,11 +69,15 @@ export function stringToStream(str: string): ReadableStream<Uint8Array> {
|
|
|
69
69
|
*/
|
|
70
70
|
export async function rscSerialize(
|
|
71
71
|
value: unknown,
|
|
72
|
+
onError?: (error: unknown) => void,
|
|
72
73
|
): Promise<string | undefined> {
|
|
73
74
|
if (value === undefined || value === null) return undefined;
|
|
74
75
|
|
|
75
76
|
const temporaryReferences = createTemporaryReferenceSet();
|
|
76
|
-
const stream = renderToReadableStream(value, {
|
|
77
|
+
const stream = renderToReadableStream(value, {
|
|
78
|
+
temporaryReferences,
|
|
79
|
+
onError,
|
|
80
|
+
});
|
|
77
81
|
return streamToString(stream);
|
|
78
82
|
}
|
|
79
83
|
|
|
@@ -102,10 +106,16 @@ export async function rscDeserialize<T>(
|
|
|
102
106
|
*
|
|
103
107
|
* Returns null only on serialization failure.
|
|
104
108
|
*/
|
|
105
|
-
export async function serializeResult(
|
|
109
|
+
export async function serializeResult(
|
|
110
|
+
value: unknown,
|
|
111
|
+
onError?: (error: unknown) => void,
|
|
112
|
+
): Promise<string | null> {
|
|
106
113
|
try {
|
|
107
114
|
const temporaryReferences = createTemporaryReferenceSet();
|
|
108
|
-
const stream = renderToReadableStream(value, {
|
|
115
|
+
const stream = renderToReadableStream(value, {
|
|
116
|
+
temporaryReferences,
|
|
117
|
+
onError,
|
|
118
|
+
});
|
|
109
119
|
return await streamToString(stream);
|
|
110
120
|
} catch (error) {
|
|
111
121
|
// Returning null silently turns a non-serializable cache value into a
|
|
@@ -145,9 +155,17 @@ export const deserializeComponent: (encoded: string) => Promise<unknown> =
|
|
|
145
155
|
* Serialize segments for storage.
|
|
146
156
|
* Each segment's component, layout, loading, and loaderData are RSC-serialized.
|
|
147
157
|
* Metadata is preserved as-is.
|
|
158
|
+
*
|
|
159
|
+
* `onError` goes to every Flight encode. A component that throws while
|
|
160
|
+
* encoding (e.g. an async server component in the tree) does not reject the
|
|
161
|
+
* encode: Flight reports it through onError and writes an error row
|
|
162
|
+
* (`1:E{...}`) that throws when the decoded tree renders. cacheRoute and the
|
|
163
|
+
* prerender producers (prerender-match.ts) pass it to refuse such an entry;
|
|
164
|
+
* without it the encode is unchanged.
|
|
148
165
|
*/
|
|
149
166
|
export async function serializeSegments(
|
|
150
167
|
segments: ResolvedSegment[],
|
|
168
|
+
onError?: (error: unknown) => void,
|
|
151
169
|
): Promise<SerializedSegmentData[]> {
|
|
152
170
|
return Promise.all(
|
|
153
171
|
segments.map(async (segment): Promise<SerializedSegmentData> => {
|
|
@@ -162,6 +180,7 @@ export async function serializeSegments(
|
|
|
162
180
|
// Serialize the component to RSC stream
|
|
163
181
|
const stream = renderToReadableStream(componentResolved, {
|
|
164
182
|
temporaryReferences,
|
|
183
|
+
onError,
|
|
165
184
|
});
|
|
166
185
|
|
|
167
186
|
// RSC-serialize loading: "null" string distinguishes explicit null from undefined
|
|
@@ -169,7 +188,7 @@ export async function serializeSegments(
|
|
|
169
188
|
segment.loading !== undefined
|
|
170
189
|
? segment.loading === null
|
|
171
190
|
? "null"
|
|
172
|
-
: await rscSerialize(segment.loading)
|
|
191
|
+
: await rscSerialize(segment.loading, onError)
|
|
173
192
|
: undefined;
|
|
174
193
|
|
|
175
194
|
// Await loaderData / loaderDataPromise if they're Promises
|
|
@@ -190,9 +209,9 @@ export async function serializeSegments(
|
|
|
190
209
|
encodedLoaderDataPromise,
|
|
191
210
|
] = await Promise.all([
|
|
192
211
|
streamToString(stream),
|
|
193
|
-
segment.layout ? rscSerialize(segment.layout) : undefined,
|
|
194
|
-
rscSerialize(loaderDataResolved),
|
|
195
|
-
rscSerialize(loaderDataPromiseResolved),
|
|
212
|
+
segment.layout ? rscSerialize(segment.layout, onError) : undefined,
|
|
213
|
+
rscSerialize(loaderDataResolved, onError),
|
|
214
|
+
rscSerialize(loaderDataPromiseResolved, onError),
|
|
196
215
|
]);
|
|
197
216
|
|
|
198
217
|
return {
|
|
@@ -68,8 +68,8 @@ function warnNoTagStore(fn: string, tags: string[]): void {
|
|
|
68
68
|
console.warn(
|
|
69
69
|
`[${fn}] No tag-capable cache store is configured; tags ` +
|
|
70
70
|
`[${tags.join(", ")}] were not invalidated. The configured store must ` +
|
|
71
|
-
`implement invalidateTags() (the built-in MemorySegmentCacheStore
|
|
72
|
-
`CFCacheStore do).`,
|
|
71
|
+
`implement invalidateTags() (the built-in MemorySegmentCacheStore, ` +
|
|
72
|
+
`CFCacheStore, and VercelCacheStore do).`,
|
|
73
73
|
);
|
|
74
74
|
}
|
|
75
75
|
|
|
@@ -98,7 +98,8 @@ function warnPartialTagStore(fn: string, incapable: number): void {
|
|
|
98
98
|
console.warn(
|
|
99
99
|
`[${fn}] ${incapable} configured cache store(s) do not implement ` +
|
|
100
100
|
`invalidateTags(); their tagged entries were NOT invalidated. Use a ` +
|
|
101
|
-
`tag-capable store (
|
|
101
|
+
`tag-capable store (MemorySegmentCacheStore / CFCacheStore / ` +
|
|
102
|
+
`VercelCacheStore) for any ` +
|
|
102
103
|
`cache({ store }) boundary whose entries you invalidate by tag.`,
|
|
103
104
|
);
|
|
104
105
|
}
|
package/src/cache/taint.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Taint symbol for request-scoped objects.
|
|
3
3
|
*
|
|
4
|
-
* Objects branded with NOCACHE_SYMBOL (ctx
|
|
4
|
+
* Objects branded with NOCACHE_SYMBOL (ctx objects) are excluded from
|
|
5
5
|
* "use cache" cache keys and trigger handle capture mode so that side
|
|
6
6
|
* effects (breadcrumbs, metadata) are recorded and replayed on cache hit.
|
|
7
7
|
*/
|
package/src/cache/types.ts
CHANGED
|
@@ -424,9 +424,8 @@ export type DocumentShellCacheEntry = ShellCacheEntry & {
|
|
|
424
424
|
/**
|
|
425
425
|
* The families a shell snapshot pins. The item/segment/response families are
|
|
426
426
|
* cache-store reads/writes (recorded by RecordingShellStore); the loader family
|
|
427
|
-
* pins the settled CONTAINER of a bake-lane loader (
|
|
428
|
-
*
|
|
429
|
-
* docs/design/loader-container-bake.md). Excludes the shell family itself
|
|
427
|
+
* pins the settled CONTAINER of a bake-lane loader (lane rule: see
|
|
428
|
+
* resolveLoaderData, loader-cache.ts). Excludes the shell family itself
|
|
430
429
|
* (getShell/putShell) — the snapshot rides INSIDE a shell entry, so recording
|
|
431
430
|
* it would be self-referential.
|
|
432
431
|
*/
|
|
@@ -542,6 +541,11 @@ export interface CachedEntryData {
|
|
|
542
541
|
* (see handle-snapshot.ts encodeHandles) so Promise/ReactNode handle values
|
|
543
542
|
* round-trip through JSON-serializing stores instead of being flattened. */
|
|
544
543
|
handles: string;
|
|
544
|
+
/**
|
|
545
|
+
* Owning loader ids index-aligned with the decoded `handles` values
|
|
546
|
+
* (HandleOwners). Absent: every value restores as a plain replay.
|
|
547
|
+
*/
|
|
548
|
+
handleOwners?: HandleOwners;
|
|
545
549
|
/** Expiration timestamp (ms since epoch) */
|
|
546
550
|
expiresAt: number;
|
|
547
551
|
/** Cache tags for invalidation */
|
|
@@ -583,3 +587,12 @@ export interface CacheDefaults {
|
|
|
583
587
|
* Structure: { handleName: [values...] }
|
|
584
588
|
*/
|
|
585
589
|
export type SegmentHandleData = Record<string, unknown[]>;
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* segmentId -> handleName -> the owning loader id of each recorded value, by
|
|
593
|
+
* index (null: not owned). Only a PPR shell capture writes owners: the
|
|
594
|
+
* settled pushes of an `ssr: false` loader's own body, which the record keeps
|
|
595
|
+
* because the prelude rendered them. restoreHandles replays an owned value
|
|
596
|
+
* through HandleStore.pushReplayed, so that loader's live re-run replaces it.
|
|
597
|
+
*/
|
|
598
|
+
export type HandleOwners = Record<string, Record<string, (string | null)[]>>;
|
package/src/client.rsc.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* RSC-environment version of client exports
|
|
3
3
|
*
|
|
4
|
-
* This file is used when importing "
|
|
4
|
+
* This file is used when importing "@rangojs/router/client" from RSC (server components).
|
|
5
5
|
* It re-exports the server's createLoader so that loader definitions work in both
|
|
6
6
|
* environments with the same import.
|
|
7
7
|
*
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
* in RSC context, while the regular client.tsx is used in client components.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
// From html.ts directly, never via ./client.js: every export of that
|
|
13
|
+
// "use client" module is an opaque client reference here.
|
|
14
|
+
import * as Html from "./html.js";
|
|
15
|
+
|
|
12
16
|
// Re-export everything from client.tsx (Outlet, useLoader, etc.)
|
|
13
17
|
// These are safe to use in RSC context
|
|
14
18
|
export {
|
|
@@ -64,7 +68,10 @@ export {
|
|
|
64
68
|
useLinkStatus,
|
|
65
69
|
type LinkStatus,
|
|
66
70
|
} from "./browser/react/use-link-status.js";
|
|
67
|
-
export {
|
|
71
|
+
export {
|
|
72
|
+
useScrollRestoration,
|
|
73
|
+
type ScrollRestorationProps,
|
|
74
|
+
} from "./browser/react/ScrollRestoration.js";
|
|
68
75
|
|
|
69
76
|
export {
|
|
70
77
|
Link,
|
|
@@ -72,11 +79,6 @@ export {
|
|
|
72
79
|
type PrefetchStrategy,
|
|
73
80
|
} from "./browser/react/Link.js";
|
|
74
81
|
|
|
75
|
-
export {
|
|
76
|
-
ScrollRestoration,
|
|
77
|
-
type ScrollRestorationProps,
|
|
78
|
-
} from "./browser/react/ScrollRestoration.js";
|
|
79
|
-
|
|
80
82
|
export {
|
|
81
83
|
NavigationProvider,
|
|
82
84
|
type NavigationProviderProps,
|
|
@@ -91,14 +93,13 @@ export { MountContext } from "./browser/react/mount-context.js";
|
|
|
91
93
|
export { createHandle, isHandle, type Handle } from "./handle.js";
|
|
92
94
|
|
|
93
95
|
export { Meta } from "./handles/meta.js";
|
|
94
|
-
export { MetaTags } from "./handles/MetaTags.js";
|
|
95
96
|
export type { MetaDescriptor, MetaDescriptorBase } from "./router/types.js";
|
|
96
97
|
export {
|
|
97
98
|
Script,
|
|
98
99
|
type ScriptConfig,
|
|
99
100
|
type ScriptAttributes,
|
|
100
101
|
} from "./handles/script.js";
|
|
101
|
-
export {
|
|
102
|
+
export { Html };
|
|
102
103
|
export { Breadcrumbs, type BreadcrumbItem } from "./handles/breadcrumbs.js";
|
|
103
104
|
|
|
104
105
|
export {
|
package/src/client.tsx
CHANGED
|
@@ -8,6 +8,9 @@ import {
|
|
|
8
8
|
Suspense,
|
|
9
9
|
type ReactNode,
|
|
10
10
|
} from "react";
|
|
11
|
+
// Not `export * as Html`: plugin-rsc's "use client" proxy transform only
|
|
12
|
+
// expands bare `export *` and throws on a named one.
|
|
13
|
+
import * as Html from "./html.js";
|
|
11
14
|
import { OutletContext, type OutletContextValue } from "./outlet-context.js";
|
|
12
15
|
import {
|
|
13
16
|
type ClientErrorBoundaryFallbackProps,
|
|
@@ -300,7 +303,7 @@ interface ErrorBoundaryState {
|
|
|
300
303
|
* @example
|
|
301
304
|
* ```tsx
|
|
302
305
|
* "use client";
|
|
303
|
-
* import { ErrorBoundary } from "
|
|
306
|
+
* import { ErrorBoundary } from "@rangojs/router/client";
|
|
304
307
|
*
|
|
305
308
|
* function MyComponent() {
|
|
306
309
|
* return (
|
|
@@ -427,7 +430,6 @@ export {
|
|
|
427
430
|
} from "./browser/react/use-link-status.js";
|
|
428
431
|
|
|
429
432
|
export {
|
|
430
|
-
ScrollRestoration,
|
|
431
433
|
useScrollRestoration,
|
|
432
434
|
type ScrollRestorationProps,
|
|
433
435
|
} from "./browser/react/ScrollRestoration.js";
|
|
@@ -436,14 +438,13 @@ export { type Handle } from "./handle.js";
|
|
|
436
438
|
export { useHandle } from "./browser/react/use-handle.js";
|
|
437
439
|
|
|
438
440
|
export { Meta } from "./handles/meta.js";
|
|
439
|
-
export { MetaTags } from "./handles/MetaTags.js";
|
|
440
441
|
export type { MetaDescriptor, MetaDescriptorBase } from "./router/types.js";
|
|
441
442
|
export {
|
|
442
443
|
Script,
|
|
443
444
|
type ScriptConfig,
|
|
444
445
|
type ScriptAttributes,
|
|
445
446
|
} from "./handles/script.js";
|
|
446
|
-
export {
|
|
447
|
+
export { Html };
|
|
447
448
|
export { Breadcrumbs, type BreadcrumbItem } from "./handles/breadcrumbs.js";
|
|
448
449
|
|
|
449
450
|
export {
|
|
@@ -3,12 +3,16 @@
|
|
|
3
3
|
import type { ReactNode, ReactElement } from "react";
|
|
4
4
|
import { MetaTags } from "../handles/MetaTags.js";
|
|
5
5
|
import { Scripts } from "../handles/Scripts.js";
|
|
6
|
+
import { ScrollRestoration } from "../browser/react/ScrollRestoration.js";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Default document component that provides a basic HTML structure.
|
|
9
10
|
* Used when no custom document is provided to createRouter.
|
|
10
|
-
*
|
|
11
|
-
*
|
|
11
|
+
* Renders every Html member: Html.Meta (charset, viewport, route meta), head +
|
|
12
|
+
* body Html.Scripts sites for the Script handle, and Html.ScrollRestoration.
|
|
13
|
+
* An app that wants a custom getKey or no scroll restoration passes its own
|
|
14
|
+
* document; rendering a second Html.ScrollRestoration under this one only
|
|
15
|
+
* warns ("[Scroll] Already initialized").
|
|
12
16
|
*
|
|
13
17
|
* Uses suppressHydrationWarning on <html> because the theme script
|
|
14
18
|
* may modify class/style attributes before React hydrates.
|
|
@@ -27,6 +31,7 @@ export function DefaultDocument({
|
|
|
27
31
|
<body>
|
|
28
32
|
<Scripts position="body" />
|
|
29
33
|
{children}
|
|
34
|
+
<ScrollRestoration />
|
|
30
35
|
</body>
|
|
31
36
|
</html>
|
|
32
37
|
);
|