UNPKG

vite-plugin-react-server

Version:
157 lines (140 loc) 5.02 kB
// vprs-side adapter for the React-RSC Node ESM loader. The actual // load/resolve hooks live in react-server-loader/loader — this file // threads vprs's worker context (parent MessagePort, serialized user // options, the `createDefaultModuleID` policy) into rsl's factory and // publishes the resulting hooks to the worker bootstrap path. import type { ResolvedUserOptions, SerializedResolvedConfig, SerializedUserOptions, } from "../types.js"; import type { ModuleInfo } from "rollup"; import { parentPort } from "node:worker_threads"; import type { MessagePort } from "node:worker_threads"; import type { InitializedReactLoaderMessage, ServerModuleMessage, } from "../worker/rsc/types.js"; import { join } from "node:path"; import { hydrateUserOptions } from "../helpers/hydrateUserOptions.js"; import type { LoadHook, ResolveHook } from "node:module"; import { createReactLoader } from "react-server-loader/loader"; import { createLogger, type Logger } from "vite"; import { createDefaultModuleID } from "../config/createModuleID.js"; export type LoaderOptions = { id: string; resolveDependencies?: boolean; format?: string; conditions?: string[]; importAssertions?: Record<string, unknown>; importAttributes?: Record<string, unknown>; source: string; }; export type LoaderFunction = (options: LoaderOptions) => Promise<ModuleInfo>; let initialized = false; let userOptions: ResolvedUserOptions; let loaderPort: MessagePort | null; let resolvedConfig: SerializedResolvedConfig; let logger: Logger; let verbose = false; let load: LoadHook = async (url, context, nextLoad) => nextLoad(url, context); let resolveHook: ResolveHook = async (specifier, context, nextResolve) => nextResolve(specifier, context); export function initialize(data: { id: string; port: MessagePort; userOptions: SerializedUserOptions; resolvedConfig: SerializedResolvedConfig; }) { const { id, port, userOptions: serializedUserOptions, resolvedConfig: serializedResolvedConfig, } = data; verbose = serializedUserOptions?.verbose ?? false; logger = createLogger(serializedResolvedConfig?.logLevel ?? "info", { prefix: id, }); resolvedConfig = serializedResolvedConfig; if (verbose) { logger.info(`Initializing with options: ${id}`); } loaderPort = port; const resolvedUserOptions = hydrateUserOptions(serializedUserOptions); if (resolvedUserOptions.type === "error") { throw resolvedUserOptions.error; } userOptions = resolvedUserOptions.userOptions; // Materialise vprs's default moduleID policy if the user didn't supply // one. Done here at init time so rsl's loader can reach for a stable // reference per call without recreating it. if (typeof userOptions.moduleID !== "function") { const buildConfigEnv = resolvedConfig?.configEnv ?? { command: "build", mode: "production" }; userOptions.moduleID = createDefaultModuleID( { moduleBase: userOptions.moduleBase, moduleBasePath: userOptions.moduleBasePath, autoDiscover: userOptions.autoDiscover, build: userOptions.build, dev: userOptions.dev, moduleBaseURL: userOptions.moduleBaseURL, projectRoot: userOptions.projectRoot, }, buildConfigEnv ); } const { load: rslLoad, resolve: rslResolve } = createReactLoader({ loader: userOptions.loader, verbose, logger, moduleID: (filePath) => { let moduleID = filePath; let finalID = filePath; if (userOptions?.normalizer) { const [, value] = userOptions.normalizer(filePath); moduleID = join(userOptions.moduleBasePath, value); finalID = userOptions.moduleID?.(moduleID) || moduleID; } return finalID; }, onTransform: ({ filePath, transformedId, source }) => { if (loaderPort) { loaderPort.postMessage({ type: "SERVER_MODULE", id: transformedId, url: filePath, source, } satisfies ServerModuleMessage); } }, }); load = rslLoad; resolveHook = rslResolve; if (!initialized && loaderPort) { loaderPort.postMessage({ type: "INITIALIZED_REACT_LOADER", id, } satisfies InitializedReactLoaderMessage); } initialized = true; } // Module-level load/resolve hooks for the worker bootstrap path. They // forward to whatever rsl's factory produced at init time; if init was // skipped (unexpected) they fall back to a pass-through that lets the // underlying loader chain handle the URL untouched. export const loadHook: LoadHook = async (url, context, nextLoad) => { if (!initialized) { initialize({ id: "react-loader", port: parentPort!, userOptions: {} as SerializedUserOptions, resolvedConfig: {} as SerializedResolvedConfig, }); } return load(url, context, nextLoad); }; export const resolve: ResolveHook = async (specifier, context, nextResolve) => resolveHook(specifier, context, nextResolve); export { loadHook as load };