@vitejs/plugin-rsc
Version:
React Server Components (RSC) support for Vite.
1,293 lines (1,287 loc) • 63.1 kB
JavaScript
import { n as __toDynamicImportESM } from "./chunk-ezxmLbPQ.js";
import { t as vitePluginRscCore } from "./plugin-D1MQNdps.js";
import { t as createDebug } from "./dist-BRSdGcl7.js";
import { a as hasDirective, n as transformDirectiveProxyExport, o as transformWrapExport, s as findDirectives, t as transformServerActionServer } from "./transforms-BcLQCXiC.js";
import { o as generateEncryptionKey, s as toBase64 } from "./encryption-utils-6p8t4Xqm.js";
import { n as createRpcServer } from "./rpc-CRpYrgKq.js";
import { t as cjsModuleRunnerPlugin } from "./cjs-DHD_0drE.js";
import { i as toCssVirtual, n as parseIdQuery, r as parseReferenceValidationVirtual, t as parseCssVirtual } from "./shared-AtH_QTi7.js";
import { createRequire } from "node:module";
import assert from "node:assert";
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 * as vite from "vite";
import { defaultServerConditions, isCSSRequest, normalizePath, parseAstAsync } from "vite";
import { crawlFrameworkPkgs } from "vitefu";
import { walk } from "estree-walker";
import { stripVTControlCharacters } from "node:util";
import { createHash } from "node:crypto";
import { stripLiteral } from "strip-literal";
//#region src/plugins/vite-utils.ts
const VALID_ID_PREFIX = `/@id/`;
const NULL_BYTE_PLACEHOLDER = `__x00__`;
const FS_PREFIX = `/@fs/`;
function wrapId(id) {
return id.startsWith(VALID_ID_PREFIX) ? id : VALID_ID_PREFIX + id.replace("\0", NULL_BYTE_PLACEHOLDER);
}
function withTrailingSlash(path$1) {
if (path$1[path$1.length - 1] !== "/") return `${path$1}/`;
return path$1;
}
const postfixRE = /[?#].*$/;
function cleanUrl(url) {
return url.replace(postfixRE, "");
}
function splitFileAndPostfix(path$1) {
const file = cleanUrl(path$1);
return {
file,
postfix: path$1.slice(file.length)
};
}
const windowsSlashRE = /\\/g;
function slash(p) {
return p.replace(windowsSlashRE, "/");
}
const isWindows = typeof process !== "undefined" && process.platform === "win32";
function injectQuery(url, queryToInject) {
const { file, postfix } = splitFileAndPostfix(url);
return `${isWindows ? slash(file) : file}?${queryToInject}${postfix[0] === "?" ? `&${postfix.slice(1)}` : postfix}`;
}
function normalizeResolvedIdToUrl(environment, url, resolved) {
const root = environment.config.root;
const depsOptimizer = environment.depsOptimizer;
if (resolved.id.startsWith(withTrailingSlash(root))) url = resolved.id.slice(root.length);
else if (depsOptimizer?.isOptimizedDepFile(resolved.id) || resolved.id !== "/@react-refresh" && path.isAbsolute(resolved.id) && fs.existsSync(cleanUrl(resolved.id))) url = path.posix.join(FS_PREFIX, resolved.id);
else url = resolved.id;
if (url[0] !== "." && url[0] !== "/") url = wrapId(resolved.id);
return url;
}
function normalizeViteImportAnalysisUrl(environment, id) {
let url = normalizeResolvedIdToUrl(environment, id, { id });
if (environment.config.consumer === "client") {
const mod = environment.moduleGraph.getModuleById(id);
if (mod && mod.lastHMRTimestamp > 0) url = injectQuery(url, `t=${mod.lastHMRTimestamp}`);
}
return url;
}
function prepareError(err) {
return {
message: stripVTControlCharacters(err.message),
stack: stripVTControlCharacters(cleanStack(err.stack || "")),
id: err.id,
frame: stripVTControlCharacters(err.frame || ""),
plugin: err.plugin,
pluginCode: err.pluginCode?.toString(),
loc: err.loc
};
}
function cleanStack(stack) {
return stack.split(/\n/).filter((l) => /^\s*at/.test(l)).join("\n");
}
function evalValue(rawValue) {
return new Function(`
var console, exports, global, module, process, require
return (\n${rawValue}\n)
`)();
}
const directRequestRE = /(\?|&)direct=?(?:&|$)/;
//#endregion
//#region src/plugins/utils.ts
function sortObject(o) {
return Object.fromEntries(Object.entries(o).sort(([a], [b]) => a.localeCompare(b)));
}
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 normalizeRelativePath(s) {
s = normalizePath(s);
return s[0] === "." ? s : "./" + s;
}
function getEntrySource(config, name = "index") {
const input = config.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 getFetchHandlerExport(exports) {
if ("default" in exports) {
const default_ = exports.default;
if (default_ && typeof default_ === "object" && "fetch" in default_ && typeof default_.fetch === "function") return default_.fetch;
if (typeof default_ === "function") return default_;
}
throw new Error("Invalid server handler entry");
}
//#endregion
//#region src/plugins/scan.ts
function scanBuildStripPlugin({ manager }) {
return {
name: "rsc:scan-strip",
apply: "build",
enforce: "post",
async transform(code, _id, _options) {
if (!manager.isScanBuild) return;
return {
code: await transformScanBuildStrip(code),
map: { mappings: "" }
};
}
};
}
const importGlobRE = /\bimport\.meta\.glob(?:<\w+>)?\s*\(/g;
async function transformScanBuildStrip(code) {
const [imports] = esModuleLexer.parse(code);
let output = imports.map((e) => e.n && `import ${JSON.stringify(e.n)};\n`).filter(Boolean).join("");
if (importGlobRE.test(code)) {
walk(await parseAstAsync(code), { enter(node) {
if (node.type === "CallExpression" && node.callee.type === "MemberExpression" && node.callee.object.type === "MetaProperty" && node.callee.object.meta.type === "Identifier" && node.callee.object.meta.name === "import" && node.callee.object.property.type === "Identifier" && node.callee.object.property.name === "meta" && node.callee.property.type === "Identifier" && node.callee.property.name === "glob") {
const importMetaGlob = code.slice(node.start, node.end);
output += `console.log(${importMetaGlob});\n`;
}
} });
output += "";
}
return output;
}
//#endregion
//#region src/plugins/validate-import.ts
function validateImportPlugin() {
return {
name: "rsc:validate-imports",
resolveId: {
order: "pre",
async handler(source, _importer, options) {
if ("scan" in options && options.scan) return;
if (source === "client-only" || source === "server-only") {
if (source === "client-only" && this.environment.name === "rsc" || source === "server-only" && this.environment.name !== "rsc") return {
id: `\0virtual:vite-rsc/validate-imports/invalid/${source}`,
moduleSideEffects: true
};
return {
id: `\0virtual:vite-rsc/validate-imports/valid/${source}`,
moduleSideEffects: false
};
}
}
},
load(id) {
if (id.startsWith("\0virtual:vite-rsc/validate-imports/invalid/")) return `throw new Error("invalid import of '${id.slice(id.lastIndexOf("/") + 1)}'")`;
if (id.startsWith("\0virtual:vite-rsc/validate-imports/")) return `export {}`;
},
transform: {
order: "post",
async handler(_code, id) {
if (this.environment.mode === "dev") {
if (id.startsWith(`\0virtual:vite-rsc/validate-imports/invalid/`)) validateImportChain(getImportChainDev(this.environment, id), this.environment.name, this.environment.config.root);
}
}
},
buildEnd() {
if (this.environment.mode === "build") {
validateImportChain(getImportChainBuild(this, "\0virtual:vite-rsc/validate-imports/invalid/server-only"), this.environment.name, this.environment.config.root);
validateImportChain(getImportChainBuild(this, "\0virtual:vite-rsc/validate-imports/invalid/client-only"), this.environment.name, this.environment.config.root);
}
}
};
}
function getImportChainDev(environment, id) {
const chain = [];
const recurse = (id$1) => {
if (chain.includes(id$1)) return;
const info = environment.moduleGraph.getModuleById(id$1);
if (!info) return;
chain.push(id$1);
const next = [...info.importers][0];
if (next && next.id) recurse(next.id);
};
recurse(id);
return chain;
}
function getImportChainBuild(ctx, id) {
const chain = [];
const recurse = (id$1) => {
if (chain.includes(id$1)) return;
const info = ctx.getModuleInfo(id$1);
if (!info) return;
chain.push(id$1);
const next = info.importers[0];
if (next) recurse(next);
};
recurse(id);
return chain;
}
function validateImportChain(chain, environmentName, root) {
if (chain.length === 0) return;
const id = chain[0];
const source = id.slice(id.lastIndexOf("/") + 1);
let result = `'${source}' cannot be imported in ${source === "server-only" ? "client" : "server"} build ('${environmentName}' environment):\n`;
result += chain.slice(1, 6).map((id$1, i) => " ".repeat(i + 1) + `imported by ${path.relative(root, id$1).replaceAll("\0", "")}\n`).join("");
if (chain.length > 6) result += " ".repeat(7) + "...\n";
const error = new Error(result);
if (chain[1]) Object.assign(error, { id: chain[1] });
throw error;
}
//#endregion
//#region src/plugins/find-source-map-url.ts
function vitePluginFindSourceMapURL() {
return [{
name: "rsc:findSourceMapURL",
apply: "serve",
configureServer(server) {
server.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, 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, 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.environments.rsc.moduleGraph.getModuleById(filename);
map = mod?.transformResult?.map;
if (map && map.mappings) map = {
...map,
mappings: ";;" + map.mappings
};
}
const base = server.config.base.slice(0, -1);
if (environmentName === "Client") try {
const url = new URL(filename).pathname.slice(base.length);
mod = server.environments.client.moduleGraph.urlToModuleMap.get(url);
map = mod?.transformResult?.map;
} catch (e) {}
if (mod && map) return {
...map,
sources: [base + mod.url]
};
}
//#endregion
//#region src/plugin.ts
const isRolldownVite = "rolldownVersion" in vite;
const BUILD_ASSETS_MANIFEST_NAME = "__vite_rsc_assets_manifest.js";
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;
}
var RscPluginManager = class {
server;
config;
rscBundle;
buildAssetsManifest;
isScanBuild = false;
clientReferenceMetaMap = {};
clientReferenceGroups = {};
serverReferenceMetaMap = {};
serverResourcesMetaMap = {};
stabilize() {
this.clientReferenceMetaMap = sortObject(this.clientReferenceMetaMap);
this.serverResourcesMetaMap = sortObject(this.serverResourcesMetaMap);
}
toRelativeId(id) {
return normalizePath(path.relative(this.config.root, id));
}
};
/** @experimental */
function getPluginApi(config) {
return config.plugins.find((p) => p.name === "rsc:minimal")?.api;
}
/** @experimental */
function vitePluginRscMinimal(rscPluginOptions = {}, manager = new RscPluginManager()) {
return [
{
name: "rsc:minimal",
enforce: "pre",
api: { manager },
async config() {
await esModuleLexer.init;
},
configResolved(config) {
manager.config = config;
for (const e of Object.values(config.environments)) e.build.outDir = path.resolve(config.root, e.build.outDir);
},
configureServer(server_) {
manager.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, manager),
...vitePluginUseServer(rscPluginOptions, manager),
...vitePluginDefineEncryptionKey(rscPluginOptions),
{
name: "rsc:reference-validation",
apply: "serve",
load: { handler(id, _options) {
if (id.startsWith("\0virtual:vite-rsc/reference-validation?")) {
const parsed = parseReferenceValidationVirtual(id);
assert(parsed);
if (parsed.type === "client") {
if (Object.values(manager.clientReferenceMetaMap).find((meta) => meta.referenceKey === parsed.id)) return `export {}`;
}
if (parsed.type === "server") {
if (Object.values(manager.serverReferenceMetaMap).find((meta) => meta.referenceKey === parsed.id)) return `export {}`;
}
this.error(`[vite-rsc] invalid ${parsed.type} reference '${parsed.id}'`);
}
} }
},
scanBuildStripPlugin({ manager })
];
}
function vitePluginRsc(rscPluginOptions = {}) {
const manager = new RscPluginManager();
const buildApp = async (builder) => {
const colors = await import("./picocolors-AGVbN-ya.js").then(__toDynamicImportESM(1));
const logStep = (msg) => {
builder.config.logger.info(colors.blue(msg));
};
if (!builder.environments.ssr?.config.build.rollupOptions.input) {
manager.isScanBuild = true;
builder.environments.rsc.config.build.write = false;
builder.environments.client.config.build.write = false;
logStep("[1/4] analyze client references...");
await builder.build(builder.environments.rsc);
logStep("[2/4] analyze server references...");
await builder.build(builder.environments.client);
manager.isScanBuild = false;
builder.environments.rsc.config.build.write = true;
builder.environments.client.config.build.write = true;
logStep("[3/4] build rsc environment...");
await builder.build(builder.environments.rsc);
manager.stabilize();
logStep("[4/4] build client environment...");
await builder.build(builder.environments.client);
writeAssetsManifest(["rsc"]);
return;
}
manager.isScanBuild = true;
builder.environments.rsc.config.build.write = false;
builder.environments.ssr.config.build.write = false;
logStep("[1/5] analyze client references...");
await builder.build(builder.environments.rsc);
logStep("[2/5] analyze server references...");
await builder.build(builder.environments.ssr);
manager.isScanBuild = false;
builder.environments.rsc.config.build.write = true;
builder.environments.ssr.config.build.write = true;
logStep("[3/5] build rsc environment...");
await builder.build(builder.environments.rsc);
manager.stabilize();
logStep("[4/5] build client environment...");
await builder.build(builder.environments.client);
logStep("[5/5] build ssr environment...");
await builder.build(builder.environments.ssr);
writeAssetsManifest(["ssr", "rsc"]);
};
function writeAssetsManifest(environmentNames) {
const assetsManifestCode = `export default ${serializeValueWithRuntime(manager.buildAssetsManifest)}`;
for (const name of environmentNames) {
const manifestPath = path.join(manager.config.environments[name].build.outDir, BUILD_ASSETS_MANIFEST_NAME);
fs.writeFileSync(manifestPath, assetsManifestCode);
}
}
let hasReactServerDomWebpack = false;
return [
{
name: "rsc",
async config(config, env) {
if (config.rsc) Object.assign(rscPluginOptions, vite.mergeConfig(config.rsc, rscPluginOptions));
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()
];
hasReactServerDomWebpack = result.ssr.noExternal.includes("react-server-dom-webpack");
const reactServerDomPackageName = hasReactServerDomWebpack ? "react-server-dom-webpack" : REACT_SERVER_DOM_NAME;
return {
appType: config.appType ?? "custom",
define: { "import.meta.env.__vite_rsc_build__": JSON.stringify(env.command === "build") },
environments: {
client: {
build: {
outDir: config.environments?.client?.build?.outDir ?? "dist/client",
rollupOptions: { input: rscPluginOptions.entries?.client && { index: rscPluginOptions.entries.client } }
},
optimizeDeps: {
include: ["react-dom/client", `${reactServerDomPackageName}/client.browser`],
exclude: [PKG_NAME]
}
},
ssr: {
build: {
outDir: config.environments?.ssr?.build?.outDir ?? "dist/ssr",
copyPublicDir: false,
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-dom/static.edge",
`${reactServerDomPackageName}/client.edge`
],
exclude: [PKG_NAME]
}
},
rsc: {
build: {
outDir: config.environments?.rsc?.build?.outDir ?? "dist/rsc",
copyPublicDir: false,
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",
`${reactServerDomPackageName}/server.edge`,
`${reactServerDomPackageName}/client.edge`
],
exclude: [PKG_NAME]
}
}
},
builder: {
sharedPlugins: true,
sharedConfigBuild: true,
async buildApp(builder) {
if (!rscPluginOptions.useBuildAppHook) await buildApp(builder);
}
}
};
},
configResolved() {
if (Number(vite.version.split(".")[0]) >= 7) rscPluginOptions.useBuildAppHook ??= true;
},
buildApp: { async handler(builder) {
if (rscPluginOptions.useBuildAppHook) await buildApp(builder);
} },
configureServer(server) {
globalThis.__viteRscDevServer = server;
const oldSend = server.environments.client.hot.send;
server.environments.client.hot.send = async function(...args) {
const e = args[0];
if (e && typeof e === "object" && e.type === "update") {
for (const update of e.updates) if (update.type === "js-update") {
const mod = server.environments.client.moduleGraph.urlToModuleMap.get(update.path);
if (mod && mod.id && manager.clientReferenceMetaMap[mod.id]) {
const serverMod = server.environments.rsc.moduleGraph.getModuleById(mod.id);
if (serverMod) server.environments.rsc.moduleGraph.invalidateModule(serverMod);
}
}
}
return oldSend.apply(this, args);
};
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 fetchHandler = getFetchHandlerExport(await environment.runner.import(resolved.id));
req.url = req.originalUrl ?? req.url;
await createRequestListener(fetchHandler)(req, res);
} catch (e) {
next(e);
}
});
};
},
async configurePreviewServer(server) {
if (rscPluginOptions.serverHandler === false) return;
const options = rscPluginOptions.serverHandler ?? {
environmentName: "rsc",
entryName: "index"
};
const handler = createRequestListener(getFetchHandlerExport(await import(pathToFileURL(path.join(manager.config.environments[options.environmentName].build.outDir, `${options.entryName}.js`)).href)));
server.middlewares.use((req, _res, next) => {
delete req.headers["accept-encoding"];
next();
});
return () => {
server.middlewares.use(async (req, res, next) => {
try {
req.url = req.originalUrl ?? req.url;
await handler(req, res);
} catch (e) {
next(e);
}
});
};
},
async hotUpdate(ctx) {
if (isCSSRequest(ctx.file)) {
if (this.environment.name === "client") return;
}
if (ctx.modules.map((mod) => mod.id).filter((v) => v !== null).length === 0) return;
if (this.environment.name === "rsc") {
for (const mod of ctx.modules) if (mod.type === "js" && mod.id && mod.id in manager.clientReferenceMetaMap) try {
await this.environment.transformRequest(mod.url);
} catch {}
}
function isInsideClientBoundary(mods) {
const visited = /* @__PURE__ */ new Set();
function recurse(mod) {
if (!mod.id) return false;
if (manager.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) {
manager.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") {
if (ctx.server.environments.rsc.moduleGraph.getModuleById(ctx.file)) {
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:react-server-dom-webpack-alias",
resolveId: {
order: "pre",
async handler(source, importer, options) {
if (hasReactServerDomWebpack && source.startsWith(`${PKG_NAME}/vendor/react-server-dom/`)) {
const newSource = source.replace(`${PKG_NAME}/vendor/react-server-dom`, "react-server-dom-webpack");
return await this.resolve(newSource, importer, {
...options,
skipSelf: true
});
}
}
}
},
{
name: "rsc:load-environment-module",
async transform(code) {
if (!code.includes("import.meta.viteRsc.loadModule")) return;
const { server } = manager;
const s = new MagicString(code);
for (const match of stripLiteral(code).matchAll(/import\.meta\.viteRsc\.loadModule\(([\s\S]*?)\)/dg)) {
const [argStart, argEnd] = match.indices[1];
const [environmentName, entryName] = evalValue(`[${code.slice(argStart, argEnd).trim()}]`);
let replacement;
if (this.environment.mode === "dev" && rscPluginOptions.loadModuleDevProxy) replacement = `import("virtual:vite-rsc/rpc-client").then((module) => module.createRpcClient(${JSON.stringify({
environmentName,
entryName
})}))`;
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 { config } = manager;
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",
configureServer(server) {
if (!rscPluginOptions.loadModuleDevProxy) return;
async function createHandler(url) {
const { environmentName, entryName } = Object.fromEntries(url.searchParams);
assert(environmentName);
assert(entryName);
const environment = server.environments[environmentName];
const source = getEntrySource(environment.config, entryName);
const resolvedEntry = await environment.pluginContainer.resolveId(source);
assert(resolvedEntry, `[vite-rsc] failed to resolve entry '${source}'`);
return createRpcServer(new Proxy({}, { get(_target, p, _receiver) {
if (typeof p !== "string" || p === "then") return;
return async (...args) => {
return (await environment.runner.import(resolvedEntry.id))[p](...args);
};
} }));
}
server.middlewares.use(async (req, res, next) => {
const url = new URL(req.url ?? "/", `http://localhost`);
if (url.pathname === "/__vite_rsc_load_module_dev_proxy") {
try {
createRequestListener(await createHandler(url))(req, res);
} catch (e) {
next(e);
}
return;
}
next();
});
}
},
{
name: "rsc:virtual:vite-rsc/rpc-client",
resolveId(source) {
if (source === "virtual:vite-rsc/rpc-client") return `\0${source}`;
},
load(id) {
if (id === "\0virtual:vite-rsc/rpc-client") {
const { server } = manager;
const origin = server.resolvedUrls?.local[0];
assert(origin, "[vite-rsc] no server for loadModuleDevProxy");
return `\
import * as __vite_rsc_rpc from "@vitejs/plugin-rsc/utils/rpc";
export function createRpcClient(params) {
const endpoint =
"${origin}" +
"__vite_rsc_load_module_dev_proxy?" +
new URLSearchParams(params);
return __vite_rsc_rpc.createRpcClient({ endpoint });
}
`;
}
}
},
{
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 manifest = {
bootstrapScriptContent: `import(${serializeValueWithRuntime(assetsURL("@id/__x00__" + VIRTUAL_ENTRIES.browser, manager))})`,
clientReferenceDeps: {}
};
return `export default ${JSON.stringify(manifest, null, 2)}`;
}
},
generateBundle(_options, bundle) {
if (this.environment.name === "rsc") manager.rscBundle = bundle;
if (this.environment.name === "client") {
const filterAssets = rscPluginOptions.copyServerAssetsToClient ?? (() => true);
const rscBuildOptions = manager.config.environments.rsc.build;
const rscViteManifest = typeof rscBuildOptions.manifest === "string" ? rscBuildOptions.manifest : rscBuildOptions.manifest && ".vite/manifest.json";
for (const asset of Object.values(manager.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(manager.rscBundle);
for (const [id, meta] of Object.entries(manager.serverResourcesMetaMap)) serverResources[meta.key] = assetsURLOfDeps({
js: [],
css: rscAssetDeps[id]?.deps.css ?? []
}, manager);
const assetDeps = collectAssetDeps(bundle);
const entry = Object.values(assetDeps).find((v) => v.chunk.name === "index");
assert(entry);
const entryUrl = assetsURL(entry.chunk.fileName, manager);
const clientReferenceDeps = {};
for (const meta of Object.values(manager.clientReferenceMetaMap)) {
const deps = assetDeps[meta.groupChunkId]?.deps ?? {
js: [],
css: []
};
clientReferenceDeps[meta.referenceKey] = assetsURLOfDeps(mergeAssetDeps(deps, entry.deps), manager);
}
let bootstrapScriptContent;
if (typeof entryUrl === "string") bootstrapScriptContent = `import(${JSON.stringify(entryUrl)})`;
else bootstrapScriptContent = new RuntimeAsset(`"import(" + JSON.stringify(${entryUrl.runtime}) + ")"`);
manager.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 stripLiteral(code).matchAll(/import\s*\.\s*meta\s*\.\s*viteRsc\s*\.\s*loadBootstrapScriptContent\(([\s\S]*?)\)/dg)) {
const [argStart, argEnd] = match.indices[1];
const argCode = code.slice(argStart, argEnd).trim();
assert(evalValue(argCode), `[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 = "";
if (await this.resolve("/@react-refresh")) 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/client-references")) {
node.remove();
}
});
});
`;
code += `
import.meta.hot.on("rsc:update", () => {
document.querySelectorAll("vite-error-overlay").forEach((n) => n.close())
});
`;
code += `import.meta.hot.on("rsc:prune", ${(e) => {
document.querySelectorAll("link[rel='stylesheet']").forEach((node) => {
if (e.paths.includes(node.dataset.rscCssHref)) node.remove();
});
}});`;
return code;
}),
...vitePluginRscMinimal(rscPluginOptions, manager),
...vitePluginFindSourceMapURL(),
...vitePluginRscCss(rscPluginOptions, manager),
{
...validateImportPlugin(),
apply: () => rscPluginOptions.validateImports !== false
},
scanBuildStripPlugin({ manager }),
...cjsModuleRunnerPlugin(),
...globalAsyncLocalStoragePlugin()
];
}
function globalAsyncLocalStoragePlugin() {
return [{
name: "rsc:inject-async-local-storage",
transform: { handler(code) {
if ((this.environment.name === "ssr" || this.environment.name === "rsc") && code.includes("typeof AsyncLocalStorage") && code.includes("new AsyncLocalStorage()") && !code.includes("__viteRscAsyncHooks")) return (this.environment.mode === "build" && !isRolldownVite ? `const __viteRscAsyncHooks = require("node:async_hooks");` : `import * as __viteRscAsyncHooks from "node:async_hooks";`) + `globalThis.AsyncLocalStorage = __viteRscAsyncHooks.AsyncLocalStorage;` + code;
} }
}];
}
function vitePluginUseClient(useClientPluginOptions, manager) {
const packageSources = /* @__PURE__ */ new Map();
const bareImportRE = /^(?![a-zA-Z]:)[\w@](?!.*:\/\/)/;
const serverEnvironmentName = useClientPluginOptions.environment?.rsc ?? "rsc";
const browserEnvironmentName = useClientPluginOptions.environment?.browser ?? "client";
let optimizerMetadata;
function warnInoncistentClientOptimization(ctx, id) {
id = normalizePath(path.relative(process.cwd(), id));
if (optimizerMetadata?.ids.includes(id)) ctx.warn("client component dependency is inconsistently optimized. It's recommended to add the dependency to 'optimizeDeps.exclude'.");
}
const debug = createDebug("vite-rsc:use-client");
return [
{
name: "rsc:use-client",
async transform(code, id) {
if (this.environment.name !== serverEnvironmentName) return;
if (!code.includes("use client")) {
delete manager.clientReferenceMetaMap[id];
return;
}
const ast = await parseAstAsync(code);
if (!hasDirective(ast.body, "use client")) {
delete manager.clientReferenceMetaMap[id];
return;
}
if (code.includes("use server")) {
const directives = findDirectives(ast, "use server");
if (directives.length > 0) this.error(`'use server' directive is not allowed inside 'use client'`, directives[0]?.start);
}
let importId;
let referenceKey;
const packageSource = packageSources.get(id);
if (!packageSource && this.environment.mode === "dev" && id.includes("/node_modules/")) {
debug(`internal client reference created through a package imported in '${this.environment.name}' environment: ${id}`);
id = cleanUrl(id);
warnInoncistentClientOptimization(this, id);
importId = `/@id/__x00__virtual:vite-rsc/client-in-server-package-proxy/${encodeURIComponent(id)}`;
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(manager.server.environments[browserEnvironmentName], id);
referenceKey = importId;
} else {
importId = id;
referenceKey = hashString(manager.toRelativeId(id));
}
const result = withRollupError(this, 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;
manager.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: "" }
};
}
},
{
name: "rsc:use-client/build-references",
resolveId(source) {
if (source.startsWith("virtual:vite-rsc/client-references")) return "\0" + source;
},
load(id) {
if (id === "\0virtual:vite-rsc/client-references") {
if (this.environment.mode === "dev") return {
code: `export default {}`,
map: null
};
if (manager.isScanBuild) {
let code$1 = ``;
for (const meta of Object.values(manager.clientReferenceMetaMap)) code$1 += `import ${JSON.stringify(meta.importId)};\n`;
return {
code: code$1,
map: null
};
}
let code = "";
manager.clientReferenceGroups = {};
for (const meta of Object.values(manager.clientReferenceMetaMap)) {
if (!meta.serverChunk) continue;
let name = useClientPluginOptions.clientChunks?.({
id: meta.importId,
normalizedId: manager.toRelativeId(meta.importId),
serverChunk: meta.serverChunk
}) ?? meta.serverChunk;
name = cleanUrl(name.replaceAll("..", "__"));
(manager.clientReferenceGroups[name] ??= []).push(meta);
meta.groupChunkId = `\0virtual:vite-rsc/client-references/group/${name}`;
}
debug("client-reference-groups", manager.clientReferenceGroups);
for (const [name, metas] of Object.entries(manager.clientReferenceGroups)) {
const groupVirtual = `virtual:vite-rsc/client-references/group/${name}`;
for (const meta of metas) code += `\
${JSON.stringify(meta.referenceKey)}: async () => {
const m = await import(${JSON.stringify(groupVirtual)});
return m.export_${meta.referenceKey};
},
`;
}
code = `export default {${code}};\n`;
return {
code,
map: null
};
}
if (id.startsWith("\0virtual:vite-rsc/client-references/group/")) {
const name = id.slice(42);
const metas = manager.clientReferenceGroups[name];
assert(metas, `unknown client reference group: ${name}`);
let code = ``;
for (const meta of metas) {
const exports = meta.renderedExports.map((name$1) => `${name$1}: import_${meta.referenceKey}.${name$1},\n`).sort().join("");
code += `
import * as import_${meta.referenceKey} from ${JSON.stringify(meta.importId)};
export const export_${meta.referenceKey} = {${exports}};
`;
}
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) && !(source === "client-only" || source === "server-only")) {
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);
return `export {${Object.values(manager.clientReferenceMetaMap).find((v) => v.packageSource === source).exportNames.join(",")}} from ${JSON.stringify(source)};\n`;
}
},
generateBundle(_options, bundle) {
if (manager.isScanBuild) return;
if (this.environment.name !== serverEnvironmentName) return;
for (const chunk of Object.values(bundle)) if (chunk.type === "chunk") {
const metas = [];
for (const id of chunk.moduleIds) {
const meta = manager.clientReferenceMetaMap[id];
if (meta) metas.push([id, meta]);
}
if (metas.length > 0) {
let serverChunk;
if (chunk.facadeModuleId) serverChunk = "facade:" + manager.toRelativeId(chunk.facadeModuleId);
else serverChunk = "shared:" + manager.toRelativeId(metas.map(([id]) => id).sort()[0]);
for (const [id, meta] of metas) {
const mod = chunk.modules[id];
assert(mod);
meta.renderedExports = mod.renderedExports;
meta.serverChunk = serverChunk;
}
}
}
}
},
...customOptimizerMetadataPlugin({ setMetadata: (metadata) => {
optimizerMetadata = metadata;
} })
];
}
function customOptimizerMetadataPlugin({ setMetadata }) {
const MEATADATA_FILE = "_metadata-rsc.json";
function optimizerPluginEsbuild() {
return {
name: "vite-rsc-metafile",
setup(build) {
build.onEnd((result) => {
if (!result.metafile?.inputs || !build.initialOptions.outdir) return;
const metadata = { ids: Object.keys(result.metafile.inputs) };
setMetadata(metadata);
fs.writeFileSync(path.join(build.initialOptions.outdir, MEATADATA_FILE), JSON.stringify(metadata, null, 2));
});
}
};
}
function optimizerPluginRolldown() {
return {
name: "vite-rsc-metafile",
writeBundle(options) {
assert(options.dir);
const metadata = { ids: [...this.getModuleIds()].map((id) => path.relative(process.cwd(), id)) };
setMetadata(metadata);
fs.writeFileSync(path.join(options.dir, MEATADATA_FILE), JSON.stringify(metadata, null, 2));
}
};
}
return [{
name: "rsc:use-client:optimizer-metadata",
apply: "serve",
config() {
return { environments: { client: { optimizeDeps: "rolldownVersion" in vite ? { rolldownOptions: { plugins: [optimizerPluginRolldown()] } } : { esbuildOptions: { plugins: [optimizerPluginEsbuild()] } } } } };
},
configResolved(config) {
const file = path.join(config.cacheDir, "deps", MEATADATA_FILE);
if (fs.existsSync(file)) try {
setMetadata(JSON.parse(fs.readFileSync(file, "utf-8")));
} catch (e) {
this.warn(`failed to load '${file}'`);
}
}
}];
}
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, serverEnvironmentName);
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 === serverEnvironmentName && emitEncryptionKey) fs.writeFileSync(path.join(this.environment.config.build.outDir, KEY_FILE), `export default ${defineEncryptionKey};\n`);
}
}];
}
function vitePluginUseServer(useServerPluginOptions, manager) {
const serverEnvironmentName = useServerPluginOptions.environment?.rsc ?? "rsc";
const browserEnvironmentName = useServerPluginOptions.environment?.browser ?? "client";
const debug = createDebug("vite-rsc:use-server");
return [{
name: "rsc:use-server",
async transform(code, id) {
if (!code.includes("use server")) {
delete manager.serverReferenceMetaMap[id];
return;
}
const ast = await parseAstAsync(code);
let normalizedId_;
const getNormalizedId = () => {
if (!normalizedId_) {
if (this.environment.mode === "dev" && id.includes("/node_modules/")) {
debug(`internal server reference created through a package imported in ${this.environment.name} environment: ${id}`);
id = cleanUrl(id);
}
if (manager.config.command === "build") normalizedId_ = hashString(manager.toRelativeId(id));
else normalizedId_ = normalizeViteImportAnalysisUrl(manager.server.environments[serverEnvironmentName], id);
}
return normalizedId_;
};
if (this.environment.name === serverEnvironmentName) {
const transformServerActionServer_ = withRollupError(this, transformServerActionServer);
const enableEncryption = useServerPluginOptions.enableActionEncryption ?? true;
const result = 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
});
const output = result.output;
if (!result || !output.hasChanged()) {
delete manager.serverReferenceMetaMap[id];
return;
}
manager.serverReferenceMetaMap[id] = {
importId: id,
referenceKey: getNormalizedId(),
exportNames: "names" in result ? result.names : result.exportNames
};
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")) {
delete manager.serverReferenceMetaMap[id];
return;
}
const result = withRollupError(this, transformDirectiveProxyExport)(ast, {
code,
runtime: (name) => `$$ReactClient.createServerReference(${JSON.stringify(getNormalizedId() + "#" + name)},$$ReactClient.callServer