UNPKG

@vitejs/plugin-rsc

Version:
1,293 lines (1,287 loc) 63.1 kB
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