@rangojs/router 0.31.0 → 0.32.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 +37 -7
- package/dist/types/browser/history-state.d.ts +13 -2
- package/dist/types/browser/react/Link.d.ts +10 -13
- package/dist/types/browser/react/location-state-shared.d.ts +2 -1
- package/dist/types/cache/document-cache-policy.d.ts +1 -1
- package/dist/types/route-definition/redirect.d.ts +5 -0
- package/dist/types/router/middleware-types.d.ts +6 -0
- package/dist/types/rsc/helpers.d.ts +5 -3
- package/dist/types/rsc/request-stages.d.ts +2 -0
- package/dist/types/server/document-location-state.d.ts +56 -0
- package/dist/types/server/request-context.d.ts +23 -6
- package/dist/types/testing/render-route.d.ts +17 -0
- package/dist/types/testing/serve-shell-request.d.ts +9 -0
- package/dist/types/types/handler-context.d.ts +18 -3
- package/dist/types/types/segments.d.ts +6 -0
- package/dist/types/vite/utils/forward-user-plugins.d.ts +32 -2
- package/dist/vite/index.js +3 -3
- package/package.json +1 -1
- package/skills/comparison/references/framework-comparison.md +3 -2
- package/skills/document-cache/SKILL.md +5 -0
- package/skills/hooks/state.md +110 -6
- package/skills/intercept/SKILL.md +2 -1
- package/skills/ppr/SKILL.md +12 -0
- package/skills/server-actions/SKILL.md +8 -0
- package/skills/testing/client-components.md +44 -19
- package/skills/testing/serve-shell-request.md +38 -13
- package/skills/ux-architecture/SKILL.md +6 -3
- package/src/browser/history-state.ts +24 -2
- package/src/browser/react/Link.tsx +10 -13
- package/src/browser/react/location-state-shared.ts +8 -1
- package/src/browser/rsc-router.tsx +5 -2
- package/src/build/runtime-discovery.ts +46 -8
- package/src/cache/document-cache-policy.ts +10 -1
- package/src/cache/document-cache.ts +7 -0
- package/src/route-definition/redirect.ts +5 -0
- package/src/router/match-middleware/cache-lookup.ts +23 -6
- package/src/router/match-result.ts +9 -1
- package/src/router/middleware-types.ts +6 -0
- package/src/router/revalidation.ts +23 -3
- package/src/rsc/helpers.ts +13 -4
- package/src/rsc/progressive-enhancement.ts +5 -0
- package/src/rsc/request-stages.ts +28 -1
- package/src/rsc/rsc-rendering.ts +39 -9
- package/src/server/document-location-state.ts +313 -0
- package/src/server/request-context.ts +24 -5
- package/src/testing/dispatch.ts +3 -0
- package/src/testing/render-route.tsx +40 -6
- package/src/testing/run-revalidate.ts +11 -1
- package/src/testing/serve-shell-request.ts +16 -0
- package/src/types/handler-context.ts +18 -3
- package/src/types/segments.ts +6 -0
- package/src/vite/utils/forward-user-plugins.ts +13 -5
package/dist/bin/rango.js
CHANGED
|
@@ -1762,7 +1762,7 @@ var init_package = __esm({
|
|
|
1762
1762
|
"package.json"() {
|
|
1763
1763
|
package_default = {
|
|
1764
1764
|
name: "@rangojs/router",
|
|
1765
|
-
version: "0.
|
|
1765
|
+
version: "0.32.0",
|
|
1766
1766
|
description: "Django-inspired RSC router with composable URL patterns",
|
|
1767
1767
|
keywords: [
|
|
1768
1768
|
"react",
|
|
@@ -9859,10 +9859,11 @@ var init_temp_rsc_server = __esm({
|
|
|
9859
9859
|
// src/vite/utils/forward-user-plugins.ts
|
|
9860
9860
|
var forward_user_plugins_exports = {};
|
|
9861
9861
|
__export(forward_user_plugins_exports, {
|
|
9862
|
+
isFrameworkPlugin: () => isFrameworkPlugin,
|
|
9862
9863
|
pickForwardedRunnerConfig: () => pickForwardedRunnerConfig,
|
|
9863
9864
|
selectForwardableResolvePlugins: () => selectForwardableResolvePlugins
|
|
9864
9865
|
});
|
|
9865
|
-
function
|
|
9866
|
+
function isFrameworkPlugin(name) {
|
|
9866
9867
|
return name.startsWith("vite:") || name === "rsc" || name.startsWith("rsc:") || name.startsWith("@rangojs/router") || name.startsWith("@cloudflare/vite-plugin") || name.startsWith("vite-plugin-cloudflare");
|
|
9867
9868
|
}
|
|
9868
9869
|
function isForwardable(p) {
|
|
@@ -9886,7 +9887,7 @@ function selectForwardableResolvePlugins(plugins) {
|
|
|
9886
9887
|
const forwarded = [];
|
|
9887
9888
|
for (const p of plugins) {
|
|
9888
9889
|
const name = p?.name;
|
|
9889
|
-
if (!name ||
|
|
9890
|
+
if (!name || isFrameworkPlugin(name)) continue;
|
|
9890
9891
|
if (!isForwardable(p)) continue;
|
|
9891
9892
|
forwarded.push(stripToResolutionHooks(p));
|
|
9892
9893
|
}
|
|
@@ -9934,9 +9935,11 @@ import { basename, resolve as resolve4 } from "node:path";
|
|
|
9934
9935
|
import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
9935
9936
|
async function discoverAndWriteRouteTypes(opts) {
|
|
9936
9937
|
let loadConfigFromFile;
|
|
9938
|
+
let resolveConfig;
|
|
9937
9939
|
try {
|
|
9938
9940
|
const vite = await import("vite");
|
|
9939
9941
|
loadConfigFromFile = vite.loadConfigFromFile;
|
|
9942
|
+
resolveConfig = vite.resolveConfig;
|
|
9940
9943
|
} catch {
|
|
9941
9944
|
throw new Error(
|
|
9942
9945
|
"Runtime discovery requires 'vite'. Install it with: pnpm add -D vite"
|
|
@@ -9952,7 +9955,11 @@ async function discoverAndWriteRouteTypes(opts) {
|
|
|
9952
9955
|
const { createTempRscServer: createTempRscServer2 } = await Promise.resolve().then(() => (init_temp_rsc_server(), temp_rsc_server_exports));
|
|
9953
9956
|
const { createDiscoveryState: createDiscoveryState2 } = await Promise.resolve().then(() => (init_state(), state_exports));
|
|
9954
9957
|
const { discoverClientUrlProjections: discoverClientUrlProjections2 } = await Promise.resolve().then(() => (init_client_urls_projection(), client_urls_projection_exports));
|
|
9955
|
-
const {
|
|
9958
|
+
const {
|
|
9959
|
+
isFrameworkPlugin: isFrameworkPlugin2,
|
|
9960
|
+
pickForwardedRunnerConfig: pickForwardedRunnerConfig2,
|
|
9961
|
+
selectForwardableResolvePlugins: selectForwardableResolvePlugins2
|
|
9962
|
+
} = await Promise.resolve().then(() => (init_forward_user_plugins(), forward_user_plugins_exports));
|
|
9956
9963
|
const entryPath = resolve4(opts.entry);
|
|
9957
9964
|
const state = createDiscoveryState2(entryPath, void 0);
|
|
9958
9965
|
state.projectRoot = opts.root;
|
|
@@ -9966,9 +9973,32 @@ async function discoverAndWriteRouteTypes(opts) {
|
|
|
9966
9973
|
);
|
|
9967
9974
|
if (loaded) {
|
|
9968
9975
|
state.userRunnerConfig = pickForwardedRunnerConfig2(loaded.config);
|
|
9969
|
-
|
|
9970
|
-
|
|
9971
|
-
|
|
9976
|
+
const userPlugins = (await flattenPluginOptions(loaded.config.plugins)).filter((p) => !isFrameworkPlugin2(p.name ?? ""));
|
|
9977
|
+
try {
|
|
9978
|
+
const resolved2 = await resolveConfig(
|
|
9979
|
+
{
|
|
9980
|
+
...loaded.config,
|
|
9981
|
+
root: opts.root,
|
|
9982
|
+
configFile: false,
|
|
9983
|
+
plugins: userPlugins
|
|
9984
|
+
},
|
|
9985
|
+
"serve",
|
|
9986
|
+
"development"
|
|
9987
|
+
);
|
|
9988
|
+
state.userResolvePlugins = selectForwardableResolvePlugins2(
|
|
9989
|
+
resolved2.plugins
|
|
9990
|
+
);
|
|
9991
|
+
} catch (err) {
|
|
9992
|
+
console.warn(
|
|
9993
|
+
`[rango] Could not resolve the Vite config (${err?.message ?? err}); forwarding its resolver plugins without their config hooks. rango() and rsc() are left out of that resolve, so a plugin that needs the rsc environment fails there`
|
|
9994
|
+
);
|
|
9995
|
+
const serveEnv = { command: "serve", mode: "development" };
|
|
9996
|
+
state.userResolvePlugins = selectForwardableResolvePlugins2(
|
|
9997
|
+
userPlugins.filter(
|
|
9998
|
+
(p) => typeof p.apply === "function" ? p.apply(loaded.config, serveEnv) : p.apply !== "build"
|
|
9999
|
+
)
|
|
10000
|
+
);
|
|
10001
|
+
}
|
|
9972
10002
|
}
|
|
9973
10003
|
} catch (err) {
|
|
9974
10004
|
console.warn(
|
|
@@ -35,8 +35,9 @@ export declare function stripShellMissMarker(): void;
|
|
|
35
35
|
* the server rendered this document without them. One pass before hydration
|
|
36
36
|
* (initBrowserApp; renderRoute's `hydrate` mode), so no reader needs to know
|
|
37
37
|
* the page began as a document load, and a slot nobody reads is removed too.
|
|
38
|
-
*
|
|
39
|
-
* (
|
|
38
|
+
* Server-set state is not lost to it: the state a document response carries
|
|
39
|
+
* is merged after this pass (applyDocumentLocationState), so a `~r` slot the
|
|
40
|
+
* server set for this load survives this load and no later one.
|
|
40
41
|
*
|
|
41
42
|
* A slot is recognised by its key alone (the `~r` suffix): at start-up the
|
|
42
43
|
* module that defines it may not be loaded.
|
|
@@ -46,6 +47,16 @@ export declare function stripShellMissMarker(): void;
|
|
|
46
47
|
* is a full page for later Back/Forward.
|
|
47
48
|
*/
|
|
48
49
|
export declare function clearLocationStateOnDocumentLoad(): void;
|
|
50
|
+
/**
|
|
51
|
+
* Merge the location state a document response carried (its bootstrap
|
|
52
|
+
* payload's `metadata.locationState`, server/document-location-state.ts)
|
|
53
|
+
* into the entry the document loaded on. Start-up runs it after
|
|
54
|
+
* clearLocationStateOnDocumentLoad and the version is set, before the entry
|
|
55
|
+
* is committed for readers (initBrowserApp; renderRoute's `hydrate` mode),
|
|
56
|
+
* so readers get it once they have hydrated, as stored state. Only typed
|
|
57
|
+
* slots are taken.
|
|
58
|
+
*/
|
|
59
|
+
export declare function applyDocumentLocationState(locationState: Record<string, unknown> | undefined): void;
|
|
49
60
|
/**
|
|
50
61
|
* Merge server-set location state into the current history entry and return
|
|
51
62
|
* the entry state written. Readers see it when the caller commits it
|
|
@@ -66,23 +66,20 @@ export interface LinkProps extends Omit<React.AnchorHTMLAttributes<HTMLAnchorEle
|
|
|
66
66
|
* keyed on Rango state + target URL. This is correct for routes whose
|
|
67
67
|
* response shape doesn't depend on where the user navigates from.
|
|
68
68
|
*
|
|
69
|
-
* Set `":source"`
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* `currentUrl` / `currentParams`, and the wildcard entry would
|
|
73
|
-
* therefore serve the wrong diff to a navigation from a different
|
|
74
|
-
* source.
|
|
69
|
+
* Set `":source"` to scope every prefetch of this Link to the page that
|
|
70
|
+
* sent it, when its response would differ by source page in a way the
|
|
71
|
+
* server cannot see.
|
|
75
72
|
*
|
|
76
|
-
*
|
|
77
|
-
* (
|
|
78
|
-
*
|
|
79
|
-
* both source-specific
|
|
80
|
-
*
|
|
73
|
+
* The server scopes a prefetch to its source automatically
|
|
74
|
+
* (`x-rsc-prefetch-scope: source`) when an intercept targets the route,
|
|
75
|
+
* whether or not it applies from this page (the modal and the full page
|
|
76
|
+
* are both source-specific), and when a `revalidate()` of the target
|
|
77
|
+
* read `currentParams`, `currentUrl` or `fromRouteName` and kept the
|
|
78
|
+
* segment: that diff is the source page's (#1331). Every other response
|
|
79
|
+
* stays shared.
|
|
81
80
|
*
|
|
82
81
|
* @example
|
|
83
82
|
* ```tsx
|
|
84
|
-
* // Route uses a `revalidate()` that branches on currentUrl — opt in
|
|
85
|
-
* // so prefetches don't bleed across source pages.
|
|
86
83
|
* <Link to="/dashboard" prefetch="hover" prefetchKey=":source" />
|
|
87
84
|
* ```
|
|
88
85
|
*/
|
|
@@ -269,7 +269,8 @@ export declare function isLocationStateDefinition(value: unknown): value is Loca
|
|
|
269
269
|
*/
|
|
270
270
|
export declare function isLocationStateEntry(value: unknown): value is LocationStateEntry;
|
|
271
271
|
/**
|
|
272
|
-
* Resolve state entries into a flat object for history.state
|
|
272
|
+
* Resolve state entries into a flat object for history.state. One slot per
|
|
273
|
+
* definition: a later entry of the same definition replaces an earlier one.
|
|
273
274
|
*/
|
|
274
275
|
export declare function resolveLocationStateEntries(entries: LocationStateEntry[]): Record<string, unknown>;
|
|
275
276
|
export {};
|
|
@@ -12,7 +12,7 @@ export interface CacheDirectives {
|
|
|
12
12
|
/**
|
|
13
13
|
* Check if response should be cached based on Cache-Control headers
|
|
14
14
|
*/
|
|
15
|
-
export declare function shouldCacheResponse(response: Response, requestCtx?: Pick<RequestContext, "_payloadVisitorTheme" | "_payloadDeferred" | "_payloadVisitorLocation">): CacheDirectives | null;
|
|
15
|
+
export declare function shouldCacheResponse(response: Response, requestCtx?: Pick<RequestContext, "_payloadVisitorTheme" | "_payloadDeferred" | "_payloadVisitorLocation" | "_documentLocationState">): CacheDirectives | null;
|
|
16
16
|
/**
|
|
17
17
|
* Whether this cache will store the response the current render produces, as
|
|
18
18
|
* far as the render can tell: the middleware runs it (a MISS or a stale
|
|
@@ -9,6 +9,11 @@ import type { LocationStateEntry } from "../browser/react/location-state-shared.
|
|
|
9
9
|
* Supports an optional state parameter to carry location state through the
|
|
10
10
|
* redirect. On the target page, state can be read via useLocationState()
|
|
11
11
|
* (use { flash: true } in createLocationState for read-once flash messages).
|
|
12
|
+
* On a client navigation or action the state travels in the redirect's
|
|
13
|
+
* payload. On a document request (a page load, a no-JS form post) it travels
|
|
14
|
+
* in a one-shot cookie the target's document request reads and clears, as
|
|
15
|
+
* JSON: give it plain JSON data there. `{ external: true }` carries none.
|
|
16
|
+
* Two entries of the same definition keep only the last.
|
|
12
17
|
*
|
|
13
18
|
* @param url - The URL to redirect to
|
|
14
19
|
* @param statusOrOptions - HTTP status code (default: 302) or options object
|
|
@@ -56,6 +56,12 @@ export interface MiddlewareContext<TEnv = any, TParams = Record<string, string |
|
|
|
56
56
|
*/
|
|
57
57
|
readonly theme?: Theme;
|
|
58
58
|
setTheme?: (theme: Theme) => void;
|
|
59
|
+
/**
|
|
60
|
+
* Attach location state entries to this response (see the handler
|
|
61
|
+
* context's `setLocationState`). Entries accumulate across definitions;
|
|
62
|
+
* for one definition the last write wins. Set it here rather than in a
|
|
63
|
+
* handler on a `ppr` route: a shell HIT runs no handler.
|
|
64
|
+
*/
|
|
59
65
|
setLocationState(entries: LocationStateEntry | LocationStateEntry[]): void;
|
|
60
66
|
/** Global route names only; see {@link GlobalReverseFunction}. */
|
|
61
67
|
reverse: GlobalReverseFunction<DefaultReverseRouteMap>;
|
|
@@ -95,10 +95,12 @@ export declare function interceptRedirectForPartial(response: Response, createRe
|
|
|
95
95
|
/**
|
|
96
96
|
* Attach location state set during a request to a payload's metadata.
|
|
97
97
|
* No-op if no location state was set. Callers must ensure payload.metadata
|
|
98
|
-
* is populated (
|
|
99
|
-
* that
|
|
98
|
+
* is populated. A `document` payload (a page load's or a no-JS form post's
|
|
99
|
+
* bootstrap payload) that carries state marks the request
|
|
100
|
+
* `_documentLocationState`: it is the visitor's, so no shared cache keeps it
|
|
101
|
+
* (server/document-location-state.ts).
|
|
100
102
|
*/
|
|
101
|
-
export declare function attachLocationStateIfPresent(payload: RscPayload): void;
|
|
103
|
+
export declare function attachLocationStateIfPresent(payload: RscPayload, document?: boolean): void;
|
|
102
104
|
/**
|
|
103
105
|
* Only cache successful responses. Non-200 statuses (errors, redirects) are
|
|
104
106
|
* not cached -- notFound() produces 500 in response routes, and explicit
|
|
@@ -36,6 +36,8 @@ export interface StageRouter<TEnv = unknown> {
|
|
|
36
36
|
readonly telemetry?: TelemetrySink;
|
|
37
37
|
readonly timeouts?: ResolvedTimeouts;
|
|
38
38
|
readonly onTimeout?: OnTimeoutCallback<TEnv>;
|
|
39
|
+
/** Names the cookie location state crosses a document redirect in. */
|
|
40
|
+
readonly resolvedStateCookieName: string;
|
|
39
41
|
}
|
|
40
42
|
/** Report an error through the router's onError. */
|
|
41
43
|
export type ReportError<TEnv = unknown> = (error: unknown, phase: ErrorPhase, context: InvokeOnErrorContext<TEnv>) => void;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Location state on document responses (#1330).
|
|
3
|
+
*
|
|
4
|
+
* A client navigation or an action carries server-set location state in its
|
|
5
|
+
* Flight payload, and a redirect on either becomes a Flight redirect that
|
|
6
|
+
* carries it too. A document request has no such channel for a 3xx: the
|
|
7
|
+
* browser follows it with a plain GET. This module carries the state across
|
|
8
|
+
* that one hop in a short-lived cookie:
|
|
9
|
+
*
|
|
10
|
+
* - `carryLocationStateAcrossRedirect` (rsc/request-stages.ts
|
|
11
|
+
* finalizeRequestResponse) writes it on a document request's same-origin
|
|
12
|
+
* 3xx when the request set location state;
|
|
13
|
+
* - `consumeDocumentLocationState` (rsc/request-stages.ts runRequestPasses)
|
|
14
|
+
* reads it on the next GET document request that navigates to the target
|
|
15
|
+
* pathname (`Sec-Fetch-Dest` a browsing context, where sent), seeds
|
|
16
|
+
* `_locationState` with its entries (so the render attaches them to the
|
|
17
|
+
* bootstrap payload, rsc/rsc-rendering.ts) and clears it.
|
|
18
|
+
*
|
|
19
|
+
* A document that carries location state is per visitor: it is marked
|
|
20
|
+
* `_documentLocationState`, never stored by the document cache or as a PPR
|
|
21
|
+
* shell, and leaves with `Cache-Control: private, no-store`
|
|
22
|
+
* (`markDocumentResponsePrivate`).
|
|
23
|
+
*
|
|
24
|
+
* The value is JSON, not Flight: a cookie holds text, and the client merges
|
|
25
|
+
* it as `history.state` would hold it. `Path=/` because the `__Host-` prefix
|
|
26
|
+
* (https) requires it, which keeps a sibling subdomain from planting state;
|
|
27
|
+
* the target pathname inside the value decides who consumes it.
|
|
28
|
+
*/
|
|
29
|
+
import { type RequestContext } from "./request-context.js";
|
|
30
|
+
/** The cookie's name for a router: `__Host-` on https. */
|
|
31
|
+
export declare function documentLocationStateCookieName(stateCookieName: string, secure: boolean): string;
|
|
32
|
+
/**
|
|
33
|
+
* Read the cookie a document redirect left for this request: seed
|
|
34
|
+
* `_locationState` with its slots and clear it. Only a GET document request
|
|
35
|
+
* of the target pathname that navigates consumes it (the request the browser
|
|
36
|
+
* makes for a 301/302/303, see isNavigationRequest); any other request, a
|
|
37
|
+
* form POST to that path or a prefetch of it included, leaves it to its
|
|
38
|
+
* expiry. A malformed value is cleared and ignored, and only `__rsc_ls_*`
|
|
39
|
+
* slots are taken from it (the value comes from the browser).
|
|
40
|
+
*/
|
|
41
|
+
export declare function consumeDocumentLocationState(reqCtx: Pick<RequestContext, "_locationState" | "setCookie">, request: Request, url: URL, stateCookieName: string): void;
|
|
42
|
+
/**
|
|
43
|
+
* Write the cookie on a document request's redirect that set location state,
|
|
44
|
+
* and mark the redirect private. Not for a partial or action request (their
|
|
45
|
+
* redirect is a Flight payload), an external or cross-origin target (the
|
|
46
|
+
* cookie would never reach it) or a response that is not a 3xx with a
|
|
47
|
+
* Location. Returns the response to send: a new one when its headers are
|
|
48
|
+
* immutable.
|
|
49
|
+
*/
|
|
50
|
+
export declare function carryLocationStateAcrossRedirect(response: Response, reqCtx: Pick<RequestContext, "_locationState">, request: Request, url: URL, stateCookieName: string): Response;
|
|
51
|
+
/**
|
|
52
|
+
* `Cache-Control: private, no-store` on a document that carries location
|
|
53
|
+
* state, after every middleware: a shared cache must never store or replay
|
|
54
|
+
* one visitor's state. Returns the response to send.
|
|
55
|
+
*/
|
|
56
|
+
export declare function markDocumentResponsePrivate(response: Response, reqCtx: Pick<RequestContext, "_documentLocationState">): Response;
|
|
@@ -547,12 +547,22 @@ export interface RequestContext<TEnv = DefaultEnv, TParams = Record<string, stri
|
|
|
547
547
|
/**
|
|
548
548
|
* Attach location state entries to the current response.
|
|
549
549
|
*
|
|
550
|
-
* For
|
|
551
|
-
* metadata and merged into history.
|
|
552
|
-
*
|
|
553
|
-
*
|
|
550
|
+
* For a client navigation or an action, the state is included in the RSC
|
|
551
|
+
* payload metadata and merged into the entry's history.state on the
|
|
552
|
+
* client. For a document request (a page load, a no-JS form post), it rides
|
|
553
|
+
* the document's bootstrap payload and is merged into the entry when the
|
|
554
|
+
* client starts; readers get it once the page has hydrated. For a redirect,
|
|
555
|
+
* the state travels with the redirect: in its Flight payload on a
|
|
556
|
+
* navigation or action, in a one-shot cookie the target's document request
|
|
557
|
+
* reads on a document request. A document that carries state is sent
|
|
558
|
+
* `Cache-Control: private, no-store` and never stored by the document cache
|
|
559
|
+
* or as a PPR shell.
|
|
554
560
|
*
|
|
555
|
-
* Multiple calls accumulate entries.
|
|
561
|
+
* Multiple calls accumulate entries across definitions. For one definition
|
|
562
|
+
* the last write wins: two entries of the same definition share its slot
|
|
563
|
+
* in history.state, so a later call (or a later entry in one array)
|
|
564
|
+
* replaces an earlier one. To combine writes, make the state an array and
|
|
565
|
+
* write it once.
|
|
556
566
|
*
|
|
557
567
|
* @example
|
|
558
568
|
* ```typescript
|
|
@@ -562,6 +572,13 @@ export interface RequestContext<TEnv = DefaultEnv, TParams = Record<string, stri
|
|
|
562
572
|
setLocationState(entries: LocationStateEntry | LocationStateEntry[]): void;
|
|
563
573
|
/** @internal Accumulated location state entries */
|
|
564
574
|
_locationState?: LocationStateEntry[];
|
|
575
|
+
/**
|
|
576
|
+
* @internal Set when a document's payload carries location state
|
|
577
|
+
* (rsc/rsc-rendering.ts, rsc/progressive-enhancement.ts), a shell HIT's
|
|
578
|
+
* included. The document is per visitor: the document cache refuses it,
|
|
579
|
+
* and it leaves `private, no-store` (server/document-location-state.ts).
|
|
580
|
+
*/
|
|
581
|
+
_documentLocationState?: boolean;
|
|
565
582
|
/**
|
|
566
583
|
* The matched route name, if the route has an explicit name.
|
|
567
584
|
* Undefined before route matching or for unnamed routes.
|
|
@@ -795,7 +812,7 @@ export interface RequestContext<TEnv = DefaultEnv, TParams = Record<string, stri
|
|
|
795
812
|
* This is the type exported to library consumers. Internal code should
|
|
796
813
|
* use the full RequestContext interface directly.
|
|
797
814
|
*/
|
|
798
|
-
export type PublicRequestContext<TEnv = DefaultEnv, TParams = Record<string, string>> = Omit<RequestContext<TEnv, TParams>, "cookie" | "cookies" | "_readCookie" | "_readCookies" | "setCookie" | "deleteCookie" | "_handleStore" | "_transitionWhenRefs" | "_pprReplayPostMatchReason" | "_cacheStore" | "_prerender" | "_onDemandProducer" | "_searchParamsFilter" | "_shellCaptureState" | "_shellImplicitCache" | "_shellLoaderSeed" | "_resolvedCacheKeys" | "_shellKey" | "_shellForcedMiss" | "_prerenderWarm" | "_runAtRequestEntry" | "_shellFragmentPayload" | "_explicitTaggedStores" | "_requestTags" | "_recordTagOwners" | "_recordLoaderTags" | "_cacheProfiles" | "_onResponseCallbacks" | "_pendingBackgroundTasks" | "_themeConfig" | "_readTheme" | "_documentCacheRender" | "_payloadVisitorTheme" | "_payloadDeferred" | "_requestKind" | "_payloadVisitorLocation" | "_locationState" | "_routeName" | "_prevRouteKey" | "_visitorMoved" | "_inActionRevalidation" | "_actionRan" | "_reportedErrors" | "_throwers" | "_printedErrors" | "_renderErrors" | "_recoveredHandlerErrors" | "_renderBarrier" | "_resolveRenderBarrier" | "_renderBarrierSegmentOrder" | "_treeHasStreaming" | "_renderBarrierWaiters" | "_handlerLoaderDeps" | "_awaitBeforeFlushLoaderIds" | "_renderBarrierHandleSnapshot" | "_renderBarrierGuardClosed" | "_reportBackgroundError" | "_debugPerformance" | "_metricsStore" | "_renderForeground" | "_activeRoutine" | "_renderDiagnosticsEnabled" | "_handlerStart" | "_handlerTiming" | "_tracing" | "_basename" | "_routerId" | "_versions" | "_setStatus" | "_rotateStateCookie" | "_setKeepCacheDirective" | "_freshReads" | "_setFreshReadsCookie" | "_requestRoot" | "_requestStart" | "_responseSent" | "_variables" | "_classifiedRoute" | "_requestMode" | "_cacheSignal" | "_dynamic" | "res">;
|
|
815
|
+
export type PublicRequestContext<TEnv = DefaultEnv, TParams = Record<string, string>> = Omit<RequestContext<TEnv, TParams>, "cookie" | "cookies" | "_readCookie" | "_readCookies" | "setCookie" | "deleteCookie" | "_handleStore" | "_transitionWhenRefs" | "_pprReplayPostMatchReason" | "_cacheStore" | "_prerender" | "_onDemandProducer" | "_searchParamsFilter" | "_shellCaptureState" | "_shellImplicitCache" | "_shellLoaderSeed" | "_resolvedCacheKeys" | "_shellKey" | "_shellForcedMiss" | "_prerenderWarm" | "_runAtRequestEntry" | "_shellFragmentPayload" | "_explicitTaggedStores" | "_requestTags" | "_recordTagOwners" | "_recordLoaderTags" | "_cacheProfiles" | "_onResponseCallbacks" | "_pendingBackgroundTasks" | "_themeConfig" | "_readTheme" | "_documentCacheRender" | "_payloadVisitorTheme" | "_payloadDeferred" | "_requestKind" | "_payloadVisitorLocation" | "_locationState" | "_documentLocationState" | "_routeName" | "_prevRouteKey" | "_visitorMoved" | "_inActionRevalidation" | "_actionRan" | "_reportedErrors" | "_throwers" | "_printedErrors" | "_renderErrors" | "_recoveredHandlerErrors" | "_renderBarrier" | "_resolveRenderBarrier" | "_renderBarrierSegmentOrder" | "_treeHasStreaming" | "_renderBarrierWaiters" | "_handlerLoaderDeps" | "_awaitBeforeFlushLoaderIds" | "_renderBarrierHandleSnapshot" | "_renderBarrierGuardClosed" | "_reportBackgroundError" | "_debugPerformance" | "_metricsStore" | "_renderForeground" | "_activeRoutine" | "_renderDiagnosticsEnabled" | "_handlerStart" | "_handlerTiming" | "_tracing" | "_basename" | "_routerId" | "_versions" | "_setStatus" | "_rotateStateCookie" | "_setKeepCacheDirective" | "_freshReads" | "_setFreshReadsCookie" | "_requestRoot" | "_requestStart" | "_responseSent" | "_variables" | "_classifiedRoute" | "_requestMode" | "_cacheSignal" | "_dynamic" | "res">;
|
|
799
816
|
/**
|
|
800
817
|
* Marker for a waitUntil-scheduled fn whose task promise must NOT enter
|
|
801
818
|
* _pendingBackgroundTasks. Used by the PPR shell capture for its own task:
|
|
@@ -185,6 +185,23 @@ export interface RenderRouteOptions {
|
|
|
185
185
|
* });
|
|
186
186
|
*/
|
|
187
187
|
locationState?: ReadonlyArray<readonly [LocationStateDefinition<any, any>, unknown]>;
|
|
188
|
+
/**
|
|
189
|
+
* Location state the document response carries, as `[def, value]` pairs:
|
|
190
|
+
* what a handler, middleware or no-JS form action set with
|
|
191
|
+
* `ctx.setLocationState()` (or a document redirect carried) for this page
|
|
192
|
+
* load. Merged into the entry at start-up as initBrowserApp merges the
|
|
193
|
+
* bootstrap payload's: after `locationState` and, with `hydrate: true`,
|
|
194
|
+
* after the `clearOnReload` pass, so a `clearOnReload` slot given here is
|
|
195
|
+
* applied. With `hydrate: true` a reader hydrates as `undefined` and gets
|
|
196
|
+
* the value once the root has hydrated.
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* await renderRoute([{ path: "/", Component: FlashBanner }], {
|
|
200
|
+
* hydrate: true,
|
|
201
|
+
* documentLocationState: [[FlashMessage, { text: "Payment declined" }]],
|
|
202
|
+
* });
|
|
203
|
+
*/
|
|
204
|
+
documentLocationState?: ReadonlyArray<readonly [LocationStateDefinition<any, any>, unknown]>;
|
|
188
205
|
/**
|
|
189
206
|
* Handles to seed by REFERENCE, for components that read handle output via
|
|
190
207
|
* `useHandle(SomeHandle)` (e.g. a client `Breadcrumbs` trail). Each entry is
|
|
@@ -229,6 +229,15 @@ export interface ServeShellRequestResult {
|
|
|
229
229
|
* hydrates without it.
|
|
230
230
|
*/
|
|
231
231
|
readPayloadFormState(): Promise<unknown>;
|
|
232
|
+
/**
|
|
233
|
+
* The payload's location state as the browser reads it: the slots
|
|
234
|
+
* (`Def.__rsc_ls_key` to value) a document's start-up merges into its
|
|
235
|
+
* entry (or a navigation's commit, for a `partial` request). Undefined when
|
|
236
|
+
* the payload carries none (or no Flight was rendered). A redirect carries
|
|
237
|
+
* a document request's state in a `Set-Cookie` instead: pass that cookie
|
|
238
|
+
* on the request of its target to read what the target's payload carries.
|
|
239
|
+
*/
|
|
240
|
+
readLocationState(): Promise<Record<string, unknown> | undefined>;
|
|
232
241
|
/**
|
|
233
242
|
* For an `action` request, the call's answer as the browser reads it: its
|
|
234
243
|
* result and the `revalidationError` its loaders' reruns report.
|
|
@@ -274,9 +274,16 @@ export type HandlerContext<TParams = {}, TEnv = DefaultEnv, TSearch extends Sear
|
|
|
274
274
|
*/
|
|
275
275
|
setTheme?: (theme: Theme) => void;
|
|
276
276
|
/**
|
|
277
|
-
* Attach location state entries to this response
|
|
278
|
-
*
|
|
279
|
-
*
|
|
277
|
+
* Attach location state entries to this response, for the useLocationState()
|
|
278
|
+
* hook to read on the entry this response renders. A client navigation
|
|
279
|
+
* delivers it with its payload; a document load (a link from outside the
|
|
280
|
+
* app, a no-JS form post) with its bootstrap payload, once the page has
|
|
281
|
+
* hydrated; a redirect carries it to its target (in a one-shot cookie on a
|
|
282
|
+
* document request). A document carrying it is sent
|
|
283
|
+
* `Cache-Control: private, no-store`.
|
|
284
|
+
*
|
|
285
|
+
* Entries accumulate across definitions; for one definition the last write
|
|
286
|
+
* wins (an earlier entry of it is replaced, not merged).
|
|
280
287
|
*
|
|
281
288
|
* @example
|
|
282
289
|
* ```typescript
|
|
@@ -390,6 +397,14 @@ export type InternalHandlerContext<TParams = {}, TEnv = DefaultEnv, TSearch exte
|
|
|
390
397
|
* request is a fill. See router/segment-resolution/prefetch-deferral.ts.
|
|
391
398
|
*/
|
|
392
399
|
_prefetchDeferral?: import("../router/segment-resolution/prefetch-deferral.js").PrefetchDeferral;
|
|
400
|
+
/**
|
|
401
|
+
* @internal A revalidate() predicate that read the source page
|
|
402
|
+
* (`currentParams`, `currentUrl`, `fromRouteName`) kept a held segment, so
|
|
403
|
+
* this match's diff is right for that source only (#1331). Set by
|
|
404
|
+
* evaluateRevalidation (router/revalidation.ts); match-result.ts turns it
|
|
405
|
+
* into `sourceHeld`, which scopes a prefetch of the response to its source.
|
|
406
|
+
*/
|
|
407
|
+
_sourceHeld?: true;
|
|
393
408
|
/**
|
|
394
409
|
* @internal DSL loaders registered and not started, by `$$id`: a prefetch
|
|
395
410
|
* deferred them, or a pass that skips loaders (`skipLoaders`) did not run
|
|
@@ -274,6 +274,12 @@ export interface MatchResult {
|
|
|
274
274
|
* source, so the RSC handler scopes its prefetch entry to it (#1007).
|
|
275
275
|
*/
|
|
276
276
|
interceptTargeted?: true;
|
|
277
|
+
/**
|
|
278
|
+
* Set on a partial match when a revalidate() predicate read the source page
|
|
279
|
+
* and kept a held segment: the diff is that source's. The RSC handler
|
|
280
|
+
* scopes its prefetch entry to it, like `interceptTargeted` (#1331).
|
|
281
|
+
*/
|
|
282
|
+
sourceHeld?: true;
|
|
277
283
|
/**
|
|
278
284
|
* Redirect URL for trailing slash normalization.
|
|
279
285
|
* When set, the RSC handler should return a 308 redirect to this URL
|
|
@@ -1,4 +1,34 @@
|
|
|
1
1
|
import type { Plugin, ResolvedConfig, UserConfig } from "vite";
|
|
2
|
+
/**
|
|
3
|
+
* Whether a user plugin must NOT be forwarded into the discovery temp server.
|
|
4
|
+
*
|
|
5
|
+
* Framework-owned plugins are matched precisely -- by exact name or a
|
|
6
|
+
* namespaced prefix -- rather than by a loose substring/word prefix, so an
|
|
7
|
+
* unrelated user resolver named e.g. `rsc-paths` or `cloudflare-kv-alias` is
|
|
8
|
+
* still forwarded (it would otherwise reproduce issue #500).
|
|
9
|
+
*
|
|
10
|
+
* - `vite:*` Vite core + official plugins (incl.
|
|
11
|
+
* @vitejs/plugin-react's `vite:react-*`). The temp
|
|
12
|
+
* server already provides its own core; forwarding
|
|
13
|
+
* these would duplicate or conflict with it.
|
|
14
|
+
* - `rsc` / `rsc:*` @vitejs/plugin-rsc. createTempRscServer instantiates
|
|
15
|
+
* its own rsc() plugin; forwarding would duplicate it.
|
|
16
|
+
* Matched exactly (`rsc`) or by the `rsc:` namespace --
|
|
17
|
+
* NOT every `rsc`-prefixed name.
|
|
18
|
+
* - `@rangojs/router*` Our own plugins. The discovery plugin spawns the
|
|
19
|
+
* temp server, so forwarding would recurse infinitely.
|
|
20
|
+
* - `@cloudflare/vite-plugin*` / @cloudflare/vite-plugin (emits the scoped
|
|
21
|
+
* `vite-plugin-cloudflare*` `@cloudflare/vite-plugin` and unscoped
|
|
22
|
+
* `vite-plugin-cloudflare` / `vite-plugin-cloudflare:*`).
|
|
23
|
+
* Forwarding re-inits workerd, defeating the Node temp
|
|
24
|
+
* server. Matched specifically so a scoped user resolver
|
|
25
|
+
* like `@cloudflare/kv-alias` is still forwarded.
|
|
26
|
+
*
|
|
27
|
+
* `rango generate --runtime` drops these before it resolves the user's config,
|
|
28
|
+
* so rango()'s configResolved (which writes the static gen files) and the
|
|
29
|
+
* Cloudflare plugin's config hooks never run there.
|
|
30
|
+
*/
|
|
31
|
+
export declare function isFrameworkPlugin(name: string): boolean;
|
|
2
32
|
/**
|
|
3
33
|
* Pick the user's resolution plugins from the resolved plugin list, denylist
|
|
4
34
|
* framework-owned plugins, keep only those with resolution hooks, and strip
|
|
@@ -20,8 +50,8 @@ export interface ForwardedRunnerConfig {
|
|
|
20
50
|
/**
|
|
21
51
|
* Extract the data-only config slice to mirror into the discovery temp server.
|
|
22
52
|
* `alias` is included here so callers no longer need to thread it separately.
|
|
23
|
-
* Accepts the unresolved config too: `rango generate --runtime`
|
|
24
|
-
*
|
|
53
|
+
* Accepts the unresolved config too: `rango generate --runtime` passes the
|
|
54
|
+
* config file's own values, without what plugin `config` hooks add.
|
|
25
55
|
*
|
|
26
56
|
* `tsconfigPaths` is forwarded so Vite 8's native tsconfig `paths` resolution
|
|
27
57
|
* (a top-level `resolve` flag, off by default) reaches the temp server. The
|
package/dist/vite/index.js
CHANGED
|
@@ -7146,7 +7146,7 @@ function resolveVersionsFrom(table, routerId2) {
|
|
|
7146
7146
|
// package.json
|
|
7147
7147
|
var package_default = {
|
|
7148
7148
|
name: "@rangojs/router",
|
|
7149
|
-
version: "0.
|
|
7149
|
+
version: "0.32.0",
|
|
7150
7150
|
description: "Django-inspired RSC router with composable URL patterns",
|
|
7151
7151
|
keywords: [
|
|
7152
7152
|
"react",
|
|
@@ -14666,7 +14666,7 @@ function bundleScanPhase(s, context, bundle) {
|
|
|
14666
14666
|
}
|
|
14667
14667
|
|
|
14668
14668
|
// src/vite/utils/forward-user-plugins.ts
|
|
14669
|
-
function
|
|
14669
|
+
function isFrameworkPlugin(name) {
|
|
14670
14670
|
return name.startsWith("vite:") || name === "rsc" || name.startsWith("rsc:") || name.startsWith("@rangojs/router") || name.startsWith("@cloudflare/vite-plugin") || name.startsWith("vite-plugin-cloudflare");
|
|
14671
14671
|
}
|
|
14672
14672
|
var IMPORT_REWRITE_PLUGINS = /* @__PURE__ */ new Set([
|
|
@@ -14693,7 +14693,7 @@ function selectForwardableResolvePlugins(plugins) {
|
|
|
14693
14693
|
const forwarded = [];
|
|
14694
14694
|
for (const p of plugins) {
|
|
14695
14695
|
const name = p?.name;
|
|
14696
|
-
if (!name ||
|
|
14696
|
+
if (!name || isFrameworkPlugin(name)) continue;
|
|
14697
14697
|
if (!isForwardable(p)) continue;
|
|
14698
14698
|
forwarded.push(stripToResolutionHooks(p));
|
|
14699
14699
|
}
|
package/package.json
CHANGED
|
@@ -558,8 +558,9 @@ analog but operates on loader/query reload, not RSC-segment render.
|
|
|
558
558
|
|
|
559
559
|
`<Link prefetch="hover|viewport|render|adaptive|none">` (default: the router's
|
|
560
560
|
`defaultPrefetch`, `"none"` in development and `"viewport"` in production), plus
|
|
561
|
-
`prefetchKey` (`":source"` scopes a prefetch to the originating page
|
|
562
|
-
|
|
561
|
+
`prefetchKey` (`":source"` scopes a prefetch to the originating page; the
|
|
562
|
+
router does so itself for an intercept target and for a `revalidate()` that
|
|
563
|
+
read the source page and kept a segment). Intercepted plain `<a href>` elements
|
|
563
564
|
inside the router basename follow the same default; common static-resource
|
|
564
565
|
extensions are excluded unless `data-prefetch="true"` identifies an application
|
|
565
566
|
route, and `data-prefetch="false"` or `"none"` opts out unsafe GET links. A
|
|
@@ -228,6 +228,11 @@ suffixes are still appended.
|
|
|
228
228
|
(`rango-state-fresh` by default), so it is not stored. A stale entry's
|
|
229
229
|
background re-run sets no fresh-reads cookie (no visitor made that
|
|
230
230
|
invalidation), so its invalidation does not keep the refreshed page out.
|
|
231
|
+
- A document that carries location state (`ctx.setLocationState()`, or the
|
|
232
|
+
one-shot cookie a document redirect left for it): it is the visitor's, so it
|
|
233
|
+
is not stored, leaves `private, no-store`, and a request that arrives with
|
|
234
|
+
that cookie, or whose earlier middleware set state, is not answered from a
|
|
235
|
+
stored entry (see `/hooks` `state.md`, "State on a document response")
|
|
231
236
|
- Non-200 responses
|
|
232
237
|
- A 200 whose render reported an error: a component that threw after the
|
|
233
238
|
response started streaming, such as an async server component whose fetch
|