vite-plugin-react-server
Version:
Vite plugin for React Server Components (RSC)
147 lines (131 loc) • 4.28 kB
text/typescript
import type { Logger } from "vite";
import type { IncomingMessage, ServerResponse } from "node:http";
import type { Worker } from "node:worker_threads";
import type { RscWorkerInputMessage } from "../worker/rsc/types.js";
import type { MessageHandler } from "../types.js";
import { cleanupServerAction } from "../dev-server/cleanupServerAction.client.js";
import { logError, toError } from "../error/index.js";
import { PassThrough } from "node:stream";
import type {
ServerActionHandlerOptions,
} from "./handleServerActionHelper.js";
import { parseServerActionRequestBody, createServerActionResponse, setupServerActionHeaders } from "./handleServerActionHelper.js";
// Use shared helper instead of duplicating logic
// Use shared helper instead of duplicating logic
/**
* Creates a pass-through stream for server action responses.
*/
export function createServerActionStream(res: ServerResponse): PassThrough {
const passThrough = new PassThrough();
passThrough.pipe(res, { end: true });
passThrough.on('end', () => {
res.end();
});
return passThrough;
}
/**
* Handles errors in server action processing.
*/
export function handleServerActionError(error: unknown, res: ServerResponse, logger?: Logger) {
const err = toError(error);
logError(err, logger);
res.statusCode = 500;
res.end(JSON.stringify(createServerActionResponse(undefined, err.message)));
}
/**
* Client-side server action handler that delegates to worker
*/
export async function handleServerAction(
req: IncomingMessage,
res: ServerResponse,
options: ServerActionHandlerOptions & { worker?: Worker }
): Promise<void> {
if (!options.worker) {
throw new Error("Worker is required for client-side server actions");
}
let messageHandler: MessageHandler<any> | null = null;
const passThrough = createServerActionStream(res);
try {
// Read request body
const chunks: Buffer[] = [];
for await (const chunk of req) {
chunks.push(chunk);
}
const body = Buffer.concat(chunks).toString();
// Parse the server action request
const { id, args } = parseServerActionRequestBody(body, req.url);
// Set up response headers
setupServerActionHeaders(res);
// Send server action request to worker
options.worker.postMessage({
type: "SERVER_ACTION",
id,
args,
} satisfies RscWorkerInputMessage);
// Handle worker messages
messageHandler = (message: any) => {
if (message.type === "RSC_CHUNK") {
passThrough.write(message.chunk);
} else if (message.type === "RSC_END") {
if (messageHandler) {
cleanupServerAction(passThrough, options.worker!, messageHandler, res);
}
} else if (message.type === "ERROR") {
if (messageHandler) {
cleanupServerAction(
passThrough,
options.worker!,
messageHandler,
res,
message.error,
options.logger
);
}
}
};
options.worker.on("message", messageHandler);
// Handle errors
passThrough.on("error", (error: unknown) => {
if (messageHandler) {
cleanupServerAction(
passThrough,
options.worker!,
messageHandler,
res,
error,
options.logger
);
}
});
} catch (error) {
handleServerActionError(error, res, options.logger);
}
}
/**
* Client-side ViteDevServer-specific handler that delegates to worker
*/
export async function handleServerActionWithViteServer(
req: IncomingMessage,
res: ServerResponse,
server: any,
handlerOptions: {
verbose?: boolean;
projectRoot: string;
worker?: Worker;
}
): Promise<void> {
return handleServerAction(req, res, {
projectRoot: handlerOptions.projectRoot,
verbose: handlerOptions.verbose,
logger: server.config.customLogger || server.config.logger,
worker: handlerOptions.worker,
});
}
// Re-export helper functions for the entry point
export {
parseServerActionRequest,
parseServerActionRequestBody,
createServerActionResponse,
setupServerActionHeaders
} from "./handleServerActionHelper.js";
export type { ServerActionRequest, ServerActionHandlerOptions } from "./handleServerActionHelper.js";