vite-plugin-react-server
Version:
Vite plugin for React Server Components (RSC)
110 lines (98 loc) • 3.49 kB
text/typescript
import { parentPort, workerData } from "node:worker_threads";
import { messageHandler } from "./messageHandler.server.js";
import { register as registerTsx } from "tsx/esm/api";
import type { ReadyMessage } from "../types.js";
import { createLogger } from "vite";
import { handleError } from "../../error/handleError.js";
import { sendMessage } from "../sendMessage.js";
import { setMaxListenersOnPort, unrefPort } from "../../stream/setMaxListeners.js";
import { ModuleRunner, ESModulesEvaluator } from "vite/module-runner";
import { createRunnerTransport } from "./createRunnerTransport.js";
import { setRunner, setRpc } from "./runnerInstance.js";
// Initialize worker
if (!parentPort) {
throw new Error("This module must be run as a worker");
}
const logger = createLogger(workerData.resolvedConfig?.logLevel ?? "info", {
prefix: "rsc-worker",
});
// parentPort handles all messages, so needs a higher limit
if (parentPort) {
setMaxListenersOnPort(parentPort, 500);
}
parentPort?.on("message", messageHandler);
try {
const isBuildMode =
workerData.configEnv?.command === "build" ||
workerData.resolvedConfig?.mode === "production";
if (workerData.verbose) {
logger.info(
isBuildMode
? "Build mode detected - files are already built, skipping tsx hook"
: "Development/dev server mode detected"
);
}
// Keep the tsx hook around for the rare non-runner import path where a
// module that isn't transformed by Vite (e.g. a vendored dependency that
// ships .ts sources) still needs TypeScript stripping. Runner-loaded
// modules receive code that's already been through Vite's transform.
registerTsx();
parentPort!.on("messageerror", (error: Error) => {
logger.error("Parent port message serialization failed.", { error });
// Can't send via parentPort since that's what failed, so just log
});
// ModuleRunner-based fetch transport for project source. Replaces Node's
// native import() so dev:ssr invalidates per-module instead of restarting
// the worker on every save.
if (!isBuildMode && workerData.runnerPort) {
try {
setMaxListenersOnPort(workerData.runnerPort, 500);
unrefPort(workerData.runnerPort);
workerData.runnerPort.start();
const { transport, rpc } = createRunnerTransport(workerData.runnerPort);
const runner = new ModuleRunner(
{
transport,
hmr: false,
sourcemapInterceptor: false,
},
new ESModulesEvaluator()
);
setRunner(runner);
setRpc(rpc);
if (workerData.verbose) {
logger.info("ModuleRunner initialized");
}
} catch (err) {
logger.error(`Failed to initialize ModuleRunner: ${String(err)}`);
}
}
// Notify parent that we're ready
parentPort!.postMessage({
type: "READY",
env: process.env["NODE_ENV"],
pid: process.pid,
id: "worker/rsc",
} satisfies ReadyMessage);
if (process.env["NODE_ENV"] === "production") {
throw new Error("This module should not run in production mode.");
}
} catch (error: unknown) {
const handledError = handleError({
error,
logger,
panicThreshold: workerData.userOptions.panicThreshold,
context: "rsc-worker",
});
// In dev mode, try to send error message before exiting
if (parentPort && handledError != null) {
sendMessage(
{
type: "ERROR",
id: "worker/rsc",
error: handledError,
},
parentPort
);
}
}