UNPKG

vite-plugin-react-server

Version:
1,774 lines (1,625 loc) 49.3 kB
import type { Readable } from "node:stream"; import type { Worker, Serializable as WorkerSerializable, } from "node:worker_threads"; import type React from "react"; // React types are imported from vendor system at runtime import type { NormalizedOutputOptions, OutputBundle, PreRenderedAsset, PreRenderedChunk, } from "rollup"; import type { PassThrough, Transform } from "stream"; import type { AliasOptions, BuildOptions, Connect, Logger, Manifest, Plugin, ResolveOptions, UserConfig, ViteDevServer, } from "vite"; import type { serializeResolvedConfig, serializeResolvedUserConfig, } from "./helpers/serializeUserOptions.js"; import type { AllowedDirectives, Program } from "react-server-loader/directives"; import type { LoaderConfig, TransformOptions } from "./loader/types.js"; import type { RenderMetrics, StreamMetrics, WorkerStartupMetrics, ModuleResolutionMetrics, } from "./metrics/types.js"; import type { HtmlWorkerOutputMessage } from "./worker/html/types.js"; import type { RscChunkOutputMessage } from "./worker/rsc/types.js"; import type { WorkerMessage } from "./worker/types.js"; import type { Strategy } from "./orchestrator/createPluginOrchestrator.server.js"; import type { RenderToPipeableStreamOptions as ClientRenderToPipeableStreamOptions } from "react-dom/server"; import type { RenderToPipeableStreamOptions as ServerRenderToPipeableStreamOptions } from "react-server-dom-esm/server.node"; export type OnEvent< Interface extends ViteReactServerComponentsPlugin = ViteReactServerComponentsPlugin > = (event: PluginEvent<Interface>) => void; export type OnMetrics = ( metrics: | RenderMetrics<"rsc-full" | "rsc-headless" | "html"> | WorkerStartupMetrics | ModuleResolutionMetrics ) => void; export type MessageHandler< T extends HtmlWorkerOutputMessage | RscChunkOutputMessage = | HtmlWorkerOutputMessage | RscChunkOutputMessage > = (message: T) => void | Promise<unknown>; export type CreateInputNormalizerProps = { root: string; preserveModulesRoot?: string | undefined; removeExtension?: boolean | RegExp | string | ((path: string) => boolean); moduleBasePath: string | undefined; moduleBaseURL: string | undefined; }; export type Serializable = | WorkerSerializable | Serializable[] | SerializableRecord; export type SerializableRecord = { [key: string]: Serializable; }; // Track HMR state export type HmrState = { timestamp: number; invalidated: boolean; routes: string[]; }; export type RenderPageResult = | { type: "skip"; reason?: unknown; html: { abort: (reason?: unknown) => void; pipe: <Writable extends NodeJS.WritableStream>( destination: Writable ) => Writable; }; rsc: { abort: (reason?: unknown) => void; pipe: <Writable extends NodeJS.WritableStream>( destination: Writable ) => Writable; }; metrics: { rscFull: RenderMetrics & { type: "rsc-full" }; rscHeadless: RenderMetrics & { type: "rsc-headless" }; html: RenderMetrics & { type: "html" }; }; } | { type: "error"; error: unknown; metrics: { rscFull: RenderMetrics & { type: "rsc-full" }; rscHeadless: RenderMetrics & { type: "rsc-headless" }; html: RenderMetrics & { type: "html" }; }; } | { type: "success"; html: { abort: (reason?: unknown) => void; pipe: <Writable extends NodeJS.WritableStream>( destination: Writable ) => Writable; }; rsc: { abort: (reason?: unknown) => void; pipe: <Writable extends NodeJS.WritableStream>( destination: Writable ) => Writable; }; metrics: { rscFull: RenderMetrics & { type: "rsc-full" }; rscHeadless: RenderMetrics & { type: "rsc-headless" }; html: RenderMetrics & { type: "html" }; }; }; export type AutoDiscoveredFiles = ResolvedBuildPages & { workerPaths: Record<string, string>; serverEntry: Record<string, string> | null; clientEntry: Record<string, string>; clientInputs: Record<string, string>; staticInputs: Record<string, string>; serverInputs: Record<string, string>; serverActions: Record<string, string>; }; // Input can be a string path, React component, tuple, or array export type NormalizerInput = unknown; export type InputNormalizer = (input: NormalizerInput) => [string, string]; export type HtmlContent = { raw: string; transformed?: string; assets?: string[]; }; export type PartialPageData = { route: string; html?: { raw: string; transformed?: string; assets?: string[]; }; rsc?: { modules: unknown[]; content: string; }; }; export type InputNormalizerWorker = ( input: NormalizerInput ) => Promise<[string, string]>; export type ResolvedUserConfig = Required< Pick<UserConfig, "root" | "mode" | "build" | "resolve"> > & Omit<UserConfig, "root" | "mode" | "build" | "resolve"> & { resolve: ResolveOptions; } & { build: NonNullable< Required< Pick< BuildOptions, | "target" | "outDir" | "assetsDir" | "ssr" | "ssrEmitAssets" | "ssrManifest" | "manifest" | "rollupOptions" | "modulePreload" > > > & Omit< BuildOptions, | "target" | "outDir" | "assetsDir" | "ssr" | "ssrEmitAssets" | "ssrManifest" | "manifest" >; }; export type SerializedUserConfig< T extends ResolvedUserConfig = ResolvedUserConfig > = ReturnType<typeof serializeResolvedUserConfig<T>>; export type SerializedUserOptions = { [k in keyof ResolvedUserOptions]: Extract< ResolvedUserOptions[k], Serializable | Record<string, Serializable> > extends infer X ? X extends never ? undefined : X : undefined; }; export type SerializedResolvedConfig = ReturnType< typeof serializeResolvedConfig >; // Client plugin options export type StreamPluginOptionsClient = { outDir?: string; build?: BuildConfig; assetsDir?: string; projectRoot?: string; moduleBase?: string; moduleBasePath?: string; moduleBaseURL?: string; clientComponents?: AliasOptions; cssFiles?: AliasOptions; }; export type StringKeys = | "moduleBase" | "moduleBasePath" | "moduleBaseURL" | "moduleRootPath" | "projectRoot" | "pageExportName" | "propsExportName" | "htmlWorkerPath" | "rscWorkerPath" | "loaderPath" | "reactLoaderPath" | "cssLoaderPath" | "envLoaderPath" | "clientEntry" | "serverEntry" | "publicOrigin"; export type NumberKeys = | "rscTimeout" | "htmlTimeout" | "htmlWorkerStartupTimeout" | "rscWorkerStartupTimeout" | "fileWriteTimeout" | "workerShutdownTimeout"; export type BooleanKeys = "verbose"; export type NestedConfigKeys = | "build" | "autoDiscover" | "loader" | "css" | "pipeableStreamOptions" | "serverPipeableStreamOptions" | "clientPipeableStreamOptions"; export type EventKeys = "onMetrics" | "onEvent"; export type NormalizerKeys = "normalizer" | "moduleID"; export type ComponentKeys = "Html" | "Root"; export type SourceURLKeys = "Page" | "props"; export type OptKey = | StringKeys | NumberKeys | BooleanKeys | EventKeys | NormalizerKeys | ComponentKeys | SourceURLKeys; export type AutoDiscoverConfig = { clientEntry: string; serverEntry: string; cssEntry: string; jsonEntry: string; htmlEntry: string; modulePattern: RegExp; serverPattern: RegExp; clientPattern: RegExp; pagePattern: RegExp; propsPattern: RegExp; cssPattern: RegExp; jsonPattern: RegExp; htmlPattern: RegExp; cssModulePattern: RegExp; vendorPattern: RegExp; nodePattern: RegExp; dotPattern: RegExp; virtualPattern: RegExp; rscPattern: RegExp; }; export type GenericModuleLoader = ( id: string ) => Promise<Record<string, unknown>>; export type BuildModuleLoader< Opt extends Pick< ResolvedUserOptions, "pageExportName" | "propsExportName" > = Pick<ResolvedUserOptions, "pageExportName" | "propsExportName">, T extends PagePropOpt = PagePropOpt > = < STR extends string, ID extends string = STR extends `${infer I extends string}${string}${string}` ? I : never, Special extends "?" | "#" | "" = STR extends `${string}${infer X extends | "?" | "#"}${string}` ? X : "", Extra extends string = STR extends `${string}${string}${infer Y extends string}` ? Y : "" >( moduleId: STR ) => Promise< STR extends `${ID}${Special}${Extra}` ? `${Special}${Extra}` extends "?inline" ? { default: string } : Extra extends Opt["pageExportName"] ? { [k in Extra]: (props: T) => unknown; } : Extra extends Opt["propsExportName"] ? { [k in Extra]: T; } : Extra extends string ? Special extends "#" ? { [k in Extra]: unknown } : Record<string, unknown> : Record<string, unknown> : Record<string, unknown> >; // Interface-aware version of BuildModuleLoader export type InterfaceAwareBuildModuleLoader< Interface extends ViteReactServerComponentsPlugin = DefaultInterface, Opt extends Pick< ResolvedUserOptions, "pageExportName" | "propsExportName" > = Pick<ResolvedUserOptions, "pageExportName" | "propsExportName"> > = BuildModuleLoader<Opt, Interface["PageProps"]>; export type StreamError = { code?: string; } & Error; export type PanicThreshold = "none" | "critical_errors" | "all_errors"; export type ResolvedUserOptions = { // Core required properties projectRoot: string; moduleBase: string; moduleBasePath: string; moduleBaseURL: string; moduleRootPath: string; publicOrigin: string; pageExportName: string; propsExportName: string; htmlExportName: string; rootExportName: string; htmlWorkerPath: string; rscWorkerPath: string; loaderPath: string; reactLoaderPath?: string; cssLoaderPath?: string; envLoaderPath?: string; verbose: boolean; rscTimeout: number; htmlTimeout: number; htmlWorkerStartupTimeout: number; rscWorkerStartupTimeout: number; fileWriteTimeout: number; workerShutdownTimeout: number; panicThreshold: PanicThreshold; availableEnvironments?: string[]; strategy?: Strategy; // Optional properties onEvent?: OnEvent<ViteReactServerComponentsPlugin> | undefined; props?: StreamPluginOptions["props"]; clientEntry?: string; serverEntry?: string; /** * Packages whose internal `"use client"` per-file directives should be * honored by the RSC pipeline (Chakra UI, MUI, Mantine, react-aria, …). * When set, the matching node_modules paths bypass the transformer's * node_modules early-return, get added to `optimizeDeps.exclude` so esbuild * doesn't strip directives, and get added to `resolve.noExternal` so they * land in the server bundle where the transform actually runs. */ clientPackages?: readonly string[]; // Required complex properties Page: StreamPluginOptions["Page"]; Html: StreamPluginOptions["Html"]; // Unresolved: can be string, function Root: StreamPluginOptions["Root"]; // Unresolved: can be string, function normalizer: InputNormalizer; moduleID: | (( id: string, sourceContent?: string, isClientByDirective?: boolean ) => string) | undefined; onMetrics: OnMetrics | undefined; // different for client/server so can't be typed pipeableStreamOptions: any; // Separate pipeable stream options for different environments serverPipeableStreamOptions?: any; // For dev server and RSC worker clientPipeableStreamOptions?: any; // For HTML worker autoDiscover: Required<AutoDiscoverConfig>; loader?: Required<LoaderConfig> | undefined; build: Required<BuildConfig>; dev: Required<DevConfig>; css: RootOptions<boolean>; components?: StreamPluginOptions["components"]; // Direct component overrides (optional) }; export type DirectiveOptions = Pick< TransformOptions, "verbose" | "panicThreshold" | "loader" > & { loader: Pick< Required<NonNullable<TransformOptions["loader"]>>, | "isServerFunctionCode" | "isClientComponentCode" | "getDirectiveType" | "parse" > & { allowedDirectives: AllowedDirectives; }; logger?: Logger; }; export type RootOptions<InlineCSS extends InlineCssOpt = InlineCssOpt> = { inlineCss?: InlineCSS; inlineThreshold?: number; inlinePatterns?: RegExp[]; linkPatterns?: RegExp[]; }; export type CssContent<InlineCSS extends InlineCssOpt = InlineCssOpt> = InlineCSS extends true ? StyleCssProps : InlineCSS extends false ? LinkCssProps : InlineCSS extends undefined | boolean ? StyleCssProps | LinkCssProps : never; /** * Boxed component type for the Root */ export type CssComponentType< InlineCSS extends InlineCssOpt = InlineCssOpt, R = any > = (props: { cssFiles: Map<string, CssContent<InlineCSS>> | CssContent[]; }) => R; export type FileWriteEvent = { type: "file.write"; data: { path: string; fileType: "html" | "rsc"; route: string; stream: Readable; onComplete: () => Promise<void>; }; }; export type FileWriteDoneEvent = { type: "file.write.done"; data: { route: string; fileType: "html" | "rsc"; content: string; chunks: number; path: string; fileName: string; baseDir: string; routePath: string; }; }; export type RouteErrorEvent = { type: "route.error"; data: { route: string; error?: unknown | null; errorInfo?: { componentStack?: string | null; digest?: string | null; }; reason?: unknown; isPanic?: boolean; panicThreshold?: PanicThreshold; }; }; export type RouteShellErrorEvent = { type: "route.shellError"; data: { route: string; error: unknown; }; }; export type RoutePostponeEvent = { type: "route.postpone"; data: { route: string; reason?: unknown; }; }; export type RouteShellReadyEvent = { type: "route.shellReady"; data: { route: string; }; }; export type RouteAllReadyEvent = { type: "route.allReady"; data: { route: string; }; }; export type PropsLoadEvent = { type: "props.load"; data: { route: string; propsPath: string; props: unknown; }; }; export type CssProcessEvent< Interface extends ViteReactServerComponentsPlugin = DefaultInterface > = { type: "css.process"; data: InterfaceAwareCssContent<Interface>; }; export type BuildStartEvent = { type: "build.start"; data: { pages: string[]; files: AutoDiscoveredFiles; }; }; export type BuildWriteBundleEventServer = { type: "build.writeBundle.server"; data: { pages: string[]; options: NormalizedOutputOptions; bundle: OutputBundle; }; }; export type BuildWriteBundleEventClient = { type: "build.writeBundle.client"; data: { pages: string[]; options: NormalizedOutputOptions; bundle: OutputBundle; }; }; export type BuildWriteBundleEventStatic = { type: "build.writeBundle.static"; data: { pages: string[]; options: NormalizedOutputOptions; bundle: OutputBundle; }; }; export type BuildEventStaticSiteGenerationStart = { type: "build.ssg.start"; // SSG process starts (from client environment) data: { pages: string[]; options: NormalizedOutputOptions; bundle: OutputBundle; }; }; export type BuildEventStaticSiteGenerationEnd = { type: "build.ssg.end"; // SSG process ends (from server environment) data: { pages: string[]; options: NormalizedOutputOptions; bundle: OutputBundle; }; }; export type BuildWriteBundleEvent = | BuildWriteBundleEventServer | BuildWriteBundleEventClient | BuildEventStaticSiteGenerationStart | BuildEventStaticSiteGenerationEnd | BuildWriteBundleEventStatic; export type PluginEvent< Interface extends ViteReactServerComponentsPlugin = ViteReactServerComponentsPlugin > = | FileWriteEvent | FileWriteDoneEvent | RouteErrorEvent | RouteShellErrorEvent | RoutePostponeEvent | PropsLoadEvent | CssProcessEvent<Interface> | BuildStartEvent | BuildWriteBundleEvent | RouteShellReadyEvent | RouteAllReadyEvent; export type PluginEventType = PluginEvent["type"]; export interface StreamPluginOptions< Interface extends ViteReactServerComponentsPlugin = DefaultInterface > { projectRoot?: string; // defaults to process.cwd() moduleBase: string; // defaults to 'src' moduleBasePath?: string; // defaults to '' moduleBaseURL?: string; // defaults to '/' moduleRootPath?: string; // defaults to client's dist folder publicOrigin?: string; // defaults to window.location.origin in client & http://localhost:port in development clientEntry?: string; serverEntry?: string; // Loader configuration loader?: { importServerPath?: string; importClientPath?: string; registerClientReferenceName?: string; registerServerReferenceName?: string; serverDirective?: RegExpOpt; clientDirective?: RegExpOpt; directivePattern?: RegExpOpt; isServerFunctionCode?: (code: string, moduleId?: string) => boolean; isClientComponentCode?: (code: string, moduleId?: string) => boolean; isClientComponentByCode?: (code: string) => boolean; isClientComponentByName?: (moduleId: string) => boolean; moduleID?: (moduleId: string) => string; allowedDirectives?: string[] | AllowedDirectives; mode?: "development" | "production" | "test"; getDirectiveType?: ( directive: string, moduleId?: string ) => "client" | "server" | undefined; parse?: (source: string) => Promise<{ ast: Program; code: string; map?: { url: string; start: number; end: number; lines: number } | null; }>; }; // Auto-discovery (zero-config) autoDiscover?: | { clientEntry: string; serverEntry: string; cssEntry: string; jsonEntry: string; htmlEntry: string; // CSS related /** * Pattern to match CSS files. If not provided, uses default pattern. * Provide either a string pattern or RegExp. * @example ```ts * autoDiscover: { * // String pattern * cssPattern: ".css$", * // Or RegExp pattern * cssPattern: /\.css$/ * } */ cssPattern?: RegExpOpt; /** * Pattern to match CSS module files. If not provided, uses default pattern. * Provide either a string pattern or RegExp. * @example ```ts * autoDiscover: { * // String pattern * cssModulePattern: ".css\\.js$", * // Or RegExp pattern * cssModulePattern: /\.css\.js$/ * } */ cssModulePattern?: RegExpOpt; // Client/Server related /** * Pattern to match client component files. If not provided, uses default pattern. * Provide either a string pattern or RegExp. * @example ```ts * autoDiscover: { * // String pattern * clientPattern: ".client\\.(js|ts|jsx|tsx)$", * // Or RegExp pattern * clientPattern: /\.client\.(js|ts|jsx|tsx)$/ * } */ clientPattern?: RegExpOpt; /** * Pattern to match server function files. If not provided, uses default pattern. * Provide either a string pattern or RegExp. * @example ```ts * autoDiscover: { * // String pattern * serverPattern: ".server\\.(js|ts|jsx|tsx)$", * // Or RegExp pattern * serverPattern: /\.server\.(js|ts|jsx|tsx)$/ * } */ serverPattern?: RegExpOpt; // HTML related /** * Pattern to match HTML files. If not provided, uses default pattern. * Provide either a string pattern or RegExp. * @example ```ts * autoDiscover: { * // String pattern * htmlPattern: ".html$", * // Or RegExp pattern * htmlPattern: /\.html$/ * } */ htmlPattern?: RegExpOpt; // JSON related /** * Pattern to match JSON files. If not provided, uses default pattern. * Provide either a string pattern or RegExp. * @example ```ts * autoDiscover: { * // String pattern * jsonPattern: ".json$", * // Or RegExp pattern * jsonPattern: /\.json$/ * } */ jsonPattern?: RegExpOpt; // JS/Module related /** * Pattern to match module files. If not provided, uses default pattern. * Provide either a string pattern or RegExp. * @example ```ts * autoDiscover: { * // String pattern * modulePattern: "\\.(js|ts|jsx|tsx)$", * // Or RegExp pattern * modulePattern: /\.(js|ts|jsx|tsx)$/ * } */ modulePattern?: RegExpOpt; // RSC related /** * Pattern to match RSC files. If not provided, uses default pattern. * Provide either a string pattern or RegExp. * @example ```ts * autoDiscover: { * // String pattern * rscPattern: ".rsc$", * // Or RegExp pattern * rscPattern: /\.rsc$/ * } */ rscPattern?: RegExpOpt; // Page/Props related /** * Pattern to match page files. If not provided, uses default pattern. * Provide either a string pattern or RegExp. * @example ```ts * autoDiscover: { * // String pattern * pagePattern: "[Pp]age\\.(js|ts|jsx|tsx)$", * // Or RegExp pattern * pagePattern: /[Pp]age\.(js|ts|jsx|tsx)$/ * } */ pagePattern?: RegExpOpt; /** * Pattern to match props files. If not provided, uses default pattern. * Provide either a string pattern or RegExp. * @example ```ts * autoDiscover: { * // String pattern * propsPattern: "[Pp]rops\\.(js|ts|jsx|tsx)$", * // Or RegExp pattern * propsPattern: /[Pp]rops\.(js|ts|jsx|tsx)$/ * } */ propsPattern?: RegExpOpt; // Special patterns /** * Pattern to match dot files. No interpolation support. * @example ```ts * autoDiscover: { * dotFiles: /^\.[^/]+$/ * } */ dotPattern?: RegExpOpt; /** * Pattern to match Node.js native modules. No interpolation support. * @example ```ts * autoDiscover: { * nodePattern: /.node/ * } */ nodePattern?: RegExpOpt; /** * Pattern to match vendor files. No interpolation support. * @example ```ts * autoDiscover: { * vendorPattern: /node_modules|_virtual/ * } */ vendorPattern?: RegExpOpt; /** * Pattern to match virtual files. No interpolation support. * @example ```ts * autoDiscover: { * virtualPattern: /^virtual:/ * } */ virtualPattern?: RegExpOpt; } | undefined; // Manual configuration Page?: UrlOpt; props?: UrlOpt; // Escape hatches htmlWorkerPath?: string; rscWorkerPath?: string; loaderPath?: string; reactLoaderPath?: string; cssLoaderPath?: string; envLoaderPath?: string; pageExportName?: Interface["PageExportName"]; propsExportName?: Interface["PropsExportName"]; htmlExportName?: Interface["HtmlExportName"]; rootExportName?: Interface["RootExportName"]; Html?: UrlOpt; Root?: UrlOpt; // Direct component overrides (bypasses string resolution) components?: { Html?: HtmlComponentType< Interface["PageProps"], Interface["As"], Interface["InlineCSS"], Interface["ReactType"] >; Root?: RootComponentType< Interface["PageProps"], Interface["As"], Interface["InlineCSS"], Interface["ReactType"] >; Page?: PageComponentType<Interface["PageProps"], Interface["ReactType"]>; }; build?: BuildConfig; dev?: DevConfig; css?: RootOptions<Interface["InlineCSS"]>; // moduleBaseExceptions?: string[]; pipeableStreamOptions?: any; // Legacy - kept for backward compatibility serverPipeableStreamOptions?: any; // For dev server and RSC worker clientPipeableStreamOptions?: any; // For HTML worker onMetrics?: OnMetrics; onEvent?: OnEvent<Interface>; normalizer?: InputNormalizer; moduleID?: ( id: string, sourceContent?: string, isClientByDirective?: boolean ) => string; verbose?: boolean; rscTimeout?: number; // Timeout in milliseconds for RSC operations htmlTimeout?: number; // Timeout in milliseconds for HTML generation operations htmlWorkerStartupTimeout?: number; // Timeout in milliseconds for HTML worker startup rscWorkerStartupTimeout?: number; // Timeout in milliseconds for RSC worker startup fileWriteTimeout?: number; // Timeout in milliseconds for file write operations workerShutdownTimeout?: number; // Timeout in milliseconds for worker shutdown operations panicThreshold?: PanicThreshold; } export type MultiPageHandlerOptions< Opt extends ResolvedUserOptions = ResolvedUserOptions > = Omit< CreateHandlerOptions<Opt>, | "pagePath" | "route" | "cssFiles" | "propsPath" | "rootPath" | "htmlPath" | "pageProps" | "PageComponent" | "RootComponent" | "HtmlComponent" >; export type CreateHandlerOptions< Opt extends Record<string, unknown> = ResolvedUserOptions, Interface extends ViteReactServerComponentsPlugin = DefaultInterface, R = Interface["ReactType"] > = Pick< Opt, | "autoDiscover" | "css" | "pageExportName" | "propsExportName" | "rootExportName" | "htmlExportName" | "moduleBase" | "moduleRootPath" | "moduleBasePath" | "moduleBaseURL" | "publicOrigin" | "onEvent" | "onMetrics" | "projectRoot" | "normalizer" | "moduleID" | "verbose" | "components" | "panicThreshold" | "projectRoot" | "rscTimeout" | "htmlTimeout" | "fileWriteTimeout" | "workerShutdownTimeout" | "rscWorkerPath" | "htmlWorkerPath" > & { id?: string; /** ID of headless stream to reuse for efficiency */ reuseHeadlessStreamId?: string; /** Storage for headless stream reuse Map<id, { PageComponent: any, errored: boolean }> */ headlessStreamElements?: Map<string, { PageComponent: any; errored: boolean }>; signal?: AbortSignal; logger: Logger; loader: BuildModuleLoader | GenericModuleLoader; pagePath: string; propsPath?: string; rootPath?: string; htmlPath?: string; pageProps?: Interface["PageProps"]; PageComponent?: | PageComponentType<Interface["PageProps"], R> | typeof React.Fragment; RootComponent?: | RootComponentType< Interface["PageProps"], Interface["As"], Interface["InlineCSS"], R > | typeof React.Fragment; HtmlComponent?: | HtmlComponentType< Interface["PageProps"], Interface["As"], Interface["InlineCSS"], R > | typeof React.Fragment; route: string; url?: string; as?: Interface["As"]; manifest: Manifest; staticManifest?: Manifest; serverManifest?: Manifest; clientManifest?: Manifest; worker?: Worker; // if available, is preferred worker for inverse streaming rscWorker?: Worker; // if rscWorker available, its used for createRscStream htmlWorker?: Worker; // if htmlWorker available, its used for createHtmlStream server?: ViteDevServer; importedCss?: Set<string>; cssFiles: Map<string, InterfaceAwareCssContent<Interface>>; globalCss: Map<string, InterfaceAwareCssContent<Interface>>; serverPipeableStreamOptions?: ServerRenderToPipeableStreamOptions; clientPipeableStreamOptions?: ClientRenderToPipeableStreamOptions; rscStream?: import("node:stream").Readable; // Optional RSC PassThrough stream to use instead of creating a new one htmlStream?: import("node:stream").Readable; // Optional HTML PassThrough stream to use instead of creating a new one metrics?: import("./metrics/types.js").StreamMetrics; // Optional metrics to use instead of creating new ones build: Pick< ResolvedUserOptions["build"], | "outDir" | "pages" | "server" | "static" | "client" | "rscOutputPath" | "htmlOutputPath" | "assetsDir" | "preserveModulesRoot" >; dev: Pick<ResolvedUserOptions["dev"], "useHtmlWorker" | "useRscWorker">; children?: React.ReactNode; }; export type ResolvePageOptions = { pagePath: string; pageExportName: string; url: string; }; export type ResolvePropsOptions = { propsPath: string; propsExportName: string; url: string; }; export type StreamResult = | { type: "success"; stream: PassThrough; assets?: { css?: string[]; }; } | { type: "error"; error: unknown } | { type: "skip" }; export type RouteConfig = { path: string; // Define page/props paths using patterns pattern?: { page?: string; // e.g. "page/_theme/[route]/page" props?: string; // e.g. "page/_theme/[route]/props" }; // Or use explicit paths paths?: { page: string; // e.g. "page/home/page" props: string; // e.g. "page/home/props" }; }; export type BuildOutput = { dir?: string; rsc?: string; ext?: string; }; export type BuildConfig = { useRscWorker?: boolean; useHtmlWorker?: boolean; pages?: string[] | (() => Promise<string[]> | string[]) | Promise<string[]>; assetsDir?: string; client?: string; // Output directory for client files server?: string; // Output directory for server files static?: string; // Output directory for static environment - works in both api?: string; // Output directory for API files outDir?: string; hash?: | string | { format?: "vite" | "hex" | "custom"; length?: number; characters?: string; prefix?: string; suffix?: string; }; preserveModulesRoot?: boolean; rscOutputPath?: string; // defaults: `index.rsc` htmlOutputPath?: string; // defaults: `index.html` entryFile?: ( n: PreRenderedChunk, ssr: boolean, sourceContent?: string ) => string; chunkFile?: ( n: PreRenderedChunk, ssr: boolean, sourceContent?: string ) => string; assetFile?: (n: PreRenderedAsset, ssr: boolean) => string; extensionMap?: Record<string, string>; moduleExtension?: string; jsExtension?: string; cssExtension?: string; htmlExtension?: string; jsonExtension?: string; rscExtension?: string; cssModuleExtension?: string; nodeExtension?: string; /** * Controls how pages are rendered during static generation. * * - `"parallel"` (default): Renders pages in concurrent batches for faster builds. * Use `batchSize` to control concurrency (default: 8). * - `"sequential"`: Renders pages one at a time. Slower but uses less memory * and produces deterministic output order. */ renderMode?: "parallel" | "sequential"; /** * Number of pages to render concurrently when `renderMode` is `"parallel"`. * Higher values use more memory but build faster. * @default 8 */ batchSize?: number; }; export type DevConfig = { useHtmlWorker?: boolean | undefined; useRscWorker?: boolean | undefined; }; export type RequestHandler = Connect.NextHandleFunction; export type RscServerModule = { /** * Get RSC data for a route * @param path - Route path (e.g. "/", "/about") * @returns Page component and props */ getRscData: (path: string) => Promise<{ /** Page component to render */ Page: React.ComponentType; /** Props to pass to the page */ props: unknown; }>; }; export type RegisterComponentMessage = { type: "REGISTER_COMPONENT"; id: string; code: string; }; export type RscBuildResult = string[]; export type ReactStreamPluginMeta = { timing: BuildTiming; }; export type BuildTiming = { start: number; configResolved?: number; buildStart?: number; buildEnd?: number; renderStart?: number; renderEnd?: number; closeBundle?: number; render?: number; total?: number; }; export type ResolvedBuildPages = { propsMap: Map<string, string>; pageMap: Map<string, string>; rootMap: Map<string, string>; htmlMap: Map<string, string>; /** * ## routeMap * * Maps props & page paths to routes * * @example * const routeMap = new Map<string, string[]>(); * routeMap.set("src/page/home/page.tsx", ["/", "/home"]); */ routeMap: Map<string, string[]>; /** * ## urlMap * * Maps urls to props & page paths * * @example * ```ts * const urlMap = new Map<string, { props?: string; page: string }>(); * urlMap.set("/", { props: "/props", page: "/page" }); * ``` */ urlMap: Map< string, { props?: string; page: string; root?: string; html?: string } >; errors: unknown[]; }; // Add branded types for safety export type ModuleId = string & { readonly __brand: unique symbol }; export type PagePath = string & { readonly __brand: unique symbol }; export interface DeserializedRegExp { source: string; flags: string; __isRegExp: boolean; } export type RegExpOpt = RegExp | string | DeserializedRegExp; export type UrlOpt = | string | ((url: string) => string) | ((url: string) => Promise<string>); export type PageName = "Page"; export type PropsName = "props"; export type HtmlName = "Html"; export type RootName = "Root"; export type HtmlProps< PageProps extends ViteReactServerComponentsPlugin["PageProps"] = DefaultInterface["PageProps"], InlineCSS extends ViteReactServerComponentsPlugin["InlineCSS"] = DefaultInterface["InlineCSS"], As extends ViteReactServerComponentsPlugin["As"] = DefaultInterface["As"], R = DefaultInterface["ReactType"] > = { pageProps?: PageProps; Page: PageComponentType<PageProps, R>; route: string; url: string; projectRoot: string; moduleBase: string; moduleBaseURL: string; moduleBasePath: string; moduleRootPath: string; cssFiles: Map<string, CssContent<InlineCSS>>; manifest: Manifest; Root: RootComponentType<PageProps, As, InlineCSS, R> | typeof React.Fragment; globalCss: Map<string, CssContent<InlineCSS>>; as?: As; }; export type PageAsset = { type: "css" | "js"; path: string; parentUrl: string; }; type BaseCssProps = { as: string; id: string; }; export type LinkCssProps = BaseCssProps & { as: "link"; type?: never; children?: never; id: string; href: string; rel: "stylesheet"; precedence?: string; }; export type StyleCssProps = BaseCssProps & { as: "style"; type: "text/css"; children?: React.ReactNode; precedence?: never; rel?: never; href?: never; }; export type CssProps<InlineCSS extends InlineCssOpt = InlineCssOpt> = { cssFiles: Map<string, CssContent<InlineCSS>>; }; export type HtmlRenderState = { id: string; rscStream: PassThrough; htmlStream: PassThrough; progressStream: PassThrough; errorTransform: Transform; htmlChunks: string[]; pipeableStreamOptions: any; streamState: StreamMetrics; }; // Metadata type for tracking render progress // Simplified result type for the entire render process export type RenderPagesResult = { type: "error" | "success" | "skip"; error?: unknown; route?: string; completedRoutes: Set<string>; failedRoutes: Map<string, unknown>; results: Map<string, RenderPageResult>; }; export type HandlerAssets<InlineCSS extends InlineCssOpt = InlineCssOpt> = { css: CssContent<InlineCSS>[]; js: string[]; bootstrapModules: string[]; }; export type CreateHandlerResult<InlineCSS extends InlineCssOpt = InlineCssOpt> = | { type: "success"; controller: AbortController; stream: PassThrough; assets: { css: CssContent<InlineCSS>[]; js: string[]; bootstrapModules: string[]; }; route: string; metrics: StreamMetrics; } | { type: "error"; error: unknown } | { type: "skip" }; // Define LoaderContext interface locally export type LoaderContext = { format?: string; importAttributes?: Record<string, string>; conditions?: string[]; env?: { targetEnvironment?: "client" | "server" | "browser"; }; url: string; userOptions?: SerializedUserOptions; // Add userOptions to the context }; // Add type declaration for import.meta.cssModules declare global { interface ImportMeta { cssModules?: Record<string, Record<string, string>>; } } export type VendorInteropConfig = { react: string; // e.g. "react" reactDOMServer: string; // e.g. "react-dom/server" }; // Client Browser exports: // - createFromFetch // - createFromReadableStream // - createServerReference // - createTemporaryReferenceSet // - encodeReply // - registerServerReference export type RSCClientBrowserInteropConfig = { createFromFetch: string; createFromReadableStream: string; createServerReference: string; createTemporaryReferenceSet: string; encodeReply: string; registerServerReference: string; }; // Client Node exports: // - createFromNodeStream // - createServerReference // - registerServerReference export type RSCClientNodeInteropConfig = { createFromNodeStream: string; createServerReference: string; registerServerReference: string; }; // Server Node exports: // - createTemporaryReferenceSet // - decodeAction // - decodeFormState // - decodeReply // - decodeReplyFromBusboy // - registerClientReference // - registerServerReference // - renderToPipeableStream // - unstable_prerenderToNodeStream export type RSCServerInteropConfig = { createTemporaryReferenceSet: string; decodeAction: string; decodeFormState: string; decodeReply: string; decodeReplyFromBusboy: string; registerClientReference: string; registerServerReference: string; renderToPipeableStream: string; unstable_prerenderToNodeStream: string; }; export type RSCInteropConfig = { client: { browser: { production: string; // e.g. "react-server-dom-esm/client.browser" development: string; // e.g. "react-server-dom-esm/client.browser" test: string; // e.g. "react-server-dom-esm/client.browser" exports: RSCClientBrowserInteropConfig; }; node: { production: string; // e.g. "react-server-dom-esm/client" development: string; // e.g. "react-server-dom-esm/client.node" test: string; // e.g. "react-server-dom-esm/client.node" exports: RSCClientNodeInteropConfig; }; }; server: { production: string; // e.g. "react-server-dom-esm/server" development: string; // e.g. "react-server-dom-esm/server.node" test: string; // e.g. "react-server-dom-esm/server.node" exports: RSCServerInteropConfig; }; }; export type FlightTarget = | "default" | "webpack" | "nextjs" | "react-server-dom-esm" | "react-server-dom-webpack" | "react-server-dom-parcel"; export type FlightConfig = { rsc: RSCInteropConfig; vendor: VendorInteropConfig; }; // Import configuration from separate file export type VitePluginMainFn = < Opt extends StreamPluginOptions<any> = StreamPluginOptions<any> >( options: Opt, strategy?: Strategy ) => Plugin<Opt>[]; export type VitePluginMainAsyncFn = < Opt extends StreamPluginOptions<any> = StreamPluginOptions<any> >( options: Opt ) => Promise<Plugin<Opt>[]>; export type VitePluginFn = < Opt extends StreamPluginOptions = StreamPluginOptions >( options: Opt ) => Plugin; // New types for component resolution strategies export type ComponentResolutionStrategy = | "serializable-path" // Use a path that can be resolved by the worker | "direct-import" // Direct import in the worker context | "worker-internal" // Component is available internally in the worker | "external-reference"; // Reference to external component system export type SerializableComponentPath = string & { readonly __brand: "SerializableComponentPath"; }; export type ComponentResolutionConfig = { strategy: ComponentResolutionStrategy; path?: SerializableComponentPath; moduleId?: string; exportName?: string; }; // Enhanced environment-specific options export type ClientEnvironmentOptions = { // Client environment cannot accept React components directly PageComponent?: never; RootComponent?: never; HtmlComponent?: never; // Client environment can accept component resolution configs pageComponentConfig?: ComponentResolutionConfig; rootComponentConfig?: ComponentResolutionConfig; htmlComponentConfig?: ComponentResolutionConfig; // Client environment can accept serializable paths pagePath?: string; rootPath?: string; htmlPath?: string; }; export type ServerEnvironmentOptions = { // Server environment can accept React components directly PageComponent?: CreateHandlerOptions["PageComponent"]; RootComponent?: CreateHandlerOptions["RootComponent"]; HtmlComponent?: CreateHandlerOptions["HtmlComponent"]; // Server environment can also accept component resolution configs pageComponentConfig?: ComponentResolutionConfig; rootComponentConfig?: ComponentResolutionConfig; htmlComponentConfig?: ComponentResolutionConfig; // Server environment can accept serializable paths pagePath?: string; rootPath?: string; htmlPath?: string; }; // Enhanced ReactStreamCommonOptions with proper environment separation export type ReactStreamCommonOptions< Env extends "client" | "server" = "client" | "server", Handles extends keyof CreateHandlerOptions = never > = Omit< CreateHandlerOptions, | Handles | "PageComponent" | "RootComponent" | "HtmlComponent" | "pagePath" | "rootPath" | "htmlPath" > & Partial<Pick<CreateHandlerOptions, Handles>> & (Env extends "client" ? ClientEnvironmentOptions : ServerEnvironmentOptions); // Enhanced handler function types with proper environment constraints export type ReactStreamHandlerFn< Env extends "client" | "server", Handles extends keyof CreateHandlerOptions, ReturnType > = (options: ReactStreamCommonOptions<Env, Handles>) => ReturnType; // Type for RSC worker message that enforces serializable constraints export type RscWorkerMessage = { // Only serializable data can be passed to workers pageComponentConfig?: ComponentResolutionConfig; rootComponentConfig?: ComponentResolutionConfig; htmlComponentConfig?: ComponentResolutionConfig; // Serializable paths pagePath?: string; rootPath?: string; htmlPath?: string; // Other serializable options route: string; url: string; pageProps?: Record<string, unknown>; cssFiles: Array<[string, unknown]>; // Serialized Map manifest: Record<string, unknown>; // Never allow direct component references PageComponent?: never; RootComponent?: never; HtmlComponent?: never; }; // Enhanced RscRenderOpt with proper serialization constraints export type RscRenderOpt = WorkerMessage & { type: "INIT"; } & { dataPort: MessagePort; controlPort: MessagePort; options: Omit< CreateHandlerOptions<ResolvedUserOptions>, // Omit non-serializable fields | "onEvent" | "onMetrics" | "loader" | "build" | "autoDiscover" | "normalizer" | "moduleID" | "PageComponent" | "RootComponent" | "HtmlComponent" | "url" | "logger" | "cssFiles" // Replace with serializable version > & { url?: string; // Component resolution configs instead of direct components pageComponentConfig?: ComponentResolutionConfig; rootComponentConfig?: ComponentResolutionConfig; htmlComponentConfig?: ComponentResolutionConfig; // Serializable CSS files cssFiles: Array<[string, unknown]>; build: Omit< CreateHandlerOptions<ResolvedUserOptions>["build"], "entryFileNames" | "chunkFileNames" | "assetFileNames" | "pages" > & { pages: string[] }; }; }; // Type for plugin component references export type PluginComponentReference = { type: "plugin-component"; componentName: "Html" | "Root" | "Css"; modulePath: string; exportName: string; }; // Enhanced component resolution types export type ResolvedComponent<T = unknown> = | { type: "success"; component: T; source: "direct" | "resolved" | "plugin" | "worker-internal"; } | { type: "error"; error: Error; source: "direct" | "resolved" | "plugin" | "worker-internal"; }; // Type for worker component loader export type WorkerComponentLoader = { loadComponent: ( config: ComponentResolutionConfig ) => Promise<ResolvedComponent>; loadPluginComponent: ( reference: PluginComponentReference ) => Promise<ResolvedComponent>; hasInternalComponent: (componentName: string) => boolean; }; // re-exorts export type { RenderMetrics, StreamMetrics, WorkerStartupMetrics, ModuleResolutionMetrics, }; // Generic React type that can be inferred from user's React import export type InferReactType<R = React.ReactNode> = R; // Simple interface override for custom React types export type CustomInterface<R = React.ReactNode> = { ReactType: R; PageProps: any; As: any; PropsExportName: string; PageExportName: string; RootExportName: string; HtmlExportName: string; InlineCSS: undefined | boolean; }; // Core interface types that can be overridden declare global { interface ViteReactServerComponentsPlugin { PageProps: any; As: any; InlineCSS: undefined | boolean; ReactType: InferReactType; PropsExportName: string; PageExportName: string; RootExportName: string; HtmlExportName: string; } } // Default interface implementation export interface DefaultInterface< T extends React.ComponentProps<any> = React.ComponentProps<any>, As extends | React.JSXElementConstructor<any> | keyof React.JSX.IntrinsicElements = | React.JSXElementConstructor<any> | keyof React.JSX.IntrinsicElements > extends ViteReactServerComponentsPlugin { ReactType: InferReactType; PageProps: T; As: As; PropsExportName: PropsName; PageExportName: PageName; RootExportName: RootName; HtmlExportName: HtmlName; } // Legacy type aliases for backward compatibility export type PagePropOpt = DefaultInterface["PageProps"]; export type AsOpt = DefaultInterface["As"]; export type InlineCssOpt = DefaultInterface["InlineCSS"]; // Configurable type system that uses the interface export type PageComponentType< PageProps extends ViteReactServerComponentsPlugin["PageProps"] = DefaultInterface["PageProps"], R = DefaultInterface["ReactType"] > = (props: PageProps) => R; export type RootComponentType< PageProps extends ViteReactServerComponentsPlugin["PageProps"] = DefaultInterface["PageProps"], As extends ViteReactServerComponentsPlugin["As"] = DefaultInterface["As"], InlineCSS extends ViteReactServerComponentsPlugin["InlineCSS"] = DefaultInterface["InlineCSS"], R = DefaultInterface["ReactType"] > = (props: RootProps<PageProps, InlineCSS, As, R>) => R; export type RootProps< PageProps extends ViteReactServerComponentsPlugin["PageProps"] = DefaultInterface["PageProps"], InlineCSS extends ViteReactServerComponentsPlugin["InlineCSS"] = DefaultInterface["InlineCSS"], As extends ViteReactServerComponentsPlugin["As"] = DefaultInterface["As"], R = DefaultInterface["ReactType"] > = { as: As; cssFiles?: Map<string, CssContent<InlineCSS>>; pageProps?: Omit<PageProps, "children">; Page: PageComponentType<Omit<PageProps, "children">, R>; id?: string; }; /** * Boxed component type for the Html component */ export type HtmlComponentType< T extends ViteReactServerComponentsPlugin["PageProps"] = DefaultInterface["PageProps"], As extends ViteReactServerComponentsPlugin["As"] = DefaultInterface["As"], InlineCSS extends ViteReactServerComponentsPlugin["InlineCSS"] = DefaultInterface["InlineCSS"], R = DefaultInterface["ReactType"] > = (props: HtmlProps<T, InlineCSS, As, R>) => R; // Interface-aware type aliases for better type safety export type InterfaceAwareCssContent< Interface extends ViteReactServerComponentsPlugin > = CssContent<Interface["InlineCSS"]>; export type InterfaceAwareRootOptions< Interface extends ViteReactServerComponentsPlugin > = RootOptions<Interface["InlineCSS"]>; export type InterfaceAwareCssComponentType< Interface extends ViteReactServerComponentsPlugin, R = Interface["ReactType"] > = CssComponentType<Interface["InlineCSS"], R>; export type InterfaceAwareCssProps< Interface extends ViteReactServerComponentsPlugin > = CssProps<Interface["InlineCSS"]>; export type InterfaceAwareHandlerAssets< Interface extends ViteReactServerComponentsPlugin > = HandlerAssets<Interface["InlineCSS"]>; export type InterfaceAwareCreateHandlerResult< Interface extends ViteReactServerComponentsPlugin > = CreateHandlerResult<Interface["InlineCSS"]>; export type VitePluginReactClientFn = < Opt extends StreamPluginOptions = StreamPluginOptions >( options: Opt ) => Plugin[]; export type VitePluginReactServerFn = < Opt extends StreamPluginOptions = StreamPluginOptions >( options: Opt ) => Plugin[];