@rangojs/router 0.17.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/README.md +3 -2
- package/dist/types/browser/cookie-name.d.ts +6 -0
- package/dist/types/browser/event-controller.d.ts +3 -3
- package/dist/types/browser/history-state.d.ts +18 -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 +48 -2
- package/dist/types/cache/cache-policy.d.ts +18 -1
- package/dist/types/cache/cache-runtime.d.ts +7 -0
- package/dist/types/cache/cache-scope.d.ts +192 -26
- package/dist/types/cache/cache-tag.d.ts +116 -11
- package/dist/types/cache/cf/cf-base64.d.ts +6 -0
- package/dist/types/cache/cf/cf-cache-constants.d.ts +11 -0
- package/dist/types/cache/cf/cf-cache-store.d.ts +224 -72
- package/dist/types/cache/cf/cf-cache-types.d.ts +31 -4
- package/dist/types/cache/cf/cf-shell-frame.d.ts +95 -0
- package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +19 -0
- package/dist/types/cache/document-cache.d.ts +10 -0
- package/dist/types/cache/handle-snapshot.d.ts +41 -8
- package/dist/types/cache/index.d.ts +1 -0
- package/dist/types/cache/invalidation-order.d.ts +23 -0
- package/dist/types/cache/isolate-tag-memo.d.ts +149 -0
- package/dist/types/cache/request-tag-mask.d.ts +54 -0
- package/dist/types/cache/shell-memo.d.ts +178 -0
- package/dist/types/cache/shell-snapshot.d.ts +141 -105
- package/dist/types/cache/tag-invalidation.d.ts +54 -3
- package/dist/types/cache/types.d.ts +267 -96
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +123 -18
- 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/prerender/build-shell-capture.d.ts +11 -4
- 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/instrument.d.ts +8 -0
- 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/middleware-types.d.ts +6 -1
- package/dist/types/router/router-options.d.ts +4 -1
- package/dist/types/router/segment-resolution/loader-cache.d.ts +58 -12
- package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -4
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +33 -48
- package/dist/types/router/segment-resolution/mask-nested.d.ts +73 -24
- 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 +21 -0
- package/dist/types/rsc/handler.d.ts +1 -1
- package/dist/types/rsc/response-cache-serve.d.ts +15 -12
- package/dist/types/rsc/shell-build-manifest.d.ts +12 -3
- package/dist/types/rsc/shell-capture-constants.d.ts +116 -12
- package/dist/types/rsc/shell-capture.d.ts +202 -74
- package/dist/types/rsc/shell-serve.d.ts +144 -50
- package/dist/types/rsc/types.d.ts +9 -0
- package/dist/types/segment-system.d.ts +7 -0
- package/dist/types/server/context.d.ts +155 -33
- package/dist/types/server/cookie-store.d.ts +33 -0
- package/dist/types/server/handle-store.d.ts +52 -1
- package/dist/types/server/request-context.d.ts +210 -63
- package/dist/types/server/request-headers.d.ts +44 -0
- package/dist/types/server.d.ts +1 -0
- package/dist/types/ssr/index.d.ts +9 -0
- package/dist/types/ssr/ssr-root.d.ts +9 -8
- package/dist/types/testing/e2e/index.d.ts +1 -1
- package/dist/types/testing/flight.entry.d.ts +7 -0
- 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-loader.d.ts +18 -3
- package/dist/types/testing/run-transition-when.d.ts +44 -69
- package/dist/types/testing/serve-shell-request.d.ts +133 -0
- package/dist/types/testing/shell-status.d.ts +44 -19
- package/dist/types/transition-when-ref.d.ts +79 -0
- package/dist/types/types/cache-types.d.ts +15 -3
- package/dist/types/types/handler-context.d.ts +14 -1
- 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 +21 -18
- package/dist/types/vite/plugins/transition-when-hoist.d.ts +45 -0
- package/dist/types/vite/router-discovery.d.ts +29 -2
- package/dist/vite/index.js +1005 -105
- package/package.json +1 -1
- package/skills/breadcrumbs/SKILL.md +17 -7
- package/skills/cache-guide/SKILL.md +175 -76
- package/skills/caching/SKILL.md +361 -33
- package/skills/catalog.json +1 -1
- package/skills/client-urls/SKILL.md +12 -7
- package/skills/cloudflare/SKILL.md +15 -0
- package/skills/comparison/references/framework-comparison.md +52 -37
- package/skills/defer-hydration/SKILL.md +50 -30
- package/skills/deployment-caching/SKILL.md +5 -2
- package/skills/document-cache/SKILL.md +32 -2
- package/skills/handler-use/SKILL.md +2 -0
- package/skills/hooks/SKILL.md +3 -1
- package/skills/hooks/handle-and-actions.md +3 -1
- package/skills/hooks/navigation.md +23 -2
- package/skills/hooks/state.md +101 -7
- package/skills/intercept/SKILL.md +29 -12
- package/skills/loader/SKILL.md +197 -44
- package/skills/middleware/SKILL.md +34 -9
- package/skills/migrate-nextjs/SKILL.md +68 -37
- package/skills/migrate-react-router/SKILL.md +2 -1
- package/skills/migrate-react-router/component-migration.md +3 -1
- package/skills/migrate-react-router/data-and-actions.md +3 -1
- package/skills/migrate-react-router/route-mapping.md +7 -4
- package/skills/observability/SKILL.md +65 -0
- package/skills/parallel/SKILL.md +13 -8
- package/skills/ppr/SKILL.md +715 -371
- package/skills/prerender/SKILL.md +30 -17
- package/skills/rango/SKILL.md +72 -34
- package/skills/route/SKILL.md +14 -7
- package/skills/router-setup/SKILL.md +4 -2
- package/skills/scripts/SKILL.md +8 -1
- package/skills/server-actions/SKILL.md +11 -0
- package/skills/shell-manifest/SKILL.md +21 -12
- package/skills/testing/SKILL.md +49 -43
- package/skills/testing/cache-prerender.md +15 -14
- package/skills/testing/client-components.md +45 -3
- package/skills/testing/loader.md +19 -1
- 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 +202 -0
- package/skills/testing/server-actions.md +9 -3
- package/skills/theme/SKILL.md +56 -4
- package/skills/typesafety/env-and-bindings.md +18 -2
- package/skills/use-cache/SKILL.md +91 -23
- package/skills/vercel/SKILL.md +14 -2
- package/skills/view-transitions/SKILL.md +150 -29
- package/src/__augment-tests__/augmented.check.ts +23 -0
- package/src/browser/cookie-name.ts +23 -0
- package/src/browser/event-controller.ts +4 -4
- package/src/browser/history-state.ts +75 -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 +9 -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 +62 -2
- package/src/cache/cache-policy.ts +31 -4
- package/src/cache/cache-runtime.ts +209 -129
- package/src/cache/cache-scope.ts +573 -69
- package/src/cache/cache-tag.ts +475 -19
- package/src/cache/cf/cf-base64.ts +36 -4
- package/src/cache/cf/cf-cache-constants.ts +12 -0
- package/src/cache/cf/cf-cache-store.ts +1094 -376
- package/src/cache/cf/cf-cache-types.ts +32 -4
- package/src/cache/cf/cf-shell-frame.ts +258 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +55 -40
- package/src/cache/document-cache.ts +53 -7
- package/src/cache/handle-snapshot.ts +53 -9
- package/src/cache/index.ts +2 -0
- package/src/cache/invalidation-order.ts +84 -0
- package/src/cache/isolate-tag-memo.ts +308 -0
- package/src/cache/request-tag-mask.ts +147 -0
- package/src/cache/shell-memo.ts +341 -0
- package/src/cache/shell-snapshot.ts +249 -277
- package/src/cache/tag-invalidation.ts +109 -18
- package/src/cache/types.ts +274 -102
- package/src/cache/vercel/vercel-cache-store.ts +561 -74
- 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 +173 -110
- package/src/route-definition/dsl-helpers.ts +6 -3
- 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 +42 -5
- package/src/router/instrument.ts +54 -2
- package/src/router/intercept-resolution.ts +30 -8
- package/src/router/loader-resolution.ts +89 -33
- package/src/router/logging.ts +5 -3
- package/src/router/match-api.ts +89 -42
- package/src/router/match-context.ts +5 -0
- package/src/router/match-handlers.ts +14 -29
- package/src/router/match-middleware/background-revalidation.ts +3 -5
- package/src/router/match-middleware/cache-lookup.ts +52 -19
- package/src/router/match-middleware/cache-store.ts +36 -10
- package/src/router/match-result.ts +1 -0
- package/src/router/middleware-types.ts +6 -1
- package/src/router/middleware.ts +20 -4
- package/src/router/navigation-snapshot.ts +6 -5
- package/src/router/prerender-match.ts +10 -0
- package/src/router/request-classification.ts +7 -4
- package/src/router/router-options.ts +4 -1
- package/src/router/segment-resolution/fresh.ts +18 -13
- package/src/router/segment-resolution/helpers.ts +4 -2
- package/src/router/segment-resolution/loader-cache.ts +366 -70
- package/src/router/segment-resolution/loader-mask.ts +3 -4
- package/src/router/segment-resolution/loader-snapshot.ts +215 -126
- package/src/router/segment-resolution/mask-nested.ts +161 -42
- package/src/router/segment-resolution/revalidation.ts +27 -13
- 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 +47 -3
- package/src/rsc/handler.ts +24 -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 +10 -32
- package/src/rsc/response-cache-serve.ts +69 -78
- package/src/rsc/response-route-handler.ts +2 -2
- package/src/rsc/rsc-rendering.ts +712 -338
- package/src/rsc/server-action.ts +8 -20
- package/src/rsc/shell-build-manifest.ts +49 -29
- package/src/rsc/shell-capture-constants.ts +182 -12
- package/src/rsc/shell-capture.ts +1126 -612
- package/src/rsc/shell-serve.ts +331 -85
- package/src/rsc/ssr-setup.ts +3 -2
- package/src/rsc/types.ts +9 -0
- package/src/segment-system.tsx +40 -7
- package/src/server/context.ts +328 -72
- package/src/server/cookie-store.ts +184 -133
- package/src/server/handle-store.ts +245 -27
- package/src/server/request-context.ts +364 -100
- package/src/server/request-headers.ts +134 -0
- package/src/server.ts +4 -0
- package/src/ssr/index.tsx +96 -37
- package/src/ssr/ssr-root.tsx +9 -8
- package/src/testing/dispatch.ts +4 -7
- package/src/testing/e2e/index.ts +1 -0
- package/src/testing/flight.entry.ts +14 -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 +67 -5
- package/src/testing/run-transition-when.ts +109 -172
- package/src/testing/serve-shell-request.ts +381 -0
- package/src/testing/shell-status.ts +73 -47
- package/src/theme/ThemeProvider.tsx +7 -6
- package/src/transition-when-ref.ts +220 -0
- package/src/types/cache-types.ts +15 -3
- package/src/types/handler-context.ts +14 -1
- 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 +21 -18
- package/src/vite/discovery/discover-routers.ts +3 -0
- package/src/vite/discovery/shell-prerender-phase.ts +9 -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 +89 -9
- package/dist/types/rsc/transition-gate.d.ts +0 -23
- package/src/rsc/transition-gate.ts +0 -86
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The guarded `ctx.request.headers` (#976) and the router's raw reads.
|
|
3
|
+
*
|
|
4
|
+
* The Request the router hands to user code (handler, middleware and loader
|
|
5
|
+
* `ctx.request`, `getRequestContext().request`) carries an own `headers`
|
|
6
|
+
* getter that shadows the prototype's and runs the identity guard
|
|
7
|
+
* (cookie-store.ts guardRequestHeaders). A Proxy around the Request is not an
|
|
8
|
+
* option: `fetch(request)`, `new Request(request)` and `request.clone()` read
|
|
9
|
+
* the platform's internal slots, which a Proxy does not have. The own getter
|
|
10
|
+
* keeps the object a real Request, and those operations never call it.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** The Headers each shadowed request's getter returns. */
|
|
14
|
+
const RAW_HEADERS = new WeakMap<Request, Headers>();
|
|
15
|
+
|
|
16
|
+
const INSPECT = Symbol.for("nodejs.util.inspect.custom");
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Set while the platform or a request facade reads a shadowed request's
|
|
20
|
+
* headers for itself, not for user code (runUnguarded).
|
|
21
|
+
*/
|
|
22
|
+
let unguarded = false;
|
|
23
|
+
|
|
24
|
+
function runUnguarded<T>(fn: () => T): T {
|
|
25
|
+
const outer = unguarded;
|
|
26
|
+
unguarded = true;
|
|
27
|
+
try {
|
|
28
|
+
return fn();
|
|
29
|
+
} finally {
|
|
30
|
+
unguarded = outer;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A request's headers, past the guard. The rule: router code never reads a
|
|
36
|
+
* request's headers through the getter, only through this, so the guard sees
|
|
37
|
+
* user reads alone. Several router reads run inside the scopes the guard
|
|
38
|
+
* refuses (the state cookie rotation at a ppr capture, match's HMR marker,
|
|
39
|
+
* a store's cf-ray inside a "use cache" body, telemetry inside a loader
|
|
40
|
+
* fill); the rest follow the rule so a new call site cannot drift into one.
|
|
41
|
+
*/
|
|
42
|
+
export function requestHeaders(request: Request): Headers {
|
|
43
|
+
return RAW_HEADERS.get(request) ?? request.headers;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Shadow `request.headers` with an own, non-enumerable getter that calls
|
|
48
|
+
* `beforeRead` and returns the request's own Headers (stable per request, so
|
|
49
|
+
* a header set through it stays visible to every reader). Idempotent. Leaves
|
|
50
|
+
* the request as is when `headers` is a non-configurable own property (a
|
|
51
|
+
* runtime that defines it per instance). Three inherited members are
|
|
52
|
+
* shadowed with it:
|
|
53
|
+
*
|
|
54
|
+
* - `clone()` returns a clone shadowed the same way. A plain clone was a
|
|
55
|
+
* Request without the getter, so `ctx.request.clone().headers` read the
|
|
56
|
+
* visitor's headers past the guard. (`new Request(ctx.request)` still
|
|
57
|
+
* returns a plain Request: the constructor cannot be intercepted.)
|
|
58
|
+
* - `_request`, on a lazy srvx NodeRequest (what @vitejs/plugin-rsc's Node
|
|
59
|
+
* dev and preview servers and the vercel-output launcher hand the
|
|
60
|
+
* router), runs unguarded: it builds the platform Request on first use
|
|
61
|
+
* from `this.headers`, for clone() and the body readers. Through the
|
|
62
|
+
* getter, a clone() inside a cache() boundary threw as a user read. It
|
|
63
|
+
* stays lazy, so a request whose body srvx reads from its buffered
|
|
64
|
+
* `rawBody` never builds one.
|
|
65
|
+
* - Node's util.inspect hook runs unguarded: undici's inspect reads
|
|
66
|
+
* `this.headers`, so `console.log(ctx.request)` inside a "use cache" body
|
|
67
|
+
* threw. A log is not a read into a shared entry.
|
|
68
|
+
*/
|
|
69
|
+
export function shadowRequestHeaders(
|
|
70
|
+
request: Request,
|
|
71
|
+
beforeRead: () => void,
|
|
72
|
+
): Request {
|
|
73
|
+
if (RAW_HEADERS.has(request)) return request;
|
|
74
|
+
const own = Object.getOwnPropertyDescriptor(request, "headers");
|
|
75
|
+
if (own && !own.configurable) return request;
|
|
76
|
+
const lazy = inheritedGetter(request, "_request");
|
|
77
|
+
if (lazy) {
|
|
78
|
+
Object.defineProperty(request, "_request", {
|
|
79
|
+
get(this: Request): unknown {
|
|
80
|
+
return runUnguarded(() => lazy.call(this));
|
|
81
|
+
},
|
|
82
|
+
configurable: true,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
const raw = request.headers;
|
|
86
|
+
RAW_HEADERS.set(request, raw);
|
|
87
|
+
Object.defineProperty(request, "headers", {
|
|
88
|
+
get(): Headers {
|
|
89
|
+
if (!unguarded) beforeRead();
|
|
90
|
+
return raw;
|
|
91
|
+
},
|
|
92
|
+
enumerable: false,
|
|
93
|
+
configurable: true,
|
|
94
|
+
});
|
|
95
|
+
const clone = request.clone;
|
|
96
|
+
defineMethod(request, "clone", function (this: Request): Request {
|
|
97
|
+
return shadowRequestHeaders(clone.call(this), beforeRead);
|
|
98
|
+
});
|
|
99
|
+
const inspect = (request as unknown as Record<symbol, unknown>)[INSPECT];
|
|
100
|
+
if (typeof inspect === "function") {
|
|
101
|
+
defineMethod(
|
|
102
|
+
request,
|
|
103
|
+
INSPECT,
|
|
104
|
+
function (this: Request, ...args: unknown[]) {
|
|
105
|
+
return runUnguarded(() => inspect.apply(this, args));
|
|
106
|
+
},
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
return request;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function defineMethod(
|
|
113
|
+
target: object,
|
|
114
|
+
key: PropertyKey,
|
|
115
|
+
value: (...args: never[]) => unknown,
|
|
116
|
+
): void {
|
|
117
|
+
Object.defineProperty(target, key, {
|
|
118
|
+
value,
|
|
119
|
+
configurable: true,
|
|
120
|
+
writable: true,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** The getter `key` resolves to on `target`'s prototype chain, if any. */
|
|
125
|
+
function inheritedGetter(
|
|
126
|
+
target: object,
|
|
127
|
+
key: string,
|
|
128
|
+
): (() => unknown) | undefined {
|
|
129
|
+
for (let o = Object.getPrototypeOf(target); o; o = Object.getPrototypeOf(o)) {
|
|
130
|
+
const found = Object.getOwnPropertyDescriptor(o, key);
|
|
131
|
+
if (found) return found.get;
|
|
132
|
+
}
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
package/src/server.ts
CHANGED
|
@@ -48,6 +48,10 @@ export {
|
|
|
48
48
|
// Component utilities (used internally for server/client boundary checks)
|
|
49
49
|
export { isClientComponent, assertClientComponent } from "./component-utils.js";
|
|
50
50
|
|
|
51
|
+
// transition({ when }) strict validation: the generated routes-manifest module
|
|
52
|
+
// enables it in the app's RSC realm (see transition-when-ref.ts).
|
|
53
|
+
export { enableTransitionWhenValidation } from "./transition-when-ref.js";
|
|
54
|
+
|
|
51
55
|
// Client URL projection registry (Vite discovery/bootstrap bridge)
|
|
52
56
|
export {
|
|
53
57
|
clearClientUrlProjections,
|
package/src/ssr/index.tsx
CHANGED
|
@@ -2,7 +2,11 @@ import React from "react";
|
|
|
2
2
|
import { createSsrRootComponent, deserializeSsrPayload } from "./ssr-root.js";
|
|
3
3
|
import { injectRSCPayloadEager } from "./inject-rsc-eager.js";
|
|
4
4
|
import { runWithPreinitNonce } from "./preinit-client-references.js";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
POST_QUIESCE_TASK_HOPS,
|
|
7
|
+
SHELL_CAPTURE_MAX_WAIT_MS,
|
|
8
|
+
} from "../rsc/shell-capture-constants.js";
|
|
9
|
+
import { isThenable } from "../handles/is-thenable.js";
|
|
6
10
|
import type { ErrorPhase } from "../types.js";
|
|
7
11
|
import type { HeadScriptsOption } from "../vite/plugin-types.js";
|
|
8
12
|
|
|
@@ -237,28 +241,64 @@ export interface SSRDependencies<TEnv = unknown> {
|
|
|
237
241
|
}
|
|
238
242
|
|
|
239
243
|
/**
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
* real module-runner I/O in dev (100ms+ cold), which no fixed count of near-
|
|
255
|
-
* zero-cost task hops can buy. captureShellHTML therefore awaits the payload-
|
|
256
|
-
* settled signal (SsrRootOptions.onPayloadSettled, deadline-bounded) between
|
|
257
|
-
* quiesce and these hops; the hops then only flush the settled tree and mark
|
|
258
|
-
* pending boundaries POSTPONED. Still task-based (masked loaders never emit,
|
|
259
|
-
* so more hops never lets a hole settle). Bounded by maxWaitMs end to end.
|
|
244
|
+
* One set per running capture, collecting the client-reference module loads
|
|
245
|
+
* requested while it runs (captureClientLoads). The Flight client resolves a
|
|
246
|
+
* client component used as an element type lazily, so the payload settles
|
|
247
|
+
* while its module is still loading, and a shell that renders it outside any
|
|
248
|
+
* Suspense boundary cannot complete until the load does. On the
|
|
249
|
+
* cloudflare-basic build a cold load (the module, its imports, its CSS
|
|
250
|
+
* virtual) settled at +890.1ms, after the payload (+873.4ms) and 1ms before
|
|
251
|
+
* the abort, and uninstrumented builds lost that race (issue #949). The
|
|
252
|
+
* Flight client asks the loader again for every import row, even for a
|
|
253
|
+
* module another render is already loading, so a capture sees each load it
|
|
254
|
+
* needs; one a concurrent render requests meanwhile is collected too and is
|
|
255
|
+
* the same bounded I/O. The sets are per capture, their contents isolate-wide:
|
|
256
|
+
* a load that never settles can only hold the captures running when it was
|
|
257
|
+
* requested, each until its deadline.
|
|
260
258
|
*/
|
|
261
|
-
const
|
|
259
|
+
const activeCaptureClientLoads = new Set<Set<PromiseLike<unknown>>>();
|
|
260
|
+
|
|
261
|
+
/** The loader wrapper {@link captureClientLoads} last installed. */
|
|
262
|
+
let recordingClientReferenceLoader: ((id: string) => unknown) | undefined;
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Start collecting the client-reference loads requested during a capture;
|
|
266
|
+
* call the returned `stop` when the capture ends. Wraps plugin-rsc's SSR
|
|
267
|
+
* loader (`globalThis.__vite_rsc_client_require__`, which
|
|
268
|
+
* `__vite_rsc_require__` reads per call) on the first capture, so an isolate
|
|
269
|
+
* that never captures keeps the bare loader. Wraps again when another wrapper
|
|
270
|
+
* replaced this one (the build shell phase bridges hashed ids the same way,
|
|
271
|
+
* vite/discovery/shell-prerender-phase.ts). Collects nothing without the
|
|
272
|
+
* loader.
|
|
273
|
+
*/
|
|
274
|
+
function captureClientLoads(): {
|
|
275
|
+
loads: Set<PromiseLike<unknown>>;
|
|
276
|
+
stop: () => void;
|
|
277
|
+
} {
|
|
278
|
+
const g = globalThis as {
|
|
279
|
+
__vite_rsc_client_require__?: (id: string) => unknown;
|
|
280
|
+
};
|
|
281
|
+
const load = g.__vite_rsc_client_require__;
|
|
282
|
+
if (typeof load === "function" && load !== recordingClientReferenceLoader) {
|
|
283
|
+
recordingClientReferenceLoader = (id) => {
|
|
284
|
+
const loading = load(id);
|
|
285
|
+
// plugin-rsc memoizes one promise per id and the Flight client stamps
|
|
286
|
+
// status "fulfilled" on it once loaded: a warm module is not collected.
|
|
287
|
+
if (
|
|
288
|
+
activeCaptureClientLoads.size > 0 &&
|
|
289
|
+
isThenable(loading) &&
|
|
290
|
+
(loading as { status?: string }).status !== "fulfilled"
|
|
291
|
+
) {
|
|
292
|
+
for (const loads of activeCaptureClientLoads) loads.add(loading);
|
|
293
|
+
}
|
|
294
|
+
return loading;
|
|
295
|
+
};
|
|
296
|
+
g.__vite_rsc_client_require__ = recordingClientReferenceLoader;
|
|
297
|
+
}
|
|
298
|
+
const loads = new Set<PromiseLike<unknown>>();
|
|
299
|
+
activeCaptureClientLoads.add(loads);
|
|
300
|
+
return { loads, stop: () => activeCaptureClientLoads.delete(loads) };
|
|
301
|
+
}
|
|
262
302
|
|
|
263
303
|
/**
|
|
264
304
|
* Route an SSR error through the deps.onError notification callback with the
|
|
@@ -421,6 +461,13 @@ interface ShellCaptureOptions {
|
|
|
421
461
|
* refuses to store the shell (issue #915).
|
|
422
462
|
*/
|
|
423
463
|
onError?: (error: unknown) => void;
|
|
464
|
+
/**
|
|
465
|
+
* Called with React's errorInfo for each task still pending when the
|
|
466
|
+
* capture's own abort froze the prelude; its componentStack is computed
|
|
467
|
+
* when read. The dev no-shell warning prints the stacks: one of them is what
|
|
468
|
+
* suspended above <body> (issue #941).
|
|
469
|
+
*/
|
|
470
|
+
onAbortedTask?: (errorInfo: { componentStack?: string } | undefined) => void;
|
|
424
471
|
}
|
|
425
472
|
|
|
426
473
|
/**
|
|
@@ -761,12 +808,13 @@ export function createShellCaptureHandler<TEnv = unknown>(
|
|
|
761
808
|
const deadline = (await isDebuggerAttached())
|
|
762
809
|
? { promise: new Promise<void>(() => {}), cancel: () => {} }
|
|
763
810
|
: createCancelableTimeout(maxWaitMs);
|
|
811
|
+
const clientLoads = captureClientLoads();
|
|
764
812
|
try {
|
|
765
813
|
// No nonce (nonce'd requests never reach capture); no formState.
|
|
766
|
-
// payloadSettled: fires when the Flight payload root settles
|
|
767
|
-
//
|
|
768
|
-
//
|
|
769
|
-
//
|
|
814
|
+
// payloadSettled: fires when the Flight payload root settles and fizz
|
|
815
|
+
// can emit the tree. The abort below gates on it and on the
|
|
816
|
+
// client-reference loads in flight in the isolate (bounded by the same
|
|
817
|
+
// deadline): Flight byte-quiet alone is NOT render-readiness.
|
|
770
818
|
let settlePayload!: () => void;
|
|
771
819
|
const payloadSettled = new Promise<void>((resolve) => {
|
|
772
820
|
settlePayload = resolve;
|
|
@@ -823,8 +871,9 @@ export function createShellCaptureHandler<TEnv = unknown>(
|
|
|
823
871
|
// render errors are NOT our sentinel and still surface through
|
|
824
872
|
// deps.onError, the same channel renderHTML uses. See
|
|
825
873
|
// docs/design/ppr-shell-resume.md.
|
|
826
|
-
onError: (error: unknown) => {
|
|
874
|
+
onError: (error: unknown, errorInfo?: { componentStack?: string }) => {
|
|
827
875
|
if (error === abortReason) {
|
|
876
|
+
opts.onAbortedTask?.(errorInfo);
|
|
828
877
|
return;
|
|
829
878
|
}
|
|
830
879
|
opts.onError?.(error);
|
|
@@ -846,25 +895,34 @@ export function createShellCaptureHandler<TEnv = unknown>(
|
|
|
846
895
|
// shell that never goes quiet (a root postpone / hung handle).
|
|
847
896
|
await Promise.race([opts.quiesce, deadline.promise]);
|
|
848
897
|
// Then wait for fizz RENDER-READINESS, bounded by the same deadline:
|
|
849
|
-
// the payload root settles
|
|
850
|
-
//
|
|
851
|
-
//
|
|
898
|
+
// the payload root settles, then the client-reference module loads the
|
|
899
|
+
// capture started settle — a client component used as an element type
|
|
900
|
+
// resolves lazily, after the payload (real module-runner I/O in dev;
|
|
901
|
+
// 100ms+ on a cold graph). Flight byte-quiet does NOT imply this — a fully
|
|
852
902
|
// REPLAYED (prerendered) route's Flight stream finishes in ~1-3ms and
|
|
853
903
|
// an abort taken on quiet-plus-task-hops alone landed BEFORE fizz could
|
|
854
904
|
// emit <html>, freezing a zero-byte prelude: the eternal-MISS shape
|
|
855
905
|
// this route class showed on every cold graph (dev cold boots, GH
|
|
856
906
|
// runners) while ordinary routes — whose live handler execution keeps
|
|
857
|
-
// Flight noisy long enough — never hit it.
|
|
858
|
-
// block
|
|
859
|
-
//
|
|
860
|
-
//
|
|
861
|
-
//
|
|
862
|
-
//
|
|
907
|
+
// Flight noisy long enough — never hit it. Holes the payload carries
|
|
908
|
+
// block neither wait (masked live loaders and loader-nested promises postpone
|
|
909
|
+
// below the root and load no module; the Flight input stays frozen),
|
|
910
|
+
// so this await costs a genuinely hole-y shell nothing; a payload that
|
|
911
|
+
// NEVER settles (hung handles) degrades at the deadline exactly as
|
|
912
|
+
// before. A prerender that SETTLES first (early success or a hard
|
|
913
|
+
// rejection) ends the wait immediately — fizz is already done either
|
|
914
|
+
// way.
|
|
863
915
|
const prerenderSettled = prerenderPromise.then(
|
|
864
916
|
() => {},
|
|
865
917
|
() => {},
|
|
866
918
|
);
|
|
867
|
-
|
|
919
|
+
// The capture's loads all started when their import rows were parsed,
|
|
920
|
+
// before the Flight input froze.
|
|
921
|
+
await Promise.race([
|
|
922
|
+
payloadSettled.then(() => Promise.allSettled(clientLoads.loads)),
|
|
923
|
+
prerenderSettled,
|
|
924
|
+
deadline.promise,
|
|
925
|
+
]);
|
|
868
926
|
// Fixed task hops before the abort: give React's fizz worker turns to flush
|
|
869
927
|
// the now-complete shell and mark the still-pending boundaries as POSTPONED
|
|
870
928
|
// rather than errored. Deterministic (the byte set is already frozen and
|
|
@@ -923,6 +981,7 @@ export function createShellCaptureHandler<TEnv = unknown>(
|
|
|
923
981
|
};
|
|
924
982
|
} finally {
|
|
925
983
|
deadline.cancel();
|
|
984
|
+
clientLoads.stop();
|
|
926
985
|
}
|
|
927
986
|
};
|
|
928
987
|
}
|
package/src/ssr/ssr-root.tsx
CHANGED
|
@@ -189,14 +189,15 @@ export interface SsrRootOptions {
|
|
|
189
189
|
nonce?: string;
|
|
190
190
|
/**
|
|
191
191
|
* Fires once when the Flight payload root settles (resolve OR reject) — the
|
|
192
|
-
* signal that
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
* a
|
|
199
|
-
*
|
|
192
|
+
* signal that fizz can start emitting the tree. A client component used as
|
|
193
|
+
* an element type resolves lazily, so its module load can still be in
|
|
194
|
+
* flight (captureShellHTML waits for those separately, issue #949). The
|
|
195
|
+
* capture pass gates its abort on this: a fully REPLAYED (prerendered)
|
|
196
|
+
* route's Flight stream goes byte-quiet in ~1-3ms, but fizz cannot render
|
|
197
|
+
* even <html> until the module loads finish (real module-runner I/O in dev;
|
|
198
|
+
* 100ms+ on a cold graph), so an abort gated on Flight quiet alone fires
|
|
199
|
+
* first and freezes a zero-byte prelude. Masked-loader holes do NOT block
|
|
200
|
+
* this signal — they postpone below the root.
|
|
200
201
|
*/
|
|
201
202
|
onPayloadSettled?: () => void;
|
|
202
203
|
/**
|
package/src/testing/dispatch.ts
CHANGED
|
@@ -627,9 +627,9 @@ export async function dispatch<TEnv = any>(
|
|
|
627
627
|
const manifestEntry = preview?.manifestEntry;
|
|
628
628
|
if (manifestEntry) {
|
|
629
629
|
// Lazy so the testing barrel's eager graph stays plugin-rsc-free (see the
|
|
630
|
-
// import note above): the leaf takes createCacheScope
|
|
631
|
-
//
|
|
632
|
-
//
|
|
630
|
+
// import note above): the leaf takes createCacheScope as an INJECTED
|
|
631
|
+
// dep so it never imports plugin-rsc; we hand it the lazily imported
|
|
632
|
+
// builder here, only once a response route actually matched.
|
|
633
633
|
const cacheScopeMod = await import("../cache/cache-scope.js");
|
|
634
634
|
const { serveResponseRouteWithCache } =
|
|
635
635
|
await import("../rsc/response-cache-serve.js");
|
|
@@ -645,10 +645,7 @@ export async function dispatch<TEnv = any>(
|
|
|
645
645
|
responseType: responseType as string,
|
|
646
646
|
url,
|
|
647
647
|
executeHandler,
|
|
648
|
-
deps: {
|
|
649
|
-
createCacheScope: cacheScopeMod.createCacheScope,
|
|
650
|
-
resolveCacheTags: cacheScopeMod.resolveCacheTags,
|
|
651
|
-
},
|
|
648
|
+
deps: { createCacheScope: cacheScopeMod.createCacheScope },
|
|
652
649
|
});
|
|
653
650
|
if (cached !== undefined) return cached;
|
|
654
651
|
}
|
package/src/testing/e2e/index.ts
CHANGED
|
@@ -18,6 +18,11 @@
|
|
|
18
18
|
* element tree, so you can assert typed prop fidelity across the client boundary
|
|
19
19
|
* (a `Date` comes back a `Date`) and detect inlined-vs-island. Serialize +
|
|
20
20
|
* deserialize only — no hydration/interaction (that is the e2e tier).
|
|
21
|
+
*
|
|
22
|
+
* `serveShellRequest` serves one request through the router's production
|
|
23
|
+
* handler, so a `ppr` route's MISS runs the real shell capture and a later
|
|
24
|
+
* request is a real HIT; only the HTML step is stubbed. `resetShellTestState`
|
|
25
|
+
* clears the per-isolate PPR state between tests.
|
|
21
26
|
*/
|
|
22
27
|
|
|
23
28
|
export {
|
|
@@ -50,3 +55,12 @@ export type {
|
|
|
50
55
|
RenderHandlerResult,
|
|
51
56
|
StateCookieSeed,
|
|
52
57
|
} from "./render-handler.js";
|
|
58
|
+
|
|
59
|
+
export {
|
|
60
|
+
serveShellRequest,
|
|
61
|
+
resetShellTestState,
|
|
62
|
+
} from "./serve-shell-request.js";
|
|
63
|
+
export type {
|
|
64
|
+
ServeShellRequestOptions,
|
|
65
|
+
ServeShellRequestResult,
|
|
66
|
+
} from "./serve-shell-request.js";
|
package/src/testing/index.ts
CHANGED
|
@@ -49,6 +49,8 @@ export type {
|
|
|
49
49
|
|
|
50
50
|
export { runTransitionWhen } from "./run-transition-when.js";
|
|
51
51
|
export type {
|
|
52
|
+
RunTransitionWhenAction,
|
|
53
|
+
RunTransitionWhenLocation,
|
|
52
54
|
RunTransitionWhenOptions,
|
|
53
55
|
RunTransitionWhenResult,
|
|
54
56
|
} from "./run-transition-when.js";
|
|
@@ -91,12 +93,15 @@ export {
|
|
|
91
93
|
export type {
|
|
92
94
|
PprReplayBypassReason,
|
|
93
95
|
PprReplayStatus,
|
|
96
|
+
ShellCachePartition,
|
|
94
97
|
ShellStatus,
|
|
95
98
|
ShellStatusTarget,
|
|
96
99
|
} from "./shell-status.js";
|
|
97
100
|
|
|
98
101
|
export { collectHandle } from "./collect-handle.js";
|
|
99
102
|
|
|
103
|
+
export { withLocationStateKey } from "./location-state-key.js";
|
|
104
|
+
|
|
100
105
|
export {
|
|
101
106
|
diffGeneratedRoutes,
|
|
102
107
|
assertGeneratedRoutesMatch,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { resolveActionRefId } from "../../router/is-action.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The id a testing primitive's `action` option names: a single imported
|
|
5
|
+
* server action (its `$id ?? $$id`, as the action boundary derives it) or a
|
|
6
|
+
* raw actionId string. A function without either id throws, naming the
|
|
7
|
+
* primitive.
|
|
8
|
+
*/
|
|
9
|
+
export function resolveTestActionId(
|
|
10
|
+
action: ((...args: never[]) => unknown) | string | undefined,
|
|
11
|
+
primitive: string,
|
|
12
|
+
): string | undefined {
|
|
13
|
+
if (action === undefined) return undefined;
|
|
14
|
+
if (typeof action === "string") return action;
|
|
15
|
+
const id = resolveActionRefId(action);
|
|
16
|
+
if (id === undefined) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
`${primitive}: \`action\` must be a single imported server action ` +
|
|
19
|
+
"(carrying its build-injected id) or an actionId string. The passed " +
|
|
20
|
+
"function has no $id/$$id — outside a built app, pass the id string " +
|
|
21
|
+
'your predicate should match (e.g. "src/actions/cart.ts#addToCart").',
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
return id;
|
|
25
|
+
}
|
|
@@ -250,7 +250,7 @@ export function snapshotRunEffects<TEnv>(ctx: RequestContext<TEnv>): {
|
|
|
250
250
|
locationState: Record<string, unknown>;
|
|
251
251
|
} {
|
|
252
252
|
return {
|
|
253
|
-
cookies:
|
|
253
|
+
cookies: ctx._readCookies(),
|
|
254
254
|
locationState: resolveLocationStateEntries(ctx._locationState ?? []),
|
|
255
255
|
};
|
|
256
256
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
peekLocationStateKey,
|
|
3
|
+
type LocationStateDefinition,
|
|
4
|
+
} from "../browser/react/location-state-shared.js";
|
|
5
|
+
|
|
6
|
+
// Typed history.state slots must carry this prefix: buildHistoryState spreads
|
|
7
|
+
// only `__rsc_ls_*` keys onto history.state (history-state.ts).
|
|
8
|
+
const KEY_PREFIX = "__rsc_ls_";
|
|
9
|
+
|
|
10
|
+
let syntheticKeyCounter = 0;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Assign a location-state key to a `createLocationState()` definition in a unit
|
|
14
|
+
* test that runs without the rango Vite plugin (which injects the key in dev
|
|
15
|
+
* and build). Outside production, a definition without a key throws on first
|
|
16
|
+
* use (`MyState(value)`, `useLocationState(MyState)`, `.read()`).
|
|
17
|
+
*
|
|
18
|
+
* - `withLocationStateKey(GridState, "GridState")` sets `__rsc_ls_GridState`
|
|
19
|
+
* (a name that already starts with `__rsc_ls_` is used as-is).
|
|
20
|
+
* - Without a name, a key already set (by the plugin or an earlier call) is
|
|
21
|
+
* kept; otherwise the definition gets a synthetic `__rsc_ls_test_<n>` key,
|
|
22
|
+
* which then stays on it.
|
|
23
|
+
*
|
|
24
|
+
* Returns the definition.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* import { withLocationStateKey } from "@rangojs/router/testing";
|
|
29
|
+
* import { GridState } from "../src/location-states";
|
|
30
|
+
*
|
|
31
|
+
* withLocationStateKey(GridState);
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export function withLocationStateKey<
|
|
35
|
+
TDefinition extends LocationStateDefinition<any, any>,
|
|
36
|
+
>(definition: TDefinition, name?: string): TDefinition {
|
|
37
|
+
if (name !== undefined) {
|
|
38
|
+
definition.__rsc_ls_key = name.startsWith(KEY_PREFIX)
|
|
39
|
+
? name
|
|
40
|
+
: KEY_PREFIX + name;
|
|
41
|
+
} else if (!peekLocationStateKey(definition)) {
|
|
42
|
+
definition.__rsc_ls_key = `${KEY_PREFIX}test_${syntheticKeyCounter++}`;
|
|
43
|
+
}
|
|
44
|
+
return definition;
|
|
45
|
+
}
|