next
Version:
The React Framework
178 lines (177 loc) • 9.23 kB
TypeScript
import type { RenderOpts, PreloadCallbacks } from './types';
import type { ActionResult, DynamicParamTypesShort, DynamicSegmentTuple, FlightRouterState } from '../../shared/lib/app-router-types';
import { type WorkStore } from '../app-render/work-async-storage.external';
import type { RequestStore } from '../app-render/work-unit-async-storage.external';
import type { NextParsedUrlQuery } from '../request-meta';
import type { LoaderTree } from '../lib/app-dir-module';
import type { AppPageModule, DevRenderContext, RouteMatch } from '../route-modules/app-page/module';
import type { BaseNextRequest, BaseNextResponse } from '../base-http';
import RenderResult, { type AppPageRenderResultMetadata, type PrerenderResult } from '../render-result';
import type { AnyStream } from './stream-ops';
import { type DevValidationWorkerMessage } from './dev-validation-worker-globals';
import { type ImplicitTags } from '../lib/implicit-tags';
import { type MissingPrefetchHintPolicy } from './create-transport-tree-from-loader-tree';
import { parseRelativeUrl } from '../../shared/lib/router/utils/parse-relative-url';
import { type OpaqueFallbackRouteParams } from '../request/fallback-params';
import type { ParsedRequestHeaders } from '../route-modules/app-page/parse-request-headers';
import type { Params } from '../request/params';
import { type AdvanceableRenderStage } from './staged-rendering';
import type { StageEndTimes } from './instant-validation/instant-validation';
import type { NonPartial } from '../../shared/lib/typescript-utils';
export type GetDynamicParamFromSegment = (loaderTree: LoaderTree) => DynamicParam | null;
export type DynamicParam = {
param: string;
value: string | string[] | null;
treeSegment: DynamicSegmentTuple;
type: DynamicParamTypesShort;
};
export type GenerateFlight = typeof generateDynamicFlightRenderResult;
export type AppSharedContext = {
buildId: string;
deploymentId: string;
clientAssetToken: string;
};
type AppRenderCapabilities = {
/**
* Whether the response may contain postponed holes. This is conservatively
* true for prerenders with PPR enabled, even when the response turns out to
* be fully static, because a false negative could cause the client to skip
* fetching dynamic data. Per-segment prefetch responses replace this with a
* more precise value during segment-data collection.
*/
isPossiblyPartialResponse: boolean;
/** Whether this route supports per-segment prefetching in the client protocol. */
supportsPerSegmentPrefetching: boolean;
};
export type AppRenderContext = {
sharedContext: AppSharedContext;
workStore: WorkStore;
missingPrefetchHintPolicy: MissingPrefetchHintPolicy;
renderCapabilities: AppRenderCapabilities;
url: ReturnType<typeof parseRelativeUrl>;
componentMod: AppPageModule;
renderOpts: RenderOpts;
parsedRequestHeaders: ParsedRequestHeaders;
getDynamicParamFromSegment: GetDynamicParamFromSegment;
interpolatedParams: Params;
/**
* The request's fallback route params (the same ones
* `getDynamicParamFromSegment` closes over). Kept on the context so the dev
* validation worker can rebuild an identical `getDynamicParamFromSegment`;
* the depth-loop segment keys are derived from it, so it must match what
* produced the seed render's Flight, not the separate fallback set validation
* uses to mark params unknown in its stores.
*/
fallbackRouteParams: OpaqueFallbackRouteParams | null;
query: NextParsedUrlQuery;
isPrefetch: boolean;
isPossibleServerAction: boolean;
requestTimestamp: number;
appUsingSizeAdjustment: boolean;
flightRouterState?: FlightRouterState;
requestId: string;
htmlRequestId: string;
pagePath: string;
assetPrefix: string;
isNotFoundPath: boolean;
nonce: string | undefined;
res: BaseNextResponse;
/**
* For now, the implicit tags are common for the whole route. If we ever start
* rendering/revalidating segments independently, they need to move to the
* work unit store.
*/
implicitTags: ImplicitTags;
};
/**
* Produces a RenderResult containing the Flight data for the given request. See
* `generateDynamicRSCPayload` for information on the contents of the render result.
*/
declare function generateDynamicFlightRenderResult(ctx: AppRenderContext, requestStore: RequestStore, options?: {
actionResult: ActionResult;
skipPageRendering: boolean;
preloadCallbacks?: PreloadCallbacks;
temporaryReferences?: WeakMap<any, string>;
waitUntil?: Promise<unknown>;
}): Promise<RenderResult>;
export type BinaryStreamOf<T> = AnyStream;
export type AppPageRender = (req: BaseNextRequest, res: BaseNextResponse, pagePath: string, query: NextParsedUrlQuery, fallbackRouteParams: OpaqueFallbackRouteParams | null, renderOpts: RenderOpts, dev: DevRenderContext | undefined, sharedContext: AppSharedContext, routeMatch: RouteMatch, parsedRequestHeaders: ParsedRequestHeaders) => Promise<RenderResult<AppPageRenderResultMetadata>>;
export type AppPagePrerender = (...args: Parameters<AppPageRender>) => Promise<PrerenderResult>;
export declare const renderToHTMLOrFlight: AppPageRender;
export declare const prerenderToHTMLOrFlight: AppPagePrerender;
/**
* A staged dev render interrupted by sync IO. It has no artifacts that could be
* used as validation inputs, only the reason it was interrupted.
*/
interface SyncInterruptedStagedDevRender {
readonly syncInterruptReason: Error;
}
/**
* The chunks and per-stage timings a staged dev render accumulates once its
* stream finishes. A render only produces them when it runs to the end without
* a sync-IO interrupt, so they back both a render's uninterrupted outcome and
* the validation inputs.
*/
interface StagedDevRenderArtifacts {
readonly accumulatedChunks: AccumulatedStreamChunks;
readonly startTime: number;
readonly stageEndTimes: StageEndTimes;
}
/**
* What `runValidationInDev` consumes: an uninterrupted render's artifacts plus
* the request store and the debug channel. Carries no `syncInterruptReason`:
* the resolution step has already surfaced and bailed on any sync interrupt.
*/
export interface ResolvedValidationInputs extends StagedDevRenderArtifacts {
readonly requestStore: RequestStore;
readonly debugChannelClient: AnyStream | undefined;
}
/**
* The inputs produced for validation, before resolution: either an
* uninterrupted render's artifacts (`ResolvedValidationInputs`) or, for a
* render interrupted by sync IO, only the interrupt reason. An interrupted
* render is never validated, so it carries nothing else: its debug channel is
* dropped at construction. Discriminate with `'syncInterruptReason' in x`; the
* resolution step surfaces and bails on the interrupted case, so the depth loop
* only ever sees `ResolvedValidationInputs`.
*/
export type DevValidationInputs = ResolvedValidationInputs | SyncInterruptedStagedDevRender;
export declare enum PrefetchingMode {
LegacySpeculative = 1,
Partial = 2
}
type AccumulatedStreamChunks = Record<AdvanceableRenderStage, Array<Uint8Array>>;
/**
* The slice of the render context the dev/build validation passes read. Both
* the in-process callers and the validation worker build one of these, so it
* names exactly what validation depends on and nothing else: no live
* request/response objects that can't be rebuilt off the main thread.
* `isDebugChannelEnabled` is the derived flag validation uses in place of the
* main render's `setReactDebugChannel` callback (validation only ever read that
* callback's presence as a boolean).
*/
export type ValidationRenderContext = Pick<AppRenderContext, 'componentMod' | 'getDynamicParamFromSegment' | 'query' | 'implicitTags' | 'nonce' | 'workStore'> & {
renderOpts: NonPartial<Pick<RenderOpts, 'images' | 'allowEmptyStaticShell' | 'partialPrefetching'>>;
reactBrowserBailout: boolean;
isDebugChannelEnabled: boolean;
};
/**
* Projects a full app render context down to the slice validation reads. The
* in-process dev and build callers hold an `AppRenderContext` and use this to
* hand validation exactly what it needs; the worker builds the same shape from
* its snapshot instead.
*/
export declare function toValidationRenderContext(ctx: AppRenderContext): ValidationRenderContext;
/**
* Worker entry point for Cached Components dev validation, reached from the
* validation worker via `ComponentMod.routeModule.runValidationInDev`. The
* worker reloads the route's compiled module and calls this so the entire
* validation (Flight re-encodes and the client prerenders) runs inside the
* app-page bundle's single React instance, alongside the user's client
* components. It rebuilds the render context, work store, and request store
* from the transported snapshot (the live objects can't cross a thread) and
* returns the validation errors for the worker to serialize and the main thread
* to deliver.
*/
export declare function runValidationInDevFromSnapshot(message: DevValidationWorkerMessage, componentMod: AppPageModule, abortSignal: AbortSignal): Promise<Array<unknown> | undefined>;
export {};