@vitejs/plugin-rsc
Version:
React Server Components (RSC) support for Vite.
1,293 lines (1,287 loc) • 62 kB
JavaScript
import { tinyassert } from "./dist-DEF94lDJ.js";
import { vitePluginRscCore } from "./plugin-CZbI4rhS.js";
import { generateEncryptionKey, toBase64 } from "./encryption-utils-BDwwcMVT.js";
import { createRpcServer } from "./rpc-tGuLT8PD.js";
import { normalizeViteImportAnalysisUrl, prepareError } from "./vite-utils-Vzd7cqfv.js";
import { createRequire } from "node:module";
import assert from "node:assert";
import { createHash } from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { createRequestListener } from "@remix-run/node-fetch-server";
import * as esModuleLexer from "es-module-lexer";
import MagicString from "magic-string";
import { defaultServerConditions, isCSSRequest, normalizePath, parseAstAsync } from "vite";
import { crawlFrameworkPkgs, findClosestPkgJsonPath } from "vitefu";
import { walk } from "estree-walker";
import { analyze, extract_names } from "periscopic";
//#region src/transforms/hoist.ts
function transformHoistInlineDirective(input, ast, { runtime, rejectNonAsyncFunction,...options }) {
const output = new MagicString(input);
const directive = typeof options.directive === "string" ? exactRegex(options.directive) : options.directive;
walk(ast, { enter(node) {
if (node.type === "ExportAllDeclaration") this.remove();
if (node.type === "ExportNamedDeclaration" && !node.declaration) this.remove();
} });
const analyzed = analyze(ast);
const names = [];
walk(ast, { enter(node, parent) {
if ((node.type === "FunctionExpression" || node.type === "FunctionDeclaration" || node.type === "ArrowFunctionExpression") && node.body.type === "BlockStatement") {
const match = matchDirective(node.body.body, directive);
if (!match) return;
if (!node.async && rejectNonAsyncFunction) throw Object.assign(/* @__PURE__ */ new Error(`"${directive}" doesn't allow non async function`), { pos: node.start });
const scope = analyzed.map.get(node);
tinyassert(scope);
const declName = node.type === "FunctionDeclaration" && node.id.name;
const originalName = declName || parent?.type === "VariableDeclarator" && parent.id.type === "Identifier" && parent.id.name || "anonymous_server_function";
const bindVars = [...scope.references].filter((ref) => {
if (ref === declName) return false;
const owner = scope.find_owner(ref);
return owner && owner !== scope && owner !== analyzed.scope;
});
let newParams = [...bindVars, ...node.params.map((n) => input.slice(n.start, n.end))].join(", ");
if (bindVars.length > 0 && options.decode) {
newParams = ["$$hoist_encoded", ...node.params.map((n) => input.slice(n.start, n.end))].join(", ");
output.appendLeft(node.body.body[0].start, `const [${bindVars.join(",")}] = ${options.decode("$$hoist_encoded")};\n`);
}
const newName = `$$hoist_${names.length}` + (originalName ? `_${originalName}` : "");
names.push(newName);
output.update(node.start, node.body.start, `\n;${options.noExport ? "" : "export "}${node.async ? "async " : ""}function ${newName}(${newParams}) `);
output.appendLeft(node.end, `;\n/* #__PURE__ */ Object.defineProperty(${newName}, "name", { value: ${JSON.stringify(originalName)} });\n`);
output.move(node.start, node.end, input.length);
let newCode = `/* #__PURE__ */ ${runtime(newName, newName, { directiveMatch: match })}`;
if (bindVars.length > 0) {
const bindArgs = options.encode ? options.encode("[" + bindVars.join(", ") + "]") : bindVars.join(", ");
newCode = `${newCode}.bind(null, ${bindArgs})`;
}
if (declName) {
newCode = `const ${declName} = ${newCode};`;
if (parent?.type === "ExportDefaultDeclaration") {
output.remove(parent.start, node.start);
newCode = `${newCode}\nexport default ${declName};`;
}
}
output.appendLeft(node.start, newCode);
}
} });
return {
output,
names
};
}
const exactRegex = (s) => /* @__PURE__ */ new RegExp("^" + s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&") + "$");
function matchDirective(body, directive) {
for (const stable of body) if (stable.type === "ExpressionStatement" && stable.expression.type === "Literal" && typeof stable.expression.value === "string") {
const match = stable.expression.value.match(directive);
if (match) return match;
}
}
//#endregion
//#region src/transforms/wrap-export.ts
function transformWrapExport(input, ast, options) {
const output = new MagicString(input);
const exportNames = [];
const toAppend = [];
const filter = options.filter ?? (() => true);
function wrapSimple(start, end, exports) {
const newCode = exports.map((e) => [filter(e.name, e.meta) && `${e.name} = /* #__PURE__ */ ${options.runtime(e.name, e.name, e.meta)};\n`, `export { ${e.name} };\n`]).flat().filter(Boolean).join("");
output.update(start, end, newCode);
output.move(start, end, input.length);
}
function wrapExport(name, exportName, meta = {}) {
if (!filter(exportName, meta)) {
toAppend.push(`export { ${name} as ${exportName} }`);
return;
}
toAppend.push(`const $$wrap_${name} = /* #__PURE__ */ ${options.runtime(name, exportName, meta)}`, `export { $$wrap_${name} as ${exportName} }`);
}
function validateNonAsyncFunction(node, ok) {
if (options.rejectNonAsyncFunction && !ok) throw Object.assign(/* @__PURE__ */ new Error(`unsupported non async function`), { pos: node.start });
}
for (const node of ast.body) {
if (node.type === "ExportNamedDeclaration") if (node.declaration) if (node.declaration.type === "FunctionDeclaration" || node.declaration.type === "ClassDeclaration") {
/**
* export function foo() {}
*/
validateNonAsyncFunction(node, node.declaration.type === "FunctionDeclaration" && node.declaration.async);
const name = node.declaration.id.name;
wrapSimple(node.start, node.declaration.start, [{
name,
meta: {
isFunction: true,
declName: name
}
}]);
} else if (node.declaration.type === "VariableDeclaration") {
/**
* export const foo = 1, bar = 2
*/
validateNonAsyncFunction(node, node.declaration.declarations.every((decl) => decl.init?.type === "ArrowFunctionExpression" && decl.init.async));
if (node.declaration.kind === "const") output.update(node.declaration.start, node.declaration.start + 5, "let");
const names = node.declaration.declarations.flatMap((decl) => extract_names(decl.id));
let isFunction = false;
if (node.declaration.declarations.length === 1) {
const decl = node.declaration.declarations[0];
isFunction = decl.id.type === "Identifier" && (decl.init?.type === "ArrowFunctionExpression" || decl.init?.type === "FunctionExpression");
}
wrapSimple(node.start, node.declaration.start, names.map((name) => ({
name,
meta: {
isFunction,
declName: name
}
})));
} else node.declaration;
else if (node.source) {
/**
* export { foo, bar as car } from './foo'
*/
output.remove(node.start, node.end);
for (const spec of node.specifiers) {
tinyassert(spec.local.type === "Identifier");
tinyassert(spec.exported.type === "Identifier");
const name = spec.local.name;
toAppend.push(`import { ${name} as $$import_${name} } from ${node.source.raw}`);
wrapExport(`$$import_${name}`, spec.exported.name);
}
} else {
/**
* export { foo, bar as car }
*/
output.remove(node.start, node.end);
for (const spec of node.specifiers) {
tinyassert(spec.local.type === "Identifier");
tinyassert(spec.exported.type === "Identifier");
wrapExport(spec.local.name, spec.exported.name);
}
}
/**
* export * from './foo'
*/
if (!options.ignoreExportAllDeclaration && node.type === "ExportAllDeclaration") throw Object.assign(/* @__PURE__ */ new Error("unsupported ExportAllDeclaration"), { pos: node.start });
/**
* export default function foo() {}
* export default class Foo {}
* export default () => {}
*/
if (node.type === "ExportDefaultDeclaration") {
validateNonAsyncFunction(node, node.declaration.type === "Identifier" || node.declaration.type === "FunctionDeclaration" && node.declaration.async);
let localName;
let isFunction = false;
let declName;
let defaultExportIdentifierName;
if ((node.declaration.type === "FunctionDeclaration" || node.declaration.type === "ClassDeclaration") && node.declaration.id) {
localName = node.declaration.id.name;
output.remove(node.start, node.declaration.start);
isFunction = node.declaration.type === "FunctionDeclaration";
declName = node.declaration.id.name;
} else {
localName = "$$default";
output.update(node.start, node.declaration.start, "const $$default = ");
if (node.declaration.type === "Identifier") defaultExportIdentifierName = node.declaration.name;
}
wrapExport(localName, "default", {
isFunction,
declName,
defaultExportIdentifierName
});
}
}
if (toAppend.length > 0) output.append([
"",
...toAppend,
""
].join(";\n"));
return {
exportNames,
output
};
}
//#endregion
//#region src/transforms/utils.ts
function hasDirective(body, directive) {
return !!body.find((stmt) => stmt.type === "ExpressionStatement" && stmt.expression.type === "Literal" && typeof stmt.expression.value === "string" && stmt.expression.value === directive);
}
//#endregion
//#region src/transforms/proxy-export.ts
function transformDirectiveProxyExport(ast, options) {
if (!hasDirective(ast.body, options.directive)) return;
return transformProxyExport(ast, options);
}
function transformProxyExport(ast, options) {
if (options.keep && typeof options.code !== "string") throw new Error("`keep` option requires `code`");
const output = new MagicString(options.code ?? " ".repeat(ast.end));
const exportNames = [];
function createExport(node, names) {
exportNames.push(...names);
const newCode = names.map((name) => (name === "default" ? `export default` : `export const ${name} =`) + ` /* #__PURE__ */ ${options.runtime(name)};\n`).join("");
output.update(node.start, node.end, newCode);
}
function validateNonAsyncFunction(node, ok) {
if (options.rejectNonAsyncFunction && !ok) throw Object.assign(/* @__PURE__ */ new Error(`unsupported non async function`), { pos: node.start });
}
for (const node of ast.body) {
if (node.type === "ExportNamedDeclaration") {
if (node.declaration) if (node.declaration.type === "FunctionDeclaration" || node.declaration.type === "ClassDeclaration") {
/**
* export function foo() {}
*/
validateNonAsyncFunction(node, node.declaration.type === "FunctionDeclaration" && node.declaration.async);
createExport(node, [node.declaration.id.name]);
} else if (node.declaration.type === "VariableDeclaration") {
/**
* export const foo = 1, bar = 2
*/
validateNonAsyncFunction(node, node.declaration.declarations.every((decl) => decl.init?.type === "ArrowFunctionExpression" && decl.init.async));
if (options.keep && options.code) {
if (node.declaration.declarations.length === 1) {
const decl = node.declaration.declarations[0];
if (decl.id.type === "Identifier" && decl.init) {
const name = decl.id.name;
const value = options.code.slice(decl.init.start, decl.init.end);
const newCode = `export const ${name} = /* #__PURE__ */ ${options.runtime(name, { value })};`;
output.update(node.start, node.end, newCode);
exportNames.push(name);
continue;
}
}
}
const names = node.declaration.declarations.flatMap((decl) => extract_names(decl.id));
createExport(node, names);
} else node.declaration;
else {
/**
* export { foo, bar as car } from './foo'
* export { foo, bar as car }
*/
const names = [];
for (const spec of node.specifiers) {
tinyassert(spec.exported.type === "Identifier");
names.push(spec.exported.name);
}
createExport(node, names);
}
continue;
}
/**
* export * from './foo'
*/
if (!options.ignoreExportAllDeclaration && node.type === "ExportAllDeclaration") throw new Error("unsupported ExportAllDeclaration");
/**
* export default function foo() {}
* export default class Foo {}
* export default () => {}
*/
if (node.type === "ExportDefaultDeclaration") {
validateNonAsyncFunction(node, node.declaration.type === "Identifier" || node.declaration.type === "FunctionDeclaration" && node.declaration.async);
createExport(node, ["default"]);
continue;
}
if (options.keep) continue;
output.remove(node.start, node.end);
}
return {
exportNames,
output
};
}
//#endregion
//#region src/transforms/server-action.ts
function transformServerActionServer(input, ast, options) {
if (hasDirective(ast.body, "use server")) return transformWrapExport(input, ast, options);
return transformHoistInlineDirective(input, ast, {
...options,
directive: "use server"
});
}
//#endregion
//#region src/plugin.ts
let serverReferences = {};
let server;
let config;
let rscBundle;
let buildAssetsManifest;
let isScanBuild = false;
const BUILD_ASSETS_MANIFEST_NAME = "__vite_rsc_assets_manifest.js";
let clientReferenceMetaMap = {};
let serverResourcesMetaMap = {};
const PKG_NAME = "@vitejs/plugin-rsc";
const REACT_SERVER_DOM_NAME = `${PKG_NAME}/vendor/react-server-dom`;
const VIRTUAL_ENTRIES = { browser: "virtual:vite-rsc/entry-browser" };
const require = createRequire(import.meta.url);
function resolvePackage(name) {
return pathToFileURL(require.resolve(name)).href;
}
/** @experimental */
function vitePluginRscMinimal(rscPluginOptions = {}) {
return [
{
name: "rsc:minimal",
enforce: "pre",
async config() {
await esModuleLexer.init;
},
configResolved(config_) {
config = config_;
},
configureServer(server_) {
server = server_;
}
},
{
name: "rsc:vite-client-raw-import",
transform: {
order: "post",
handler(code) {
if (code.includes("__vite_rsc_raw_import__")) return code.replace("__vite_rsc_raw_import__", "import");
}
}
},
...vitePluginRscCore(),
...vitePluginUseClient(rscPluginOptions),
...vitePluginUseServer(rscPluginOptions),
...vitePluginDefineEncryptionKey(rscPluginOptions)
];
}
function vitePluginRsc(rscPluginOptions = {}) {
const buildApp = async (builder) => {
if (!builder.environments.ssr?.config.build.rollupOptions.input) {
isScanBuild = true;
builder.environments.rsc.config.build.write = false;
builder.environments.client.config.build.write = false;
await builder.build(builder.environments.rsc);
await builder.build(builder.environments.client);
isScanBuild = false;
builder.environments.rsc.config.build.write = true;
builder.environments.client.config.build.write = true;
await builder.build(builder.environments.rsc);
clientReferenceMetaMap = sortObject(clientReferenceMetaMap);
serverResourcesMetaMap = sortObject(serverResourcesMetaMap);
await builder.build(builder.environments.client);
writeAssetsManifest(["rsc"]);
return;
}
isScanBuild = true;
builder.environments.rsc.config.build.write = false;
builder.environments.ssr.config.build.write = false;
await builder.build(builder.environments.rsc);
await builder.build(builder.environments.ssr);
isScanBuild = false;
builder.environments.rsc.config.build.write = true;
builder.environments.ssr.config.build.write = true;
await builder.build(builder.environments.rsc);
clientReferenceMetaMap = sortObject(clientReferenceMetaMap);
serverResourcesMetaMap = sortObject(serverResourcesMetaMap);
await builder.build(builder.environments.client);
await builder.build(builder.environments.ssr);
writeAssetsManifest(["ssr", "rsc"]);
};
function writeAssetsManifest(environmentNames) {
const assetsManifestCode = `export default ${serializeValueWithRuntime(buildAssetsManifest)}`;
for (const name of environmentNames) {
const manifestPath = path.join(config.environments[name].build.outDir, BUILD_ASSETS_MANIFEST_NAME);
fs.writeFileSync(manifestPath, assetsManifestCode);
}
}
return [
{
name: "rsc",
async config(config$1, env) {
const result = await crawlFrameworkPkgs({
root: process.cwd(),
isBuild: env.command === "build",
isFrameworkPkgByJson(pkgJson) {
if ([PKG_NAME, "react-dom"].includes(pkgJson.name)) return;
const deps = pkgJson["peerDependencies"];
return deps && "react" in deps;
}
});
const noExternal = [
"react",
"react-dom",
"server-only",
"client-only",
PKG_NAME,
...result.ssr.noExternal.sort()
];
return {
appType: "custom",
define: { "import.meta.env.__vite_rsc_build__": JSON.stringify(env.command === "build") },
environments: {
client: {
build: {
outDir: config$1.environments?.client?.build?.outDir ?? "dist/client",
rollupOptions: { input: rscPluginOptions.entries?.client && { index: rscPluginOptions.entries.client } }
},
optimizeDeps: {
include: ["react-dom/client", `${REACT_SERVER_DOM_NAME}/client.browser`],
exclude: [PKG_NAME]
}
},
ssr: {
build: {
outDir: config$1.environments?.ssr?.build?.outDir ?? "dist/ssr",
rollupOptions: { input: rscPluginOptions.entries?.ssr && { index: rscPluginOptions.entries.ssr } }
},
resolve: { noExternal },
optimizeDeps: {
include: [
"react",
"react-dom",
"react/jsx-runtime",
"react/jsx-dev-runtime",
"react-dom/server.edge",
`${REACT_SERVER_DOM_NAME}/client.edge`
],
exclude: [PKG_NAME]
}
},
rsc: {
build: {
outDir: config$1.environments?.rsc?.build?.outDir ?? "dist/rsc",
emitAssets: true,
rollupOptions: { input: rscPluginOptions.entries?.rsc && { index: rscPluginOptions.entries.rsc } }
},
resolve: {
conditions: ["react-server", ...defaultServerConditions],
noExternal
},
optimizeDeps: {
include: [
"react",
"react-dom",
"react/jsx-runtime",
"react/jsx-dev-runtime",
`${REACT_SERVER_DOM_NAME}/server.edge`,
`${REACT_SERVER_DOM_NAME}/client.edge`
],
exclude: [PKG_NAME]
}
}
},
builder: {
sharedPlugins: true,
sharedConfigBuild: true,
buildApp: rscPluginOptions.useBuildAppHook ? void 0 : buildApp
}
};
},
buildApp: rscPluginOptions.useBuildAppHook ? buildApp : void 0,
configureServer() {
globalThis.__viteRscDevServer = server;
if (rscPluginOptions.disableServerHandler) return;
if (rscPluginOptions.serverHandler === false) return;
const options = rscPluginOptions.serverHandler ?? {
environmentName: "rsc",
entryName: "index"
};
const environment = server.environments[options.environmentName];
const source = getEntrySource(environment.config, options.entryName);
return () => {
server.middlewares.use(async (req, res, next) => {
try {
const resolved = await environment.pluginContainer.resolveId(source);
assert(resolved, `[vite-rsc] failed to resolve server handler '${source}'`);
const mod = await environment.runner.import(resolved.id);
await createRequestListener(mod.default)(req, res);
} catch (e) {
next(e);
}
});
};
},
async configurePreviewServer(server$1) {
if (rscPluginOptions.disableServerHandler) return;
if (rscPluginOptions.serverHandler === false) return;
const options = rscPluginOptions.serverHandler ?? {
environmentName: "rsc",
entryName: "index"
};
const entryFile = path.join(config.environments[options.environmentName].build.outDir, `${options.entryName}.js`);
const entry = pathToFileURL(entryFile).href;
const mod = await import(
/* @vite-ignore */
entry
);
const handler = createRequestListener(mod.default);
server$1.middlewares.use((req, _res, next) => {
delete req.headers["accept-encoding"];
next();
});
return () => {
server$1.middlewares.use(async (req, res, next) => {
try {
await handler(req, res);
} catch (e) {
next(e);
}
});
};
},
async hotUpdate(ctx) {
if (isCSSRequest(ctx.file)) {
if (this.environment.name === "client") return ctx.modules.filter((m) => !m.id?.includes("?direct"));
}
const ids = ctx.modules.map((mod) => mod.id).filter((v) => v !== null);
if (ids.length === 0) return;
function isInsideClientBoundary(mods) {
const visited = /* @__PURE__ */ new Set();
function recurse(mod) {
if (!mod.id) return false;
if (clientReferenceMetaMap[mod.id]) return true;
if (visited.has(mod.id)) return false;
visited.add(mod.id);
for (const importer of mod.importers) if (recurse(importer)) return true;
return false;
}
return mods.some((mod) => recurse(mod));
}
if (!isInsideClientBoundary(ctx.modules)) {
if (this.environment.name === "rsc") {
if (ctx.modules.length === 1) {
const importers = [...ctx.modules[0].importers];
if (importers.length > 0 && importers.every((m) => m.id && isCSSRequest(m.id))) return [];
}
for (const mod of ctx.modules) if (mod.type === "js") try {
await this.environment.transformRequest(mod.url);
} catch (e) {
server.environments.client.hot.send({
type: "error",
err: prepareError(e)
});
throw e;
}
ctx.server.environments.client.hot.send({
type: "custom",
event: "rsc:update",
data: { file: ctx.file }
});
}
if (this.environment.name === "client") {
const env = ctx.server.environments.rsc;
const mod = env.moduleGraph.getModuleById(ctx.file);
if (mod) {
for (const clientMod of ctx.modules) for (const importer of clientMod.importers) if (importer.id && isCSSRequest(importer.id)) await this.environment.reloadModule(importer);
return [];
}
}
}
}
},
{
name: "rsc:load-ssr-module",
transform(code) {
if (code.includes("import.meta.viteRsc.loadSsrModule(")) return code.replaceAll(`import.meta.viteRsc.loadSsrModule(`, `import.meta.viteRsc.loadModule("ssr", `);
}
},
{
name: "rsc:load-environment-module",
async transform(code) {
if (!code.includes("import.meta.viteRsc.loadModule")) return;
const s = new MagicString(code);
for (const match of code.matchAll(/import\.meta\.viteRsc\.loadModule\(([\s\S]*?)\)/dg)) {
const argCode = match[1].trim();
const [environmentName, entryName] = evalValue(`[${argCode}]`);
let replacement;
if (this.environment.mode === "dev" && rscPluginOptions.loadModuleDevProxy) {
const origin = server.resolvedUrls?.local[0];
assert(origin, "[vite-rsc] no server for loadModueleDevProxy");
const endpoint = origin + "__vite_rsc_load_module_dev_proxy?" + new URLSearchParams({
environmentName,
entryName
});
replacement = `__vite_rsc_rpc.createRpcClient(${JSON.stringify({ endpoint })})`;
s.prepend(`import * as __vite_rsc_rpc from "@vitejs/plugin-rsc/utils/rpc";`);
} else if (this.environment.mode === "dev") {
const environment = server.environments[environmentName];
const source = getEntrySource(environment.config, entryName);
const resolved = await environment.pluginContainer.resolveId(source);
assert(resolved, `[vite-rsc] failed to resolve entry '${source}'`);
replacement = `globalThis.__viteRscDevServer.environments[${JSON.stringify(environmentName)}].runner.import(${JSON.stringify(resolved.id)})`;
} else replacement = JSON.stringify(`__vite_rsc_load_module:${this.environment.name}:${environmentName}:${entryName}`);
const [start, end] = match.indices[0];
s.overwrite(start, end, replacement);
}
if (s.hasChanged()) return {
code: s.toString(),
map: s.generateMap({ hires: "boundary" })
};
},
renderChunk(code, chunk) {
if (!code.includes("__vite_rsc_load_module")) return;
const s = new MagicString(code);
for (const match of code.matchAll(/['"]__vite_rsc_load_module:(\w+):(\w+):(\w+)['"]/dg)) {
const [fromEnv, toEnv, entryName] = match.slice(1);
const importPath = normalizeRelativePath(path.relative(path.join(config.environments[fromEnv].build.outDir, chunk.fileName, ".."), path.join(config.environments[toEnv].build.outDir, `${entryName}.js`)));
const replacement = `(import(${JSON.stringify(importPath)}))`;
const [start, end] = match.indices[0];
s.overwrite(start, end, replacement);
}
if (s.hasChanged()) return {
code: s.toString(),
map: s.generateMap({ hires: "boundary" })
};
}
},
{
name: "vite-rsc-load-module-dev-proxy",
apply: () => !!rscPluginOptions.loadModuleDevProxy,
configureServer(server$1) {
async function createHandler(url) {
const { environmentName, entryName } = Object.fromEntries(url.searchParams);
assert(environmentName);
assert(entryName);
const environment = server$1.environments[environmentName];
const source = getEntrySource(environment.config, entryName);
const resolvedEntry = await environment.pluginContainer.resolveId(source);
assert(resolvedEntry, `[vite-rsc] failed to resolve entry '${source}'`);
const runnerProxy = new Proxy({}, { get(_target, p, _receiver) {
if (typeof p !== "string" || p === "then") return;
return async (...args) => {
const mod = await environment.runner.import(resolvedEntry.id);
return mod[p](...args);
};
} });
return createRpcServer(runnerProxy);
}
server$1.middlewares.use(async (req, res, next) => {
const url = new URL(req.url ?? "/", `http://localhost`);
if (url.pathname === "/__vite_rsc_load_module_dev_proxy") {
try {
const handler = await createHandler(url);
createRequestListener(handler)(req, res);
} catch (e) {
next(e);
}
return;
}
next();
});
}
},
{
name: "rsc:virtual:vite-rsc/assets-manifest",
resolveId(source) {
if (source === "virtual:vite-rsc/assets-manifest") {
if (this.environment.mode === "build") return {
id: source,
external: true
};
return `\0` + source;
}
},
load(id) {
if (id === "\0virtual:vite-rsc/assets-manifest") {
assert(this.environment.name !== "client");
assert(this.environment.mode === "dev");
const entryUrl = assetsURL("@id/__x00__" + VIRTUAL_ENTRIES.browser);
const manifest = {
bootstrapScriptContent: `import(${serializeValueWithRuntime(entryUrl)})`,
clientReferenceDeps: {}
};
return `export default ${JSON.stringify(manifest, null, 2)}`;
}
},
generateBundle(_options, bundle) {
if (this.environment.name === "rsc") rscBundle = bundle;
if (this.environment.name === "client") {
const filterAssets = rscPluginOptions.copyServerAssetsToClient ?? (() => true);
const rscBuildOptions = config.environments.rsc.build;
const rscViteManifest = typeof rscBuildOptions.manifest === "string" ? rscBuildOptions.manifest : rscBuildOptions.manifest && ".vite/manifest.json";
for (const asset of Object.values(rscBundle)) {
if (asset.fileName === rscViteManifest) continue;
if (asset.type === "asset" && filterAssets(asset.fileName)) this.emitFile({
type: "asset",
fileName: asset.fileName,
source: asset.source
});
}
const serverResources = {};
const rscAssetDeps = collectAssetDeps(rscBundle);
for (const [id, meta] of Object.entries(serverResourcesMetaMap)) serverResources[meta.key] = assetsURLOfDeps({
js: [],
css: rscAssetDeps[id]?.deps.css ?? []
});
const assetDeps = collectAssetDeps(bundle);
const entry = Object.values(assetDeps).find((v) => v.chunk.name === "index");
assert(entry);
const entryUrl = assetsURL(entry.chunk.fileName);
const clientReferenceDeps = {};
for (const [id, meta] of Object.entries(clientReferenceMetaMap)) {
const deps = assetDeps[id]?.deps ?? {
js: [],
css: []
};
clientReferenceDeps[meta.referenceKey] = assetsURLOfDeps(mergeAssetDeps(deps, entry.deps));
}
let bootstrapScriptContent;
if (typeof entryUrl === "string") bootstrapScriptContent = `import(${JSON.stringify(entryUrl)})`;
else bootstrapScriptContent = new RuntimeAsset(`"import(" + JSON.stringify(${entryUrl.runtime}) + ")"`);
buildAssetsManifest = {
bootstrapScriptContent,
clientReferenceDeps,
serverResources
};
}
},
renderChunk(code, chunk) {
if (code.includes("virtual:vite-rsc/assets-manifest")) {
assert(this.environment.name !== "client");
const replacement = normalizeRelativePath(path.relative(path.join(chunk.fileName, ".."), BUILD_ASSETS_MANIFEST_NAME));
code = code.replaceAll("virtual:vite-rsc/assets-manifest", () => replacement);
return { code };
}
}
},
createVirtualPlugin("vite-rsc/bootstrap-script-content", function() {
assert(this.environment.name !== "client");
return `\
import assetsManifest from "virtual:vite-rsc/assets-manifest";
export default assetsManifest.bootstrapScriptContent;
`;
}),
{
name: "rsc:bootstrap-script-content",
async transform(code) {
if (!code.includes("loadBootstrapScriptContent") || !/import\s*\.\s*meta\s*\.\s*viteRsc\s*\.\s*loadBootstrapScriptContent/.test(code)) return;
assert(this.environment.name !== "client");
const output = new MagicString(code);
for (const match of code.matchAll(/import\s*\.\s*meta\s*\.\s*viteRsc\s*\.\s*loadBootstrapScriptContent\(([\s\S]*?)\)/dg)) {
const argCode = match[1].trim();
const entryName = evalValue(argCode);
assert(entryName, `[vite-rsc] expected 'loadBootstrapScriptContent("index")' but got ${argCode}`);
let replacement = `Promise.resolve(__vite_rsc_assets_manifest.bootstrapScriptContent)`;
const [start, end] = match.indices[0];
output.overwrite(start, end, replacement);
}
if (output.hasChanged()) {
if (!code.includes("__vite_rsc_assets_manifest")) output.prepend(`import __vite_rsc_assets_manifest from "virtual:vite-rsc/assets-manifest";`);
return {
code: output.toString(),
map: output.generateMap({ hires: "boundary" })
};
}
}
},
createVirtualPlugin(VIRTUAL_ENTRIES.browser.slice(8), async function() {
assert(this.environment.mode === "dev");
let code = "";
const resolved = await this.resolve("/@react-refresh");
if (resolved) code += `
import RefreshRuntime from "/@react-refresh";
RefreshRuntime.injectIntoGlobalHook(window);
window.$RefreshReg$ = () => {};
window.$RefreshSig$ = () => (type) => type;
window.__vite_plugin_react_preamble_installed__ = true;
`;
const source = getEntrySource(this.environment.config, "index");
const resolvedEntry = await this.resolve(source);
assert(resolvedEntry, `[vite-rsc] failed to resolve entry '${source}'`);
code += `await import(${JSON.stringify(resolvedEntry.id)});`;
code += `
const ssrCss = document.querySelectorAll("link[rel='stylesheet']");
import.meta.hot.on("vite:beforeUpdate", () => {
ssrCss.forEach(node => {
if (node.dataset.precedence?.startsWith("vite-rsc/")) {
node.remove();
}
});
});
`;
code += `
import.meta.hot.on("rsc:update", () => {
document.querySelectorAll("vite-error-overlay").forEach((n) => n.close())
});
`;
return code;
}),
{
name: "rsc:inject-async-local-storage",
async configureServer() {
const __viteRscAyncHooks = await import("node:async_hooks");
globalThis.AsyncLocalStorage = __viteRscAyncHooks.AsyncLocalStorage;
},
banner(chunk) {
if ((this.environment.name === "ssr" || this.environment.name === "rsc") && this.environment.mode === "build" && chunk.isEntry) return `\
import * as __viteRscAyncHooks from "node:async_hooks";
globalThis.AsyncLocalStorage = __viteRscAyncHooks.AsyncLocalStorage;
`;
return "";
}
},
...vitePluginRscMinimal(rscPluginOptions),
...vitePluginFindSourceMapURL(),
...vitePluginRscCss({ rscCssTransform: rscPluginOptions.rscCssTransform }),
...rscPluginOptions.validateImports !== false ? [validateImportPlugin()] : [],
...vendorUseSyncExternalStorePlugin(),
scanBuildStripPlugin(),
detectNonOptimizedCjsPlugin()
];
}
function detectNonOptimizedCjsPlugin() {
return {
name: "rsc:detect-non-optimized-cjs",
apply: "serve",
async transform(code, id) {
if (id.includes("/node_modules/") && !id.startsWith(this.environment.config.cacheDir) && /\b(require|exports)\b/.test(code)) {
id = parseIdQuery(id).filename;
let isEsm = id.endsWith(".mjs");
if (id.endsWith(".js")) {
const pkgJsonPath = await findClosestPkgJsonPath(path.dirname(id));
if (pkgJsonPath) {
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
isEsm = pkgJson.type === "module";
}
}
if (!isEsm) this.warn(`[vite-rsc] found non-optimized CJS dependency in '${this.environment.name}' environment. It is recommended to manually add the dependency to 'environments.${this.environment.name}.optimizeDeps.include'.`);
}
}
};
}
function scanBuildStripPlugin() {
return {
name: "rsc:scan-strip",
apply: "build",
enforce: "post",
transform(code, _id, _options) {
if (!isScanBuild) return;
const [imports] = esModuleLexer.parse(code);
const output = imports.map((e) => e.n && `import ${JSON.stringify(e.n)};\n`).filter(Boolean).join("");
return {
code: output,
map: { mappings: "" }
};
}
};
}
function normalizeRelativePath(s) {
s = normalizePath(s);
return s[0] === "." ? s : "./" + s;
}
function getEntrySource(config$1, name = "index") {
const input = config$1.build.rollupOptions.input;
assert(typeof input === "object" && !Array.isArray(input) && name in input && typeof input[name] === "string", `[vite-rsc:getEntrySource] expected 'build.rollupOptions.input' to be an object with a '${name}' property that is a string, but got ${JSON.stringify(input)}`);
return input[name];
}
function hashString(v) {
return createHash("sha256").update(v).digest().toString("hex").slice(0, 12);
}
function vitePluginUseClient(useClientPluginOptions) {
const packageSources = /* @__PURE__ */ new Map();
const bareImportRE = /^(?![a-zA-Z]:)[\w@](?!.*:\/\/)/;
const serverEnvironmentName = useClientPluginOptions.environment?.rsc ?? "rsc";
const browserEnvironmentName = useClientPluginOptions.environment?.browser ?? "client";
return [
{
name: "rsc:use-client",
async transform(code, id) {
if (this.environment.name !== serverEnvironmentName) return;
if (!code.includes("use client")) return;
const ast = await parseAstAsync(code);
if (!hasDirective(ast.body, "use client")) return;
let importId;
let referenceKey;
const packageSource = packageSources.get(id);
if (!packageSource && id.includes("?v=")) {
assert(this.environment.mode === "dev");
const ignored = useClientPluginOptions.ignoredPackageWarnings?.some((pattern) => pattern instanceof RegExp ? pattern.test(id) : id.includes(`/node_modules/${pattern}/`));
if (!ignored) this.warn(`[vite-rsc] detected an internal client boundary created by a package imported on rsc environment`);
importId = `/@id/__x00__virtual:vite-rsc/client-in-server-package-proxy/${encodeURIComponent(id.split("?v=")[0])}`;
referenceKey = importId;
} else if (packageSource) if (this.environment.mode === "dev") {
importId = `/@id/__x00__virtual:vite-rsc/client-package-proxy/${packageSource}`;
referenceKey = importId;
} else {
importId = packageSource;
referenceKey = hashString(packageSource);
}
else if (this.environment.mode === "dev") {
importId = normalizeViteImportAnalysisUrl(server.environments[browserEnvironmentName], id);
referenceKey = importId;
} else {
importId = id;
referenceKey = hashString(normalizePath(path.relative(config.root, id)));
}
const transformDirectiveProxyExport_ = withRollupError(this, transformDirectiveProxyExport);
const result = transformDirectiveProxyExport_(ast, {
directive: "use client",
code,
keep: !!useClientPluginOptions.keepUseCientProxy,
runtime: (name, meta) => {
let proxyValue = `() => { throw new Error("Unexpectedly client reference export '" + ` + JSON.stringify(name) + ` + "' is called on server") }`;
if (meta?.value) proxyValue = `(${meta.value})`;
return `$$ReactServer.registerClientReference( ${proxyValue}, ${JSON.stringify(referenceKey)}, ${JSON.stringify(name)})`;
}
});
if (!result) return;
const { output, exportNames } = result;
clientReferenceMetaMap[id] = {
importId,
referenceKey,
packageSource,
exportNames,
renderedExports: []
};
const importSource = resolvePackage(`${PKG_NAME}/react/rsc`);
output.prepend(`import * as $$ReactServer from "${importSource}";\n`);
return {
code: output.toString(),
map: { mappings: "" }
};
}
},
createVirtualPlugin("vite-rsc/client-references", function() {
if (this.environment.mode === "dev") return {
code: `export default {}`,
map: null
};
let code = "";
for (const meta of Object.values(clientReferenceMetaMap)) {
const key = JSON.stringify(meta.referenceKey);
const id = JSON.stringify(meta.importId);
const exports = meta.renderedExports.map((name) => name === "default" ? "default: _default" : name).sort();
code += `
${key}: async () => {
const {${exports}} = await import(${id});
return {${exports}};
},
`;
}
code = `export default {${code}};\n`;
return {
code,
map: null
};
}),
{
name: "rsc:virtual-client-in-server-package",
async load(id) {
if (id.startsWith("\0virtual:vite-rsc/client-in-server-package-proxy/")) {
assert.equal(this.environment.mode, "dev");
assert(this.environment.name !== serverEnvironmentName);
id = decodeURIComponent(id.slice(49));
return `
export * from ${JSON.stringify(id)};
import * as __all__ from ${JSON.stringify(id)};
export default __all__.default;
`;
}
}
},
{
name: "rsc:virtual-client-package",
resolveId: {
order: "pre",
async handler(source, importer, options) {
if (this.environment.name === serverEnvironmentName && bareImportRE.test(source)) {
const resolved = await this.resolve(source, importer, options);
if (resolved && resolved.id.includes("/node_modules/")) {
packageSources.set(resolved.id, source);
return resolved;
}
}
}
},
async load(id) {
if (id.startsWith("\0virtual:vite-rsc/client-package-proxy/")) {
assert(this.environment.mode === "dev");
const source = id.slice(39);
const meta = Object.values(clientReferenceMetaMap).find((v) => v.packageSource === source);
const exportNames = meta.exportNames;
return `export {${exportNames.join(",")}} from ${JSON.stringify(source)};\n`;
}
},
generateBundle(_options, bundle) {
if (this.environment.name !== serverEnvironmentName) return;
for (const chunk of Object.values(bundle)) if (chunk.type === "chunk") for (const [id, mod] of Object.entries(chunk.modules)) {
const meta = clientReferenceMetaMap[id];
if (meta) meta.renderedExports = mod.renderedExports;
}
}
}
];
}
function vitePluginDefineEncryptionKey(useServerPluginOptions) {
let defineEncryptionKey;
let emitEncryptionKey = false;
const KEY_PLACEHOLDER = "__vite_rsc_define_encryption_key";
const KEY_FILE = "__vite_rsc_encryption_key.js";
const serverEnvironmentName = useServerPluginOptions.environment?.rsc ?? "rsc";
return [{
name: "rsc:encryption-key",
async configEnvironment(name, _config, env) {
if (name === serverEnvironmentName && !env.isPreview) defineEncryptionKey = useServerPluginOptions.defineEncryptionKey ?? JSON.stringify(toBase64(await generateEncryptionKey()));
},
resolveId(source) {
if (source === "virtual:vite-rsc/encryption-key") return {
id: "\0" + source,
moduleSideEffects: false
};
},
load(id) {
if (id === "\0virtual:vite-rsc/encryption-key") {
if (this.environment.mode === "build") return `export default () => ${KEY_PLACEHOLDER}`;
return `export default () => (${defineEncryptionKey})`;
}
},
renderChunk(code, chunk) {
if (code.includes(KEY_PLACEHOLDER)) {
assert.equal(this.environment.name, "rsc");
emitEncryptionKey = true;
const normalizedPath = normalizeRelativePath(path.relative(path.join(chunk.fileName, ".."), KEY_FILE));
const replacement = `import(${JSON.stringify(normalizedPath)}).then(__m => __m.default)`;
code = code.replaceAll(KEY_PLACEHOLDER, () => replacement);
return { code };
}
},
writeBundle() {
if (this.environment.name === "rsc" && emitEncryptionKey) fs.writeFileSync(path.join(this.environment.config.build.outDir, KEY_FILE), `export default ${defineEncryptionKey};\n`);
}
}];
}
function vitePluginUseServer(useServerPluginOptions) {
const serverEnvironmentName = useServerPluginOptions.environment?.rsc ?? "rsc";
const browserEnvironmentName = useServerPluginOptions.environment?.browser ?? "client";
return [{
name: "rsc:use-server",
async transform(code, id) {
if (!code.includes("use server")) return;
const ast = await parseAstAsync(code);
let normalizedId_;
const getNormalizedId = () => {
if (!normalizedId_) {
if (id.includes("?v=")) {
assert(this.environment.mode === "dev");
const ignored = useServerPluginOptions.ignoredPackageWarnings?.some((pattern) => pattern instanceof RegExp ? pattern.test(id) : id.includes(`/node_modules/${pattern}/`));
if (!ignored) this.warn(`[vite-rsc] detected an internal server function created by a package imported on ${this.environment.name} environment`);
id = id.split("?v=")[0];
}
if (config.command === "build") normalizedId_ = hashString(path.relative(config.root, id));
else normalizedId_ = normalizeViteImportAnalysisUrl(server.environments[serverEnvironmentName], id);
}
return normalizedId_;
};
if (this.environment.name === serverEnvironmentName) {
const transformServerActionServer_ = withRollupError(this, transformServerActionServer);
const enableEncryption = useServerPluginOptions.enableActionEncryption ?? true;
const { output } = transformServerActionServer_(code, ast, {
runtime: (value, name) => `$$ReactServer.registerServerReference(${value}, ${JSON.stringify(getNormalizedId())}, ${JSON.stringify(name)})`,
rejectNonAsyncFunction: true,
encode: enableEncryption ? (value) => `__vite_rsc_encryption_runtime.encryptActionBoundArgs(${value})` : void 0,
decode: enableEncryption ? (value) => `await __vite_rsc_encryption_runtime.decryptActionBoundArgs(${value})` : void 0
});
if (!output.hasChanged()) return;
serverReferences[getNormalizedId()] = id;
const importSource = resolvePackage(`${PKG_NAME}/react/rsc`);
output.prepend(`import * as $$ReactServer from "${importSource}";\n`);
if (enableEncryption) {
const importSource$1 = resolvePackage(`${PKG_NAME}/utils/encryption-runtime`);
output.prepend(`import * as __vite_rsc_encryption_runtime from ${JSON.stringify(importSource$1)};\n`);
}
return {
code: output.toString(),
map: output.generateMap({ hires: "boundary" })
};
} else {
if (!hasDirective(ast.body, "use server")) return;
const transformDirectiveProxyExport_ = withRollupError(this, transformDirectiveProxyExport);
const result = transformDirectiveProxyExport_(ast, {
code,
runtime: (name$1) => `$$ReactClient.createServerReference(${JSON.stringify(getNormalizedId() + "#" + name$1)},$$ReactClient.callServer, undefined, ` + (this.environment.mode === "dev" ? `$$ReactClient.findSourceMapURL,` : "undefined,") + `${JSON.stringify(name$1)})`,
directive: "use server",
rejectNonAsyncFunction: true
});
const output = result?.output;
if (!output?.hasChanged()) return;
serverReferences[getNormalizedId()] = id;
const name = this.environment.name === browserEnvironmentName ? "browser" : "ssr";
const importSource = resolvePackage(`${PKG_NAME}/react/${name}`);
output.prepend(`import * as $$ReactClient from "${importSource}";\n`);
return {
code: output.toString(),
map: output.generateMap({ hires: "boundary" })
};
}
}
}, createVirtualPlugin("vite-rsc/server-references", function() {
if (this.environment.mode === "dev") return {
code: `export {}`,
map: null
};
const code = generateDynamicImportCode(serverReferences);
return {
code,
map: null
};
})];
}
function withRollupError(ctx, f) {
function processError(e) {
if (e && typeof e === "object" && typeof e.pos === "number") return ctx.error(e, e.pos);
throw e;
}
return function(...args) {
try {
const result = f.apply(this, args);
if (result instanceof Promise) return result.catch((e) => processError(e));
return result;
} catch (e) {
processError(e);
}
};
}
function createVirtualPlugin(name, load) {
name = "virtual:" + name;
return {
name: `rsc:virtual-${name}`,
resolveId(source, _importer, _options) {
return source === name ? "\0" + name : void 0;
},
load(id, options) {
if (id === "\0" + name) return load.apply(this, [id, options]);
}
};
}
function generateDynamicImportCode(map) {
let code = Object.entries(map).map(([key, id]) => `${JSON.stringify(key)}: () => import(${JSON.stringify(id)}),`).join("\n");
return `export default {${code}};\n`;
}
var RuntimeAsset = class {
runtime;
constructor(value) {
this.runtime = value;
}
};
function serializeValueWithRuntime(value) {
const replacements = [];
let result = JSON.stringify(value, (_key, value$1) => {
if (value$1 instanceof RuntimeAsset) {
const placeholder = `__runtime_placeholder_${replacements.length}__`;
replacements.push([placeholder, value$1.runtime]);
return placeholder;
}
return value$1;
}, 2);
for (const [placeholder, runtime] of replacements) result = result.replace(`"${placeholder}"`, runtime);
return result;
}
function assetsURL(url) {
if (config.command === "build" && typeof config.experimental?.renderBuiltUrl === "function") {
const result = config.experimental.renderBuiltUrl(url, {
type: "asset",
hostType: "js",
ssr: true,
hostId: ""
});
if (typeof result === "object") {
if (result.runtime) return new RuntimeAsset(result.runtime);
assert(!result.relative, "\"result.relative\" not supported on renderBuiltUrl() for RSC");
} else if (result) return result;
}
return config.base + url;
}
function assetsURLOfDeps(deps) {
return {
js: deps.js.map((href) => {
assert(typeof href === "string");
return assetsURL(href);
}),
css: deps.css.map((href) => {
assert(typeof href === "string");
return assetsURL(href);
})
};
}
function mergeAssetDeps(a, b) {
return {
js: [...new Set([...a.js, ...b.js])],
css: [...new Set([...a.css, ...b.css])]
};
}
function collectAssetDeps(bundle) {
const chunkToDeps = /* @__PURE__ */ new Map();
for (const chunk of Object.values(bundle)) if (chunk.type === "chunk") chunkToDeps.set(chunk, collectAssetDepsInner(chunk.fileName, bundle));
const idToDeps = {};
for (const [chunk, deps] of chunkToDeps.entries()) for (const id of chunk.moduleIds) idToDeps[id] = {
chunk,
deps
};
return idToDeps;
}
function collectAssetDepsInner(fileName, bundle) {
const visited = /* @__PURE__ */ new Set();
const css = [];
function recurse(k) {
if (visited.has(k)) return;
visited.add(k);
const v = bundle[k];
assert(v, `Not found '${k}' in the bundle`);
if (v.type === "chunk") {
css.push(...v.viteMetadata?.importedCss ?? []);
for (const k2 of v.imports) if (k2 in bundle) recurse(k2);
}
}
recurse(fileName);
return {
js: [...visited],
css: [...new Set(css)]
};
}
function vitePluginFindSourceMapURL() {
return [{
name: "rsc:findSourceMapURL",
apply: "serve",
configureServer(server$1) {
server$1.middlewares.use(async (req, res, next) => {
const url = new URL(req.url, `http://localhost`);
if (url.pathname === "/__vite_rsc_findSourceMapURL") {
let filename = url.searchParams.get("filename");
let environmentName = url.searchParams.get("environmentName");
try {
const map = await findSourceMapURL(server$1, filename, environmentName);
res.setHeader("content-type", "application/json");
if (!map) res.statusCode = 404;
res.end(JSON.stringify(map ?? {}));
} catch (e) {
next(e);
}
return;
}
next();
});
}
}];
}
async function findSourceMapURL(server$1, filename, environmentName) {
if (filename.startsWith("file://")) {
filename = fileURLToPath(filename);
if (fs.existsSync(filename)) {
const content = fs.readFileSync(filename, "utf-8");
return {
version: 3,
sources: [filename],
sourcesContent: [content],
mappings: "AAAA" + ";AACA".repeat(content.split("\n").length)
};
}
return;
}
let mod;
let map;
if (environmentName === "Server") {
mod = server$1.environments.rsc.moduleGraph.getModuleById(filename);
map = mod?.transformResult?.map;
if (map && map.mappings) map = {
...map,
mappings: ";;" + map.mappings
};
}
const base = server$1.config.base.slice(0, -1);
if (environmentName === "Client") try {
const url = new URL(filename).pathname.slice(base.length);
mod = server$1.environments.client.moduleGraph.urlToModuleMap.get(url);
map = mod?.transformResult?.map;
} catch (e) {}
if (mod && map) return {
.