next
Version:
The React Framework
198 lines (197 loc) • 9.61 kB
TypeScript
import type { FlightRouterState } from '../../../shared/lib/app-router-types';
import { EntryStatus } from './cache';
import type { RouteCacheKey } from './cache-key';
import { FetchStrategy, type PrefetchTaskFetchStrategy, PrefetchPriority } from './types';
import type { NavigationLockPrefetch } from './navigation-testing-lock';
import type { SegmentRequestKey } from '../../../shared/lib/segment-cache/segment-value-encoding';
export type PrefetchTask = {
key: RouteCacheKey;
/**
* The FlightRouterState at the time the task was initiated. This is needed
* when falling back to the non-PPR behavior, which only prefetches up to
* the first loading boundary.
*/
treeAtTimeOfPrefetch: FlightRouterState;
/**
* The cache versions at the time the task was initiated. Used to determine
* if the cache was invalidated since the task was initiated. Route and
* segment caches have separate versions so they can be invalidated
* independently.
*/
routeCacheVersion: number;
segmentCacheVersion: number;
/**
* Whether to prefetch dynamic data, in addition to static data. This is
* used by `<Link prefetch={true}>`.
*
* Note that a task with `FetchStrategy.PPR` might need to use
* `FetchStrategy.LoadingBoundary` instead if we find out that a route
* does not support PPR after doing the initial route prefetch.
*/
fetchStrategy: PrefetchTaskFetchStrategy;
/**
* sortId is an incrementing counter
*
* Newer prefetches are prioritized over older ones, so that as new links
* enter the viewport, they are not starved by older links that are no
* longer relevant. In the future, we can add additional prioritization
* heuristics, like removing prefetches once a link leaves the viewport.
*
* The sortId is assigned when the prefetch is initiated, and reassigned if
* the same task is prefetched again (effectively bumping it to the top of
* the queue).
*
* TODO: We can add additional fields here to indicate what kind of prefetch
* it is. For example, was it initiated by a link? Or was it an imperative
* call? If it was initiated by a link, we can remove it from the queue when
* the link leaves the viewport, but if it was an imperative call, then we
* should keep it in the queue until it's fulfilled.
*
* We can also add priority levels. For example, hovering over a link could
* increase the priority of its prefetch.
*/
sortId: number;
/**
* The priority of the task. Like sortId, this affects the task's position in
* the queue, so it must never be updated without resifting the heap.
*/
priority: PrefetchPriority;
/**
* The phase of the task. Tasks are split into multiple phases so that their
* priority can be adjusted based on what kind of work they're doing.
* Concretely, prefetching the route tree is higher priority than prefetching
* segment data.
*/
phase: PrefetchPhase;
/**
* These fields are temporary state for tracking the currently running task.
* They are reset after each iteration of the task queue.
*/
hasBackgroundWork: boolean;
/**
* Set during a pass when the task spawned a segment request, or encountered
* one that was already in flight (a Pending entry) — i.e. a response the
* pass cares about but hasn't received yet. The task blocks instead of
* advancing, and is re-pinged as each awaited entry settles; see
* blockTaskOnPendingResponse for the full rationale. Each ping re-runs a
* full traversal for the task, so expect roughly one re-pass per settling
* entry. Re-runs don't re-spawn revalidations: when a completed
* revalidation was re-keyed, its upsert evicts any superseded entry that
* would shadow it (see upsertSegmentEntry in cache.ts), and when its upsert
* was declined, the settled entry left in the revalidation slot dedupes
* further attempts (see pingFullSegmentRevalidation).
*/
hasPendingResponses: boolean;
spawnedRuntimePrefetches: Set<SegmentRequestKey> | null;
/**
* True if the prefetch was cancelled.
*/
isCanceled: boolean;
/**
* Tracks whether the task has attempted to upgrade a fallback ISR response
* to one based on concrete params.
*
* When the server serves an upgradeable fallback shell (the page hadn't been
* prerendered with concrete params yet, but the route can be upgraded), we
* poll the server a few times until the upgrade is complete, or until we
* reach a limit and give up.
*
* - `Empty`: no loop has run yet.
* - `Pending`: a loop is currently running.
* - `Fulfilled`: a loop completed and obtained the upgraded version.
* - `Rejected`: a loop ran but gave up (exhausted its retries, hit an error,
* or the task was canceled).
*
* To prevent against unbounded upgrade attempts, the loop is only attempted
* once per task, even a Link's prefetch is rescheduled many times.
*/
fallbackRetryStatus: EntryStatus;
/**
* The callback passed to `router.prefetch`, if given.
*/
onInvalidate: null | (() => void);
/**
* The index of the task in the heap's backing array. Used to efficiently
* change the priority of a task by re-sifting it, which requires knowing
* where it is in the array. This is only used internally by the heap
* algorithm. The naive alternative is indexOf every time a task is queued,
* which has O(n) complexity.
*
* We also use this field to check whether a task is currently in the queue.
*/
_heapIndex: number;
/**
* Instant Navigation Testing API only. Non-null when this prefetch task drives
* a locked navigation (the `ensurePrefetchThenNavigate` path). Holds that
* navigation's "wait for prefetch to fulfill" state: each spawned pending entry
* is tracked against it (see `upgradeToPendingSegment`), and the scheduler
* signals it when done spawning. See navigation-testing-lock.ts.
*/
_navigationLockPrefetch?: NavigationLockPrefetch | null;
};
/**
* Prefetch tasks are processed in phases so that high-leverage work runs
* before per-link work:
*
* - RouteTree: fetch the route's tree structure.
* - Shell: fetch the route's reusable App Shell (param-free loading state),
* if the route can produce one and the feature is enabled. Bounded by
* filesystem-route count, not link count — so all Shell prefetches across
* queued tasks complete before any Speculative prefetch runs, because
* shell responses are shared across every navigation to the same route.
* - Speculative: fetch the route's concrete per-link segment data.
*
* Higher numbers run earlier (matches heap-sort convention).
*/
declare const enum PrefetchPhase {
RouteTree = 2,
Shell = 1,
Speculative = 0
}
export type PrefetchSubtaskResult<T> = {
/**
* A promise that resolves when the network connection is closed.
*/
closed: Promise<void>;
value: T;
};
/**
* Called by the cache when revalidation occurs. Starts a cooldown period
* during which prefetch requests are blocked to allow CDN cache propagation.
*/
export declare function startRevalidationCooldown(): void;
export type IncludeDynamicData = null | 'full' | 'dynamic';
/**
* Initiates a prefetch task for the given URL. If a prefetch for the same URL
* is already in progress, this will bump it to the top of the queue.
*
* This is not a user-facing function. By the time this is called, the href is
* expected to be validated and normalized.
*
* @param key The RouteCacheKey to prefetch.
* @param treeAtTimeOfPrefetch The app's current FlightRouterState
* @param fetchStrategy Whether to prefetch dynamic data, in addition to
* static data. This is used by `<Link prefetch={true}>`.
* @param navigationLockPrefetch Testing API only. Non-null when this prefetch
* drives a locked navigation (from `ensurePrefetchThenNavigate`); carries that
* navigation's "wait for prefetch to fulfill" state. Null otherwise.
*/
export declare function schedulePrefetchTask(key: RouteCacheKey, treeAtTimeOfPrefetch: FlightRouterState, fetchStrategy: PrefetchTaskFetchStrategy, priority: PrefetchPriority, onInvalidate: null | (() => void), navigationLockPrefetch: NavigationLockPrefetch | null): PrefetchTask;
export declare function cancelPrefetchTask(task: PrefetchTask): void;
export declare function reschedulePrefetchTask(task: PrefetchTask, treeAtTimeOfPrefetch: FlightRouterState, fetchStrategy: PrefetchTaskFetchStrategy, priority: PrefetchPriority): void;
export declare function isPrefetchTaskDirty(task: PrefetchTask, nextUrl: string | null, tree: FlightRouterState): boolean;
export declare function pingPrefetchScheduler(): void;
/**
* Notify the scheduler that we've received new data for an in-progress
* prefetch. The corresponding task will be added back to the queue (unless the
* task has been canceled in the meantime).
*/
export declare function pingPrefetchTask(task: PrefetchTask): void;
/**
* Decides whether to skip the speculative prefetch of a subtree. Usually we
* only perform a speculative prefetch if the Link's prefetch prop is set to
* true. However, we also will do a speculative prefetch if the prefetching
* mode of the segment is set to "unstable_eager".
*/
export declare function subtreeHasSpeculativePrefetch(fetchStrategy: FetchStrategy, prefetchHints: number): boolean;
export {};