UNPKG

vite-plugin-react-server

Version:
172 lines (149 loc) 7.16 kB
import { readFileSync } from "node:fs"; import type { VitePluginFn } from "../../types.js"; import { configureReactServer } from "./configureReactServer.client.js"; import { resolveOptions } from "../config/resolveOptions.js"; import { detectClientModule } from "react-server-loader/directives"; import type { ConfigEnv } from "vite"; /** * Dev server plugin for client environment. * Uses configureServer hook for proper dev server setup. */ export const vitePluginReactDevServer: VitePluginFn = function _vitePluginReactServerDevClient(options) { if (options == null) { throw new Error("options is required"); } if (options.verbose) { } const resolvedOptions = resolveOptions(options); if (resolvedOptions.type === "error") { if (resolvedOptions.error != null) { throw resolvedOptions.error; } throw new Error("Failed to resolve options"); } const userOptions = resolvedOptions.userOptions; let configEnv: ConfigEnv | undefined; let hmrHandler: { sendHmrUpdate: (file: string, routes?: string[]) => void } | null = null; let isProcessingHmr = false; // Prevent recursive HMR updates return { name: "vite-plugin-react-server:dev-server-client", apply: "serve", // Only apply in dev server mode // No applyToEnvironment — hotUpdate needs to run for all environments // that should work regardless of environment filtering config(_config, viteConfigEnv) { configEnv = viteConfigEnv; }, configureServer(server) { // Log that plugin is being configured server.config.logger.info(`[vite-plugin-react-server] Dev server plugin configured for client environment`); // Configure the React server for client environment (worker-based) // This uses the existing configureReactServer.client.js implementation hmrHandler = configureReactServer({ server, autoDiscoveredFiles: { propsMap: new Map(), pageMap: new Map(), rootMap: new Map(), htmlMap: new Map(), routeMap: new Map(), urlMap: new Map(), errors: [], workerPaths: {}, serverEntry: null, clientEntry: {}, clientInputs: {}, staticInputs: {}, serverInputs: {}, // staticManifest removed from AutoDiscoveredFiles serverActions: {}, }, userOptions, configEnv: configEnv!, serverManifest: {}, resolvedConfig: server.config, }); }, hotUpdate(ctx: any) { const { file, server } = ctx; const envName = ctx.environment?.name ?? 'unknown'; // Only run worker invalidation from the client environment (once per change) if (envName !== 'client') return; // Prevent recursive HMR updates if (isProcessingHmr) { return undefined; } // Handle server component file changes const moduleBase = userOptions.moduleBase || "src"; const projectRoot = userOptions.projectRoot || server.config.root; // Normalize paths for comparison (handle both absolute and relative) const normalizedFile = file.replace(projectRoot, '').replace(/^\/+/, ''); const isInModuleBase = normalizedFile.startsWith(moduleBase + '/'); const isSourceFile = isInModuleBase && (file.endsWith('.tsx') || file.endsWith('.ts') || file.endsWith('.jsx') || file.endsWith('.js')); // CSS edits route through the same worker-invalidation path so the // ModuleRunner cache drops every reachable CSS module before the // next render asks for class-name hashes. const isCssFile = isInModuleBase && (file.endsWith('.css') || file.endsWith('.scss') || file.endsWith('.sass') || file.endsWith('.less')); // Skip client components — Vite owns client-side HMR (Fast Refresh // when `@vitejs/plugin-react` is installed, plain reload otherwise). // Worker invalidation is for the server tree. const isClientFile = isSourceFile && (() => { try { const source = readFileSync(file, "utf-8"); return detectClientModule({ source, moduleId: file }); } catch { return false; } })(); const isServerFile = isSourceFile && !isClientFile; const shouldInvalidateWorker = isServerFile || isCssFile; if (shouldInvalidateWorker && hmrHandler) { isProcessingHmr = true; try { if (userOptions.verbose) { server.config.logger.info(`[vite-plugin-react-server] File changed: ${file}, sending HMR update...`); } // Tell the worker to invalidate. Its HMR_UPDATE handler clears the // ModuleRunner cache so the next import re-fetches transformed code // through Vite — no worker restart needed. hmrHandler.sendHmrUpdate(file); // Notify the browser to refetch the RSC stream. In dev:rsc the // equivalent send lives in plugin.server.ts's hmrPlugin, which only // runs under the react-server orchestrator. dev:ssr (this plugin) // never loads that orchestrator, so without sending the event here // the worker invalidates correctly but the browser keeps showing // pre-edit content — `useRscHmr` listens for this event and only // refetches on receipt. For CSS files the consumer's <link> tag // still points at the same URL after the edit, so we tag the event // so the client also cache-busts matching stylesheets. server.ws.send({ type: "custom", event: "vite-plugin-react-server:server-component-update", data: { file: normalizedFile, path: file, kind: isCssFile ? "css" : "component" }, }); // The runner takes care of per-module invalidation, so the only // thing left to clear here is the local processing flag. setTimeout(() => { isProcessingHmr = false; }, 100); } catch (error) { server.config.logger.error(`[vite-plugin-react-server] Error handling HMR update: ${error}`); isProcessingHmr = false; } // For CSS edits in dev:ssr, suppress Vite's default behavior. Vite's // fallback for module-graph-untracked CSS is a full page reload, and // even tracked CSS modules in dev:ssr can fall back to reload because // vprs renders them server-side via the <Css cssFiles={...}/> pattern // (the client never directly imports them, so Vite's CSS HMR isn't // reachable). useRscHmr handles both shapes: // - inlined <style>: refetch brings new content // - <link href=…>: refreshCssLinks cache-busts the URL if (isCssFile) return []; } else if (shouldInvalidateWorker && !hmrHandler) { if (userOptions.verbose) { server.config.logger.warn(`[vite-plugin-react-server] Source file changed but HMR handler not available yet: ${file}`); } } // Don't suppress — plugin.server.ts hotUpdate handles page reload prevention }, }; };