UNPKG

next

Version:

The React Framework

198 lines (197 loc) 9.61 kB
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 {};