UNPKG

vite-plugin-react-server

Version:
178 lines (158 loc) 5.02 kB
import { toError } from "../error/toError.js"; import type { GenericModuleLoader, HtmlComponentType, RootComponentType } from "../types.js"; export type ComponentName = "Root" | "Html"; type ResolveComponentResult<T = unknown> = | { type: "success"; component: T; error?: never; } | { type: "error"; error: Error; component?: never } | { type: "skip"; error?: never; component?: never }; type ResolveComponentOptions = { componentPath: string; exportName: string; loader: GenericModuleLoader; }; /** * Resolves a component (Root or Html) from a string path. * * This function handles: * - String paths: "src/Root.tsx" * - Fragment syntax: "src/components.tsx#MyRoot" * - Export name resolution * * @param options.componentPath - The path to the component file * @param options.exportName - The name of the export to resolve (e.g. 'Root', 'Html') * @param options.loader - The loader function to use for loading the module * * @returns A result object containing the resolved component or error */ export async function resolveComponent<T = RootComponentType | HtmlComponentType>( options: ResolveComponentOptions ): Promise<ResolveComponentResult<T>> { const { componentPath, exportName, loader } = options; try { // Handle fragment syntax (e.g., "src/components.tsx#MyRoot") let modulePath = componentPath; let moduleExportName = exportName; if (componentPath.includes('#')) { const [path, fragmentExport] = componentPath.split('#'); modulePath = path; moduleExportName = fragmentExport; } // Load the module const module = await loader(`${modulePath}#${moduleExportName}`); if (module == null) { return { type: "error", error: new Error(`Module ${modulePath} not found`), }; } if (module instanceof Error) { return { type: "error", error: module, }; } // Get the component from the module const component = module[moduleExportName]; if (!(moduleExportName in module)) { if ("error" in module) { return { type: "error", error: toError(module["error"]), }; } return { type: "error", error: new Error( `Export "${moduleExportName}" not found in module ${modulePath}. ` + (moduleExportName !== "default" ? `Did you use \`export default\`? Use \`export function ${moduleExportName}(...)\` or set pageExportName: "default" in your plugin config.` : `The module does not have a default export.`) ), }; } if (!component) { return { type: "error", error: new Error( `Export "${moduleExportName}" is null or undefined in module ${modulePath}.` ), }; } if (component instanceof Error) { return { type: "error", error: component, }; } return { type: "success", component: component as T, }; } catch (error) { return { type: "error", error: error instanceof Error ? error : new Error(String(error)), }; } } /** * Resolves Root and Html components from user options. * * This function checks if Root/Html are strings and resolves them to components. * If they're already components, it returns them as-is. * * @param options - Object containing Root, Html, and resolution options * @returns Resolved components or original values if not strings */ export async function resolveComponentOptions(options: { Root: RootComponentType | string; Html: HtmlComponentType | string; rootExportName: string; htmlExportName: string; loader: GenericModuleLoader; }): Promise<{ Root: RootComponentType; Html: HtmlComponentType; errors: Error[]; }> { const errors: Error[] = []; let resolvedRoot = options.Root; let resolvedHtml = options.Html; // Resolve Root if it's a string if (typeof options.Root === "string") { const rootResult = await resolveComponent<RootComponentType>({ componentPath: options.Root, exportName: options.rootExportName, loader: options.loader, }); if (rootResult.type === "success") { resolvedRoot = rootResult.component; } else if (rootResult.type === "error") { errors.push(rootResult.error); // Keep original value as fallback } } // Resolve Html if it's a string if (typeof options.Html === "string") { const htmlResult = await resolveComponent<HtmlComponentType>({ componentPath: options.Html, exportName: options.htmlExportName, loader: options.loader, }); if (htmlResult.type === "success") { resolvedHtml = htmlResult.component; } else if (htmlResult.type === "error") { errors.push(htmlResult.error); // Keep original value as fallback } } return { Root: resolvedRoot as RootComponentType, Html: resolvedHtml as HtmlComponentType, errors, }; }