UNPKG

vite-plugin-react-server

Version:
120 lines (112 loc) 4.33 kB
import type { PageName, PropsName, RootName, HtmlName, ResolvedUserOptions } from "../types.js"; type SupportedOptionNames = PropsName | PageName | RootName | HtmlName; type ResolvePageAndPropsOptionsSuccess<N extends SupportedOptionNames> = { [optionName in N]: string; } & { type: "success"; error?: never }; type ResolvePageAndPropsOptionsError<N extends SupportedOptionNames> = { [optionName in N]?: never; } & { type: "error"; error: unknown }; export type ResolvePageAndPropsReturn<N extends SupportedOptionNames> = | ResolvePageAndPropsOptionsSuccess<N> | ResolvePageAndPropsOptionsError<N>; export type ResolvePageAndPropsOptionsFn = <N extends SupportedOptionNames>( options: Pick< ResolvedUserOptions, PropsName | PageName | RootName | HtmlName | "pageExportName" | "propsExportName" | "rootExportName" | "htmlExportName" >, optionName: N, url: string ) => Promise<ResolvePageAndPropsReturn<N>>; /** * Resolves Page or props options to file paths. * * ## Usage Pattern: * 1. **BUILD TIME**: Called by `resolveBuildPages.ts` for each page in `build.pages` * - Resolves all Page/props paths statically * - Builds urlMap/pageMap/propsMap caches for fast runtime lookup * * 2. **RUNTIME**: Called by `getRouteFiles.ts` as dynamic fallback * - Only when route not found in auto-discovered urlMap cache * - Enables dynamic routing for routes not in build.pages * * ## Supported Option Types: * - **String**: Direct file path (e.g., "./src/HomePage.tsx") * - **Function**: Router function that takes URL and returns file path * - Sync: `(url) => "./src/pages/" + url + ".tsx"` * - Async: `(url) => Promise.resolve("./src/pages/" + url + ".tsx")` * * ## Discovery Limitation: * Function-based resolvers create a chicken-and-egg problem for auto-discovery: * - Build-time discovery needs to know all possible URLs to call resolvers * - URL discovery typically comes from scanning filesystem for Page files * - But function resolvers can't be statically analyzed without URLs * * This is why extending this pattern to Html/Root is complex when * build.pages is not specified - the system can't discover what files exist * without first knowing what URLs to resolve. */ export const resolveUrlOption: ResolvePageAndPropsOptionsFn = async function _resolveUrlOption( options, optionName, url ) { let configName = optionName as string; if(optionName === options.pageExportName) { configName = "Page" as never; } else if(optionName === options.propsExportName) { configName = "props" as never; } else if(optionName === options.rootExportName) { configName = "Root" as never; } else if(optionName === options.htmlExportName) { configName = "Html" as never; } if(!(configName === "Page" || configName === "props" || configName === "Root" || configName === "Html")) { throw new Error(`Invalid option name: ${optionName}`); } try { let optionValue: any; switch (configName) { case "Page": optionValue = options.Page; break; case "props": optionValue = options.props; break; case "Root": optionValue = options.Root; break; case "Html": optionValue = options.Html; break; default: return { type: "error", error: new Error(`Unknown config name: ${configName}`) }; } switch (typeof optionValue) { case "function": { const fn = optionValue; const result = fn(url); if (typeof result === "string") { return { type: "success", [configName]: result } as any; } if (result instanceof Promise) { try { const promiseResult = await result; if (typeof promiseResult === "string") { return { type: "success", [configName]: promiseResult } as any; } } catch (error) { return { type: "error", error: error as Error }; } } break; } case "string": return { type: "success", [configName]: optionValue } as any; default: break; } return { type: "error", error: new Error(`${configName} must be string or function that returns a string`) }; } catch (error) { return { type: "error", error: error as Error }; } }