@rangojs/router 0.31.0 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/bin/browser-7KIJ5HSV.js +15 -0
- package/dist/bin/chunk-3B7CKBRO.js +463 -0
- package/dist/bin/chunk-524VACCG.js +71 -0
- package/dist/bin/chunk-LD7UBHT4.js +196 -0
- package/dist/bin/chunk-TO6CRWLN.js +1429 -0
- package/dist/bin/chunk-TTXC2U3G.js +2477 -0
- package/dist/bin/client-urls-projection-DQLWPDAT.js +28 -0
- package/dist/bin/forward-user-plugins-URBZKJP4.js +62 -0
- package/dist/bin/rango.js +42 -10143
- package/dist/bin/runtime-EPUDEHO4.js +596 -0
- package/dist/bin/runtime-discovery-CZ2QHT4Q.js +352 -0
- package/dist/bin/state-ACD2MGKJ.js +16 -0
- package/dist/bin/temp-rsc-server-QRR6HD6T.js +3190 -0
- package/dist/types/bin/rango.d.ts +1 -0
- package/dist/types/browser/action-fence.d.ts +20 -0
- package/dist/types/browser/cookie-name.d.ts +51 -10
- package/dist/types/browser/history-state.d.ts +13 -2
- package/dist/types/browser/invalidate-client-cache.d.ts +8 -6
- package/dist/types/browser/navigation-bridge.d.ts +36 -1
- package/dist/types/browser/navigation-client.d.ts +19 -0
- package/dist/types/browser/navigation-store-handle.d.ts +37 -4
- package/dist/types/browser/navigation-store.d.ts +2 -2
- package/dist/types/browser/navigation-transaction.d.ts +0 -1
- package/dist/types/browser/partial-update.d.ts +13 -0
- package/dist/types/browser/prefetch/cache.d.ts +96 -8
- package/dist/types/browser/prefetch/fetch.d.ts +26 -2
- package/dist/types/browser/prefetch/queue.d.ts +3 -2
- package/dist/types/browser/rango-state.d.ts +34 -5
- package/dist/types/browser/react/Link.d.ts +19 -13
- package/dist/types/browser/react/location-state-shared.d.ts +2 -1
- package/dist/types/browser/types.d.ts +25 -0
- package/dist/types/build/generate-route-types.d.ts +1 -1
- package/dist/types/build/route-types/include-resolution.d.ts +9 -1
- package/dist/types/build/route-types/router-processing.d.ts +3 -1
- package/dist/types/cache/cache-key-utils.d.ts +12 -8
- package/dist/types/cache/cache-scope.d.ts +4 -2
- package/dist/types/cache/cf/cf-cache-types.d.ts +9 -5
- package/dist/types/cache/document-cache-policy.d.ts +9 -1
- package/dist/types/cache/memory-segment-store.d.ts +3 -2
- package/dist/types/cache/segment-codec.d.ts +2 -2
- package/dist/types/cache/shell-snapshot.d.ts +1 -1
- package/dist/types/cache/types.d.ts +12 -7
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +2 -1
- package/dist/types/client-urls/types.d.ts +7 -5
- package/dist/types/prefetch-markers.d.ts +9 -0
- package/dist/types/prefetch-source-scope.d.ts +66 -0
- package/dist/types/route-definition/helpers-types.d.ts +8 -5
- package/dist/types/route-definition/redirect.d.ts +5 -0
- package/dist/types/router/intercept-resolution.d.ts +25 -11
- package/dist/types/router/match-context.d.ts +1 -0
- package/dist/types/router/match-handlers.d.ts +1 -1
- package/dist/types/router/middleware-types.d.ts +6 -0
- package/dist/types/router/prefetch-cache-ttl.d.ts +11 -3
- package/dist/types/router/router-context.d.ts +1 -1
- package/dist/types/router/router-options.d.ts +6 -3
- package/dist/types/router/segment-resolution/loader-cache.d.ts +8 -0
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +23 -7
- package/dist/types/router/segment-resolution/revalidation.d.ts +19 -0
- package/dist/types/router/source-reads.d.ts +53 -0
- package/dist/types/router/types.d.ts +1 -1
- package/dist/types/rsc/helpers.d.ts +8 -3
- package/dist/types/rsc/prefetch-render-error.d.ts +7 -2
- package/dist/types/rsc/request-stages.d.ts +4 -0
- package/dist/types/rsc/shell-serve.d.ts +6 -1
- package/dist/types/server/context.d.ts +23 -6
- package/dist/types/server/cookie-store.d.ts +15 -1
- package/dist/types/server/document-location-state.d.ts +59 -0
- package/dist/types/server/handle-store.d.ts +2 -0
- package/dist/types/server/request-context.d.ts +65 -12
- package/dist/types/testing/e2e/server.d.ts +16 -1
- package/dist/types/testing/render-route.d.ts +83 -2
- package/dist/types/testing/serve-shell-request.d.ts +15 -3
- package/dist/types/types/handler-context.d.ts +21 -4
- package/dist/types/types/loader-types.d.ts +7 -4
- package/dist/types/types/segments.d.ts +18 -1
- package/dist/types/urls/path-helper-types.d.ts +7 -5
- package/dist/types/vite/temp-rsc-server.d.ts +2 -2
- package/dist/types/vite/utils/forward-user-plugins.d.ts +32 -2
- package/dist/vite/index.js +451 -191
- package/package.json +2 -2
- package/skills/app-structure/SKILL.md +74 -0
- package/skills/app-structure/e-commerce.md +10 -9
- package/skills/app-structure/large-app.md +15 -2
- package/skills/caching/SKILL.md +10 -5
- package/skills/client-urls/SKILL.md +18 -11
- package/skills/comparison/references/framework-comparison.md +16 -10
- package/skills/document-cache/SKILL.md +13 -0
- package/skills/hooks/navigation.md +8 -8
- package/skills/hooks/state.md +132 -12
- package/skills/intercept/SKILL.md +33 -11
- package/skills/layout/SKILL.md +11 -0
- package/skills/links/SKILL.md +101 -4
- package/skills/loader/SKILL.md +49 -22
- package/skills/migrate-nextjs/SKILL.md +8 -2
- package/skills/migrate-react-router/data-and-actions.md +5 -0
- package/skills/ppr/SKILL.md +38 -5
- package/skills/prerender/SKILL.md +4 -1
- package/skills/route/SKILL.md +6 -6
- package/skills/router-setup/SKILL.md +2 -1
- package/skills/server-actions/SKILL.md +49 -8
- package/skills/testing/client-components.md +73 -32
- package/skills/testing/serve-shell-request.md +45 -20
- package/skills/testing/server-actions.md +8 -0
- package/skills/ux-architecture/SKILL.md +67 -15
- package/src/bin/rango.ts +39 -13
- package/src/browser/action-fence.ts +72 -0
- package/src/browser/cookie-name.ts +82 -14
- package/src/browser/history-state.ts +24 -2
- package/src/browser/invalidate-client-cache.ts +9 -7
- package/src/browser/navigation-bridge.ts +164 -32
- package/src/browser/navigation-client.ts +95 -20
- package/src/browser/navigation-store-handle.ts +67 -5
- package/src/browser/navigation-store.ts +86 -17
- package/src/browser/navigation-transaction.ts +0 -6
- package/src/browser/partial-update.ts +31 -12
- package/src/browser/prefetch/cache.ts +188 -11
- package/src/browser/prefetch/fetch.ts +211 -51
- package/src/browser/prefetch/queue.ts +3 -2
- package/src/browser/rango-state.ts +50 -8
- package/src/browser/react/Link.tsx +24 -16
- package/src/browser/react/location-state-shared.ts +8 -1
- package/src/browser/react/use-navigation.ts +1 -1
- package/src/browser/rsc-router.tsx +13 -3
- package/src/browser/server-action-bridge.ts +3 -3
- package/src/browser/types.ts +25 -0
- package/src/build/generate-route-types.ts +1 -0
- package/src/build/route-types/include-resolution.ts +40 -3
- package/src/build/route-types/router-processing.ts +170 -25
- package/src/build/route-types/scan-filter.ts +1 -0
- package/src/build/runtime-discovery.ts +46 -8
- package/src/cache/cache-key-utils.ts +12 -8
- package/src/cache/cache-runtime.ts +5 -1
- package/src/cache/cache-scope.ts +28 -7
- package/src/cache/cf/cf-cache-types.ts +9 -5
- package/src/cache/document-cache-policy.ts +37 -1
- package/src/cache/document-cache.ts +12 -0
- package/src/cache/memory-segment-store.ts +3 -2
- package/src/cache/segment-codec.ts +2 -2
- package/src/cache/shell-snapshot.ts +1 -1
- package/src/cache/types.ts +12 -7
- package/src/cache/vercel/vercel-cache-store.ts +2 -1
- package/src/client-urls/types.ts +7 -5
- package/src/prefetch-markers.ts +10 -0
- package/src/prefetch-source-scope.ts +137 -0
- package/src/route-definition/helpers-types.ts +8 -5
- package/src/route-definition/redirect.ts +5 -0
- package/src/router/intercept-resolution.ts +94 -50
- package/src/router/loader-resolution.ts +8 -6
- package/src/router/match-api.ts +33 -29
- package/src/router/match-context.ts +5 -0
- package/src/router/match-handlers.ts +2 -1
- package/src/router/match-middleware/cache-lookup.ts +39 -14
- package/src/router/match-result.ts +45 -85
- package/src/router/middleware-types.ts +6 -0
- package/src/router/navigation-snapshot.ts +18 -3
- package/src/router/prefetch-cache-ttl.ts +15 -3
- package/src/router/revalidation.ts +63 -13
- package/src/router/router-context.ts +2 -1
- package/src/router/router-options.ts +6 -3
- package/src/router/segment-resolution/loader-cache.ts +21 -3
- package/src/router/segment-resolution/prefetch-deferral.ts +38 -31
- package/src/router/segment-resolution/revalidation.ts +40 -12
- package/src/router/source-reads.ts +122 -0
- package/src/router/types.ts +2 -1
- package/src/router.ts +11 -12
- package/src/rsc/helpers.ts +20 -6
- package/src/rsc/prefetch-render-error.ts +7 -2
- package/src/rsc/progressive-enhancement.ts +8 -2
- package/src/rsc/request-stages.ts +87 -1
- package/src/rsc/rsc-rendering.ts +78 -32
- package/src/rsc/server-action.ts +2 -1
- package/src/rsc/shell-capture.ts +4 -5
- package/src/rsc/shell-serve.ts +12 -1
- package/src/segment-system.tsx +1 -1
- package/src/server/context.ts +37 -6
- package/src/server/cookie-store.ts +22 -1
- package/src/server/document-location-state.ts +334 -0
- package/src/server/handle-store.ts +2 -0
- package/src/server/request-context.ts +137 -33
- package/src/testing/dispatch.ts +6 -0
- package/src/testing/e2e/fixture.ts +4 -12
- package/src/testing/e2e/server.ts +91 -4
- package/src/testing/render-route.tsx +309 -10
- package/src/testing/run-revalidate.ts +11 -1
- package/src/testing/serve-shell-request.ts +22 -3
- package/src/types/handler-context.ts +21 -4
- package/src/types/loader-types.ts +7 -4
- package/src/types/segments.ts +18 -1
- package/src/urls/path-helper-types.ts +7 -5
- package/src/vite/temp-rsc-server.ts +2 -2
- package/src/vite/utils/forward-user-plugins.ts +13 -5
|
@@ -35,6 +35,16 @@
|
|
|
35
35
|
* not modeled). navigate(url, { streamUntil }) gives the navigation a
|
|
36
36
|
* response stream that stays open past the commit (isStreaming).
|
|
37
37
|
* useAction().state stays idle: no action runs.
|
|
38
|
+
* - prefetch(url) keeps a page as a prefetch made now carries it, and
|
|
39
|
+
* action() lands a server action's response: the page on screen
|
|
40
|
+
* re-renders with its post-write loader data, and, without
|
|
41
|
+
* `keepClientCache`, production's store marks every history entry and
|
|
42
|
+
* prefetch stale (browser/navigation-store.ts). A navigate(url) to a
|
|
43
|
+
* prefetched page commits the prefetch's data; when an action made it
|
|
44
|
+
* stale, the page then revalidates once the navigation settled, in the
|
|
45
|
+
* lane production's Back uses (useNavigation() idle), with the loader
|
|
46
|
+
* data current then. The prefetch request, its decode and the HTTP cache
|
|
47
|
+
* are e2e territory.
|
|
38
48
|
* - A spec's transition({ when }) decides every commit with production's
|
|
39
49
|
* browser decision: navigate() and useRouter().push model kind "push",
|
|
40
50
|
* useRouter().replace kind "replace", refresh() kind "revalidate", and a
|
|
@@ -49,8 +59,8 @@
|
|
|
49
59
|
* written there, and another page is on screen, its page is restored
|
|
50
60
|
* from the history cache, with the handle values it had, as production's
|
|
51
61
|
* restore is; otherwise the tree keeps its location. A stale entry's
|
|
52
|
-
* revalidation, a restore that fetches, a server action's
|
|
53
|
-
* redirect's and an intercept's are e2e territory.
|
|
62
|
+
* revalidation on Back, a restore that fetches, a server action's
|
|
63
|
+
* location state, a redirect's and an intercept's are e2e territory.
|
|
54
64
|
* What it DOES cover: client hooks that read NavigationProvider /
|
|
55
65
|
* OutletContext — useParams, useReverse, useHref, useMount, useNavigation,
|
|
56
66
|
* useRouter, usePathname, useSearchParams, Outlet/useOutlet nesting and seeded
|
|
@@ -116,6 +126,7 @@ import {
|
|
|
116
126
|
} from "../browser/react/location-state-shared.js";
|
|
117
127
|
import {
|
|
118
128
|
buildHistoryState,
|
|
129
|
+
applyDocumentLocationState,
|
|
119
130
|
clearLocationStateOnDocumentLoad,
|
|
120
131
|
pushHistoryWithIdx,
|
|
121
132
|
resolveNavigationState,
|
|
@@ -125,10 +136,17 @@ import type { Handle } from "../handle.js";
|
|
|
125
136
|
import type { ThemeConfig } from "../theme/types.js";
|
|
126
137
|
import { resolveThemeConfig } from "../theme/constants.js";
|
|
127
138
|
import { isUnderTestRunner } from "../runtime-env.js";
|
|
128
|
-
import {
|
|
139
|
+
import {
|
|
140
|
+
scheduleStalePrefetchRevalidation,
|
|
141
|
+
setupNavigationBridgeDelegatedPrefetch,
|
|
142
|
+
} from "../browser/navigation-bridge.js";
|
|
129
143
|
import { resetPrefetchObserverForTesting } from "../browser/prefetch/observer.js";
|
|
130
144
|
import type { PrefetchStrategy } from "../router/prefetch-default.js";
|
|
131
145
|
import { auditTreeCause, forgetSuspenseAudit } from "../suspense-audit.js";
|
|
146
|
+
import {
|
|
147
|
+
currentPrefetchEpoch,
|
|
148
|
+
isPrefetchStale,
|
|
149
|
+
} from "../browser/prefetch/cache.js";
|
|
132
150
|
|
|
133
151
|
const TEST_ORIGIN = "http://localhost";
|
|
134
152
|
let activePrefetchRegistrations = 0;
|
|
@@ -274,6 +292,25 @@ export interface RenderRouteOptions {
|
|
|
274
292
|
locationState?: ReadonlyArray<
|
|
275
293
|
readonly [LocationStateDefinition<any, any>, unknown]
|
|
276
294
|
>;
|
|
295
|
+
/**
|
|
296
|
+
* Location state the document response carries, as `[def, value]` pairs:
|
|
297
|
+
* what a handler, middleware or no-JS form action set with
|
|
298
|
+
* `ctx.setLocationState()` (or a document redirect carried) for this page
|
|
299
|
+
* load. Merged into the entry at start-up as initBrowserApp merges the
|
|
300
|
+
* bootstrap payload's: after `locationState` and, with `hydrate: true`,
|
|
301
|
+
* after the `clearOnReload` pass, so a `clearOnReload` slot given here is
|
|
302
|
+
* applied. With `hydrate: true` a reader hydrates as `undefined` and gets
|
|
303
|
+
* the value once the root has hydrated.
|
|
304
|
+
*
|
|
305
|
+
* @example
|
|
306
|
+
* await renderRoute([{ path: "/", Component: FlashBanner }], {
|
|
307
|
+
* hydrate: true,
|
|
308
|
+
* documentLocationState: [[FlashMessage, { text: "Payment declined" }]],
|
|
309
|
+
* });
|
|
310
|
+
*/
|
|
311
|
+
documentLocationState?: ReadonlyArray<
|
|
312
|
+
readonly [LocationStateDefinition<any, any>, unknown]
|
|
313
|
+
>;
|
|
277
314
|
/**
|
|
278
315
|
* Handles to seed by REFERENCE, for components that read handle output via
|
|
279
316
|
* `useHandle(SomeHandle)` (e.g. a client `Breadcrumbs` trail). Each entry is
|
|
@@ -497,6 +534,59 @@ export interface TestRouterHandle {
|
|
|
497
534
|
* transition({ when }) decides it with kind "revalidate" (`to` is `from`).
|
|
498
535
|
*/
|
|
499
536
|
refresh(): Promise<void>;
|
|
537
|
+
/**
|
|
538
|
+
* Prefetch `url`, as a `<Link>` hover or viewport trigger does: the page is
|
|
539
|
+
* kept with the loader data its response would carry now (the render-time
|
|
540
|
+
* seeds, `options.loaders` merged over). A later `navigate(url)` without
|
|
541
|
+
* `loaders` commits that data, whatever the seeds are by then.
|
|
542
|
+
*
|
|
543
|
+
* The prefetch is stamped with production's stale epoch
|
|
544
|
+
* (browser/prefetch/cache.ts): an `action()` without `keepClientCache`
|
|
545
|
+
* makes it stale. A click on a stale prefetch commits it at once, then the
|
|
546
|
+
* page revalidates once the navigation settled (its `streamUntil` ended),
|
|
547
|
+
* in Back's stale-revalidation lane: `useNavigation().state` stays "idle"
|
|
548
|
+
* and `isNavigating` reads true while its request is open, `useLinkStatus`
|
|
549
|
+
* shows no pending state, and the commit runs through commitInTransition
|
|
550
|
+
* with kind "revalidate", reading the loader data current then. A newer
|
|
551
|
+
* navigation aborts it.
|
|
552
|
+
*
|
|
553
|
+
* @example
|
|
554
|
+
* router.prefetch("/cart"); // carries count 2
|
|
555
|
+
* await router.action({ loaders: [[BasketLoader, { count: 3 }]] });
|
|
556
|
+
* await router.navigate("/cart"); // count 2, at once
|
|
557
|
+
* await waitFor(() => expect(count()).toBe("3")); // revalidated
|
|
558
|
+
*/
|
|
559
|
+
prefetch(url: string, options?: Pick<RenderRouteOptions, "loaders">): void;
|
|
560
|
+
/**
|
|
561
|
+
* Land a server action's response on the page on screen. `loaders` are the
|
|
562
|
+
* loader values after the action's write: they become the data every
|
|
563
|
+
* later response carries, and the page on screen re-renders with them in
|
|
564
|
+
* the action lane (commitInTransition, kind "action", unless
|
|
565
|
+
* transition({ when }) gates it off). Without `keepClientCache`,
|
|
566
|
+
* production's store then marks every history entry and every prefetch
|
|
567
|
+
* stale (`markCacheAsStaleAndBroadcast()`), as the action bridge does:
|
|
568
|
+
* nothing is flushed and the state cookie is not rotated.
|
|
569
|
+
* `keepClientCache: true` is the `keepClientCache()` directive: caches stay
|
|
570
|
+
* fresh. The action's own lifecycle (`useAction()`), its follow-up and its
|
|
571
|
+
* redirect are e2e territory.
|
|
572
|
+
*/
|
|
573
|
+
action(
|
|
574
|
+
options?: Pick<RenderRouteOptions, "loaders"> & {
|
|
575
|
+
keepClientCache?: boolean;
|
|
576
|
+
},
|
|
577
|
+
): Promise<void>;
|
|
578
|
+
/**
|
|
579
|
+
* The background revalidations a click on a stale prefetch started, in
|
|
580
|
+
* order: the page's pathname and search, and the segment ids the request
|
|
581
|
+
* holds, as production's bridge plans them
|
|
582
|
+
* (navigation-bridge.ts scheduleStalePrefetchRevalidation). A click on a
|
|
583
|
+
* fresh prefetch, or after a `keepClientCache` action, starts none.
|
|
584
|
+
*
|
|
585
|
+
* @example
|
|
586
|
+
* await router.navigate("/cart"); // a stale prefetch
|
|
587
|
+
* await waitFor(() => expect(router.revalidations()).toHaveLength(1));
|
|
588
|
+
*/
|
|
589
|
+
revalidations(): ReadonlyArray<{ url: string; segmentIds: string[] }>;
|
|
500
590
|
/** The current committed pathname. */
|
|
501
591
|
pathname(): string;
|
|
502
592
|
/** The current committed params. */
|
|
@@ -510,6 +600,19 @@ export interface TestRouterHandle {
|
|
|
510
600
|
/** Result of renderRoute: RTL's render result plus the router handle. */
|
|
511
601
|
export type RenderRouteResult = RenderResult & { router: TestRouterHandle };
|
|
512
602
|
|
|
603
|
+
/** `[def, value]` seeds as `history.state` slots, each def keyed. */
|
|
604
|
+
function locationStateSlots(
|
|
605
|
+
seeds:
|
|
606
|
+
| ReadonlyArray<readonly [LocationStateDefinition<any, any>, unknown]>
|
|
607
|
+
| undefined,
|
|
608
|
+
): Record<string, unknown> {
|
|
609
|
+
const slots: Record<string, unknown> = {};
|
|
610
|
+
for (const [def, value] of seeds ?? []) {
|
|
611
|
+
slots[withLocationStateKey(def).__rsc_ls_key] = value;
|
|
612
|
+
}
|
|
613
|
+
return slots;
|
|
614
|
+
}
|
|
615
|
+
|
|
513
616
|
/** Result of renderRoute with `hydrate: true`. */
|
|
514
617
|
export type RenderRouteHydrateResult = RenderRouteResult & {
|
|
515
618
|
/** The HTML the server pass produced, as placed in the container. */
|
|
@@ -743,14 +846,15 @@ export async function renderRoute(
|
|
|
743
846
|
// The tree has no app version (its bridge reports none), so a seed needs
|
|
744
847
|
// none either. Reset what an initBrowserApp in the same file may have left.
|
|
745
848
|
setLocationStateVersion(undefined);
|
|
746
|
-
|
|
747
|
-
for (const [def, value] of options.locationState ?? []) {
|
|
748
|
-
stateObj[withLocationStateKey(def).__rsc_ls_key] = value;
|
|
749
|
-
}
|
|
750
|
-
window.history.replaceState(stateObj, "");
|
|
849
|
+
window.history.replaceState(locationStateSlots(options.locationState), "");
|
|
751
850
|
// Hydrate mode is a document load of the seeded entry: run start-up's
|
|
752
|
-
//
|
|
851
|
+
// steps (initBrowserApp), not a copy of them.
|
|
753
852
|
if (options.hydrate) clearLocationStateOnDocumentLoad();
|
|
853
|
+
if (options.documentLocationState) {
|
|
854
|
+
applyDocumentLocationState(
|
|
855
|
+
locationStateSlots(options.documentLocationState),
|
|
856
|
+
);
|
|
857
|
+
}
|
|
754
858
|
}
|
|
755
859
|
|
|
756
860
|
const resolve = (pathname: string): ResolvedMatch => {
|
|
@@ -857,6 +961,8 @@ export async function renderRoute(
|
|
|
857
961
|
}
|
|
858
962
|
|
|
859
963
|
type NavigateCall = Pick<RenderRouteOptions, "loaders" | "handles"> & {
|
|
964
|
+
/** The loader data the response carries: a prefetch's (prefetch()). */
|
|
965
|
+
seeds?: Record<string, unknown>;
|
|
860
966
|
history?: Pick<NavigateOptionsInternal, "state" | "replace" | "transition">;
|
|
861
967
|
kind?: TransitionWhenKind;
|
|
862
968
|
transition?: boolean;
|
|
@@ -897,6 +1003,30 @@ export async function renderRoute(
|
|
|
897
1003
|
const written = entries.get(entryIndex());
|
|
898
1004
|
if (written) written.state = window.history.state;
|
|
899
1005
|
});
|
|
1006
|
+
/**
|
|
1007
|
+
* The history-cache reads production's stale-prefetch guards make
|
|
1008
|
+
* (navigation-bridge.ts scheduleStalePrefetchRevalidation), over this
|
|
1009
|
+
* harness's history: the store's own history key never moves here, so the
|
|
1010
|
+
* key on screen is its location's, and the one cached page is the entry on
|
|
1011
|
+
* screen. Marking it stale writes nothing: a stale entry's revalidation on
|
|
1012
|
+
* Back is not modeled.
|
|
1013
|
+
*/
|
|
1014
|
+
const historyView: Pick<
|
|
1015
|
+
NavigationStore,
|
|
1016
|
+
"getCachedSegments" | "getHistoryKey" | "updateCacheHandleData"
|
|
1017
|
+
> = {
|
|
1018
|
+
getHistoryKey: () => generateHistoryKey(eventController.getLocation().href),
|
|
1019
|
+
getCachedSegments: (key) => {
|
|
1020
|
+
const page = entries.get(entryOnScreen);
|
|
1021
|
+
if (!page || generateHistoryKey(page.url.href) !== key) return undefined;
|
|
1022
|
+
return {
|
|
1023
|
+
segments: page.segments,
|
|
1024
|
+
stale: false,
|
|
1025
|
+
handleData: page.handleData,
|
|
1026
|
+
};
|
|
1027
|
+
},
|
|
1028
|
+
updateCacheHandleData: () => {},
|
|
1029
|
+
};
|
|
900
1030
|
/** The page being left keeps its pushes for a back/forward onto it. */
|
|
901
1031
|
const leaveEntry = (): void => {
|
|
902
1032
|
const left = entries.get(entryOnScreen);
|
|
@@ -911,9 +1041,38 @@ export async function renderRoute(
|
|
|
911
1041
|
target: string,
|
|
912
1042
|
navOptions?: NavigateCall,
|
|
913
1043
|
): Promise<void> => {
|
|
1044
|
+
// A click on a prefetched page commits the prefetch's data (prefetch());
|
|
1045
|
+
// refresh() and a navigation that seeds its own loaders fetch.
|
|
1046
|
+
const prefetchedPage =
|
|
1047
|
+
navOptions?.kind === undefined && navOptions?.loaders === undefined
|
|
1048
|
+
? prefetchedPages.get(prefetchKeyOf(target))
|
|
1049
|
+
: undefined;
|
|
1050
|
+
if (prefetchedPage) {
|
|
1051
|
+
navOptions = { ...navOptions, seeds: prefetchedPage.loaderData };
|
|
1052
|
+
// A stale entry is served once (cache.ts consumePrefetch): the next
|
|
1053
|
+
// click on the link fetches fresh.
|
|
1054
|
+
if (isPrefetchStale(prefetchedPage)) {
|
|
1055
|
+
prefetchedPages.delete(prefetchKeyOf(target));
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
914
1058
|
const handle = eventController.startNavigation(
|
|
915
1059
|
new URL(target, TEST_ORIGIN).href,
|
|
916
1060
|
);
|
|
1061
|
+
// An action made the prefetch stale: once this navigation settled (its
|
|
1062
|
+
// commit and its stream), the page revalidates. Production's own guards
|
|
1063
|
+
// and plan decide it (navigation-bridge.ts
|
|
1064
|
+
// scheduleStalePrefetchRevalidation), over the history this harness
|
|
1065
|
+
// keeps (historyView).
|
|
1066
|
+
const entry = eventController.getUnsettledNavigation();
|
|
1067
|
+
if (prefetchedPage && isPrefetchStale(prefetchedPage) && entry) {
|
|
1068
|
+
scheduleStalePrefetchRevalidation(
|
|
1069
|
+
historyView,
|
|
1070
|
+
eventController,
|
|
1071
|
+
entry,
|
|
1072
|
+
generateHistoryKey(new URL(target, TEST_ORIGIN).href),
|
|
1073
|
+
({ segmentIds }) => void revalidateStalePrefetch(target, segmentIds),
|
|
1074
|
+
);
|
|
1075
|
+
}
|
|
917
1076
|
// Runs in the caller's task, so a <Link> click's own act() takes it.
|
|
918
1077
|
// Awaited: React retries a held page with this update, and an unawaited
|
|
919
1078
|
// act() drops that suspended retry for good.
|
|
@@ -975,7 +1134,7 @@ export async function renderRoute(
|
|
|
975
1134
|
const segments = buildSegments(
|
|
976
1135
|
routes,
|
|
977
1136
|
match.params,
|
|
978
|
-
seedLoaders(loaderData, navOptions?.loaders),
|
|
1137
|
+
seedLoaders(navOptions?.seeds ?? loaderData, navOptions?.loaders),
|
|
979
1138
|
mount,
|
|
980
1139
|
);
|
|
981
1140
|
const metadata = makeMetadata(nextUrl.pathname, segments, match.params);
|
|
@@ -1075,6 +1234,143 @@ export async function renderRoute(
|
|
|
1075
1234
|
return navigate(current.pathname + current.search, { kind: "revalidate" });
|
|
1076
1235
|
};
|
|
1077
1236
|
|
|
1237
|
+
// prefetch(): the pages prefetched so far, by pathname and search, each with
|
|
1238
|
+
// the loader data its response carried and the stale epoch it began under.
|
|
1239
|
+
interface PrefetchedPage {
|
|
1240
|
+
loaderData: Record<string, unknown>;
|
|
1241
|
+
epoch: number;
|
|
1242
|
+
}
|
|
1243
|
+
const prefetchedPages = new Map<string, PrefetchedPage>();
|
|
1244
|
+
const prefetchKeyOf = (target: string): string => {
|
|
1245
|
+
const parsed = new URL(target, TEST_ORIGIN);
|
|
1246
|
+
return parsed.pathname + parsed.search;
|
|
1247
|
+
};
|
|
1248
|
+
const prefetch = (
|
|
1249
|
+
target: string,
|
|
1250
|
+
prefetchOptions?: Pick<RenderRouteOptions, "loaders">,
|
|
1251
|
+
): void => {
|
|
1252
|
+
prefetchedPages.set(prefetchKeyOf(target), {
|
|
1253
|
+
loaderData: seedLoaders(loaderData, prefetchOptions?.loaders),
|
|
1254
|
+
epoch: currentPrefetchEpoch(),
|
|
1255
|
+
});
|
|
1256
|
+
};
|
|
1257
|
+
|
|
1258
|
+
/**
|
|
1259
|
+
* Re-render the page at `url` with the loader data current now, decided by
|
|
1260
|
+
* transition({ when }) as `kind`. `commit()` hands it over as production's
|
|
1261
|
+
* action and stale-revalidation commits do (browser/partial-update.ts):
|
|
1262
|
+
* through commitInTransition unless gated off.
|
|
1263
|
+
*/
|
|
1264
|
+
const renderPageAgain = async (
|
|
1265
|
+
url: URL,
|
|
1266
|
+
kind: "action" | "revalidate",
|
|
1267
|
+
renderOptions: { forceAwait?: boolean; isAction?: boolean },
|
|
1268
|
+
): Promise<{ commit(): Promise<void> }> => {
|
|
1269
|
+
const match = resolve(url.pathname);
|
|
1270
|
+
const segments = buildSegments(routes, match.params, loaderData, mount);
|
|
1271
|
+
const metadata = makeMetadata(url.pathname, segments, match.params);
|
|
1272
|
+
const gatedOff = decideTransitionGatedOff(segments, () => ({
|
|
1273
|
+
kind,
|
|
1274
|
+
from: {
|
|
1275
|
+
url: eventController.getLocation().href,
|
|
1276
|
+
params: eventController.getParams(),
|
|
1277
|
+
routeName: leaf.name,
|
|
1278
|
+
state: window.history.state,
|
|
1279
|
+
},
|
|
1280
|
+
to: {
|
|
1281
|
+
url,
|
|
1282
|
+
params: match.params,
|
|
1283
|
+
routeName: leaf.name,
|
|
1284
|
+
state: window.history.state,
|
|
1285
|
+
},
|
|
1286
|
+
}));
|
|
1287
|
+
const root = await renderSegments(segments, {
|
|
1288
|
+
...renderOptions,
|
|
1289
|
+
outletPending: options.outletPending,
|
|
1290
|
+
transitionGatedOff: gatedOff,
|
|
1291
|
+
});
|
|
1292
|
+
return {
|
|
1293
|
+
commit: async () => {
|
|
1294
|
+
store.setSegmentIds(segments.map((s) => s.id));
|
|
1295
|
+
const shown = entries.get(entryOnScreen);
|
|
1296
|
+
if (shown) shown.segments = segments;
|
|
1297
|
+
const emit: UpdateSubscriber = (update) => store.emitUpdate(update);
|
|
1298
|
+
await act(async () => {
|
|
1299
|
+
auditTreeCause(kind === "action" ? "action" : "stale-revalidation");
|
|
1300
|
+
if (gatedOff) emit({ root, metadata });
|
|
1301
|
+
else {
|
|
1302
|
+
commitInTransition(
|
|
1303
|
+
store.loaders,
|
|
1304
|
+
emit,
|
|
1305
|
+
segments,
|
|
1306
|
+
{ root, metadata },
|
|
1307
|
+
kind,
|
|
1308
|
+
shouldStartViewTransition(segments, gatedOff) ? ["action"] : [],
|
|
1309
|
+
);
|
|
1310
|
+
}
|
|
1311
|
+
});
|
|
1312
|
+
},
|
|
1313
|
+
};
|
|
1314
|
+
};
|
|
1315
|
+
|
|
1316
|
+
/**
|
|
1317
|
+
* Back's stale-revalidation lane (navigation-bridge.ts revalidateInBackground),
|
|
1318
|
+
* after a click on a stale prefetch: a background navigation
|
|
1319
|
+
* (skipLoadingState) whose response arrives a task later, rendered with
|
|
1320
|
+
* the loader data current then, kind "revalidate". `segmentIds` are the
|
|
1321
|
+
* ids production's plan holds; the request is recorded (revalidations()).
|
|
1322
|
+
*/
|
|
1323
|
+
const revalidations: Array<{ url: string; segmentIds: string[] }> = [];
|
|
1324
|
+
const revalidateStalePrefetch = async (
|
|
1325
|
+
target: string,
|
|
1326
|
+
segmentIds: string[],
|
|
1327
|
+
): Promise<void> => {
|
|
1328
|
+
const nextUrl = new URL(target, TEST_ORIGIN);
|
|
1329
|
+
revalidations.push({
|
|
1330
|
+
url: nextUrl.pathname + nextUrl.search,
|
|
1331
|
+
segmentIds,
|
|
1332
|
+
});
|
|
1333
|
+
const handle = eventController.startNavigation(nextUrl.href, {
|
|
1334
|
+
skipLoadingState: true,
|
|
1335
|
+
});
|
|
1336
|
+
try {
|
|
1337
|
+
await act(() => eventController.flushRouteState());
|
|
1338
|
+
// Outside act(): a navigate() the test starts meanwhile runs its own.
|
|
1339
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1340
|
+
if (handle.signal.aborted) return;
|
|
1341
|
+
const page = await renderPageAgain(nextUrl, "revalidate", {
|
|
1342
|
+
forceAwait: true,
|
|
1343
|
+
});
|
|
1344
|
+
if (handle.signal.aborted) return;
|
|
1345
|
+
handle.complete(nextUrl);
|
|
1346
|
+
await page.commit();
|
|
1347
|
+
} finally {
|
|
1348
|
+
await act(() => {
|
|
1349
|
+
handle[Symbol.dispose]();
|
|
1350
|
+
eventController.flushRouteState();
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
};
|
|
1354
|
+
|
|
1355
|
+
// action(): the response of a server action, landing on the page on screen.
|
|
1356
|
+
const action = async (
|
|
1357
|
+
actionOptions?: Pick<RenderRouteOptions, "loaders"> & {
|
|
1358
|
+
keepClientCache?: boolean;
|
|
1359
|
+
},
|
|
1360
|
+
): Promise<void> => {
|
|
1361
|
+
for (const [loader, data] of actionOptions?.loaders ?? []) {
|
|
1362
|
+
loaderData[ensureSyntheticId(loader)] = data;
|
|
1363
|
+
}
|
|
1364
|
+
const page = await renderPageAgain(
|
|
1365
|
+
new URL(eventController.getLocation().href),
|
|
1366
|
+
"action",
|
|
1367
|
+
{ isAction: true },
|
|
1368
|
+
);
|
|
1369
|
+
// The action bridge's landing (server-action-bridge.ts finalizeAction).
|
|
1370
|
+
if (!actionOptions?.keepClientCache) store.markCacheAsStaleAndBroadcast();
|
|
1371
|
+
await page.commit();
|
|
1372
|
+
};
|
|
1373
|
+
|
|
1078
1374
|
// A back/forward onto the mounted entry or one a navigation wrote, whose
|
|
1079
1375
|
// page is not the one on screen: its page is restored from the entry, as production's
|
|
1080
1376
|
// history-cache restore does (navigation-bridge.ts handlePopstate). Not
|
|
@@ -1289,6 +1585,9 @@ export async function renderRoute(
|
|
|
1289
1585
|
: undefined,
|
|
1290
1586
|
}),
|
|
1291
1587
|
refresh,
|
|
1588
|
+
prefetch,
|
|
1589
|
+
action,
|
|
1590
|
+
revalidations: () => [...revalidations],
|
|
1292
1591
|
pathname: () => new URL(eventController.getLocation().href).pathname,
|
|
1293
1592
|
params: () => eventController.getParams(),
|
|
1294
1593
|
store,
|
|
@@ -121,7 +121,11 @@ export async function runRevalidate<TEnv = any>(
|
|
|
121
121
|
belongsToRoute: opts.belongsToRoute ?? true,
|
|
122
122
|
};
|
|
123
123
|
const fns = Array.isArray(fn) ? fn : [fn];
|
|
124
|
-
|
|
124
|
+
// evaluateRevalidation marks the context it decides on (_sourceHeld); a
|
|
125
|
+
// caller's own context leaves the run as it came in.
|
|
126
|
+
const own = opts.context as Record<string, unknown> | undefined;
|
|
127
|
+
const ownHadMark = own !== undefined && "_sourceHeld" in own;
|
|
128
|
+
const decision = runWithRequestContext(ctx as never, () =>
|
|
125
129
|
evaluateRevalidation({
|
|
126
130
|
segment,
|
|
127
131
|
prevParams: currentParams,
|
|
@@ -146,4 +150,10 @@ export async function runRevalidate<TEnv = any>(
|
|
|
146
150
|
stale: opts.stale ?? false,
|
|
147
151
|
}),
|
|
148
152
|
);
|
|
153
|
+
if (own === undefined || ownHadMark) return decision;
|
|
154
|
+
try {
|
|
155
|
+
return await decision;
|
|
156
|
+
} finally {
|
|
157
|
+
delete own._sourceHeld;
|
|
158
|
+
}
|
|
149
159
|
}
|
|
@@ -99,9 +99,12 @@ export interface ServeShellRequestOptions<TEnv = any> {
|
|
|
99
99
|
* (default none), advertising fragment passthrough. The replay decision is
|
|
100
100
|
* `result.replayStatus`.
|
|
101
101
|
*
|
|
102
|
-
* `prefetch: true` sends
|
|
103
|
-
* `X-Rango-Prefetch`
|
|
104
|
-
* entries are deferred: `result.readDeferred()`
|
|
102
|
+
* `prefetch: true` sends the speculative kind a viewport or render
|
|
103
|
+
* `<Link>` prefetch sends (`X-Rango-Prefetch: 1`), so `prefetch: false`
|
|
104
|
+
* loaders and `loading()` entries are deferred: `result.readDeferred()`
|
|
105
|
+
* lists them. A hover's or `prefetch()`'s intent prefetch defers nothing;
|
|
106
|
+
* serve it with `prefetch: true` and `headers: { "X-Rango-Prefetch":
|
|
107
|
+
* "intent" }`, which `prefetch: true` leaves as given. `fill: true`
|
|
105
108
|
* sends the request the browser follows an adopted prefetch with
|
|
106
109
|
* (`_rsc_fill`): pass the ids the prefetch delivered, without the deferred
|
|
107
110
|
* ones, as `segments`, and the same URL as `from`. `interceptSource` sends
|
|
@@ -269,6 +272,15 @@ export interface ServeShellRequestResult {
|
|
|
269
272
|
* hydrates without it.
|
|
270
273
|
*/
|
|
271
274
|
readPayloadFormState(): Promise<unknown>;
|
|
275
|
+
/**
|
|
276
|
+
* The payload's location state as the browser reads it: the slots
|
|
277
|
+
* (`Def.__rsc_ls_key` to value) a document's start-up merges into its
|
|
278
|
+
* entry (or a navigation's commit, for a `partial` request). Undefined when
|
|
279
|
+
* the payload carries none (or no Flight was rendered). A redirect carries
|
|
280
|
+
* a document request's state in a `Set-Cookie` instead: pass that cookie
|
|
281
|
+
* on the request of its target to read what the target's payload carries.
|
|
282
|
+
*/
|
|
283
|
+
readLocationState(): Promise<Record<string, unknown> | undefined>;
|
|
272
284
|
/**
|
|
273
285
|
* For an `action` request, the call's answer as the browser reads it: its
|
|
274
286
|
* result and the `revalidationError` its loaders' reruns report.
|
|
@@ -806,6 +818,13 @@ export async function serveShellRequest<TEnv = any>(
|
|
|
806
818
|
}>(flight);
|
|
807
819
|
return payload.formState;
|
|
808
820
|
},
|
|
821
|
+
async readLocationState() {
|
|
822
|
+
if (!flight) return undefined;
|
|
823
|
+
const payload = await decodePayload<{
|
|
824
|
+
metadata?: { locationState?: Record<string, unknown> };
|
|
825
|
+
}>(flight);
|
|
826
|
+
return payload.metadata?.locationState;
|
|
827
|
+
},
|
|
809
828
|
async readAction() {
|
|
810
829
|
return flight ? decodeAction(flight) : undefined;
|
|
811
830
|
},
|
|
@@ -381,9 +381,16 @@ export type HandlerContext<
|
|
|
381
381
|
*/
|
|
382
382
|
setTheme?: (theme: Theme) => void;
|
|
383
383
|
/**
|
|
384
|
-
* Attach location state entries to this response
|
|
385
|
-
*
|
|
386
|
-
*
|
|
384
|
+
* Attach location state entries to this response, for the useLocationState()
|
|
385
|
+
* hook to read on the entry this response renders. A client navigation
|
|
386
|
+
* delivers it with its payload; a document load (a link from outside the
|
|
387
|
+
* app, a no-JS form post) with its bootstrap payload, once the page has
|
|
388
|
+
* hydrated; a redirect carries it to its target (in a one-shot cookie on a
|
|
389
|
+
* document request). A document carrying it is sent
|
|
390
|
+
* `Cache-Control: private, no-store`.
|
|
391
|
+
*
|
|
392
|
+
* Entries accumulate across definitions; for one definition the last write
|
|
393
|
+
* wins (an earlier entry of it is replaced, not merged).
|
|
387
394
|
*
|
|
388
395
|
* @example
|
|
389
396
|
* ```typescript
|
|
@@ -510,6 +517,16 @@ export type InternalHandlerContext<
|
|
|
510
517
|
* request is a fill. See router/segment-resolution/prefetch-deferral.ts.
|
|
511
518
|
*/
|
|
512
519
|
_prefetchDeferral?: import("../router/segment-resolution/prefetch-deferral.js").PrefetchDeferral;
|
|
520
|
+
/**
|
|
521
|
+
* @internal A revalidate() predicate that read the source page
|
|
522
|
+
* (`currentParams`, `currentUrl`, `fromRouteName`) kept a held segment, so
|
|
523
|
+
* this match's diff is right only for pages with equal values of what the
|
|
524
|
+
* kept decisions read (#1331). Set by evaluateRevalidation
|
|
525
|
+
* (router/revalidation.ts) and the stored-entry keep
|
|
526
|
+
* (match-middleware/cache-lookup.ts); match-result.ts turns it into
|
|
527
|
+
* `sourceHeld`, which scopes a prefetch of the response to that group.
|
|
528
|
+
*/
|
|
529
|
+
_sourceHeld?: import("../router/source-reads.js").SourceHeld;
|
|
513
530
|
/**
|
|
514
531
|
* @internal DSL loaders registered and not started, by `$$id`: a prefetch
|
|
515
532
|
* deferred them, or a pass that skips loaders (`skipLoaders`) did not run
|
|
@@ -664,7 +681,7 @@ export type IsActionFn = (...actions: ActionRef[]) => boolean;
|
|
|
664
681
|
export type ShouldRevalidateFn<TParams = GenericParams, TEnv = any> = (args: {
|
|
665
682
|
/** Route params from the page being navigated away from. */
|
|
666
683
|
currentParams: TParams;
|
|
667
|
-
/** Full URL of the page being navigated away from. */
|
|
684
|
+
/** Full URL of the page being navigated away from, without its #fragment. */
|
|
668
685
|
currentUrl: URL;
|
|
669
686
|
/** Route params for the navigation target. */
|
|
670
687
|
nextParams: TParams;
|
|
@@ -207,10 +207,13 @@ export type LoaderFn<
|
|
|
207
207
|
export type LoaderOptions = {
|
|
208
208
|
ssr?: boolean;
|
|
209
209
|
/**
|
|
210
|
-
* `prefetch: false` keeps this loader out of prefetches. A
|
|
211
|
-
* prefetch (a partial request with
|
|
212
|
-
*
|
|
213
|
-
*
|
|
210
|
+
* `prefetch: false` keeps this loader out of speculative prefetches. A
|
|
211
|
+
* viewport or render `<Link>` prefetch (a partial request with
|
|
212
|
+
* `X-Rango-Prefetch: 1`) does not execute it; the payload marks it
|
|
213
|
+
* deferred. A hover prefetch and `useRouter().prefetch()`
|
|
214
|
+
* (`X-Rango-Prefetch: intent`) count as intent to click and run it, so a
|
|
215
|
+
* click after a hover needs no follow-up request. A document request and a
|
|
216
|
+
* navigation with no prefetch to adopt run it as usual.
|
|
214
217
|
*
|
|
215
218
|
* A deferred loader never blocks the navigation that adopts the prefetch:
|
|
216
219
|
* it behaves like a loader that is still streaming. Its own `<Suspense>`
|
package/src/types/segments.ts
CHANGED
|
@@ -161,7 +161,6 @@ export interface ResolvedSegment {
|
|
|
161
161
|
parallelName?: string; // For parallels: the parallel group name (used to match with revalidations)
|
|
162
162
|
// Loader-specific fields
|
|
163
163
|
loaderId?: string; // For loaders: the loader $$id identifier
|
|
164
|
-
_inherited?: boolean; // For inherited loaders: dedup marker for buildMatchResult
|
|
165
164
|
loaderData?: any; // For loaders: the resolved data from loader execution
|
|
166
165
|
/**
|
|
167
166
|
* True when this loader was awaited before first flush
|
|
@@ -299,6 +298,24 @@ export interface MatchResult {
|
|
|
299
298
|
* source, so the RSC handler scopes its prefetch entry to it (#1007).
|
|
300
299
|
*/
|
|
301
300
|
interceptTargeted?: true;
|
|
301
|
+
/**
|
|
302
|
+
* Set on a partial match when a revalidate() predicate read the source page
|
|
303
|
+
* and kept a held segment: the diff is right for the pages with equal values
|
|
304
|
+
* of what it read. The RSC handler scopes its prefetch entry to that group,
|
|
305
|
+
* or to the source page when the predicate read `currentUrl` (#1331).
|
|
306
|
+
*/
|
|
307
|
+
sourceHeld?: import("../router/source-reads.js").SourceHeld;
|
|
308
|
+
/**
|
|
309
|
+
* Set on a partial match whose answer has nothing live in it: the cache
|
|
310
|
+
* lookup served it (a cache() record, a Prerender entry, a shell replay's
|
|
311
|
+
* record, not a stale cache() record or stale overlay entry), it sends a
|
|
312
|
+
* segment, every segment it
|
|
313
|
+
* sends is one that lookup decoded, and no loader ran. A stored segment
|
|
314
|
+
* cannot fail after the headers left, since every writer refuses a failed
|
|
315
|
+
* render; a live segment or a loader can. The RSC handler marks the request
|
|
316
|
+
* `_servedFromStorage` from it.
|
|
317
|
+
*/
|
|
318
|
+
fromStorage?: true;
|
|
302
319
|
/**
|
|
303
320
|
* Redirect URL for trailing slash normalization.
|
|
304
321
|
* When set, the RSC handler should return a 308 redirect to this URL
|
|
@@ -364,9 +364,10 @@ export type PathHelpers<TEnv> = {
|
|
|
364
364
|
* freezes into the shell (nested promises stay live holes) — the pre-flush
|
|
365
365
|
* promise applied to the prelude.
|
|
366
366
|
*
|
|
367
|
-
* Pass `{ prefetch: false }` to keep the loader out of
|
|
368
|
-
* prefetch skips it and the click
|
|
369
|
-
* one follow-up request
|
|
367
|
+
* Pass `{ prefetch: false }` to keep the loader out of speculative
|
|
368
|
+
* prefetches (viewport, render): such a prefetch skips it and the click
|
|
369
|
+
* that adopts it fetches it in one follow-up request; a hover prefetch runs
|
|
370
|
+
* it (see {@link LoaderOptions}). Only on a segment the
|
|
370
371
|
* client does not have yet: a loader the client already holds runs in the
|
|
371
372
|
* prefetch when it revalidates.
|
|
372
373
|
*/
|
|
@@ -380,8 +381,9 @@ export type PathHelpers<TEnv> = {
|
|
|
380
381
|
* Attach a loading component to the current route/layout.
|
|
381
382
|
*
|
|
382
383
|
* `{ ssr: false }` skips the fallback on document requests. `{ prefetch:
|
|
383
|
-
* false }` keeps what the fallback covers out of
|
|
384
|
-
*
|
|
384
|
+
* false }` keeps what the fallback covers out of viewport and render
|
|
385
|
+
* prefetches (a hover prefetch runs it), when this entry's segment is new
|
|
386
|
+
* to the client: the entry's handler, its loaders,
|
|
385
387
|
* its orphan layouts and parallel slots and, for a layout, every deeper
|
|
386
388
|
* entry. The click that adopts the prefetch shows the fallback and fetches
|
|
387
389
|
* them in one follow-up request, which reads cache() and "use cache" as
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Sits in src/vite/, not src/vite/discovery/: the loader hook URL below
|
|
9
9
|
* resolves against this module's import.meta.url, and the published bundles
|
|
10
|
-
* (dist/vite/index.js, dist/bin/
|
|
11
|
-
* path.
|
|
10
|
+
* (dist/vite/index.js, and the CLI's code-split chunks in dist/bin/) ship the
|
|
11
|
+
* hook at the same relative path.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import type { ViteDevServer } from "vite";
|