@rangojs/router 0.33.2 → 0.34.1
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/{chunk-YP4HENIU.js → chunk-4HHFQSDC.js} +6 -1
- package/dist/bin/{chunk-3B7CKBRO.js → chunk-MWOHBDPM.js} +9 -9
- package/dist/bin/{client-urls-projection-J2BGDKQ4.js → client-urls-projection-4Q5EQDAL.js} +2 -2
- package/dist/bin/rango.js +1 -1
- package/dist/bin/{runtime-EPUDEHO4.js → runtime-6WLCJCN7.js} +1 -1
- package/dist/bin/{runtime-discovery-O6KQ5KO2.js → runtime-discovery-RY6NMQM3.js} +2 -2
- package/dist/bin/{temp-rsc-server-BOHXBOR7.js → temp-rsc-server-F7AHOPV4.js} +3 -3
- package/dist/types/client-urls/server-projection.d.ts +7 -0
- package/dist/types/client-urls/types.d.ts +13 -14
- package/dist/types/errors.d.ts +5 -0
- package/dist/types/loader-store.d.ts +19 -7
- package/dist/types/prefetch-markers.d.ts +13 -4
- package/dist/types/route-definition/helpers-types.d.ts +14 -10
- package/dist/types/router/match-context.d.ts +12 -0
- package/dist/types/router/navigation-snapshot.d.ts +17 -0
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +39 -21
- package/dist/types/server/context.d.ts +6 -5
- package/dist/types/testing/serve-shell-request.d.ts +7 -7
- package/dist/types/types/loader-types.d.ts +10 -8
- package/dist/types/urls/path-helper-types.d.ts +9 -8
- package/dist/vite/index.js +26 -12
- package/package.json +1 -1
- package/skills/app-structure/large-app.md +3 -0
- package/skills/client-urls/SKILL.md +13 -10
- package/skills/hooks/data.md +4 -1
- package/skills/intercept/SKILL.md +8 -4
- package/skills/links/SKILL.md +15 -12
- package/skills/loader/SKILL.md +52 -39
- package/skills/ppr/SKILL.md +1 -1
- package/skills/route/SKILL.md +15 -9
- package/skills/testing/serve-shell-request.md +19 -7
- package/skills/ux-architecture/SKILL.md +16 -11
- package/src/browser/navigation-bridge.ts +6 -0
- package/src/browser/partial-update.ts +12 -4
- package/src/browser/prefetch/fetch.ts +3 -3
- package/src/client-urls/client-urls.ts +11 -7
- package/src/client-urls/server-projection.ts +21 -3
- package/src/client-urls/types.ts +13 -14
- package/src/errors.ts +17 -0
- package/src/loader-store.ts +28 -12
- package/src/prefetch-markers.ts +18 -4
- package/src/route-definition/dsl-helpers.ts +13 -6
- package/src/route-definition/helpers-types.ts +14 -10
- package/src/router/loader-resolution.ts +4 -4
- package/src/router/match-api.ts +3 -3
- package/src/router/match-context.ts +22 -1
- package/src/router/match-middleware/cache-lookup.ts +17 -8
- package/src/router/match-middleware/intercept-resolution.ts +3 -3
- package/src/router/navigation-snapshot.ts +39 -8
- package/src/router/segment-resolution/prefetch-deferral.ts +108 -50
- package/src/rsc/rsc-rendering.ts +1 -1
- package/src/server/context.ts +6 -5
- package/src/testing/serve-shell-request.ts +7 -7
- package/src/types/loader-types.ts +10 -8
- package/src/urls/path-helper-types.ts +9 -8
- package/src/use-loader.tsx +5 -3
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
prefetchDirect,
|
|
4
4
|
prefetchQueued,
|
|
5
5
|
schedulePrefetchWhenRouterIdle
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-MWOHBDPM.js";
|
|
7
7
|
import {
|
|
8
8
|
findTsFiles,
|
|
9
9
|
firstCodeMatchIndex,
|
|
@@ -1386,8 +1386,13 @@ function serializeLoaderOptions(loaders, owner, options) {
|
|
|
1386
1386
|
loaders,
|
|
1387
1387
|
({ prefetch }) => prefetch === false
|
|
1388
1388
|
);
|
|
1389
|
+
const intentLoaderIndices = indicesWhere(
|
|
1390
|
+
loaders,
|
|
1391
|
+
({ prefetch }) => prefetch === "intent"
|
|
1392
|
+
);
|
|
1389
1393
|
return {
|
|
1390
1394
|
...deferrableLoaderIndices.length > 0 ? { deferrableLoaderIndices: Object.freeze(deferrableLoaderIndices) } : {},
|
|
1395
|
+
...intentLoaderIndices.length > 0 ? { intentLoaderIndices: Object.freeze(intentLoaderIndices) } : {},
|
|
1391
1396
|
...loaderRevalidate.some((keys) => keys.length > 0) ? {
|
|
1392
1397
|
loaderRevalidate: Object.freeze(
|
|
1393
1398
|
loaderRevalidate.map((keys) => Object.freeze(keys))
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
// src/prefetch-markers.ts
|
|
2
|
+
var PREFETCH_HEADER = "X-Rango-Prefetch";
|
|
3
|
+
var PREFETCH_INTENT = "intent";
|
|
4
|
+
var PREFETCH_FAILED_ROW = ":H_0\n";
|
|
5
|
+
|
|
1
6
|
// src/internal-debug.ts
|
|
2
7
|
var INTERNAL_RANGO_DEBUG = typeof process !== "undefined" && Boolean(process.env?.INTERNAL_RANGO_DEBUG);
|
|
3
8
|
|
|
@@ -209,7 +214,7 @@ var requestGeneration = 0;
|
|
|
209
214
|
function loadRuntime() {
|
|
210
215
|
if (runtime) return Promise.resolve(runtime);
|
|
211
216
|
if (!runtimePromise) {
|
|
212
|
-
runtimePromise = import("./runtime-
|
|
217
|
+
runtimePromise = import("./runtime-6WLCJCN7.js").then(
|
|
213
218
|
(loaded) => {
|
|
214
219
|
runtime = loaded;
|
|
215
220
|
if (decoder) loaded.setPrefetchDecoder(decoder);
|
|
@@ -423,12 +428,10 @@ function isActionFenceActive() {
|
|
|
423
428
|
return fenceCount > 0;
|
|
424
429
|
}
|
|
425
430
|
|
|
426
|
-
// src/prefetch-markers.ts
|
|
427
|
-
var PREFETCH_HEADER = "X-Rango-Prefetch";
|
|
428
|
-
var PREFETCH_INTENT = "intent";
|
|
429
|
-
var PREFETCH_FAILED_ROW = ":H_0\n";
|
|
430
|
-
|
|
431
431
|
export {
|
|
432
|
+
PREFETCH_HEADER,
|
|
433
|
+
PREFETCH_INTENT,
|
|
434
|
+
PREFETCH_FAILED_ROW,
|
|
432
435
|
IS_BROWSER_DEBUG,
|
|
433
436
|
debugLog,
|
|
434
437
|
notifyListeners,
|
|
@@ -454,9 +457,6 @@ export {
|
|
|
454
457
|
clearPrefetchInflight,
|
|
455
458
|
isFragmentPassthroughEnabled,
|
|
456
459
|
isActionFenceActive,
|
|
457
|
-
PREFETCH_HEADER,
|
|
458
|
-
PREFETCH_INTENT,
|
|
459
|
-
PREFETCH_FAILED_ROW,
|
|
460
460
|
prefetchDirect,
|
|
461
461
|
prefetchQueued,
|
|
462
462
|
schedulePrefetchWhenRouterIdle
|
|
@@ -10,8 +10,8 @@ import {
|
|
|
10
10
|
refreshRecordedClientUrlProjections,
|
|
11
11
|
resolveClientUrlsSource,
|
|
12
12
|
scanForClientUrlModules
|
|
13
|
-
} from "./chunk-
|
|
14
|
-
import "./chunk-
|
|
13
|
+
} from "./chunk-4HHFQSDC.js";
|
|
14
|
+
import "./chunk-MWOHBDPM.js";
|
|
15
15
|
import "./chunk-LD7UBHT4.js";
|
|
16
16
|
export {
|
|
17
17
|
CLIENT_IMPORT_RE,
|
package/dist/bin/rango.js
CHANGED
|
@@ -229,7 +229,7 @@ ${formatNestedRouterConflictError(nestedRouterConflict, "[rango]")}
|
|
|
229
229
|
}
|
|
230
230
|
let discoverAndWriteRouteTypes;
|
|
231
231
|
try {
|
|
232
|
-
const mod = await import("./runtime-discovery-
|
|
232
|
+
const mod = await import("./runtime-discovery-RY6NMQM3.js");
|
|
233
233
|
discoverAndWriteRouteTypes = mod.discoverAndWriteRouteTypes;
|
|
234
234
|
} catch (err) {
|
|
235
235
|
if (err.code === "ERR_MODULE_NOT_FOUND" || err.code === "MODULE_NOT_FOUND") {
|
|
@@ -163,9 +163,9 @@ async function discoverAndWriteRouteTypes(opts) {
|
|
|
163
163
|
"Runtime discovery requires '@vitejs/plugin-rsc'. Install it with: pnpm add -D @vitejs/plugin-rsc"
|
|
164
164
|
);
|
|
165
165
|
}
|
|
166
|
-
const { createTempRscServer } = await import("./temp-rsc-server-
|
|
166
|
+
const { createTempRscServer } = await import("./temp-rsc-server-F7AHOPV4.js");
|
|
167
167
|
const { createDiscoveryState } = await import("./state-ACD2MGKJ.js");
|
|
168
|
-
const { discoverClientUrlProjections } = await import("./client-urls-projection-
|
|
168
|
+
const { discoverClientUrlProjections } = await import("./client-urls-projection-4Q5EQDAL.js");
|
|
169
169
|
const {
|
|
170
170
|
isFrameworkPlugin,
|
|
171
171
|
pickForwardedRunnerConfig,
|
|
@@ -27,8 +27,8 @@ import {
|
|
|
27
27
|
propertyKeyIs,
|
|
28
28
|
recordClientUrlsModule,
|
|
29
29
|
unwrapExpression
|
|
30
|
-
} from "./chunk-
|
|
31
|
-
import "./chunk-
|
|
30
|
+
} from "./chunk-4HHFQSDC.js";
|
|
31
|
+
import "./chunk-MWOHBDPM.js";
|
|
32
32
|
import {
|
|
33
33
|
codeMatchIndices,
|
|
34
34
|
isSkippedSourceScanDir
|
|
@@ -47,7 +47,7 @@ var DEFAULT_ROUTER_VERSIONS_KEY = "*";
|
|
|
47
47
|
// package.json
|
|
48
48
|
var package_default = {
|
|
49
49
|
name: "@rangojs/router",
|
|
50
|
-
version: "0.
|
|
50
|
+
version: "0.34.1",
|
|
51
51
|
description: "Django-inspired RSC router with composable URL patterns",
|
|
52
52
|
keywords: [
|
|
53
53
|
"react",
|
|
@@ -38,6 +38,11 @@ export interface ClientUrlProjectionRoute {
|
|
|
38
38
|
* loader(), so a prefetch of the route defers them. Absent (= none) in
|
|
39
39
|
* projections serialized before the option existed. */
|
|
40
40
|
readonly deferrableLoaderIndices?: readonly number[];
|
|
41
|
+
/** Indices into loaderIds of loaders declared loader(Def, { prefetch:
|
|
42
|
+
* "intent" }); materialization passes the option through, so a
|
|
43
|
+
* speculative prefetch defers them and an intent prefetch runs them.
|
|
44
|
+
* Absent (= none) in projections serialized before the option existed. */
|
|
45
|
+
readonly intentLoaderIndices?: readonly number[];
|
|
41
46
|
/** Per loader (aligned with loaderIds), its revalidate() arguments in
|
|
42
47
|
* declaration order: a hoisted predicate's key, whose registration
|
|
43
48
|
* materialization attaches (revalidate-registry.ts), or a boolean
|
|
@@ -64,6 +69,8 @@ export interface ClientUrlProjectionIntercept {
|
|
|
64
69
|
readonly hasLoading: boolean;
|
|
65
70
|
/** As on a route (ClientUrlProjectionRoute.deferrableLoaderIndices). */
|
|
66
71
|
readonly deferrableLoaderIndices?: readonly number[];
|
|
72
|
+
/** As on a route (ClientUrlProjectionRoute.intentLoaderIndices). */
|
|
73
|
+
readonly intentLoaderIndices?: readonly number[];
|
|
67
74
|
/** As on a route (ClientUrlProjectionRoute.loaderRevalidate). */
|
|
68
75
|
readonly loaderRevalidate?: readonly (readonly (string | boolean)[])[];
|
|
69
76
|
}
|
|
@@ -2,6 +2,7 @@ import type { ComponentType, ReactNode } from "react";
|
|
|
2
2
|
import type { LoaderDefinition, LoaderOptions, ShouldRevalidateFn, TransitionConfig } from "../types.js";
|
|
3
3
|
import type { DefaultEnv } from "../types/global-namespace.js";
|
|
4
4
|
import type { HoistedRevalidateRef } from "./hoisted-revalidate.js";
|
|
5
|
+
import type { PrefetchFlag } from "../prefetch-markers.js";
|
|
5
6
|
import type { TrieMatchResult } from "../router/trie-matching.js";
|
|
6
7
|
import type { PathOptions } from "../urls/pattern-types.js";
|
|
7
8
|
import type { SearchSchema } from "../search-params.js";
|
|
@@ -48,13 +49,12 @@ export interface ClientUrlLoaderRecord {
|
|
|
48
49
|
*/
|
|
49
50
|
readonly ssr?: false;
|
|
50
51
|
/**
|
|
51
|
-
* loader(Def, { prefetch: false }): a
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* Projected onto the materialized stub.
|
|
52
|
+
* loader(Def, { prefetch: false | "intent" }): a prefetch of the route, or
|
|
53
|
+
* of an intercept's target from its source, defers this loader and the
|
|
54
|
+
* click that adopts it runs it in its fill request (see
|
|
55
|
+
* {@link LoaderOptions}). Projected onto the materialized stub.
|
|
56
56
|
*/
|
|
57
|
-
readonly prefetch?:
|
|
57
|
+
readonly prefetch?: PrefetchFlag;
|
|
58
58
|
}
|
|
59
59
|
/**
|
|
60
60
|
* The TransitionConfig subset a clientUrls() route may declare: ViewTransition
|
|
@@ -118,14 +118,13 @@ export interface ClientUrlHelpers {
|
|
|
118
118
|
* settled return freezes into the shell (nested promises stay live
|
|
119
119
|
* holes).
|
|
120
120
|
*
|
|
121
|
-
* Pass `{ prefetch: false }` to keep this loader out of the
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
* render prefetch of the intercept from its source, as in urls().
|
|
121
|
+
* Pass `{ prefetch: false | "intent" }` to keep this loader out of the
|
|
122
|
+
* route's prefetches (see {@link LoaderOptions}). An in-group click still
|
|
123
|
+
* shows the destination at once, commits the adopted prefetch with the
|
|
124
|
+
* other loaders' data, and runs only the flagged loaders in its one fill
|
|
125
|
+
* request. A `layout()` loader with the
|
|
126
|
+
* flag is flagged on every route below it. Inside intercept() it defers
|
|
127
|
+
* the loader in a prefetch of the intercept from its source, as in urls().
|
|
129
128
|
*/
|
|
130
129
|
readonly loader: <TData>(definition: LoaderDefinition<TData>, optionsOrUse?: Pick<LoaderOptions, "ssr" | "prefetch"> | ClientUrlUse, use?: ClientUrlUse) => ClientUrlItem;
|
|
131
130
|
readonly loading: (component: ReactNode) => ClientUrlItem;
|
package/dist/types/errors.d.ts
CHANGED
|
@@ -247,6 +247,11 @@ export declare function invariant(condition: any, message: string): asserts cond
|
|
|
247
247
|
* passes), as an invariant.
|
|
248
248
|
*/
|
|
249
249
|
export declare function assertBooleanOption(value: unknown, option: string, effect: string): void;
|
|
250
|
+
/**
|
|
251
|
+
* Rejects a `prefetch` DSL option that is neither a boolean nor `"intent"`
|
|
252
|
+
* (undefined passes), as an invariant.
|
|
253
|
+
*/
|
|
254
|
+
export declare function assertPrefetchOption(value: unknown, option: string, effect: string): void;
|
|
250
255
|
/**
|
|
251
256
|
* Sanitize errors for production - prevents leaking sensitive information
|
|
252
257
|
*
|
|
@@ -28,6 +28,10 @@
|
|
|
28
28
|
* cancelled on resubscribe so a StrictMode / transition remount does not
|
|
29
29
|
* reclaim a bucket that is about to be reused, and is held until any
|
|
30
30
|
* in-flight load settles.
|
|
31
|
+
* - Owned buckets (one reader's private refresh-group bucket,
|
|
32
|
+
* `useLoader(L, { refreshGroup })` without a key) are sticky while that
|
|
33
|
+
* reader is mounted and dropped like an ephemeral bucket once it leaves:
|
|
34
|
+
* no other reader names the bucket, and a remount takes a new one.
|
|
31
35
|
*
|
|
32
36
|
* Each router's NavigationStore owns one (navigation-store.ts), and hooks reach
|
|
33
37
|
* it through NavigationStoreContext: the router store is the single source of
|
|
@@ -79,6 +83,13 @@ export interface SubscribeOptions {
|
|
|
79
83
|
* subscriber attaches, and from then on resets via `clearFamily`.
|
|
80
84
|
*/
|
|
81
85
|
ephemeral?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* When true, the bucket belongs to this subscriber alone (a private
|
|
88
|
+
* refresh-group bucket, which no other reader names): it is reset by
|
|
89
|
+
* `clearFamily` while subscribed, and dropped after the subscriber leaves
|
|
90
|
+
* even when sticky. Read at unsubscribe, so pass it on every subscribe.
|
|
91
|
+
*/
|
|
92
|
+
owned?: boolean;
|
|
82
93
|
/**
|
|
83
94
|
* Cross-loader refresh group name(s). Tags this bucket so `refreshGroups(name)`
|
|
84
95
|
* can refresh it alongside buckets of other loaders. A bucket may be tagged
|
|
@@ -130,8 +141,8 @@ export declare class LoaderStore {
|
|
|
130
141
|
* Subscribe to entry changes for `bucketKey`.
|
|
131
142
|
* Returns an unsubscribe function. A sticky bucket's entry is kept around even
|
|
132
143
|
* after the last subscriber leaves so that an in-flight `load()` can still
|
|
133
|
-
* commit if the consumer remounts. An ephemeral bucket is dropped
|
|
134
|
-
* last subscriber leaves (deferred, see maybeScheduleRefcountClear).
|
|
144
|
+
* commit if the consumer remounts. An ephemeral or owned bucket is dropped
|
|
145
|
+
* once its last subscriber leaves (deferred, see maybeScheduleRefcountClear).
|
|
135
146
|
*/
|
|
136
147
|
subscribe(bucketKey: string, cb: () => void, options?: SubscribeOptions): () => void;
|
|
137
148
|
private addToGroup;
|
|
@@ -149,14 +160,14 @@ export declare class LoaderStore {
|
|
|
149
160
|
*/
|
|
150
161
|
refreshGroups(groups: string | string[]): Promise<void>;
|
|
151
162
|
/**
|
|
152
|
-
* Schedule a deferred drop for an ad-hoc (never-sticky)
|
|
153
|
-
* subscriber just left. Deferred to a microtask so a StrictMode /
|
|
154
|
-
* remount that resubscribes in the same tick cancels it.
|
|
163
|
+
* Schedule a deferred drop for an ad-hoc (never-sticky) or owned bucket
|
|
164
|
+
* whose last subscriber just left. Deferred to a microtask so a StrictMode /
|
|
165
|
+
* transition remount that resubscribes in the same tick cancels it.
|
|
155
166
|
*/
|
|
156
167
|
private maybeScheduleRefcountClear;
|
|
157
168
|
/** Remove a bucket entry entirely and prune it from its family/group indexes. */
|
|
158
169
|
private dropBucket;
|
|
159
|
-
/** Drop an ephemeral bucket that settled with no subscribers left. */
|
|
170
|
+
/** Drop an ephemeral or owned bucket that settled with no subscribers left. */
|
|
160
171
|
private dropIfSettled;
|
|
161
172
|
/**
|
|
162
173
|
* Returns the current snapshot for `bucketKey`. Stable reference between
|
|
@@ -205,7 +216,8 @@ export declare class LoaderStore {
|
|
|
205
216
|
* Reset a single bucket entry. Bumps `latestRequestId` so any in-flight
|
|
206
217
|
* `load()` whose promise is still pending will fail its gate when it resolves
|
|
207
218
|
* and be dropped — prevents pre-navigation loads from clobbering the new
|
|
208
|
-
* route's context. The entry itself is kept (sticky-bucket semantics)
|
|
219
|
+
* route's context. The entry itself is kept (sticky-bucket semantics),
|
|
220
|
+
* unless it was only waiting for that request to be dropped.
|
|
209
221
|
*/
|
|
210
222
|
clear(bucketKey: string): void;
|
|
211
223
|
/**
|
|
@@ -10,12 +10,21 @@ export declare const PREFETCH_HEADER = "X-Rango-Prefetch";
|
|
|
10
10
|
/**
|
|
11
11
|
* PREFETCH_HEADER's value on an intent prefetch: a hover or an explicit
|
|
12
12
|
* `prefetch()` call (browser/prefetch/fetch.ts prefetchDirect). The server
|
|
13
|
-
*
|
|
14
|
-
* flagged `
|
|
15
|
-
*
|
|
16
|
-
* this one reads as speculative.
|
|
13
|
+
* runs the work flagged `prefetch: "intent"` in it and defers the work
|
|
14
|
+
* flagged `prefetch: false`. A speculative prefetch (viewport, render) sends
|
|
15
|
+
* `1` and defers both; any value other than this one reads as speculative.
|
|
17
16
|
*/
|
|
18
17
|
export declare const PREFETCH_INTENT = "intent";
|
|
18
|
+
/**
|
|
19
|
+
* A recorded `prefetch` flag of loader() or loading(): `false` defers the
|
|
20
|
+
* work in every prefetch, `"intent"` in a speculative one only
|
|
21
|
+
* (router/segment-resolution/prefetch-deferral.ts).
|
|
22
|
+
*/
|
|
23
|
+
export type PrefetchFlag = false | typeof PREFETCH_INTENT;
|
|
24
|
+
/** A `prefetch` DSL option as written: a boolean, or `"intent"`. */
|
|
25
|
+
export type PrefetchOption = boolean | PrefetchFlag;
|
|
26
|
+
/** The option asks for deferral: `prefetch: false` or `prefetch: "intent"`. */
|
|
27
|
+
export declare function isPrefetchFlag(value: unknown): value is PrefetchFlag;
|
|
19
28
|
/**
|
|
20
29
|
* The last row of a prefetch whose render reported an error
|
|
21
30
|
* (rsc/prefetch-render-error.ts): a Flight hint with a code React does not
|
|
@@ -2,6 +2,7 @@ import type { ReactNode } from "react";
|
|
|
2
2
|
import type { ExtractRouteParams, Handler, PartialCacheOptions, ErrorBoundaryHandler, LoaderDefinition, LoaderOptions, MiddlewareFn, NotFoundBoundaryHandler, ResolvedRouteMap, RouteDefinition, ShouldRevalidateFn, TransitionConfig } from "../types.js";
|
|
3
3
|
import type { AllUseItems, LayoutItem, RouteItem, ParallelItem, InterceptItem, MiddlewareItem, RevalidateItem, LoaderItem, LoadingItem, ErrorBoundaryItem, NotFoundBoundaryItem, LayoutUseItem, RouteUseItem, ParallelUseItem, InterceptUseItem, LoaderUseItem, CacheItem, TransitionItem, UseItems } from "../route-types.js";
|
|
4
4
|
import type { StaticHandlerRef } from "../static-handler.js";
|
|
5
|
+
import type { PrefetchOption } from "../prefetch-markers.js";
|
|
5
6
|
export type { AllUseItems, LayoutItem, RouteItem, ParallelItem, InterceptItem, MiddlewareItem, RevalidateItem, LoaderItem, ErrorBoundaryItem, NotFoundBoundaryItem, LayoutUseItem, RouteUseItem, ParallelUseItem, InterceptUseItem, CacheItem, } from "../route-types.js";
|
|
6
7
|
export type { InterceptConfig, InterceptSelectorContext, InterceptSegmentsState, InterceptWhenFn, } from "../server/context";
|
|
7
8
|
/**
|
|
@@ -231,15 +232,16 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
231
232
|
* loader(RecommendationsLoader), // still streams behind loading()
|
|
232
233
|
* ```
|
|
233
234
|
*
|
|
234
|
-
* Pass `{ prefetch: false }` to keep the loader out of
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
235
|
+
* Pass `{ prefetch: false }` to keep the loader out of prefetches: a
|
|
236
|
+
* prefetch skips it and the click that adopts it fetches it in one
|
|
237
|
+
* follow-up request. `{ prefetch: "intent" }` defers it in viewport and
|
|
238
|
+
* render prefetches only (see {@link LoaderOptions}). Only on a segment the
|
|
238
239
|
* client does not have yet: a loader the client already holds runs in the
|
|
239
240
|
* prefetch when it revalidates.
|
|
240
241
|
*
|
|
241
242
|
* ```typescript
|
|
242
243
|
* loader(ReviewsLoader, { prefetch: false }),
|
|
244
|
+
* loader(RelatedLoader, { prefetch: "intent" }),
|
|
243
245
|
* ```
|
|
244
246
|
*
|
|
245
247
|
* @param loaderDef - Loader created with createLoader()
|
|
@@ -256,16 +258,18 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
256
258
|
* // Skip loading on SSR, only show on client navigation
|
|
257
259
|
* loading(<Skeleton />, { ssr: false })
|
|
258
260
|
*
|
|
259
|
-
* // Keep everything behind this fallback out of
|
|
260
|
-
* // prefetches (a hover prefetch runs it)
|
|
261
|
+
* // Keep everything behind this fallback out of prefetches
|
|
261
262
|
* loading(<Skeleton />, { prefetch: false })
|
|
263
|
+
*
|
|
264
|
+
* // Deferred in viewport and render prefetches only
|
|
265
|
+
* loading(<Skeleton />, { prefetch: "intent" })
|
|
262
266
|
* ```
|
|
263
267
|
* @param component - The loading UI to show during navigation
|
|
264
268
|
* @param options - Configuration options
|
|
265
269
|
* @param options.ssr - If false, skip showing loading on document requests (SSR)
|
|
266
|
-
* @param options.prefetch -
|
|
267
|
-
*
|
|
268
|
-
*
|
|
270
|
+
* @param options.prefetch - `false` or `"intent"` (see
|
|
271
|
+
* {@link LoaderOptions}); the flag applies when this entry's segment is
|
|
272
|
+
* new to the client. A prefetch then skips the whole unit: the
|
|
269
273
|
* handler, its loaders, its orphan layouts and parallel slots and, for a
|
|
270
274
|
* layout, every deeper entry; the click that adopts the prefetch shows
|
|
271
275
|
* this fallback and fetches them in one follow-up request, which reads
|
|
@@ -280,7 +284,7 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
280
284
|
*/
|
|
281
285
|
loading: (component: ReactNode | (() => ReactNode), options?: {
|
|
282
286
|
ssr?: boolean;
|
|
283
|
-
prefetch?:
|
|
287
|
+
prefetch?: PrefetchOption;
|
|
284
288
|
}) => LoadingItem;
|
|
285
289
|
/**
|
|
286
290
|
* Attach an error boundary to catch errors in this segment and children
|
|
@@ -112,6 +112,7 @@ import type { CacheScope } from "../cache/cache-scope.js";
|
|
|
112
112
|
import type { EntryData, InterceptSelectorContext, MetricsStore } from "../server/context.js";
|
|
113
113
|
import type { HandlerContext, ResolvedSegment } from "../types.js";
|
|
114
114
|
import type { RouteMatchResult } from "./pattern-matching.js";
|
|
115
|
+
import type { PrevPage } from "./navigation-snapshot.js";
|
|
115
116
|
import type { InterceptResult } from "./router-context.js";
|
|
116
117
|
import type { ResolutionPassInput } from "./segment-resolution/resolution-pass.js";
|
|
117
118
|
import type { ActionContext } from "./types.js";
|
|
@@ -130,7 +131,10 @@ export interface MatchContext<TEnv = any> {
|
|
|
130
131
|
heldBoundary?: string;
|
|
131
132
|
prevUrl: URL;
|
|
132
133
|
prevParams: Record<string, string>;
|
|
134
|
+
/** The match of the page left: the intercept's URL while one is open. */
|
|
133
135
|
prevMatch: RouteMatchResult | null;
|
|
136
|
+
/** The page an open intercept's slot was rendered for. */
|
|
137
|
+
interceptPrev: PrevPage;
|
|
134
138
|
matched: RouteMatchResult;
|
|
135
139
|
manifestEntry: EntryData;
|
|
136
140
|
entries: EntryData[];
|
|
@@ -169,6 +173,14 @@ export interface MatchContext<TEnv = any> {
|
|
|
169
173
|
export declare function rendersLive(ctx: Pick<MatchContext, "isAction" | "stale">): boolean;
|
|
170
174
|
/** This match as the input of a revalidating resolution pass (resolution-pass.ts). */
|
|
171
175
|
export declare function resolutionPassInput<TEnv>(ctx: MatchContext<TEnv>): ResolutionPassInput<TEnv>;
|
|
176
|
+
/**
|
|
177
|
+
* This match as the input of the pass that resolves its intercept's slot: its
|
|
178
|
+
* held segments compare with the intercept's own URL (`interceptPrev`), not
|
|
179
|
+
* with the source page under it. From /item/2 over /list/1, a link to /item/1
|
|
180
|
+
* compared with the source's params would read as param-equal and keep item
|
|
181
|
+
* 2's slot.
|
|
182
|
+
*/
|
|
183
|
+
export declare function interceptPassInput<TEnv>(ctx: MatchContext<TEnv>): ResolutionPassInput<TEnv>;
|
|
172
184
|
/**
|
|
173
185
|
* Mutable state that flows through the pipeline
|
|
174
186
|
*/
|
|
@@ -1,10 +1,27 @@
|
|
|
1
1
|
import type { RouteMatchResult } from "./pattern-matching.js";
|
|
2
|
+
/** A page held segments were rendered for: what their revalidation compares with. */
|
|
3
|
+
export interface PrevPage {
|
|
4
|
+
url: URL;
|
|
5
|
+
params: Record<string, string>;
|
|
6
|
+
}
|
|
2
7
|
export interface NavigationSnapshot {
|
|
8
|
+
/**
|
|
9
|
+
* `prevUrl` and `prevParams`: the page the held page segments were rendered
|
|
10
|
+
* for. That is the page left, or the source page while an intercept is open
|
|
11
|
+
* (`hasInterceptSource`).
|
|
12
|
+
*/
|
|
3
13
|
prevUrl: URL;
|
|
4
14
|
/** The visitor's URL of the page left, when `prevUrl` is its rewrite target. */
|
|
5
15
|
prevVisitorUrl: URL | null;
|
|
6
16
|
prevParams: Record<string, string>;
|
|
17
|
+
/** The match of the page left: the intercept's URL while one is open. */
|
|
7
18
|
prevMatch: RouteMatchResult | null;
|
|
19
|
+
/**
|
|
20
|
+
* The page an open intercept's slot was rendered for: the intercept's own
|
|
21
|
+
* URL (match-context.ts interceptPassInput). Equals `prevUrl`/`prevParams`
|
|
22
|
+
* when the request names no intercept source.
|
|
23
|
+
*/
|
|
24
|
+
interceptPrev: PrevPage;
|
|
8
25
|
interceptContextUrl: URL;
|
|
9
26
|
interceptContextMatch: RouteMatchResult | null;
|
|
10
27
|
clientSegmentIds: string[];
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `prefetch: false`: what a prefetch request defers
|
|
3
|
-
* runs (docs/design/prefetch-false.md).
|
|
2
|
+
* `prefetch: false` and `prefetch: "intent"`: what a prefetch request defers
|
|
3
|
+
* and what its fill request runs (docs/design/prefetch-false.md). A
|
|
4
|
+
* speculative prefetch (viewport, render) defers both flags; an intent
|
|
5
|
+
* prefetch (a hover, `prefetch()`) defers `false` only and runs `"intent"`
|
|
6
|
+
* work as if it were unflagged.
|
|
4
7
|
*
|
|
5
8
|
* Two layers. The SCOPE is static per matched chain: which loader
|
|
6
9
|
* registrations are deferrable and which entries can be a handler unit. The
|
|
@@ -30,20 +33,15 @@ import type { CacheScope } from "../../cache/cache-scope.js";
|
|
|
30
33
|
/**
|
|
31
34
|
* What a request says it is, read once when its context is created
|
|
32
35
|
* (RequestContext._requestKind). A fill is marked on the raw URL and wins
|
|
33
|
-
* over the prefetch header
|
|
34
|
-
* render) and `"intent"` a hover or an explicit `prefetch()` call
|
|
35
|
-
* (PREFETCH_INTENT): both are prefetches, and only the first defers. Whether
|
|
36
|
-
* a prefetch defers anything is decided per match, by partialDeferralMode: a
|
|
37
|
-
* shell capture inherits the context of the request that scheduled it and is
|
|
38
|
-
* never a prefetch.
|
|
36
|
+
* over the prefetch header; an intent prefetch carries PREFETCH_INTENT.
|
|
39
37
|
*/
|
|
40
38
|
export declare function requestKind(request: Request, rawUrl: URL): RequestKind | undefined;
|
|
41
39
|
/** requestKind's answer for a marked request. */
|
|
42
40
|
export type RequestKind = "prefetch" | "intent" | "fill";
|
|
43
41
|
/** A prefetch of either kind: speculative or intent. */
|
|
44
42
|
export declare function isPrefetchKind(kind: RequestKind | undefined): boolean;
|
|
45
|
-
/**
|
|
46
|
-
interface
|
|
43
|
+
/** What one kind of prefetch can defer in a matched chain. */
|
|
44
|
+
interface DeferralSet {
|
|
47
45
|
/**
|
|
48
46
|
* Loader registrations a prefetch can defer, each with what makes it so:
|
|
49
47
|
* `true` for its own flag, else the segment ids of the flagged entries whose
|
|
@@ -51,8 +49,6 @@ interface DeferralScope {
|
|
|
51
49
|
* segments is new to the client.
|
|
52
50
|
*/
|
|
53
51
|
readonly loaders: ReadonlyMap<LoaderEntry, true | readonly string[]>;
|
|
54
|
-
/** `$$id`s of `loaders`: the loaders that cannot call ctx.rendered(). */
|
|
55
|
-
readonly loaderIds: ReadonlySet<string>;
|
|
56
52
|
/**
|
|
57
53
|
* Entries whose handler a prefetch can skip (a flagged loading() with a
|
|
58
54
|
* fallback to show). `index` is the chain index that owns the entry: its
|
|
@@ -62,6 +58,23 @@ interface DeferralScope {
|
|
|
62
58
|
*/
|
|
63
59
|
readonly units: ReadonlyMap<EntryData, DeferralUnit>;
|
|
64
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Static, per matched chain: what a speculative prefetch defers (every flag,
|
|
63
|
+
* `false` or `"intent"`), and in `intent` what an intent prefetch defers.
|
|
64
|
+
*/
|
|
65
|
+
interface DeferralScope extends DeferralSet {
|
|
66
|
+
/**
|
|
67
|
+
* `$$id`s of `loaders`: the loaders that cannot call ctx.rendered(). The
|
|
68
|
+
* speculative set on every request: a loader an intent prefetch runs is
|
|
69
|
+
* still run by a speculative prefetch's fill, where its handler does not.
|
|
70
|
+
*/
|
|
71
|
+
readonly loaderIds: ReadonlySet<string>;
|
|
72
|
+
/**
|
|
73
|
+
* What an intent prefetch defers: the scope with every `"intent"` flag
|
|
74
|
+
* read as unflagged. Absent when no flag is `"intent"`: the scope itself.
|
|
75
|
+
*/
|
|
76
|
+
readonly intent?: DeferralSet;
|
|
77
|
+
}
|
|
65
78
|
interface DeferralUnit {
|
|
66
79
|
readonly index: number;
|
|
67
80
|
readonly ids: readonly string[];
|
|
@@ -69,6 +82,11 @@ interface DeferralUnit {
|
|
|
69
82
|
/** Per match: InternalHandlerContext._prefetchDeferral. */
|
|
70
83
|
export interface PrefetchDeferral {
|
|
71
84
|
readonly scope: DeferralScope;
|
|
85
|
+
/**
|
|
86
|
+
* What this request defers: `scope`, or `scope.intent` for an intent
|
|
87
|
+
* prefetch. The ctx.rendered() guards read `scope`.
|
|
88
|
+
*/
|
|
89
|
+
readonly defers: DeferralSet;
|
|
72
90
|
/**
|
|
73
91
|
* "prefetch": deferral replaces execution. "fill": the request that follows
|
|
74
92
|
* an adopted prefetch; a held segment is skipped outright. Unset on every
|
|
@@ -151,7 +169,7 @@ export interface DeferredLoader {
|
|
|
151
169
|
/**
|
|
152
170
|
* The deferral scope of a matched chain, memoized on its leaf entry (entries
|
|
153
171
|
* are per-isSSR manifest objects, replaced on HMR). EMPTY_SCOPE when nothing
|
|
154
|
-
* in the tree declares `prefetch: false`.
|
|
172
|
+
* in the tree declares `prefetch: false` or `prefetch: "intent"`.
|
|
155
173
|
*/
|
|
156
174
|
export declare function resolveDeferralScope(entries: readonly EntryData[]): DeferralScope;
|
|
157
175
|
/**
|
|
@@ -164,10 +182,8 @@ export declare function resolveDeferralScope(entries: readonly EntryData[]): Def
|
|
|
164
182
|
*/
|
|
165
183
|
export declare function resolveInterceptDeferralScope(intercept: InterceptEntry): DeferralScope;
|
|
166
184
|
/**
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
* only: an intent prefetch defers nothing, like the navigation it starts
|
|
170
|
-
* early (R16). Both halves are GETs: a request of
|
|
185
|
+
* The kind a partial request plans its deferral as, if any (requestKind's
|
|
186
|
+
* answer, filtered). Both halves of the feature are GETs: a request of
|
|
171
187
|
* another method that carries either marker is a normal request. A prefetch
|
|
172
188
|
* defers only on a plain GET navigation: an action re-renders what it
|
|
173
189
|
* changed, and a shell capture is never a prefetch whatever the request that
|
|
@@ -175,20 +191,22 @@ export declare function resolveInterceptDeferralScope(intercept: InterceptEntry)
|
|
|
175
191
|
* capture runs the full match): each one states its own reason.
|
|
176
192
|
*/
|
|
177
193
|
export declare function partialDeferralMode(request: {
|
|
178
|
-
|
|
179
|
-
prefetch: boolean;
|
|
194
|
+
kind: RequestKind | undefined;
|
|
180
195
|
method: string;
|
|
181
196
|
isAction: boolean;
|
|
182
197
|
isShellCapture: boolean;
|
|
183
|
-
}):
|
|
198
|
+
}): RequestKind | undefined;
|
|
184
199
|
export declare const NOTHING_HELD: ReadonlySet<string>;
|
|
185
200
|
/**
|
|
186
201
|
* The plan for one match, or undefined when the tree declares no flag and the
|
|
187
202
|
* request is not a fill (the common case: nothing to carry). `held` is the
|
|
188
203
|
* client's segment ids as the request listed them. `scope` is the chain's
|
|
189
204
|
* unless the request resolves an intercept (resolveInterceptDeferralScope).
|
|
205
|
+
* An intent prefetch plans as a prefetch over `scope.intent`: a tree whose
|
|
206
|
+
* flags are all `"intent"` defers nothing in it, and its response is the
|
|
207
|
+
* navigation's (no `_payloadDeferred`, so no source scope).
|
|
190
208
|
*/
|
|
191
|
-
export declare function planPrefetchDeferral(entries: readonly EntryData[],
|
|
209
|
+
export declare function planPrefetchDeferral(entries: readonly EntryData[], requested: RequestKind | undefined, matched: {
|
|
192
210
|
pr?: boolean;
|
|
193
211
|
od?: boolean;
|
|
194
212
|
}, cacheScope: CacheScope | null | undefined, held?: ReadonlySet<string>, scope?: DeferralScope): PrefetchDeferral | undefined;
|
|
@@ -3,6 +3,7 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
3
3
|
import type { ReactNode } from "react";
|
|
4
4
|
import type { PartialCacheOptions, ErrorBoundaryHandler, Handler, LoaderDefinition, MiddlewareFn, NotFoundBoundaryHandler, ShouldRevalidateFn, TransitionConfig } from "../types";
|
|
5
5
|
import type { ContextVar } from "../context-var.js";
|
|
6
|
+
import type { PrefetchFlag } from "../prefetch-markers.js";
|
|
6
7
|
import { type WarmContext } from "../prerender/warm-request.js";
|
|
7
8
|
/**
|
|
8
9
|
* Performance metric entry for a single measured operation
|
|
@@ -91,13 +92,13 @@ export type EntryPropDatas = {
|
|
|
91
92
|
export type EntryPropRender = {
|
|
92
93
|
loading?: ReactNode | false;
|
|
93
94
|
/**
|
|
94
|
-
* loading(fallback, { prefetch: false }) with a renderable
|
|
95
|
-
*
|
|
95
|
+
* loading(fallback, { prefetch: false | "intent" }) with a renderable
|
|
96
|
+
* fallback: a prefetch defers what the fallback covers while the entry's
|
|
96
97
|
* segment is new to the client
|
|
97
98
|
* (router/segment-resolution/prefetch-deferral.ts). Set on both DSL
|
|
98
99
|
* evaluations, whatever `ssr` did to `loading`.
|
|
99
100
|
*/
|
|
100
|
-
loadingPrefetch?:
|
|
101
|
+
loadingPrefetch?: PrefetchFlag;
|
|
101
102
|
/**
|
|
102
103
|
* loading(fallback, { ssr: false }), on both DSL evaluations; a document
|
|
103
104
|
* then sets `loading` to false (applyDocumentModeLoading).
|
|
@@ -137,11 +138,11 @@ export type LoaderEntry = {
|
|
|
137
138
|
*/
|
|
138
139
|
bake?: true;
|
|
139
140
|
/**
|
|
140
|
-
* loader(Def, { prefetch: false }): a
|
|
141
|
+
* loader(Def, { prefetch: false | "intent" }): a prefetch defers this
|
|
141
142
|
* registration while its segment is new to the client
|
|
142
143
|
* (router/segment-resolution/prefetch-deferral.ts).
|
|
143
144
|
*/
|
|
144
|
-
prefetch?:
|
|
145
|
+
prefetch?: PrefetchFlag;
|
|
145
146
|
/**
|
|
146
147
|
* The declaring layout's / parallel's / cache()'s include scope, for the
|
|
147
148
|
* loader body's `ctx.reverse(".name")` (its DSL kickoff carries it,
|
|
@@ -66,13 +66,13 @@ export interface ServeShellRequestOptions<TEnv = any> {
|
|
|
66
66
|
* `result.replayStatus`.
|
|
67
67
|
*
|
|
68
68
|
* `prefetch: true` sends the speculative kind a viewport or render
|
|
69
|
-
* `<Link>` prefetch sends (`X-Rango-Prefetch: 1`), so
|
|
70
|
-
*
|
|
71
|
-
* lists them. A hover's
|
|
72
|
-
* serve it with `prefetch: true` and `headers: {
|
|
73
|
-
* "intent" }`, which `prefetch: true` leaves as given.
|
|
74
|
-
* sends the request the browser follows an adopted prefetch
|
|
75
|
-
* (`_rsc_fill`): pass the ids the prefetch delivered, without the deferred
|
|
69
|
+
* `<Link>` prefetch sends (`X-Rango-Prefetch: 1`), so loaders and
|
|
70
|
+
* `loading()` entries flagged `prefetch: false` or `"intent"` are deferred:
|
|
71
|
+
* `result.readDeferred()` lists them. A hover's intent prefetch defers
|
|
72
|
+
* `false` only; serve it with `prefetch: true` and `headers: {
|
|
73
|
+
* "X-Rango-Prefetch": "intent" }`, which `prefetch: true` leaves as given.
|
|
74
|
+
* `fill: true` sends the request the browser follows an adopted prefetch
|
|
75
|
+
* with (`_rsc_fill`): pass the ids the prefetch delivered, without the deferred
|
|
76
76
|
* ones, as `segments`, and the same URL as `from`. `interceptSource` sends
|
|
77
77
|
* the intercept source (`X-RSC-Router-Intercept-Source`) the browser sends
|
|
78
78
|
* from an open modal and with the fill of an intercept, whose `from` is
|