UNPKG

vite-plugin-react-server

Version:
207 lines (193 loc) 6.23 kB
import { type MessagePort } from "node:worker_threads"; import type { LoadHook, ResolveHook, ModuleFormat } from "node:module"; import type { ResolvedUserOptions, SerializedResolvedConfig, SerializedUserOptions, } from "../types.js"; import { fileURLToPath } from "node:url"; import { preprocessCSS, resolveConfig } from "vite"; import { readFile } from "node:fs/promises"; import { env } from "../utils/env.js"; import type { CssFileMessage, InitializedCssLoaderMessage, } from "../worker/rsc/types.js"; import { hydrateUserOptions } from "../helpers/hydrateUserOptions.js"; import { toError } from "../error/toError.js"; import { sendMessage } from "../worker/sendMessage.js"; import type { ErrorMessage } from "../worker/types.js"; /** * Global port for communication between the main thread and the CSS loader. * This port is used to send CSS file requests and receive responses. */ export let loaderPort: MessagePort | undefined; let resolvedConfig: SerializedResolvedConfig | null; let userOptions: ResolvedUserOptions | undefined; /** * Initializes the CSS loader with the necessary communication channels. * Sets up message handlers for CSS file requests and responses. * * @param data - Configuration data for the CSS loader * @param data.port - The message port for communication */ export async function initialize(data: { id: string; port: MessagePort; resolvedConfig: SerializedResolvedConfig; userOptions: SerializedUserOptions; }) { loaderPort = data.port; resolvedConfig = data.resolvedConfig; const resolvedUserOptions = hydrateUserOptions(data.userOptions); if (resolvedUserOptions.type === "error") { if (loaderPort) { sendMessage( { type: "ERROR", id: "css-loader", error: resolvedUserOptions.error, } satisfies ErrorMessage, loaderPort ); } throw resolvedUserOptions.error; } // Use the hydrated user options directly (includes recreated functions) userOptions = resolvedUserOptions.userOptions; if (loaderPort) { sendMessage( { type: "INITIALIZED_CSS_LOADER", id: data.id, } satisfies InitializedCssLoaderMessage, loaderPort ); } } /** * Processes a CSS file request. * Sends a request to the main thread and waits for the processed CSS. * * @param filePath - The file system path of the CSS file * @param config - The Vite config * @returns A promise that resolves to the processed CSS content */ async function processCssFile( filePath: string, inline: boolean ): Promise<{ format: ModuleFormat; source: string; shortCircuit: boolean }> { try { // Convert file URL to path if needed const path = filePath.startsWith("file://") ? fileURLToPath(filePath) : filePath; // Process CSS using Vite's preprocessCSS const source = await readFile(path, "utf-8"); let moduleID = path; if (userOptions?.normalizer) { const [, value] = userOptions.normalizer(path); moduleID = userOptions.moduleID?.(value || path) || path; } // Try to process CSS with preprocessCSS, fall back to raw CSS if config is incomplete let processed: { code: string; modules?: any }; try { // Create a minimal config with environments that preprocessCSS expects const viteConfig = await resolveConfig( { ...resolvedConfig, env: env, // do-not re-resolve the config file as it would import the plugin again which we do not need. configFile: false, }, "serve" ); processed = await preprocessCSS(source, path, viteConfig); } catch (error) { // If preprocessCSS fails, fall back to raw CSS if (loaderPort) { sendMessage( { type: "ERROR", id: moduleID, error: toError(error), } satisfies ErrorMessage, loaderPort ); } processed = { code: source, modules: {} }; } // If we're processing CSS for a specific page, notify the message handler if (loaderPort) { sendMessage( { type: "CSS_FILE", id: moduleID, content: processed.code, } satisfies CssFileMessage, loaderPort ); } // Return a module that can be used by React components if (inline) { return { format: "module", source: processed.code, shortCircuit: true, }; } return { format: "module", source: `export default ${JSON.stringify(processed.modules || {})};`, shortCircuit: true, }; } catch (error) { const err = toError(error); if (loaderPort) { sendMessage( { type: "ERROR", id: "css-loader", error: err, } satisfies ErrorMessage, loaderPort ); } throw err; } } /** * Vite's load hook implementation for CSS files. * Handles CSS file loading requests and returns a placeholder module. * The actual CSS content is processed in the main thread. * * @param url - The URL of the module to load * @param context - The load hook context * @param defaultLoad - The default load function * @returns A promise that resolves to the module content */ export const load: LoadHook = async (url, context, defaultLoad) => { const [name, query] = url.split("?"); if (name.endsWith(".css")) { let isInline = query?.startsWith("inline") || query?.includes("&inline"); if (isInline && query.includes('inline=')) { // handle = true/false const match = query.match(/inline=(1|true|0|false)/)?.[1]; isInline = match === '1' || match === 'true'; } return processCssFile(url, isInline); } return defaultLoad(url, context); }; /** * Vite's resolve hook implementation. * Handles module resolution during development. * * @param specifier - The module specifier to resolve * @param context - The resolve hook context * @param defaultResolve - The default resolve function * @returns A promise that resolves to the resolved module */ export const resolve: ResolveHook = (specifier, context, defaultResolve) => { return defaultResolve(specifier, context); };