@rangojs/router 0.22.1 → 0.24.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/bin/rango.js +10 -4
- package/dist/testing/vitest.js +31 -1
- package/dist/types/browser/defer.d.ts +48 -0
- package/dist/types/browser/document-revealed.d.ts +17 -0
- package/dist/types/browser/event-controller.d.ts +7 -1
- package/dist/types/browser/merge-segment-loaders.d.ts +5 -2
- package/dist/types/browser/navigation-transaction.d.ts +21 -7
- package/dist/types/browser/partial-update.d.ts +39 -8
- package/dist/types/browser/react/NavigationProvider.d.ts +19 -5
- package/dist/types/browser/react/context.d.ts +3 -2
- package/dist/types/browser/react/filter-segment-order.d.ts +7 -0
- package/dist/types/browser/response-adapter.d.ts +7 -0
- package/dist/types/browser/types.d.ts +24 -10
- package/dist/types/browser/validate-redirect-origin.d.ts +11 -0
- package/dist/types/build/generate-route-types.d.ts +1 -1
- package/dist/types/build/route-types/router-processing.d.ts +1 -1
- package/dist/types/build/route-types/scan-filter.d.ts +22 -2
- package/dist/types/cache/cf/cf-cache-store.d.ts +16 -1
- package/dist/types/cache/cf/cf-cache-types.d.ts +23 -2
- package/dist/types/cache/document-cache-policy.d.ts +24 -0
- package/dist/types/cache/document-cache.d.ts +2 -10
- package/dist/types/client-urls/client-urls.d.ts +1 -1
- package/dist/types/client-urls/hoisted-revalidate.d.ts +13 -0
- package/dist/types/client-urls/navigation.d.ts +0 -37
- package/dist/types/client-urls/revalidate-registry.d.ts +4 -0
- package/dist/types/client-urls/server-projection.d.ts +29 -1
- package/dist/types/client-urls/types.d.ts +34 -69
- package/dist/types/client.d.ts +0 -1
- package/dist/types/context-var.d.ts +12 -0
- package/dist/types/errors.d.ts +15 -0
- package/dist/types/handles/deferred-resolution.d.ts +2 -1
- package/dist/types/handles/pushed-by.d.ts +31 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.rsc.d.ts +2 -0
- package/dist/types/loader-store.d.ts +8 -0
- package/dist/types/prefetch-markers.d.ts +11 -0
- package/dist/types/response-utils.d.ts +7 -0
- package/dist/types/reverse.d.ts +8 -8
- package/dist/types/route-definition/helpers-types.d.ts +27 -0
- package/dist/types/route-definition/rewrite.d.ts +56 -0
- package/dist/types/route-map-builder.d.ts +7 -2
- package/dist/types/router/basename.d.ts +7 -0
- package/dist/types/router/handler-context.d.ts +1 -5
- package/dist/types/router/internal-params.d.ts +8 -0
- package/dist/types/router/is-action.d.ts +2 -1
- package/dist/types/router/middleware-types.d.ts +15 -0
- package/dist/types/router/revalidation.d.ts +1 -2
- package/dist/types/router/route-trie-builder.d.ts +10 -0
- package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -2
- package/dist/types/router/segment-resolution/mask-nested.d.ts +4 -2
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +264 -0
- package/dist/types/rsc/full-payload.d.ts +11 -0
- package/dist/types/rsc/handler.d.ts +33 -0
- package/dist/types/rsc/helpers.d.ts +8 -0
- package/dist/types/rsc/loader-fetch.d.ts +16 -0
- package/dist/types/rsc/rewrite-passes.d.ts +74 -0
- package/dist/types/rsc/ssr-setup.d.ts +3 -1
- package/dist/types/server/context.d.ts +21 -1
- package/dist/types/server/handle-store.d.ts +9 -1
- package/dist/types/server/request-context.d.ts +52 -2
- package/dist/types/server/rewrite-trail.d.ts +49 -0
- package/dist/types/server/slot-accessors.d.ts +29 -0
- package/dist/types/server.d.ts +1 -0
- package/dist/types/testing/flight.entry.d.ts +1 -1
- package/dist/types/testing/index.d.ts +4 -4
- package/dist/types/testing/internal/action-id.d.ts +10 -1
- package/dist/types/testing/internal/client-urls-projection.d.ts +16 -0
- package/dist/types/testing/internal/test-action.d.ts +8 -0
- package/dist/types/testing/render-route.d.ts +8 -0
- package/dist/types/testing/run-loader.d.ts +2 -1
- package/dist/types/testing/run-middleware.d.ts +20 -0
- package/dist/types/testing/run-revalidate.d.ts +74 -0
- package/dist/types/testing/run-transition-when.d.ts +2 -3
- package/dist/types/testing/serve-shell-request.d.ts +40 -1
- package/dist/types/testing/vitest.d.ts +11 -0
- package/dist/types/thenable-status.d.ts +6 -0
- package/dist/types/types/boundaries.d.ts +9 -0
- package/dist/types/types/handler-context.d.ts +14 -0
- package/dist/types/types/loader-types.d.ts +30 -0
- package/dist/types/types/request-scope.d.ts +7 -0
- package/dist/types/types/segments.d.ts +21 -4
- package/dist/types/urls/path-helper-types.d.ts +25 -4
- package/dist/types/urls/type-extraction.d.ts +6 -2
- package/dist/types/vite/discovery/discover-routers.d.ts +9 -0
- package/dist/types/vite/discovery/state.d.ts +14 -4
- package/dist/types/vite/plugins/client-urls-revalidate-hoist.d.ts +54 -0
- package/dist/types/vite/plugins/hoist/literal.d.ts +83 -0
- package/dist/types/vite/plugins/hoist/scope.d.ts +40 -0
- package/dist/types/vite/plugins/hoist/source.d.ts +41 -0
- package/dist/types/vite/plugins/transition-when-hoist.d.ts +8 -11
- package/dist/types/vite/router-discovery.d.ts +10 -0
- package/dist/vite/index.js +974 -379
- package/package.json +1 -1
- package/skills/caching/SKILL.md +54 -5
- package/skills/catalog.json +3 -3
- package/skills/client-urls/SKILL.md +197 -24
- package/skills/cloudflare/SKILL.md +14 -9
- package/skills/hooks/SKILL.md +16 -3
- package/skills/hooks/data.md +3 -1
- package/skills/links/SKILL.md +24 -0
- package/skills/loader/SKILL.md +199 -2
- package/skills/middleware/SKILL.md +183 -2
- package/skills/migrate-nextjs/SKILL.md +2 -2
- package/skills/migrate-react-router/SKILL.md +10 -7
- package/skills/migrate-react-router/data-and-actions.md +1 -1
- package/skills/ppr/SKILL.md +22 -0
- package/skills/prerender/SKILL.md +22 -8
- package/skills/rango/SKILL.md +13 -7
- package/skills/route/SKILL.md +139 -0
- package/skills/server-actions/SKILL.md +27 -0
- package/skills/testing/SKILL.md +3 -2
- package/skills/testing/client-components.md +2 -1
- package/skills/testing/middleware.md +31 -10
- package/skills/testing/predicates.md +67 -32
- package/skills/testing/response-routes.md +1 -0
- package/skills/testing/serve-shell-request.md +107 -19
- package/skills/testing/setup.md +2 -2
- package/skills/typesafety/route-types.md +2 -2
- package/skills/use-cache/SKILL.md +4 -2
- package/skills/view-transitions/SKILL.md +26 -0
- package/src/__augment-tests__/augmented.check.ts +20 -0
- package/src/browser/defer.ts +82 -0
- package/src/browser/document-revealed.ts +52 -0
- package/src/browser/event-controller.ts +46 -32
- package/src/browser/merge-segment-loaders.ts +31 -18
- package/src/browser/navigation-bridge.ts +82 -16
- package/src/browser/navigation-client.ts +48 -36
- package/src/browser/navigation-store.ts +13 -0
- package/src/browser/navigation-transaction.ts +25 -8
- package/src/browser/partial-update.ts +554 -146
- package/src/browser/prefetch/fetch.ts +2 -1
- package/src/browser/react/Link.tsx +7 -9
- package/src/browser/react/NavigationProvider.tsx +88 -49
- package/src/browser/react/context.ts +3 -2
- package/src/browser/react/filter-segment-order.ts +13 -2
- package/src/browser/react/use-router.ts +2 -4
- package/src/browser/response-adapter.ts +20 -0
- package/src/browser/rsc-router.tsx +17 -45
- package/src/browser/server-action-bridge.ts +25 -63
- package/src/browser/types.ts +24 -10
- package/src/browser/validate-redirect-origin.ts +16 -0
- package/src/build/generate-route-types.ts +1 -0
- package/src/build/route-types/router-processing.ts +3 -6
- package/src/build/route-types/scan-filter.ts +45 -11
- package/src/cache/cf/cf-cache-store.ts +59 -21
- package/src/cache/cf/cf-cache-types.ts +24 -2
- package/src/cache/document-cache-policy.ts +131 -0
- package/src/cache/document-cache.ts +21 -107
- package/src/client-urls/client-urls.ts +22 -6
- package/src/client-urls/hoisted-revalidate.ts +25 -0
- package/src/client-urls/navigation.ts +0 -96
- package/src/client-urls/revalidate-registry.ts +30 -0
- package/src/client-urls/server-projection.ts +151 -65
- package/src/client-urls/types.ts +39 -71
- package/src/client.tsx +9 -5
- package/src/context-var.ts +32 -2
- package/src/errors.ts +34 -0
- package/src/handles/deferred-resolution.ts +28 -5
- package/src/handles/pushed-by.ts +148 -0
- package/src/index.rsc.ts +8 -0
- package/src/index.ts +7 -0
- package/src/loader-store.ts +18 -7
- package/src/prefetch-markers.ts +13 -0
- package/src/response-utils.ts +19 -0
- package/src/reverse.ts +17 -14
- package/src/route-content-wrapper.tsx +18 -4
- package/src/route-definition/dsl-helpers.ts +27 -4
- package/src/route-definition/helpers-types.ts +27 -1
- package/src/route-definition/redirect.ts +2 -15
- package/src/route-definition/rewrite.ts +146 -0
- package/src/route-map-builder.ts +14 -11
- package/src/router/basename.ts +20 -0
- package/src/router/handler-context.ts +3 -13
- package/src/router/intercept-resolution.ts +35 -16
- package/src/router/internal-params.ts +16 -0
- package/src/router/is-action.ts +2 -1
- package/src/router/loader-resolution.ts +85 -4
- package/src/router/match-api.ts +36 -1
- package/src/router/match-middleware/background-revalidation.ts +16 -4
- package/src/router/match-middleware/cache-lookup.ts +35 -7
- package/src/router/match-middleware/cache-store.ts +15 -1
- package/src/router/match-middleware/intercept-resolution.ts +4 -2
- package/src/router/match-result.ts +14 -2
- package/src/router/middleware-types.ts +16 -0
- package/src/router/middleware.ts +127 -49
- package/src/router/navigation-snapshot.ts +2 -1
- package/src/router/revalidation.ts +1 -1
- package/src/router/route-trie-builder.ts +28 -1
- package/src/router/segment-resolution/fresh.ts +22 -18
- package/src/router/segment-resolution/loader-cache.ts +2 -1
- package/src/router/segment-resolution/loader-mask.ts +5 -3
- package/src/router/segment-resolution/loader-snapshot.ts +18 -4
- package/src/router/segment-resolution/mask-nested.ts +8 -4
- package/src/router/segment-resolution/prefetch-deferral.ts +583 -0
- package/src/router/segment-resolution/revalidation.ts +215 -82
- package/src/rsc/full-payload.ts +37 -5
- package/src/rsc/handler.ts +82 -55
- package/src/rsc/helpers.ts +24 -0
- package/src/rsc/loader-fetch.ts +79 -24
- package/src/rsc/response-cache-serve.ts +18 -2
- package/src/rsc/response-route-handler.ts +4 -7
- package/src/rsc/rewrite-passes.ts +203 -0
- package/src/rsc/rsc-rendering.ts +54 -9
- package/src/rsc/shell-capture.ts +2 -1
- package/src/rsc/ssr-setup.ts +11 -5
- package/src/segment-loader-promise.ts +23 -20
- package/src/segment-system.tsx +60 -26
- package/src/server/context.ts +39 -5
- package/src/server/cookie-parse.ts +36 -12
- package/src/server/cookie-store.ts +7 -3
- package/src/server/handle-store.ts +152 -25
- package/src/server/request-context.ts +175 -64
- package/src/server/request-headers.ts +4 -14
- package/src/server/rewrite-trail.ts +190 -0
- package/src/server/slot-accessors.ts +57 -0
- package/src/server.ts +4 -0
- package/src/ssr/index.tsx +118 -16
- package/src/testing/action-form.ts +2 -11
- package/src/testing/dispatch.ts +138 -67
- package/src/testing/e2e/page-helpers.ts +3 -1
- package/src/testing/flight.entry.ts +1 -0
- package/src/testing/index.ts +7 -3
- package/src/testing/internal/action-id.ts +16 -1
- package/src/testing/internal/client-urls-projection.ts +70 -0
- package/src/testing/internal/test-action.ts +25 -0
- package/src/testing/render-route.tsx +16 -3
- package/src/testing/run-loader.ts +4 -1
- package/src/testing/run-middleware.ts +56 -11
- package/src/testing/run-revalidate.ts +142 -0
- package/src/testing/run-transition-when.ts +7 -8
- package/src/testing/serve-shell-request.ts +131 -8
- package/src/testing/vitest.ts +67 -1
- package/src/thenable-status.ts +11 -0
- package/src/types/boundaries.ts +12 -0
- package/src/types/handler-context.ts +14 -0
- package/src/types/loader-types.ts +30 -0
- package/src/types/request-scope.ts +8 -0
- package/src/types/segments.ts +26 -4
- package/src/urls/path-helper-types.ts +27 -5
- package/src/urls/type-extraction.ts +30 -10
- package/src/use-loader.tsx +18 -13
- package/src/vite/discovery/client-urls-projection.ts +20 -21
- package/src/vite/discovery/discover-routers.ts +94 -26
- package/src/vite/discovery/state.ts +28 -4
- package/src/vite/discovery/virtual-module-codegen.ts +22 -17
- package/src/vite/plugins/client-urls-revalidate-hoist.ts +564 -0
- package/src/vite/plugins/expose-internal-ids.ts +2 -3
- package/src/vite/plugins/hoist/literal.ts +417 -0
- package/src/vite/plugins/hoist/scope.ts +370 -0
- package/src/vite/plugins/hoist/source.ts +84 -0
- package/src/vite/plugins/transition-when-hoist.ts +113 -779
- package/src/vite/rango.ts +2 -0
- package/src/vite/router-discovery.ts +116 -53
- package/dist/types/client-urls/revalidate-chain.d.ts +0 -33
- package/dist/types/client-urls/revalidation-protocol.d.ts +0 -25
- package/dist/types/testing/run-client-revalidate.d.ts +0 -43
- package/src/client-urls/revalidate-chain.ts +0 -83
- package/src/client-urls/revalidation-protocol.ts +0 -56
- package/src/testing/run-client-revalidate.ts +0 -92
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {
|
|
2
|
+
projectClientUrlsModule,
|
|
3
|
+
setClientUrlProjection,
|
|
4
|
+
type ClientUrlProjection,
|
|
5
|
+
} from "../../client-urls/server-projection.js";
|
|
6
|
+
import { isHoistedRevalidateRef } from "../../client-urls/hoisted-revalidate.js";
|
|
7
|
+
import { registerClientUrlRevalidators } from "../../client-urls/revalidate-registry.js";
|
|
8
|
+
import type { ClientUrlPatterns } from "../../client-urls/types.js";
|
|
9
|
+
import type { ShouldRevalidateFn } from "../../types.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Install the projection of a `"use client"` clientUrls() module under its
|
|
13
|
+
* client reference id, as discovery installs it for an app
|
|
14
|
+
* (discoverClientUrlProjections in vite/discovery/client-urls-projection.ts):
|
|
15
|
+
* the same projection step, into the same registry include() materializes
|
|
16
|
+
* from. rangoUseClientTransform (testing/vitest.ts) appends a call to every
|
|
17
|
+
* such module, passing the module's own source evaluated without the
|
|
18
|
+
* transform, so a router under test mounts the group with no setup.
|
|
19
|
+
*
|
|
20
|
+
* A Vitest project runs no hoist plugin, so the source's revalidate()
|
|
21
|
+
* predicates are plain functions. Each is registered under a key named for
|
|
22
|
+
* its reference, route and loader, as the plugin's generated module registers
|
|
23
|
+
* its own, and the projection carries the keys: materialization attaches them
|
|
24
|
+
* and the server runs them, as in an app.
|
|
25
|
+
*/
|
|
26
|
+
export function installClientUrlsProjection(
|
|
27
|
+
referenceId: string,
|
|
28
|
+
source: Readonly<Record<string, unknown>>,
|
|
29
|
+
): void {
|
|
30
|
+
const projection = projectClientUrlsModule(source);
|
|
31
|
+
if (!projection) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`Cannot install the client URL projection for reference "${referenceId}": ` +
|
|
34
|
+
'the module default export must be created by clientUrls() and have __brand === "client-urls"',
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
setClientUrlProjection(
|
|
38
|
+
referenceId,
|
|
39
|
+
withRevalidateKeys(
|
|
40
|
+
referenceId,
|
|
41
|
+
source.default as ClientUrlPatterns,
|
|
42
|
+
projection,
|
|
43
|
+
),
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function withRevalidateKeys(
|
|
48
|
+
referenceId: string,
|
|
49
|
+
definition: ClientUrlPatterns,
|
|
50
|
+
projection: ClientUrlProjection,
|
|
51
|
+
): ClientUrlProjection {
|
|
52
|
+
const fns: Record<string, ShouldRevalidateFn<any, any>> = {};
|
|
53
|
+
const routes = projection.routes.map((route) => {
|
|
54
|
+
const record = definition.routes.find(({ id }) => id === route.id);
|
|
55
|
+
const loaderRevalidate = (record?.loaders ?? []).map(
|
|
56
|
+
({ revalidate }, loaderIndex) =>
|
|
57
|
+
revalidate.map((entry, index) => {
|
|
58
|
+
if (isHoistedRevalidateRef(entry)) return entry.$$rangoRevalidate;
|
|
59
|
+
const key = `${referenceId}:${route.id}:${loaderIndex}:${index}`;
|
|
60
|
+
fns[key] = entry;
|
|
61
|
+
return key;
|
|
62
|
+
}),
|
|
63
|
+
);
|
|
64
|
+
return loaderRevalidate.some((keys) => keys.length > 0)
|
|
65
|
+
? { ...route, loaderRevalidate }
|
|
66
|
+
: route;
|
|
67
|
+
});
|
|
68
|
+
registerClientUrlRevalidators(fns);
|
|
69
|
+
return { ...projection, routes };
|
|
70
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as RSDServer from "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge";
|
|
2
|
+
import { registerServerModule } from "./server-modules.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Register `action` under its reference id, `<module id>#<export name>`,
|
|
6
|
+
* tagged as a server reference like a built "use server" export so it keeps
|
|
7
|
+
* its `$$id`, where the plugin-rsc stub's decodeAction / loadServerAction
|
|
8
|
+
* resolve it. `primitive` names the caller in the error for a malformed id.
|
|
9
|
+
* Loads the react-server build: callers run under the react-server condition.
|
|
10
|
+
*/
|
|
11
|
+
export function registerTestAction(
|
|
12
|
+
action: (...args: any[]) => unknown,
|
|
13
|
+
id: string,
|
|
14
|
+
primitive: string,
|
|
15
|
+
): void {
|
|
16
|
+
const [moduleId, name] = id.split("#");
|
|
17
|
+
if (!moduleId || !name) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
`${primitive}: id must be "<module id>#<export name>", received ${JSON.stringify(id)}`,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
registerServerModule(moduleId, {
|
|
23
|
+
[name]: RSDServer.registerServerReference(action, moduleId, name),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -382,6 +382,14 @@ export interface RenderRouteOptions {
|
|
|
382
382
|
* - Needs @testing-library/react >= 16.2.0; throws on older versions.
|
|
383
383
|
*/
|
|
384
384
|
hydrate?: boolean;
|
|
385
|
+
/**
|
|
386
|
+
* The path middleware rewrote `request` to (`throw rewrite()`): the leaf
|
|
387
|
+
* route is matched against it for params, while the location stays
|
|
388
|
+
* `request`, as on a rewritten document. The server pass and hydration
|
|
389
|
+
* both see `request` (`usePathname()`, `useSearchParams()`), as production
|
|
390
|
+
* renders a rewritten page with the visitor's URL (`/middleware` skill).
|
|
391
|
+
*/
|
|
392
|
+
rewrite?: string;
|
|
385
393
|
}
|
|
386
394
|
|
|
387
395
|
/**
|
|
@@ -687,7 +695,12 @@ export async function renderRoute(
|
|
|
687
695
|
pathname,
|
|
688
696
|
};
|
|
689
697
|
};
|
|
690
|
-
|
|
698
|
+
// A rewritten request renders the target's route at the visitor's URL.
|
|
699
|
+
const renderedPathname =
|
|
700
|
+
options.rewrite === undefined
|
|
701
|
+
? url.pathname
|
|
702
|
+
: new URL(options.rewrite, url).pathname;
|
|
703
|
+
const initialMatch = resolve(renderedPathname);
|
|
691
704
|
|
|
692
705
|
const historyKey = generateHistoryKey(url.href);
|
|
693
706
|
const mount = normalizeBasename(options.mount);
|
|
@@ -706,10 +719,10 @@ export async function renderRoute(
|
|
|
706
719
|
options.request !== undefined &&
|
|
707
720
|
Object.keys(options.params ?? {}).length === 0 &&
|
|
708
721
|
isUnderTestRunner() &&
|
|
709
|
-
matchLeaf(leaf.path,
|
|
722
|
+
matchLeaf(leaf.path, renderedPathname) === null
|
|
710
723
|
) {
|
|
711
724
|
throw new Error(
|
|
712
|
-
`renderRoute: request "${
|
|
725
|
+
`renderRoute: request "${renderedPathname}" does not match the leaf route ` +
|
|
713
726
|
`"${leaf.path}"${mount ? ` (mount "${mount}")` : ""}. renderRoute paths ` +
|
|
714
727
|
`are include-RELATIVE: pass a request that matches "${leaf.path}" ` +
|
|
715
728
|
`(e.g. "${staticPrefix(leaf.path)}"). A mount does NOT auto-rewrite the ` +
|
|
@@ -20,8 +20,9 @@
|
|
|
20
20
|
* - `ctx.reverse()` throws unless `routeMap` is provided (it does NOT fall back
|
|
21
21
|
* to the global route map — that would leak whichever routes another test
|
|
22
22
|
* registered).
|
|
23
|
-
* - `ctx.
|
|
23
|
+
* - `ctx.get(handle)` follows the production rule: reading a handle before
|
|
24
24
|
* `await ctx.rendered()` throws (pass `rendered` to mock the barrier).
|
|
25
|
+
* `ctx.use(handle)` is the push and needs no barrier.
|
|
25
26
|
* - `use cache` functions only cache (and only fire their taint/profile guards)
|
|
26
27
|
* when a `cacheStore` is provided — without one, registerCachedFunction
|
|
27
28
|
* bypasses (it checks for a store first). Pass `cacheStore`/`cacheProfiles`
|
|
@@ -39,6 +40,7 @@ import {
|
|
|
39
40
|
} from "../server/request-context.js";
|
|
40
41
|
import { createReverseFunction } from "../router/handler-context.js";
|
|
41
42
|
import { NOCACHE_SYMBOL } from "../cache/taint.js";
|
|
43
|
+
import { defineRewrittenFrom } from "../server/rewrite-trail.js";
|
|
42
44
|
import { getFetchableLoader } from "../server/fetchable-loader-store.js";
|
|
43
45
|
import type { LoaderContext, LoaderDefinition } from "../types.js";
|
|
44
46
|
import type { ContextVar } from "../context-var.js";
|
|
@@ -365,6 +367,7 @@ function runWithLoaderContext<R>(
|
|
|
365
367
|
};
|
|
366
368
|
|
|
367
369
|
// Request-scoped for "use cache" keys, as production brands it (#940).
|
|
370
|
+
defineRewrittenFrom(loaderCtx, reqCtx);
|
|
368
371
|
(loaderCtx as Record<symbol, unknown>)[NOCACHE_SYMBOL] = true;
|
|
369
372
|
return fn(loaderCtx);
|
|
370
373
|
});
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
*
|
|
14
14
|
* `nextCalled` counts how many times the terminal `next()` (the finalHandler)
|
|
15
15
|
* ran: 0 when the chain short-circuited, 1 when it ran to completion.
|
|
16
|
+
*
|
|
17
|
+
* A `throw rewrite(target)` is reported as `result.rewrite` (the resolved
|
|
18
|
+
* target) instead of failing the run: production re-dispatches the request
|
|
19
|
+
* there, which this primitive does not do.
|
|
16
20
|
*/
|
|
17
21
|
|
|
18
22
|
import {
|
|
@@ -31,6 +35,9 @@ import {
|
|
|
31
35
|
type StateCookieSeed,
|
|
32
36
|
} from "./internal/context.js";
|
|
33
37
|
import type { ThemeConfig } from "../theme/types.js";
|
|
38
|
+
import { isRewrite } from "../route-definition/rewrite.js";
|
|
39
|
+
import { resolveRewriteTarget } from "../rsc/rewrite-passes.js";
|
|
40
|
+
import { getGlobalRouteMap } from "../route-map-builder.js";
|
|
34
41
|
import type { SegmentCacheStore } from "../cache/types.js";
|
|
35
42
|
import type { CacheProfile } from "../cache/profile-registry.js";
|
|
36
43
|
|
|
@@ -94,6 +101,13 @@ export interface RunMiddlewareOptions<TEnv = any> {
|
|
|
94
101
|
stateCookie?: StateCookieSeed;
|
|
95
102
|
}
|
|
96
103
|
|
|
104
|
+
/** A rewrite the chain threw (RunMiddlewareResult.rewrite). */
|
|
105
|
+
export interface RunMiddlewareRewrite {
|
|
106
|
+
pathname: string;
|
|
107
|
+
search: string;
|
|
108
|
+
routeName?: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
97
111
|
/**
|
|
98
112
|
* Result of runMiddleware.
|
|
99
113
|
*/
|
|
@@ -109,6 +123,16 @@ export interface RunMiddlewareResult<TEnv = any> {
|
|
|
109
123
|
ctx: RequestContext<TEnv>;
|
|
110
124
|
/** Number of times the terminal handler ran (0 = short-circuited, 1 = passed through). */
|
|
111
125
|
nextCalled: number;
|
|
126
|
+
/**
|
|
127
|
+
* The target of a `throw rewrite(...)` from the chain, resolved as
|
|
128
|
+
* production resolves it (a path basename-prefixed, a route name through
|
|
129
|
+
* `routeMap`): `pathname` and `search` of the URL the request would render,
|
|
130
|
+
* and `routeName` when the rewrite named a route. `undefined` when the chain
|
|
131
|
+
* did not rewrite. On a rewrite, `response` carries what the chain set
|
|
132
|
+
* before the throw (status, headers, cookies), which production keeps for
|
|
133
|
+
* the target's response.
|
|
134
|
+
*/
|
|
135
|
+
rewrite?: RunMiddlewareRewrite;
|
|
112
136
|
/**
|
|
113
137
|
* Whether the chain called `ctx.dynamic()` (the PPR shell opt-out). The public
|
|
114
138
|
* way to assert the opt-out without reading the `@internal` `ctx._dynamic`.
|
|
@@ -192,17 +216,37 @@ export async function runMiddleware<TEnv = any>(
|
|
|
192
216
|
reverse as RequestContext<TEnv>["reverse"];
|
|
193
217
|
}
|
|
194
218
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
219
|
+
let rewrite: RunMiddlewareRewrite | undefined;
|
|
220
|
+
const response = await runWithRequestContext(ctx, async () => {
|
|
221
|
+
try {
|
|
222
|
+
return await executeLoaderMiddleware<TEnv>(
|
|
223
|
+
mwArray,
|
|
224
|
+
builtRequest,
|
|
225
|
+
(opts.env ?? {}) as TEnv,
|
|
226
|
+
opts.params ?? {},
|
|
227
|
+
variables,
|
|
228
|
+
finalHandler,
|
|
229
|
+
reverse,
|
|
230
|
+
);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
if (!isRewrite(error)) throw error;
|
|
233
|
+
const target = resolveRewriteTarget(
|
|
234
|
+
error,
|
|
235
|
+
opts.routeMap ?? getGlobalRouteMap(),
|
|
236
|
+
new URL(builtRequest.url),
|
|
237
|
+
ctx._basename,
|
|
238
|
+
);
|
|
239
|
+
rewrite = {
|
|
240
|
+
pathname: target.pathname,
|
|
241
|
+
search: target.search,
|
|
242
|
+
...("name" in error.target && { routeName: error.target.name }),
|
|
243
|
+
};
|
|
244
|
+
return new Response(null, {
|
|
245
|
+
status: ctx.res.status,
|
|
246
|
+
headers: ctx.res.headers,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
});
|
|
206
250
|
|
|
207
251
|
const { cookies, locationState } = snapshotRunEffects(ctx);
|
|
208
252
|
const headers = headersToObject(response.headers);
|
|
@@ -210,6 +254,7 @@ export async function runMiddleware<TEnv = any>(
|
|
|
210
254
|
response,
|
|
211
255
|
ctx,
|
|
212
256
|
nextCalled,
|
|
257
|
+
...(rewrite && { rewrite }),
|
|
213
258
|
dynamic: ctx._dynamic === true,
|
|
214
259
|
cookies,
|
|
215
260
|
headers,
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* runRevalidate — unit-test revalidate() predicates: a urls() segment's, or a
|
|
3
|
+
* clientUrls() loader's (which the build moves to the server, where it runs
|
|
4
|
+
* the same way).
|
|
5
|
+
*
|
|
6
|
+
* Evaluates the predicate(s) through the router's own evaluateRevalidation
|
|
7
|
+
* (router/revalidation.ts) inside a seeded request context: the default
|
|
8
|
+
* decision, the argument object, boolean short-circuit, soft-verdict
|
|
9
|
+
* threading and the fail-open on a throw are the production code paths, not
|
|
10
|
+
* a re-implementation. Pass an array to test a chain in declaration order.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { evaluateRevalidation } from "../router/revalidation.js";
|
|
14
|
+
import { runWithRequestContext } from "../server/request-context.js";
|
|
15
|
+
import type { ResolvedSegment, ShouldRevalidateFn } from "../types.js";
|
|
16
|
+
import {
|
|
17
|
+
normalizeTestAction,
|
|
18
|
+
resolveTestActionId,
|
|
19
|
+
type ImportedAction,
|
|
20
|
+
} from "./internal/action-id.js";
|
|
21
|
+
import { createTestRequestContext } from "./internal/context.js";
|
|
22
|
+
import type { VarsInit } from "./internal/seed-vars.js";
|
|
23
|
+
import { toURL } from "./to-url.js";
|
|
24
|
+
|
|
25
|
+
type RevalidateArgs = Parameters<ShouldRevalidateFn<any, any>>[0];
|
|
26
|
+
|
|
27
|
+
/** The action fields a predicate receives on an action request. */
|
|
28
|
+
export interface RunRevalidateAction {
|
|
29
|
+
/** The triggering action: an imported server action or its actionId. */
|
|
30
|
+
ref?: ImportedAction | string;
|
|
31
|
+
/** `actionResult`: the action's return value. */
|
|
32
|
+
result?: unknown;
|
|
33
|
+
/** `formData`: the action's FormData argument. */
|
|
34
|
+
formData?: FormData;
|
|
35
|
+
/** `actionUrl`. Defaults to `currentUrl`. */
|
|
36
|
+
url?: string | URL;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Options for {@link runRevalidate}. Defaults model a same-URL navigation
|
|
41
|
+
* with no action, for a route-owned loader segment.
|
|
42
|
+
*/
|
|
43
|
+
export interface RunRevalidateOptions<TEnv = any> {
|
|
44
|
+
/** The page being left. Defaults to `http://localhost/`. */
|
|
45
|
+
currentUrl?: string | URL;
|
|
46
|
+
/** The destination. Defaults to `currentUrl`. */
|
|
47
|
+
nextUrl?: string | URL;
|
|
48
|
+
currentParams?: Record<string, string>;
|
|
49
|
+
/** Defaults to `currentParams`. */
|
|
50
|
+
nextParams?: Record<string, string>;
|
|
51
|
+
/**
|
|
52
|
+
* The triggering action: an imported server action (id via
|
|
53
|
+
* `$id ?? $$id`), an actionId string, or `{ ref, result, formData, url }`.
|
|
54
|
+
* Makes the request a POST, as an action request is.
|
|
55
|
+
*/
|
|
56
|
+
action?: ImportedAction | string | RunRevalidateAction;
|
|
57
|
+
/** Overrides the request method (`POST` with `action`, else `GET`). */
|
|
58
|
+
method?: string;
|
|
59
|
+
/** The segment the predicate is attached to. Defaults to `"loader"`. */
|
|
60
|
+
segmentType?: RevalidateArgs["segmentType"];
|
|
61
|
+
/**
|
|
62
|
+
* The segment belongs to the matched route (a route's own loaders, a
|
|
63
|
+
* clientUrls() group loader). Defaults to true; false models a layout's
|
|
64
|
+
* loader, which a navigation keeps by default.
|
|
65
|
+
*/
|
|
66
|
+
belongsToRoute?: boolean;
|
|
67
|
+
/** `stale`: a history-entry background revalidation. */
|
|
68
|
+
stale?: boolean;
|
|
69
|
+
/** `toRouteName` (and `routeName`). */
|
|
70
|
+
routeName?: string;
|
|
71
|
+
/** `fromRouteName`. */
|
|
72
|
+
fromRouteName?: string;
|
|
73
|
+
/** Environment bindings of the seeded context. */
|
|
74
|
+
env?: TEnv;
|
|
75
|
+
/** Variables a middleware would have set, readable with `context.get()`. */
|
|
76
|
+
vars?: VarsInit;
|
|
77
|
+
/** `context`, in place of the seeded request context. */
|
|
78
|
+
context?: RevalidateArgs["context"];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const DEFAULT_URL = "http://localhost/";
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Run one revalidate() predicate, or a chain in declaration order, the way
|
|
85
|
+
* the router does for a request. Resolves to the final decision: true
|
|
86
|
+
* re-runs the segment, false keeps the client's copy.
|
|
87
|
+
*/
|
|
88
|
+
export async function runRevalidate<TEnv = any>(
|
|
89
|
+
fn: ShouldRevalidateFn<any, TEnv> | readonly ShouldRevalidateFn<any, TEnv>[],
|
|
90
|
+
opts: RunRevalidateOptions<TEnv> = {},
|
|
91
|
+
): Promise<boolean> {
|
|
92
|
+
const currentUrl = toURL(opts.currentUrl, new URL(DEFAULT_URL));
|
|
93
|
+
const nextUrl = toURL(opts.nextUrl, currentUrl);
|
|
94
|
+
const currentParams = opts.currentParams ?? {};
|
|
95
|
+
const nextParams = opts.nextParams ?? currentParams;
|
|
96
|
+
const action = normalizeTestAction(opts.action);
|
|
97
|
+
const method = opts.method ?? (action ? "POST" : "GET");
|
|
98
|
+
const request = new Request(nextUrl, { method });
|
|
99
|
+
const { ctx } = createTestRequestContext<TEnv>({
|
|
100
|
+
env: opts.env,
|
|
101
|
+
request,
|
|
102
|
+
vars: opts.vars,
|
|
103
|
+
params: nextParams,
|
|
104
|
+
});
|
|
105
|
+
if (opts.fromRouteName !== undefined) ctx._prevRouteKey = opts.fromRouteName;
|
|
106
|
+
|
|
107
|
+
const segment: ResolvedSegment = {
|
|
108
|
+
id: "runRevalidate",
|
|
109
|
+
namespace: "runRevalidate",
|
|
110
|
+
type: opts.segmentType ?? "loader",
|
|
111
|
+
index: 0,
|
|
112
|
+
component: null,
|
|
113
|
+
params: nextParams,
|
|
114
|
+
belongsToRoute: opts.belongsToRoute ?? true,
|
|
115
|
+
};
|
|
116
|
+
const fns = Array.isArray(fn) ? fn : [fn];
|
|
117
|
+
return runWithRequestContext(ctx as never, () =>
|
|
118
|
+
evaluateRevalidation({
|
|
119
|
+
segment,
|
|
120
|
+
prevParams: currentParams,
|
|
121
|
+
getPrevSegment: null,
|
|
122
|
+
request,
|
|
123
|
+
prevUrl: currentUrl,
|
|
124
|
+
nextUrl,
|
|
125
|
+
revalidations: fns.map((entry, index) => ({
|
|
126
|
+
name: `runRevalidate#${index}`,
|
|
127
|
+
fn: entry,
|
|
128
|
+
})),
|
|
129
|
+
routeKey: opts.routeName ?? "",
|
|
130
|
+
context: (opts.context ?? ctx) as RevalidateArgs["context"],
|
|
131
|
+
actionContext: action
|
|
132
|
+
? {
|
|
133
|
+
actionId: resolveTestActionId(action.ref, "runRevalidate") ?? "",
|
|
134
|
+
actionUrl: toURL(action.url, currentUrl),
|
|
135
|
+
actionResult: action.result,
|
|
136
|
+
formData: action.formData,
|
|
137
|
+
}
|
|
138
|
+
: undefined,
|
|
139
|
+
stale: opts.stale ?? false,
|
|
140
|
+
}),
|
|
141
|
+
);
|
|
142
|
+
}
|
|
@@ -22,7 +22,11 @@ import {
|
|
|
22
22
|
resolveNavigationState,
|
|
23
23
|
} from "../browser/history-state.js";
|
|
24
24
|
import { isLocationStateEntry } from "../browser/react/location-state-shared.js";
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
normalizeTestAction,
|
|
27
|
+
resolveTestActionId,
|
|
28
|
+
type ImportedAction,
|
|
29
|
+
} from "./internal/action-id.js";
|
|
26
30
|
import type {
|
|
27
31
|
RouteLocation,
|
|
28
32
|
TransitionConfig,
|
|
@@ -31,8 +35,6 @@ import type {
|
|
|
31
35
|
TransitionWhenKind,
|
|
32
36
|
} from "../types/segments.js";
|
|
33
37
|
|
|
34
|
-
type ImportedAction = (...args: never[]) => unknown;
|
|
35
|
-
|
|
36
38
|
/** A navigation side: a URL, or a partial RouteLocation. */
|
|
37
39
|
export type RunTransitionWhenLocation =
|
|
38
40
|
| string
|
|
@@ -58,7 +60,7 @@ export interface RunTransitionWhenOptions {
|
|
|
58
60
|
/** The destination. Defaults to `from`; always `from` for "action" and "revalidate". */
|
|
59
61
|
to?: RunTransitionWhenLocation;
|
|
60
62
|
/**
|
|
61
|
-
* The triggering action (like
|
|
63
|
+
* The triggering action (like runRevalidate's): an imported server
|
|
62
64
|
* action (id via `$id ?? $$id`), an actionId string, or
|
|
63
65
|
* `{ ref, formData, result, error }`.
|
|
64
66
|
*/
|
|
@@ -105,10 +107,7 @@ export function runTransitionWhen(
|
|
|
105
107
|
whenOrConfig: TransitionWhenFn | TransitionConfig,
|
|
106
108
|
opts: RunTransitionWhenOptions = {},
|
|
107
109
|
): RunTransitionWhenResult {
|
|
108
|
-
const actionOption =
|
|
109
|
-
typeof opts.action === "function" || typeof opts.action === "string"
|
|
110
|
-
? { ref: opts.action }
|
|
111
|
-
: opts.action;
|
|
110
|
+
const actionOption = normalizeTestAction(opts.action);
|
|
112
111
|
const from = toLocation(opts.from, DEFAULT_URL);
|
|
113
112
|
const context = createTransitionWhenContext({
|
|
114
113
|
kind: opts.kind ?? (actionOption ? "action" : "push"),
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
*
|
|
30
30
|
* A `Prerender` route serves its handler layer from the artifact
|
|
31
31
|
* `router.matchForPrerender` bakes for the URL (on its first request, kept
|
|
32
|
-
* until resetShellTestState), through the production prerender store
|
|
32
|
+
* until resetShellTestState), through the production prerender store; an
|
|
33
|
+
* intercept of it serves from the intercept variant the bake keeps. Its
|
|
33
34
|
* first `ppr` request is a MISS with a runtime capture, as a URL without a
|
|
34
35
|
* build-time shell is in production; build-time shells are e2e-only. An
|
|
35
36
|
* on-demand route (`Prerender(..., { onDemand })`) bakes nothing here: it
|
|
@@ -57,7 +58,9 @@ import {
|
|
|
57
58
|
SHELL_CAPTURE_MAX_WAIT_MS,
|
|
58
59
|
} from "../rsc/shell-capture-constants.js";
|
|
59
60
|
import { SEGMENT_FRAGMENT_CAPABILITY_HEADER } from "../segment-fragments.js";
|
|
61
|
+
import { FILL_PARAM, PREFETCH_HEADER } from "../prefetch-markers.js";
|
|
60
62
|
import { resolveDeferredHandleValues } from "../handles/deferred-resolution.js";
|
|
63
|
+
import { withoutPushedBy } from "../handles/pushed-by.js";
|
|
61
64
|
import type { HandleData } from "../server/handle-store.js";
|
|
62
65
|
import { _getRequestContext } from "../server/request-context.js";
|
|
63
66
|
import {
|
|
@@ -88,8 +91,22 @@ export interface ServeShellRequestOptions<TEnv = any> {
|
|
|
88
91
|
* of a document GET: from `from` (default `/`) with `segments` mounted
|
|
89
92
|
* (default none), advertising fragment passthrough. The replay decision is
|
|
90
93
|
* `result.replayStatus`.
|
|
94
|
+
*
|
|
95
|
+
* `prefetch: true` sends it as a `<Link>` prefetch does (the
|
|
96
|
+
* `X-Rango-Prefetch` header), so `prefetch: false` loaders and `loading()`
|
|
97
|
+
* entries are deferred: `result.readDeferred()` lists them. `fill: true`
|
|
98
|
+
* sends the request the browser follows an adopted prefetch with
|
|
99
|
+
* (`_rsc_fill`): pass the ids the prefetch delivered, without the deferred
|
|
100
|
+
* ones, as `segments`, and the same URL as `from`.
|
|
91
101
|
*/
|
|
92
|
-
partial?:
|
|
102
|
+
partial?:
|
|
103
|
+
| true
|
|
104
|
+
| {
|
|
105
|
+
from?: string;
|
|
106
|
+
segments?: readonly string[];
|
|
107
|
+
prefetch?: boolean;
|
|
108
|
+
fill?: boolean;
|
|
109
|
+
};
|
|
93
110
|
/**
|
|
94
111
|
* POST this form as a submit before hydration: the no-JS request the
|
|
95
112
|
* progressive-enhancement path runs the action for and re-renders the page
|
|
@@ -98,6 +115,30 @@ export interface ServeShellRequestOptions<TEnv = any> {
|
|
|
98
115
|
* Not with `partial`.
|
|
99
116
|
*/
|
|
100
117
|
form?: FormData;
|
|
118
|
+
/**
|
|
119
|
+
* Send the request a JS server action call makes from the page at `url`:
|
|
120
|
+
* a POST of `args` (encoded as the browser encodes them) for `ref`, with
|
|
121
|
+
* the page's mounted `segments` (default none), as
|
|
122
|
+
* browser/server-action-bridge.ts sends it. The response is the action's
|
|
123
|
+
* Flight payload (its return value and the re-rendered segments).
|
|
124
|
+
* Not with `partial` or `form`.
|
|
125
|
+
*/
|
|
126
|
+
action?: ServeShellRequestAction;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** The JS server action call of {@link ServeShellRequestOptions.action}. */
|
|
130
|
+
export interface ServeShellRequestAction {
|
|
131
|
+
/** The server action. */
|
|
132
|
+
ref: (...args: any[]) => unknown;
|
|
133
|
+
/**
|
|
134
|
+
* Its reference id, `<module id>#<export name>`, as the build gives a
|
|
135
|
+
* "use server" export; registered so the handler loads `ref` by it.
|
|
136
|
+
*/
|
|
137
|
+
id: string;
|
|
138
|
+
/** The arguments the page calls it with. */
|
|
139
|
+
args?: readonly unknown[];
|
|
140
|
+
/** The segment ids the page holds (`_rsc_segments`). */
|
|
141
|
+
segments?: readonly string[];
|
|
101
142
|
}
|
|
102
143
|
|
|
103
144
|
/** A response's handle data, as the browser consumes it. */
|
|
@@ -157,6 +198,12 @@ export interface ServeShellRequestResult {
|
|
|
157
198
|
* values resolved. Undefined when no Flight was rendered.
|
|
158
199
|
*/
|
|
159
200
|
readHandles(): Promise<ShellRequestHandles | undefined>;
|
|
201
|
+
/**
|
|
202
|
+
* The ids of the segments `flight` marks deferred (a prefetch skipped
|
|
203
|
+
* them; the fill request renders them), in payload order. Empty when
|
|
204
|
+
* nothing was deferred, undefined when no Flight was rendered.
|
|
205
|
+
*/
|
|
206
|
+
readDeferred(): Promise<string[] | undefined>;
|
|
160
207
|
/**
|
|
161
208
|
* The form state the SSR render received (the `formState` render option),
|
|
162
209
|
* or undefined when it received none.
|
|
@@ -297,9 +344,22 @@ async function decodePayloadHandles(
|
|
|
297
344
|
for await (const data of metadata?.handles ?? []) hydration = data;
|
|
298
345
|
const late: HandleData[] = [];
|
|
299
346
|
for await (const data of metadata?.handlesLate ?? []) {
|
|
300
|
-
late.push(await resolveDeferredHandleValues(data));
|
|
347
|
+
late.push(withoutPushedBy(await resolveDeferredHandleValues(data)));
|
|
301
348
|
}
|
|
302
|
-
return {
|
|
349
|
+
return {
|
|
350
|
+
hydration: withoutPushedBy(await resolveDeferredHandleValues(hydration)),
|
|
351
|
+
late,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** The ids of the segments a payload marks deferred. */
|
|
356
|
+
async function decodeDeferredIds(flight: string): Promise<string[]> {
|
|
357
|
+
const { metadata } = await decodePayload<{
|
|
358
|
+
metadata?: { segments?: Array<{ id: string; deferred?: boolean }> };
|
|
359
|
+
}>(flight);
|
|
360
|
+
return (metadata?.segments ?? [])
|
|
361
|
+
.filter((segment) => segment.deferred === true)
|
|
362
|
+
.map((segment) => segment.id);
|
|
303
363
|
}
|
|
304
364
|
|
|
305
365
|
/**
|
|
@@ -356,10 +416,22 @@ async function servePrerenderArtifacts(
|
|
|
356
416
|
const { hashParams } = await import("../prerender/param-hash.js");
|
|
357
417
|
baked.add(pathname);
|
|
358
418
|
if (result && !result.passthrough) {
|
|
419
|
+
const key = `${result.routeName}/${hashParams(result.params)}`;
|
|
359
420
|
payloads.set(
|
|
360
|
-
|
|
421
|
+
key,
|
|
361
422
|
JSON.stringify({ segments: result.segments, handles: result.handles }),
|
|
362
423
|
);
|
|
424
|
+
// The intercept variant, keyed and shaped as the build writes it
|
|
425
|
+
// (vite/discovery/prerender-collection.ts).
|
|
426
|
+
if (result.interceptSegments?.length) {
|
|
427
|
+
payloads.set(
|
|
428
|
+
`${key}/i`,
|
|
429
|
+
JSON.stringify({
|
|
430
|
+
segments: [...result.segments, ...result.interceptSegments],
|
|
431
|
+
handles: result.interceptHandles ?? "",
|
|
432
|
+
}),
|
|
433
|
+
);
|
|
434
|
+
}
|
|
363
435
|
}
|
|
364
436
|
}
|
|
365
437
|
const [{ createPrerenderStore }, { setPrerenderStoreForTests }] =
|
|
@@ -435,9 +507,46 @@ async function getHandler(
|
|
|
435
507
|
return handler;
|
|
436
508
|
}
|
|
437
509
|
|
|
438
|
-
function
|
|
510
|
+
async function buildActionRequest(
|
|
511
|
+
url: URL,
|
|
512
|
+
headers: Headers,
|
|
513
|
+
action: ServeShellRequestAction,
|
|
514
|
+
): Promise<Request> {
|
|
515
|
+
const [{ registerTestAction }] = await Promise.all([
|
|
516
|
+
import("./internal/test-action.js"),
|
|
517
|
+
import("./internal/flight-client-globals.js"),
|
|
518
|
+
]);
|
|
519
|
+
const { encodeReply } =
|
|
520
|
+
await import("@vitejs/plugin-rsc/vendor/react-server-dom/client.edge");
|
|
521
|
+
registerTestAction(action.ref, action.id, "serveShellRequest");
|
|
522
|
+
const target = new URL(url);
|
|
523
|
+
target.searchParams.set("_rsc_action", action.id);
|
|
524
|
+
target.searchParams.set("_rsc_segments", (action.segments ?? []).join(","));
|
|
525
|
+
headers.set("rsc-action", action.id);
|
|
526
|
+
if (!headers.has("X-RSC-Router-Client-Path")) {
|
|
527
|
+
headers.set("X-RSC-Router-Client-Path", url.href);
|
|
528
|
+
}
|
|
529
|
+
return new Request(target, {
|
|
530
|
+
method: "POST",
|
|
531
|
+
headers,
|
|
532
|
+
body: await encodeReply([...(action.args ?? [])]),
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
async function buildRequest(
|
|
537
|
+
url: URL,
|
|
538
|
+
options: ServeShellRequestOptions,
|
|
539
|
+
): Promise<Request> {
|
|
439
540
|
const headers = new Headers(options.headers);
|
|
440
541
|
const partial = options.partial;
|
|
542
|
+
if (options.action) {
|
|
543
|
+
if (partial || options.form) {
|
|
544
|
+
throw new Error(
|
|
545
|
+
"serveShellRequest: action cannot be combined with partial or form",
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
return buildActionRequest(url, headers, options.action);
|
|
549
|
+
}
|
|
441
550
|
if (options.form) {
|
|
442
551
|
if (partial) {
|
|
443
552
|
throw new Error(
|
|
@@ -454,10 +563,21 @@ function buildRequest(url: URL, options: ServeShellRequestOptions): Request {
|
|
|
454
563
|
if (!headers.has("accept")) headers.set("accept", "text/html");
|
|
455
564
|
return new Request(url, { headers });
|
|
456
565
|
}
|
|
457
|
-
const {
|
|
566
|
+
const {
|
|
567
|
+
from = "/",
|
|
568
|
+
segments = [],
|
|
569
|
+
prefetch = false,
|
|
570
|
+
fill = false,
|
|
571
|
+
} = partial === true ? {} : partial;
|
|
458
572
|
const target = new URL(url);
|
|
459
573
|
target.searchParams.set("_rsc_partial", "true");
|
|
460
574
|
target.searchParams.set("_rsc_segments", segments.join(","));
|
|
575
|
+
// As the browser sends them: browser/navigation-client.ts (fill) and
|
|
576
|
+
// browser/prefetch/fetch.ts (prefetch).
|
|
577
|
+
if (fill) target.searchParams.set(FILL_PARAM, "1");
|
|
578
|
+
if (prefetch && !headers.has(PREFETCH_HEADER)) {
|
|
579
|
+
headers.set(PREFETCH_HEADER, "1");
|
|
580
|
+
}
|
|
461
581
|
if (!headers.has("X-RSC-Router-Client-Path")) {
|
|
462
582
|
headers.set("X-RSC-Router-Client-Path", new URL(from, url).href);
|
|
463
583
|
}
|
|
@@ -536,7 +656,7 @@ export async function serveShellRequest<TEnv = any>(
|
|
|
536
656
|
const target = new URL(url, "http://localhost");
|
|
537
657
|
await ensureRouterManifest(router.id);
|
|
538
658
|
const handler = await getHandler(router, options.cacheStore);
|
|
539
|
-
const request = buildRequest(target, options);
|
|
659
|
+
const request = await buildRequest(target, options);
|
|
540
660
|
const executionContext = createCollectingExecutionContext();
|
|
541
661
|
const { tasks } = executionContext;
|
|
542
662
|
const recorder: Recorder = {};
|
|
@@ -594,6 +714,9 @@ export async function serveShellRequest<TEnv = any>(
|
|
|
594
714
|
async readHandles() {
|
|
595
715
|
return flight ? decodePayloadHandles(flight) : undefined;
|
|
596
716
|
},
|
|
717
|
+
async readDeferred() {
|
|
718
|
+
return flight ? decodeDeferredIds(flight) : undefined;
|
|
719
|
+
},
|
|
597
720
|
formState: recorder.formState,
|
|
598
721
|
async readPayloadFormState() {
|
|
599
722
|
if (!flight) return undefined;
|