From 0da1dc9544b4e1ee3c3aeadfe9c53ddf0bfe3d92 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 18 Jun 2026 19:16:45 +0100 Subject: [PATCH 1/8] refactor(use-cache): move server function directives to user land --- packages/vinext/src/index.ts | 40 +- .../src/plugins/use-cache-server-functions.ts | 441 ++++++++++++++++++ tests/app-router-production-server.test.ts | 7 + tests/e2e/app-router-prod/use-cache.spec.ts | 8 + tests/use-cache-transform.test.ts | 121 ++++- 5 files changed, 575 insertions(+), 42 deletions(-) create mode 100644 packages/vinext/src/plugins/use-cache-server-functions.ts diff --git a/packages/vinext/src/index.ts b/packages/vinext/src/index.ts index 08d2e23a0c..59bf70d879 100644 --- a/packages/vinext/src/index.ts +++ b/packages/vinext/src/index.ts @@ -6,6 +6,7 @@ import type { UserConfig, ViteDevServer, } from "vite"; +import type { ServerFunctionDirectiveContext } from "@vitejs/plugin-rsc/plugin"; import { loadEnv, parseAst, transformWithOxc } from "vite"; import { pagesRouter, @@ -117,6 +118,7 @@ import { createMiddlewareServerOnlyPlugin } from "./plugins/middleware-server-on import { createOptimizeImportsPlugin } from "./plugins/optimize-imports.js"; import { createDynamicPreloadMetadataPlugin } from "./plugins/dynamic-preload-metadata.js"; import { createOgInlineFetchAssetsPlugin, createOgAssetsPlugin } from "./plugins/og-assets.js"; +import { createUseCacheServerFunctionPlugins } from "./plugins/use-cache-server-functions.js"; import { generateRouteTypes } from "./typegen.js"; import { mergeOptimizeDepsExclude, @@ -187,16 +189,6 @@ import { randomBytes, randomUUID } from "node:crypto"; import commonjs from "vite-plugin-commonjs"; import { normalizePathSeparators, stripViteModuleQuery } from "./utils/path.js"; -type ServerFunctionDirectiveContext = { - value: string; - name: string; - id: string; - directiveMatch: RegExpMatchArray; - location: "inline" | "module"; - parameters?: { count: number; hasRest: boolean }; - runtime?: string; -}; - function parseUseCacheVariant(directive: string): string { return directive === "use cache" ? "" @@ -943,15 +935,18 @@ export default function vinext(options: VinextOptions = {}): PluginOption[] { } const rscImport = import(pathToFileURL(resolvedRscPath).href); rscPluginPromise = rscImport - .then((mod) => { + .then(async (mod) => { const rsc = mod.default; - return rsc({ + const plugins: Plugin[] = rsc({ entries: { rsc: VIRTUAL_RSC_ENTRY, ssr: VIRTUAL_APP_SSR_ENTRY, client: VIRTUAL_APP_BROWSER_ENTRY, }, - serverFunctionDirectives: [ + }); + const [serverFunctionPlugin, metadataPlugin] = await createUseCacheServerFunctionPlugins({ + projectRoot: earlyBaseDir, + definitions: [ { directive: /^use cache.*$/, test: (code: string) => code.includes("use cache"), @@ -1002,15 +997,7 @@ export default function vinext(options: VinextOptions = {}): PluginOption[] { : ""; return `${runtime}.registerCachedFunction(${value}, ${JSON.stringify(id + ":" + name)}, ${JSON.stringify(variant)}${pageOptions})`; }, - filterExport: ({ - name, - id, - meta, - }: { - name: string; - id: string; - meta: { isFunction?: boolean }; - }) => { + filterExport: ({ name, id, meta }) => { if (meta.isFunction === false) return false; if (/\/(layout|template)\.(tsx?|jsx?|mjs)$/.test(id) && name === "default") { return false; @@ -1019,7 +1006,16 @@ export default function vinext(options: VinextOptions = {}): PluginOption[] { }, }, ], + serverEnvironmentName: "rsc", + browserEnvironmentName: "client", }); + const useServerIndex = plugins.findIndex((plugin) => plugin.name === "rsc:use-server"); + if (useServerIndex === -1 || !serverFunctionPlugin || !metadataPlugin) { + throw new Error("vinext: Failed to locate @vitejs/plugin-rsc use-server plugin."); + } + plugins.splice(useServerIndex, 0, serverFunctionPlugin); + plugins.splice(useServerIndex + 2, 0, metadataPlugin); + return plugins; }) .catch((cause) => { throw new Error("vinext: Failed to load @vitejs/plugin-rsc.", { diff --git a/packages/vinext/src/plugins/use-cache-server-functions.ts b/packages/vinext/src/plugins/use-cache-server-functions.ts new file mode 100644 index 0000000000..463345ccef --- /dev/null +++ b/packages/vinext/src/plugins/use-cache-server-functions.ts @@ -0,0 +1,441 @@ +import { createHash } from "node:crypto"; +import fs from "node:fs"; +import { createRequire } from "node:module"; +import path from "node:path"; +import { pathToFileURL } from "node:url"; +import type { RscPluginManager, ServerFunctionDirective } from "@vitejs/plugin-rsc/plugin"; +import type { SourceMap } from "magic-string"; +import type { Plugin, Rollup, ViteDevServer } from "vite"; +import { parseAstAsync, transformWithOxc } from "vite"; +import { isUnknownRecord } from "../utils/record.js"; +import { escapeRegExp } from "../utils/regex.js"; + +type RscTransforms = typeof import("@vitejs/plugin-rsc/transforms"); +type Program = Parameters[0]; +type ModuleDirective = NonNullable< + Parameters[2]["moduleDirective"] +> & { start?: number }; +type StringDirective = ModuleDirective & { type: "Literal"; value: string }; + +type Options = { + projectRoot: string; + definitions: ServerFunctionDirective[]; + serverEnvironmentName: string; + browserEnvironmentName: string; +}; + +type OwnedServerReference = { + referenceKey: string; + exportNames: string[]; +}; + +const SERVER_FUNCTION_DIRECTIVE_MARKER = "/* __vinext_server_function_directives__ */"; + +function resolvePluginRscModule(projectRoot: string, specifier: string): string { + try { + return createRequire(path.join(projectRoot, "package.json")).resolve(specifier); + } catch {} + + try { + return createRequire(import.meta.url).resolve(specifier); + } catch { + throw new Error(`vinext: Installed @vitejs/plugin-rsc does not expose ${specifier}.`); + } +} + +async function parseProgram(code: string): Promise { + return (await parseAstAsync(code)) as unknown as Program; +} + +function matchDirective(value: string, directive: string | RegExp): RegExpMatchArray | undefined { + const pattern = + typeof directive === "string" + ? new RegExp(`^${escapeRegExp(directive)}$`) + : new RegExp(directive.source, directive.flags); + pattern.lastIndex = 0; + return value.match(pattern) ?? undefined; +} + +function isStringLiteral(value: unknown): value is StringDirective { + return isUnknownRecord(value) && value.type === "Literal" && typeof value.value === "string"; +} + +function isExpressionStatement( + value: unknown, +): value is Record & { type: "ExpressionStatement"; expression: unknown } { + return isUnknownRecord(value) && value.type === "ExpressionStatement" && "expression" in value; +} + +function isBlockStatement( + value: unknown, +): value is Record & { type: "BlockStatement"; body: unknown[] } { + return isUnknownRecord(value) && value.type === "BlockStatement" && Array.isArray(value.body); +} + +function findModuleDirective( + ast: Program, + directive: string | RegExp, +): StringDirective | undefined { + for (const node of ast.body) { + if (node.type !== "ExpressionStatement") continue; + if (isStringLiteral(node.expression) && matchDirective(node.expression.value, directive)) { + return node.expression; + } + } +} + +function findInlineDirective( + ast: Program, + directive: string | RegExp, +): StringDirective | undefined { + let result: StringDirective | undefined; + + function visit(value: unknown): void { + if (result) return; + if (Array.isArray(value)) { + for (const child of value) visit(child); + return; + } + if (!isUnknownRecord(value)) return; + + const nodeType = typeof value.type === "string" ? value.type : undefined; + if ( + (nodeType === "FunctionDeclaration" || + nodeType === "FunctionExpression" || + nodeType === "ArrowFunctionExpression") && + isBlockStatement(value.body) + ) { + for (const statement of value.body.body) { + if ( + isExpressionStatement(statement) && + isStringLiteral(statement.expression) && + matchDirective(statement.expression.value, directive) + ) { + result = statement.expression; + return; + } + } + } + + for (const [key, child] of Object.entries(value)) { + if (key === "parent" || key === "loc" || key === "start" || key === "end") continue; + visit(child); + } + } + + visit(ast); + return result; +} + +function hashString(value: string): string { + return createHash("sha256").update(value).digest("hex").slice(0, 12); +} + +function normalizeViteImportAnalysisUrl( + environment: ViteDevServer["environments"][string], + id: string, +): string { + const root = environment.config.root; + const rootPrefix = root.endsWith("/") ? root : `${root}/`; + if (id.startsWith(rootPrefix)) return id.slice(root.length); + + const cleanId = id.split("?", 1)[0] ?? id; + if (path.isAbsolute(cleanId) && fs.existsSync(cleanId)) return path.posix.join("/@fs/", id); + if (id.startsWith(".") || id.startsWith("/")) return id; + return `/@id/${id.replace("\0", "__x00__")}`; +} + +async function expandExportAll( + transforms: RscTransforms, + context: Rollup.TransformPluginContext, + code: string, + ast: Program, + id: string, +): Promise<{ code: string } | undefined> { + return transforms.transformExpandExportAll({ + code, + ast, + importer: id, + resolve: async (source, importer) => (await context.resolve(source, importer))?.id, + load: async (resolvedId) => { + const source = await fs.promises.readFile(resolvedId, "utf8"); + const transformed = await transformWithOxc(source, resolvedId, { sourcemap: false }); + return parseProgram(transformed.code); + }, + }); +} + +export async function createUseCacheServerFunctionPlugins(options: Options): Promise { + const rscModulePath = resolvePluginRscModule(options.projectRoot, "@vitejs/plugin-rsc"); + const transformsPath = resolvePluginRscModule( + options.projectRoot, + "@vitejs/plugin-rsc/transforms", + ); + const rscRuntime = pathToFileURL( + resolvePluginRscModule(options.projectRoot, "@vitejs/plugin-rsc/react/rsc"), + ).href; + const browserRuntime = pathToFileURL( + resolvePluginRscModule(options.projectRoot, "@vitejs/plugin-rsc/react/browser"), + ).href; + const ssrRuntime = pathToFileURL( + resolvePluginRscModule(options.projectRoot, "@vitejs/plugin-rsc/react/ssr"), + ).href; + const encryptionRuntime = pathToFileURL( + resolvePluginRscModule(options.projectRoot, "@vitejs/plugin-rsc/utils/encryption-runtime"), + ).href; + const rscModule: typeof import("@vitejs/plugin-rsc") = await import( + pathToFileURL(rscModulePath).href + ); + const transforms: RscTransforms = await import(pathToFileURL(transformsPath).href); + const { getPluginApi } = rscModule; + let manager: RscPluginManager | undefined; + const ownedReferences = new Map(); + + const transformPlugin: Plugin = { + name: "vinext:use-cache-server-functions", + + configResolved(config) { + manager = getPluginApi(config)?.manager; + }, + + transform: { + async handler(code, id) { + if (code.includes(SERVER_FUNCTION_DIRECTIVE_MARKER)) return; + + const active = options.definitions.filter( + (definition) => + (definition.test?.(code) ?? code.includes("use ")) && + (!definition.filter || definition.filter(id)), + ); + const isServer = this.environment.name === options.serverEnvironmentName; + if (active.length === 0) { + if (isServer) { + ownedReferences.delete(id); + if (manager) delete manager.serverReferenceMetaMap[id]; + } + return; + } + if (!manager) { + throw new Error("vinext: failed to access @vitejs/plugin-rsc through getPluginApi()."); + } + + let ast = await parseProgram(code); + const useServerBoundary = transforms.hasDirective(ast.body, "use server"); + if (!isServer && useServerBoundary) return; + + const normalizedId = + manager.config.command === "build" + ? hashString(manager.toRelativeId(id)) + : normalizeViteImportAnalysisUrl( + manager.server.environments[options.serverEnvironmentName], + id, + ); + + if (!isServer) { + for (const definition of active) { + const inlineDirective = findInlineDirective(ast, definition.directive); + if (inlineDirective && definition.clientError) { + throw Object.assign( + new Error(definition.clientError({ id, environment: this.environment.name })), + { pos: inlineDirective.start }, + ); + } + } + + const matches: Array = []; + for (const definition of active) { + const moduleDirective = findModuleDirective(ast, definition.directive); + if (moduleDirective) matches.push([definition, moduleDirective]); + } + if (matches.length === 0) return; + if (matches.length > 1) { + throw Object.assign( + new Error("Multiple server function directives match this module."), + { + pos: matches[1]?.[1].start, + }, + ); + } + + const match = matches[0]; + if (!match) return; + const [, moduleDirective] = match; + const result = transforms.transformDirectiveProxyExport(ast, { + code, + directive: moduleDirective.value, + runtime: (name) => + `$$ReactClient.createServerReference(${JSON.stringify(`${normalizedId}#${name}`)},$$ReactClient.callServer,undefined,${this.environment.mode === "dev" ? "$$ReactClient.findSourceMapURL" : "undefined"},${JSON.stringify(name)})`, + }); + if (!result?.output.hasChanged()) return; + const ownedReference = { + referenceKey: normalizedId, + exportNames: result.exportNames, + }; + ownedReferences.set(id, ownedReference); + manager.serverReferenceMetaMap[id] = { + importId: id, + ...ownedReference, + }; + result.output.prepend( + `${SERVER_FUNCTION_DIRECTIVE_MARKER}\nimport * as $$ReactClient from ${JSON.stringify(this.environment.name === options.browserEnvironmentName ? browserRuntime : ssrRuntime)};\n`, + ); + return { + code: result.output.toString(), + map: result.output.generateMap({ hires: "boundary", source: id }), + }; + } + + const exportNames = new Set(); + let needsReactRuntime = false; + let needsEncryptionRuntime = false; + let outputMap: SourceMap | undefined; + + for (const definition of active) { + const runtimeName = definition.runtime + ? `$$server_function_directive_${hashString(definition.runtime)}` + : undefined; + let runtimeUsed = false; + const getRuntime = () => { + if (runtimeName) runtimeUsed = true; + return runtimeName; + }; + + let moduleDirective = findModuleDirective(ast, definition.directive); + if (moduleDirective) { + if (useServerBoundary) { + throw Object.assign( + new Error( + `A module cannot contain both ${JSON.stringify(moduleDirective.value)} and "use server" directives.`, + ), + { pos: moduleDirective.start }, + ); + } + const expanded = await expandExportAll(transforms, this, code, ast, id); + if (expanded) { + code = expanded.code; + ast = await parseProgram(code); + moduleDirective = findModuleDirective(ast, definition.directive); + } + } + + const moduleMatch = moduleDirective + ? matchDirective(moduleDirective.value, definition.directive) + : undefined; + if (moduleMatch) { + definition.validate?.({ id, directive: moduleMatch[0], location: "module" }); + } + + const result = transforms.transformServerActionServer(code, ast, { + runtime: (value, name) => + `$$ReactServer.registerServerReference(${value}, ${JSON.stringify(normalizedId)}, ${JSON.stringify(name)})`, + directive: definition.directive, + moduleDirective, + moduleRuntime: (value, name, meta) => { + if (!moduleMatch) return value; + needsReactRuntime = true; + return `$$ReactServer.registerServerReference(${definition.wrap({ value, name, id, directiveMatch: moduleMatch, location: "module", hasBoundArgs: false, parameters: meta.parameters, runtime: getRuntime(), meta })}, ${JSON.stringify(normalizedId)}, ${JSON.stringify(name)})`; + }, + inlineRuntime: (value, name, meta) => { + definition.validate?.({ + id, + directive: meta.directiveMatch[0], + location: "inline", + }); + const wrapped = definition.wrap({ + value, + name, + id, + directiveMatch: meta.directiveMatch, + location: "inline", + hasBoundArgs: meta.hasBoundArgs, + parameters: meta.parameters, + runtime: getRuntime(), + }); + if (useServerBoundary) return wrapped; + + needsReactRuntime = true; + if (meta.hasBoundArgs) { + needsEncryptionRuntime = true; + return `$$ReactServer.registerServerReference((($$wrapped) => async ($$encoded, ...$$args) => $$wrapped(...await __vite_rsc_encryption_runtime.decryptActionBoundArgs($$encoded), ...$$args))(${wrapped}), ${JSON.stringify(normalizedId)}, ${JSON.stringify(name)})`; + } + return `$$ReactServer.registerServerReference(${wrapped}, ${JSON.stringify(normalizedId)}, ${JSON.stringify(name)})`; + }, + filter: (name, meta) => definition.filterExport?.({ name, id, meta }) ?? true, + rejectNonAsyncFunction: definition.rejectNonAsyncFunction, + rejectNonAsyncModule: definition.rejectNonAsyncModule, + encode: (value) => { + needsEncryptionRuntime = true; + return `__vite_rsc_encryption_runtime.encryptActionBoundArgs(${value})`; + }, + stableName: true, + exportWrappedHoist: !useServerBoundary, + detectUseServerModule: false, + rejectForbiddenExpressions: true, + }); + if (!result.output.hasChanged()) continue; + + if (runtimeUsed && definition.runtime && runtimeName) { + result.output.prepend( + `import * as ${runtimeName} from ${JSON.stringify(definition.runtime)};\n`, + ); + } + + const transformedNames = "names" in result ? result.names : result.exportNames; + transformedNames.forEach((name) => exportNames.add(name)); + outputMap = result.output.generateMap({ hires: "boundary", source: id }); + code = result.output.toString(); + ast = await parseProgram(code); + } + + if (!useServerBoundary) { + if (exportNames.size === 0) { + ownedReferences.delete(id); + delete manager.serverReferenceMetaMap[id]; + } else { + const ownedReference = { + referenceKey: normalizedId, + exportNames: [...exportNames], + }; + ownedReferences.set(id, ownedReference); + manager.serverReferenceMetaMap[id] = { + importId: id, + ...ownedReference, + }; + } + } + + const imports = [ + needsReactRuntime && `import * as $$ReactServer from ${JSON.stringify(rscRuntime)};`, + needsEncryptionRuntime && + `import * as __vite_rsc_encryption_runtime from ${JSON.stringify(encryptionRuntime)};`, + ].filter(Boolean); + return { + code: `${SERVER_FUNCTION_DIRECTIVE_MARKER}\n${imports.join("\n")}\n${code}`, + map: outputMap, + }; + }, + }, + }; + + const metadataPlugin: Plugin = { + name: "vinext:use-cache-server-function-metadata", + transform: { + handler(_code, id) { + if (!manager) return; + const ownedReference = ownedReferences.get(id); + if (!ownedReference) return; + + const existing = manager.serverReferenceMetaMap[id]; + manager.serverReferenceMetaMap[id] = { + importId: id, + referenceKey: ownedReference.referenceKey, + exportNames: existing + ? [...new Set([...existing.exportNames, ...ownedReference.exportNames])] + : ownedReference.exportNames, + }; + }, + }, + }; + + return [transformPlugin, metadataPlugin]; +} diff --git a/tests/app-router-production-server.test.ts b/tests/app-router-production-server.test.ts index 9b4ca4725a..84f2ddb0ea 100644 --- a/tests/app-router-production-server.test.ts +++ b/tests/app-router-production-server.test.ts @@ -1452,6 +1452,13 @@ describe("App Router Production server (startProdServer)", () => { expect(res.status).toBe(200); const html = await res.text(); + // React Flight encodes server-function props with the `$h` token used by + // this React build's SERVER_DECODE_REFERENCE_PREFIX path. Keep this + // assertion next to the action round-trip so the test proves both halves: + // the payload uses the server-reference encoding and the decoded reference + // resolves through vinext's production manifest below. + expect(html).toContain('\\"getDate\\":\\"$h'); + // The flight payload embeds each cached function prop as a server // reference whose id is "<12-hex normalised key>#". const refIds = [ diff --git a/tests/e2e/app-router-prod/use-cache.spec.ts b/tests/e2e/app-router-prod/use-cache.spec.ts index 627858bdd1..ef915601d8 100644 --- a/tests/e2e/app-router-prod/use-cache.spec.ts +++ b/tests/e2e/app-router-prod/use-cache.spec.ts @@ -1,6 +1,14 @@ import { expect, test } from "@playwright/test"; test.describe('production "use cache" server function references', () => { + test("invokes file-level cached exports imported by a Client Component", async ({ page }) => { + await page.goto("/use-cache-client-import"); + await page.locator("#call-client-imported-cache").click(); + await expect(page.getByTestId("client-imported-cache-result")).toHaveText( + /^client-cache:direct:[0-9.e+-]+$/, + ); + }); + test("replays cached RSC through SSR and invokes nested functions from the browser", async ({ page, }) => { diff --git a/tests/use-cache-transform.test.ts b/tests/use-cache-transform.test.ts index 1b2455caf7..5c6a20a3c1 100644 --- a/tests/use-cache-transform.test.ts +++ b/tests/use-cache-transform.test.ts @@ -1,6 +1,6 @@ /** - * Tests the plugin-rsc serverFunctionDirectives integration used for function-level - * "use cache" directives. Vinext supplies cache wrapper expressions; plugin-rsc + * Tests the vinext user-land server function directive integration used for function-level + * "use cache" directives. Vinext owns the directive plugin while plugin-rsc * owns directive discovery, closure hoisting, encryption, reference ids, and * server-reference manifest metadata. */ @@ -47,16 +47,97 @@ async function configurePluginRsc(plugins: Plugin[]) { rsc: { build: { outDir: path.join(APP_FIXTURE_DIR, "dist/rsc") } }, }, }); + const useCachePlugin = plugins.find( + (plugin) => plugin.name === "vinext:use-cache-server-functions", + )!; + unwrapHook(useCachePlugin.configResolved)!.call(useCachePlugin, { plugins }); // oxlint-disable-next-line typescript/no-explicit-any return (minimal as any).api.manager; } describe("plugin-rsc inline use-cache references", () => { + it("restores user-land reference metadata after rsc:use-server clears it", async () => { + const plugins = await getPlugins(); + const manager = await configurePluginRsc(plugins); + const useCacheIndex = plugins.findIndex( + (candidate) => candidate.name === "vinext:use-cache-server-functions", + ); + const useServerIndex = plugins.findIndex((candidate) => candidate.name === "rsc:use-server"); + const metadataIndex = plugins.findIndex( + (candidate) => candidate.name === "vinext:use-cache-server-function-metadata", + ); + const manifestIndex = plugins.findIndex( + (candidate) => candidate.name === "rsc:virtual-vite-rsc/server-references", + ); + expect(useCacheIndex).toBeLessThan(useServerIndex); + expect(metadataIndex).toBeGreaterThan(useServerIndex); + expect(metadataIndex).toBeLessThan(manifestIndex); + + const context = { environment: { name: "rsc", mode: "build" } }; + const transformed = await unwrapHook(plugins[useCacheIndex]!.transform)!.call( + context, + inlineCacheCode, + moduleId, + ); + expect(manager.serverReferenceMetaMap[moduleId]).toBeDefined(); + + await unwrapHook(plugins[useServerIndex]!.transform)!.call( + context, + transformed!.code, + moduleId, + ); + expect(manager.serverReferenceMetaMap[moduleId]).toBeUndefined(); + + unwrapHook(plugins[metadataIndex]!.transform)!.call(context, transformed!.code, moduleId); + expect(manager.serverReferenceMetaMap[moduleId]).toBeDefined(); + + const ssrContext = { environment: { name: "ssr", mode: "build" } }; + const proxied = await unwrapHook(plugins[useCacheIndex]!.transform)!.call( + ssrContext, + fileCacheCode, + moduleId, + ); + await unwrapHook(plugins[useServerIndex]!.transform)!.call(ssrContext, proxied!.code, moduleId); + expect(manager.serverReferenceMetaMap[moduleId]).toBeUndefined(); + + unwrapHook(plugins[metadataIndex]!.transform)!.call(ssrContext, proxied!.code, moduleId); + expect(manager.serverReferenceMetaMap[moduleId]).toMatchObject({ + importId: moduleId, + exportNames: ["getData"], + }); + }); + + it("matches Vite's dev reference key for files outside the project root", async () => { + const plugins = await getPlugins(); + const manager = await configurePluginRsc(plugins); + manager.config.command = "serve"; + manager.server = { + environments: { + rsc: { + config: { root: APP_FIXTURE_DIR }, + moduleGraph: { getModuleById: () => undefined }, + }, + }, + }; + const plugin = plugins.find( + (candidate) => candidate.name === "vinext:use-cache-server-functions", + )!; + const externalId = import.meta.filename; + const result = await unwrapHook(plugin.transform)!.call( + { environment: { name: "rsc", mode: "dev" } }, + inlineCacheCode, + externalId, + ); + const expectedKey = path.posix.join("/@fs/", externalId); + expect(result!.code).toContain(JSON.stringify(expectedKey)); + expect(manager.serverReferenceMetaMap[externalId].referenceKey).toBe(expectedKey); + }); + it("wraps and registers inline cache functions with plugin-rsc's build reference key", async () => { const plugins = await getPlugins(); const manager = await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "rsc:server-function-directives", + (candidate) => candidate.name === "vinext:use-cache-server-functions", )!; const transform = unwrapHook(plugin.transform)!; const result = await transform.call( @@ -84,7 +165,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "rsc:server-function-directives", + (candidate) => candidate.name === "vinext:use-cache-server-functions", )!; const transform = unwrapHook(plugin.transform)!; const original = await transform.call( @@ -109,7 +190,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); const manager = await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "rsc:server-function-directives", + (candidate) => candidate.name === "vinext:use-cache-server-functions", )!; const transform = unwrapHook(plugin.transform)!; await transform.call( @@ -130,7 +211,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "rsc:server-function-directives", + (candidate) => candidate.name === "vinext:use-cache-server-functions", )!; const transform = unwrapHook(plugin.transform)!; const closureCode = [ @@ -164,7 +245,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "rsc:server-function-directives", + (candidate) => candidate.name === "vinext:use-cache-server-functions", )!; const transform = unwrapHook(plugin.transform)!; @@ -182,7 +263,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "rsc:server-function-directives", + (candidate) => candidate.name === "vinext:use-cache-server-functions", )!; const transform = unwrapHook(plugin.transform)!; const result = await transform.call( @@ -197,7 +278,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "rsc:server-function-directives", + (candidate) => candidate.name === "vinext:use-cache-server-functions", )!; const transform = unwrapHook(plugin.transform)!; const result = await transform.call( @@ -212,7 +293,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "rsc:server-function-directives", + (candidate) => candidate.name === "vinext:use-cache-server-functions", )!; const transform = unwrapHook(plugin.transform)!; const result = await transform.call( @@ -227,7 +308,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); const manager = await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "rsc:server-function-directives", + (candidate) => candidate.name === "vinext:use-cache-server-functions", )!; const transform = unwrapHook(plugin.transform)!; const result = await transform.call( @@ -256,7 +337,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "rsc:server-function-directives", + (candidate) => candidate.name === "vinext:use-cache-server-functions", )!; const transform = unwrapHook(plugin.transform)!; await expect( @@ -274,7 +355,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "rsc:server-function-directives", + (candidate) => candidate.name === "vinext:use-cache-server-functions", )!; const transform = unwrapHook(plugin.transform)!; await expect( @@ -315,7 +396,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "rsc:server-function-directives", + (candidate) => candidate.name === "vinext:use-cache-server-functions", )!; const transform = unwrapHook(plugin.transform)!; const result = await transform.call( @@ -331,7 +412,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "rsc:server-function-directives", + (candidate) => candidate.name === "vinext:use-cache-server-functions", )!; const transform = unwrapHook(plugin.transform)!; await expect( @@ -349,7 +430,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "rsc:server-function-directives", + (candidate) => candidate.name === "vinext:use-cache-server-functions", )!; const context = { environment: { name: "rsc", mode: "build" } }; const source = [ @@ -368,7 +449,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "rsc:server-function-directives", + (candidate) => candidate.name === "vinext:use-cache-server-functions", )!; const transform = unwrapHook(plugin.transform)!; await expect( @@ -384,7 +465,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "rsc:server-function-directives", + (candidate) => candidate.name === "vinext:use-cache-server-functions", )!; const result = await unwrapHook(plugin.transform)!.call( { environment: { name: "rsc", mode: "build" } }, @@ -398,7 +479,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); const manager = await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "rsc:server-function-directives", + (candidate) => candidate.name === "vinext:use-cache-server-functions", )!; const transform = unwrapHook(plugin.transform)!; const result = await transform.call( @@ -419,7 +500,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "rsc:server-function-directives", + (candidate) => candidate.name === "vinext:use-cache-server-functions", )!; const transform = unwrapHook(plugin.transform)!; const result = await transform.call( From 6d2d5e95fef025aed7d66536609dbe8b366e0f8e Mon Sep 17 00:00:00 2001 From: James Date: Fri, 19 Jun 2026 00:01:40 +0100 Subject: [PATCH 2/8] refactor(use-cache): clarify generic directive plugin naming --- packages/vinext/src/index.ts | 4 +- ...tions.ts => server-function-directives.ts} | 6 +-- tests/use-cache-transform.test.ts | 44 +++++++++---------- 3 files changed, 27 insertions(+), 27 deletions(-) rename packages/vinext/src/plugins/{use-cache-server-functions.ts => server-function-directives.ts} (98%) diff --git a/packages/vinext/src/index.ts b/packages/vinext/src/index.ts index 59bf70d879..f64ca377af 100644 --- a/packages/vinext/src/index.ts +++ b/packages/vinext/src/index.ts @@ -118,7 +118,7 @@ import { createMiddlewareServerOnlyPlugin } from "./plugins/middleware-server-on import { createOptimizeImportsPlugin } from "./plugins/optimize-imports.js"; import { createDynamicPreloadMetadataPlugin } from "./plugins/dynamic-preload-metadata.js"; import { createOgInlineFetchAssetsPlugin, createOgAssetsPlugin } from "./plugins/og-assets.js"; -import { createUseCacheServerFunctionPlugins } from "./plugins/use-cache-server-functions.js"; +import { createServerFunctionDirectivePlugins } from "./plugins/server-function-directives.js"; import { generateRouteTypes } from "./typegen.js"; import { mergeOptimizeDepsExclude, @@ -944,7 +944,7 @@ export default function vinext(options: VinextOptions = {}): PluginOption[] { client: VIRTUAL_APP_BROWSER_ENTRY, }, }); - const [serverFunctionPlugin, metadataPlugin] = await createUseCacheServerFunctionPlugins({ + const [serverFunctionPlugin, metadataPlugin] = await createServerFunctionDirectivePlugins({ projectRoot: earlyBaseDir, definitions: [ { diff --git a/packages/vinext/src/plugins/use-cache-server-functions.ts b/packages/vinext/src/plugins/server-function-directives.ts similarity index 98% rename from packages/vinext/src/plugins/use-cache-server-functions.ts rename to packages/vinext/src/plugins/server-function-directives.ts index 463345ccef..bfc65bdc70 100644 --- a/packages/vinext/src/plugins/use-cache-server-functions.ts +++ b/packages/vinext/src/plugins/server-function-directives.ts @@ -165,7 +165,7 @@ async function expandExportAll( }); } -export async function createUseCacheServerFunctionPlugins(options: Options): Promise { +export async function createServerFunctionDirectivePlugins(options: Options): Promise { const rscModulePath = resolvePluginRscModule(options.projectRoot, "@vitejs/plugin-rsc"); const transformsPath = resolvePluginRscModule( options.projectRoot, @@ -192,7 +192,7 @@ export async function createUseCacheServerFunctionPlugins(options: Options): Pro const ownedReferences = new Map(); const transformPlugin: Plugin = { - name: "vinext:use-cache-server-functions", + name: "vinext:server-function-directives", configResolved(config) { manager = getPluginApi(config)?.manager; @@ -418,7 +418,7 @@ export async function createUseCacheServerFunctionPlugins(options: Options): Pro }; const metadataPlugin: Plugin = { - name: "vinext:use-cache-server-function-metadata", + name: "vinext:server-function-directive-metadata", transform: { handler(_code, id) { if (!manager) return; diff --git a/tests/use-cache-transform.test.ts b/tests/use-cache-transform.test.ts index 5c6a20a3c1..585fe49ba5 100644 --- a/tests/use-cache-transform.test.ts +++ b/tests/use-cache-transform.test.ts @@ -48,7 +48,7 @@ async function configurePluginRsc(plugins: Plugin[]) { }, }); const useCachePlugin = plugins.find( - (plugin) => plugin.name === "vinext:use-cache-server-functions", + (plugin) => plugin.name === "vinext:server-function-directives", )!; unwrapHook(useCachePlugin.configResolved)!.call(useCachePlugin, { plugins }); // oxlint-disable-next-line typescript/no-explicit-any @@ -60,11 +60,11 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); const manager = await configurePluginRsc(plugins); const useCacheIndex = plugins.findIndex( - (candidate) => candidate.name === "vinext:use-cache-server-functions", + (candidate) => candidate.name === "vinext:server-function-directives", ); const useServerIndex = plugins.findIndex((candidate) => candidate.name === "rsc:use-server"); const metadataIndex = plugins.findIndex( - (candidate) => candidate.name === "vinext:use-cache-server-function-metadata", + (candidate) => candidate.name === "vinext:server-function-directive-metadata", ); const manifestIndex = plugins.findIndex( (candidate) => candidate.name === "rsc:virtual-vite-rsc/server-references", @@ -120,7 +120,7 @@ describe("plugin-rsc inline use-cache references", () => { }, }; const plugin = plugins.find( - (candidate) => candidate.name === "vinext:use-cache-server-functions", + (candidate) => candidate.name === "vinext:server-function-directives", )!; const externalId = import.meta.filename; const result = await unwrapHook(plugin.transform)!.call( @@ -137,7 +137,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); const manager = await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "vinext:use-cache-server-functions", + (candidate) => candidate.name === "vinext:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; const result = await transform.call( @@ -165,7 +165,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "vinext:use-cache-server-functions", + (candidate) => candidate.name === "vinext:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; const original = await transform.call( @@ -190,7 +190,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); const manager = await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "vinext:use-cache-server-functions", + (candidate) => candidate.name === "vinext:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; await transform.call( @@ -211,7 +211,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "vinext:use-cache-server-functions", + (candidate) => candidate.name === "vinext:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; const closureCode = [ @@ -245,7 +245,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "vinext:use-cache-server-functions", + (candidate) => candidate.name === "vinext:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; @@ -263,7 +263,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "vinext:use-cache-server-functions", + (candidate) => candidate.name === "vinext:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; const result = await transform.call( @@ -278,7 +278,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "vinext:use-cache-server-functions", + (candidate) => candidate.name === "vinext:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; const result = await transform.call( @@ -293,7 +293,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "vinext:use-cache-server-functions", + (candidate) => candidate.name === "vinext:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; const result = await transform.call( @@ -308,7 +308,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); const manager = await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "vinext:use-cache-server-functions", + (candidate) => candidate.name === "vinext:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; const result = await transform.call( @@ -337,7 +337,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "vinext:use-cache-server-functions", + (candidate) => candidate.name === "vinext:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; await expect( @@ -355,7 +355,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "vinext:use-cache-server-functions", + (candidate) => candidate.name === "vinext:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; await expect( @@ -396,7 +396,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "vinext:use-cache-server-functions", + (candidate) => candidate.name === "vinext:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; const result = await transform.call( @@ -412,7 +412,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "vinext:use-cache-server-functions", + (candidate) => candidate.name === "vinext:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; await expect( @@ -430,7 +430,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "vinext:use-cache-server-functions", + (candidate) => candidate.name === "vinext:server-function-directives", )!; const context = { environment: { name: "rsc", mode: "build" } }; const source = [ @@ -449,7 +449,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "vinext:use-cache-server-functions", + (candidate) => candidate.name === "vinext:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; await expect( @@ -465,7 +465,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "vinext:use-cache-server-functions", + (candidate) => candidate.name === "vinext:server-function-directives", )!; const result = await unwrapHook(plugin.transform)!.call( { environment: { name: "rsc", mode: "build" } }, @@ -479,7 +479,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); const manager = await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "vinext:use-cache-server-functions", + (candidate) => candidate.name === "vinext:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; const result = await transform.call( @@ -500,7 +500,7 @@ describe("plugin-rsc inline use-cache references", () => { const plugins = await getPlugins(); await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "vinext:use-cache-server-functions", + (candidate) => candidate.name === "vinext:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; const result = await transform.call( From 4bd11b1ad899b9a4e54f1cf53f71cca48a747791 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 19 Jun 2026 00:14:41 +0100 Subject: [PATCH 3/8] refactor(use-cache): own directive plugin types --- packages/vinext/src/index.ts | 6 ++-- .../src/plugins/server-function-directives.ts | 32 ++++++++++++++++++- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/packages/vinext/src/index.ts b/packages/vinext/src/index.ts index f64ca377af..9307d017d4 100644 --- a/packages/vinext/src/index.ts +++ b/packages/vinext/src/index.ts @@ -6,7 +6,6 @@ import type { UserConfig, ViteDevServer, } from "vite"; -import type { ServerFunctionDirectiveContext } from "@vitejs/plugin-rsc/plugin"; import { loadEnv, parseAst, transformWithOxc } from "vite"; import { pagesRouter, @@ -118,7 +117,10 @@ import { createMiddlewareServerOnlyPlugin } from "./plugins/middleware-server-on import { createOptimizeImportsPlugin } from "./plugins/optimize-imports.js"; import { createDynamicPreloadMetadataPlugin } from "./plugins/dynamic-preload-metadata.js"; import { createOgInlineFetchAssetsPlugin, createOgAssetsPlugin } from "./plugins/og-assets.js"; -import { createServerFunctionDirectivePlugins } from "./plugins/server-function-directives.js"; +import { + createServerFunctionDirectivePlugins, + type ServerFunctionDirectiveContext, +} from "./plugins/server-function-directives.js"; import { generateRouteTypes } from "./typegen.js"; import { mergeOptimizeDepsExclude, diff --git a/packages/vinext/src/plugins/server-function-directives.ts b/packages/vinext/src/plugins/server-function-directives.ts index bfc65bdc70..728190408a 100644 --- a/packages/vinext/src/plugins/server-function-directives.ts +++ b/packages/vinext/src/plugins/server-function-directives.ts @@ -3,7 +3,6 @@ import fs from "node:fs"; import { createRequire } from "node:module"; import path from "node:path"; import { pathToFileURL } from "node:url"; -import type { RscPluginManager, ServerFunctionDirective } from "@vitejs/plugin-rsc/plugin"; import type { SourceMap } from "magic-string"; import type { Plugin, Rollup, ViteDevServer } from "vite"; import { parseAstAsync, transformWithOxc } from "vite"; @@ -11,11 +10,42 @@ import { isUnknownRecord } from "../utils/record.js"; import { escapeRegExp } from "../utils/regex.js"; type RscTransforms = typeof import("@vitejs/plugin-rsc/transforms"); +type RscPluginManager = NonNullable< + ReturnType +>["manager"]; type Program = Parameters[0]; type ModuleDirective = NonNullable< Parameters[2]["moduleDirective"] > & { start?: number }; type StringDirective = ModuleDirective & { type: "Literal"; value: string }; +type ExportFilter = NonNullable[2]["filter"]>; +type ExportMeta = Parameters[1]; +type FunctionParameters = NonNullable; + +export type ServerFunctionDirectiveContext = { + value: string; + name: string; + id: string; + directiveMatch: RegExpMatchArray; + location: "inline" | "module"; + hasBoundArgs: boolean; + parameters?: FunctionParameters; + runtime?: string; + meta?: ExportMeta; +}; + +export type ServerFunctionDirective = { + directive: string | RegExp; + test?: (code: string) => boolean; + filter?: (id: string) => boolean; + validate?: (context: { id: string; directive: string; location: "inline" | "module" }) => void; + rejectNonAsyncFunction?: boolean; + rejectNonAsyncModule?: boolean; + runtime?: string; + wrap: (context: ServerFunctionDirectiveContext) => string; + filterExport?: (context: { name: string; id: string; meta: ExportMeta }) => boolean; + clientError?: (context: { id: string; environment: string }) => string; +}; type Options = { projectRoot: string; From 75f7fb08534c4600a4fbdd374ff2d6be69ab5761 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 19 Jun 2026 00:32:35 +0100 Subject: [PATCH 4/8] refactor(use-cache): use plugin-rsc metadata map directly --- packages/vinext/src/index.ts | 5 +- .../src/plugins/server-function-directives.ts | 52 +++---------------- tests/use-cache-transform.test.ts | 16 +----- 3 files changed, 10 insertions(+), 63 deletions(-) diff --git a/packages/vinext/src/index.ts b/packages/vinext/src/index.ts index 9307d017d4..6f5b04353e 100644 --- a/packages/vinext/src/index.ts +++ b/packages/vinext/src/index.ts @@ -946,7 +946,7 @@ export default function vinext(options: VinextOptions = {}): PluginOption[] { client: VIRTUAL_APP_BROWSER_ENTRY, }, }); - const [serverFunctionPlugin, metadataPlugin] = await createServerFunctionDirectivePlugins({ + const [serverFunctionPlugin] = await createServerFunctionDirectivePlugins({ projectRoot: earlyBaseDir, definitions: [ { @@ -1012,11 +1012,10 @@ export default function vinext(options: VinextOptions = {}): PluginOption[] { browserEnvironmentName: "client", }); const useServerIndex = plugins.findIndex((plugin) => plugin.name === "rsc:use-server"); - if (useServerIndex === -1 || !serverFunctionPlugin || !metadataPlugin) { + if (useServerIndex === -1 || !serverFunctionPlugin) { throw new Error("vinext: Failed to locate @vitejs/plugin-rsc use-server plugin."); } plugins.splice(useServerIndex, 0, serverFunctionPlugin); - plugins.splice(useServerIndex + 2, 0, metadataPlugin); return plugins; }) .catch((cause) => { diff --git a/packages/vinext/src/plugins/server-function-directives.ts b/packages/vinext/src/plugins/server-function-directives.ts index 728190408a..0f38eea34c 100644 --- a/packages/vinext/src/plugins/server-function-directives.ts +++ b/packages/vinext/src/plugins/server-function-directives.ts @@ -54,12 +54,7 @@ type Options = { browserEnvironmentName: string; }; -type OwnedServerReference = { - referenceKey: string; - exportNames: string[]; -}; - -const SERVER_FUNCTION_DIRECTIVE_MARKER = "/* __vinext_server_function_directives__ */"; +const SERVER_FUNCTION_DIRECTIVE_MARKER = "/* __vite_rsc_server_function_directives__ */"; function resolvePluginRscModule(projectRoot: string, specifier: string): string { try { @@ -219,7 +214,6 @@ export async function createServerFunctionDirectivePlugins(options: Options): Pr const transforms: RscTransforms = await import(pathToFileURL(transformsPath).href); const { getPluginApi } = rscModule; let manager: RscPluginManager | undefined; - const ownedReferences = new Map(); const transformPlugin: Plugin = { name: "vinext:server-function-directives", @@ -239,10 +233,7 @@ export async function createServerFunctionDirectivePlugins(options: Options): Pr ); const isServer = this.environment.name === options.serverEnvironmentName; if (active.length === 0) { - if (isServer) { - ownedReferences.delete(id); - if (manager) delete manager.serverReferenceMetaMap[id]; - } + if (isServer && manager) delete manager.serverReferenceMetaMap[id]; return; } if (!manager) { @@ -297,14 +288,10 @@ export async function createServerFunctionDirectivePlugins(options: Options): Pr `$$ReactClient.createServerReference(${JSON.stringify(`${normalizedId}#${name}`)},$$ReactClient.callServer,undefined,${this.environment.mode === "dev" ? "$$ReactClient.findSourceMapURL" : "undefined"},${JSON.stringify(name)})`, }); if (!result?.output.hasChanged()) return; - const ownedReference = { - referenceKey: normalizedId, - exportNames: result.exportNames, - }; - ownedReferences.set(id, ownedReference); manager.serverReferenceMetaMap[id] = { importId: id, - ...ownedReference, + referenceKey: normalizedId, + exportNames: result.exportNames, }; result.output.prepend( `${SERVER_FUNCTION_DIRECTIVE_MARKER}\nimport * as $$ReactClient from ${JSON.stringify(this.environment.name === options.browserEnvironmentName ? browserRuntime : ssrRuntime)};\n`, @@ -419,17 +406,12 @@ export async function createServerFunctionDirectivePlugins(options: Options): Pr if (!useServerBoundary) { if (exportNames.size === 0) { - ownedReferences.delete(id); delete manager.serverReferenceMetaMap[id]; } else { - const ownedReference = { - referenceKey: normalizedId, - exportNames: [...exportNames], - }; - ownedReferences.set(id, ownedReference); manager.serverReferenceMetaMap[id] = { importId: id, - ...ownedReference, + referenceKey: normalizedId, + exportNames: [...exportNames], }; } } @@ -447,25 +429,5 @@ export async function createServerFunctionDirectivePlugins(options: Options): Pr }, }; - const metadataPlugin: Plugin = { - name: "vinext:server-function-directive-metadata", - transform: { - handler(_code, id) { - if (!manager) return; - const ownedReference = ownedReferences.get(id); - if (!ownedReference) return; - - const existing = manager.serverReferenceMetaMap[id]; - manager.serverReferenceMetaMap[id] = { - importId: id, - referenceKey: ownedReference.referenceKey, - exportNames: existing - ? [...new Set([...existing.exportNames, ...ownedReference.exportNames])] - : ownedReference.exportNames, - }; - }, - }, - }; - - return [transformPlugin, metadataPlugin]; + return [transformPlugin]; } diff --git a/tests/use-cache-transform.test.ts b/tests/use-cache-transform.test.ts index 585fe49ba5..abd1fa7c7e 100644 --- a/tests/use-cache-transform.test.ts +++ b/tests/use-cache-transform.test.ts @@ -56,22 +56,14 @@ async function configurePluginRsc(plugins: Plugin[]) { } describe("plugin-rsc inline use-cache references", () => { - it("restores user-land reference metadata after rsc:use-server clears it", async () => { + it("preserves user-land reference metadata through rsc:use-server", async () => { const plugins = await getPlugins(); const manager = await configurePluginRsc(plugins); const useCacheIndex = plugins.findIndex( (candidate) => candidate.name === "vinext:server-function-directives", ); const useServerIndex = plugins.findIndex((candidate) => candidate.name === "rsc:use-server"); - const metadataIndex = plugins.findIndex( - (candidate) => candidate.name === "vinext:server-function-directive-metadata", - ); - const manifestIndex = plugins.findIndex( - (candidate) => candidate.name === "rsc:virtual-vite-rsc/server-references", - ); expect(useCacheIndex).toBeLessThan(useServerIndex); - expect(metadataIndex).toBeGreaterThan(useServerIndex); - expect(metadataIndex).toBeLessThan(manifestIndex); const context = { environment: { name: "rsc", mode: "build" } }; const transformed = await unwrapHook(plugins[useCacheIndex]!.transform)!.call( @@ -86,9 +78,6 @@ describe("plugin-rsc inline use-cache references", () => { transformed!.code, moduleId, ); - expect(manager.serverReferenceMetaMap[moduleId]).toBeUndefined(); - - unwrapHook(plugins[metadataIndex]!.transform)!.call(context, transformed!.code, moduleId); expect(manager.serverReferenceMetaMap[moduleId]).toBeDefined(); const ssrContext = { environment: { name: "ssr", mode: "build" } }; @@ -98,9 +87,6 @@ describe("plugin-rsc inline use-cache references", () => { moduleId, ); await unwrapHook(plugins[useServerIndex]!.transform)!.call(ssrContext, proxied!.code, moduleId); - expect(manager.serverReferenceMetaMap[moduleId]).toBeUndefined(); - - unwrapHook(plugins[metadataIndex]!.transform)!.call(ssrContext, proxied!.code, moduleId); expect(manager.serverReferenceMetaMap[moduleId]).toMatchObject({ importId: moduleId, exportNames: ["getData"], From b07d3174924c4e3036bf177417984815fc5c696c Mon Sep 17 00:00:00 2001 From: James Date: Mon, 20 Jul 2026 23:40:23 +0100 Subject: [PATCH 5/8] refactor(use-cache): own server reference metadata lifecycle --- packages/vinext/src/index.ts | 6 +- .../src/plugins/server-function-directives.ts | 115 +++++++++++--- packages/vinext/src/shims/cache-runtime.ts | 11 +- pnpm-lock.yaml | 76 +++++----- pnpm-workspace.yaml | 2 +- tests/use-cache-transform.test.ts | 140 ++++++++++++++++-- 6 files changed, 270 insertions(+), 80 deletions(-) diff --git a/packages/vinext/src/index.ts b/packages/vinext/src/index.ts index 6f5b04353e..2a4d3eba18 100644 --- a/packages/vinext/src/index.ts +++ b/packages/vinext/src/index.ts @@ -946,7 +946,7 @@ export default function vinext(options: VinextOptions = {}): PluginOption[] { client: VIRTUAL_APP_BROWSER_ENTRY, }, }); - const [serverFunctionPlugin] = await createServerFunctionDirectivePlugins({ + const serverFunctionPlugins = await createServerFunctionDirectivePlugins({ projectRoot: earlyBaseDir, definitions: [ { @@ -1011,11 +1011,13 @@ export default function vinext(options: VinextOptions = {}): PluginOption[] { serverEnvironmentName: "rsc", browserEnvironmentName: "client", }); + const [serverFunctionPlugin, serverFunctionMetadataPlugin] = serverFunctionPlugins; const useServerIndex = plugins.findIndex((plugin) => plugin.name === "rsc:use-server"); - if (useServerIndex === -1 || !serverFunctionPlugin) { + if (useServerIndex === -1 || !serverFunctionPlugin || !serverFunctionMetadataPlugin) { throw new Error("vinext: Failed to locate @vitejs/plugin-rsc use-server plugin."); } plugins.splice(useServerIndex, 0, serverFunctionPlugin); + plugins.splice(useServerIndex + 2, 0, serverFunctionMetadataPlugin); return plugins; }) .catch((cause) => { diff --git a/packages/vinext/src/plugins/server-function-directives.ts b/packages/vinext/src/plugins/server-function-directives.ts index 0f38eea34c..664499a17f 100644 --- a/packages/vinext/src/plugins/server-function-directives.ts +++ b/packages/vinext/src/plugins/server-function-directives.ts @@ -54,7 +54,61 @@ type Options = { browserEnvironmentName: string; }; -const SERVER_FUNCTION_DIRECTIVE_MARKER = "/* __vite_rsc_server_function_directives__ */"; +const SERVER_FUNCTION_DIRECTIVE_MARKER = "/* __vinext_server_function_directives__ */"; + +type ServerReferenceMetadata = RscPluginManager["serverReferenceMetaMap"][string]; + +function mergeServerReferenceMetadata( + manager: RscPluginManager, + id: string, + referenceKey: string, + exportNames: Iterable, +): void { + const existing = manager.serverReferenceMetaMap[id]; + manager.serverReferenceMetaMap[id] = { + importId: existing?.importId ?? id, + referenceKey: existing?.referenceKey ?? referenceKey, + exportNames: [...new Set([...(existing?.exportNames ?? []), ...exportNames])], + }; +} + +function removeOwnedServerReferenceMetadata( + manager: RscPluginManager, + ownedReferences: Map, + id: string, +): void { + const owned = ownedReferences.get(id); + if (!owned) return; + ownedReferences.delete(id); + + const existing = manager.serverReferenceMetaMap[id]; + if (!existing) return; + + const ownedExportNames = new Set(owned.exportNames); + const exportNames = existing.exportNames.filter((name) => !ownedExportNames.has(name)); + if (exportNames.length === 0) { + delete manager.serverReferenceMetaMap[id]; + } else { + manager.serverReferenceMetaMap[id] = { ...existing, exportNames }; + } +} + +function setOwnedServerReferenceMetadata( + manager: RscPluginManager, + ownedReferences: Map, + id: string, + referenceKey: string, + exportNames: Iterable, +): void { + removeOwnedServerReferenceMetadata(manager, ownedReferences, id); + const metadata = { + importId: id, + referenceKey, + exportNames: [...new Set(exportNames)], + }; + ownedReferences.set(id, metadata); + mergeServerReferenceMetadata(manager, id, referenceKey, metadata.exportNames); +} function resolvePluginRscModule(projectRoot: string, specifier: string): string { try { @@ -197,7 +251,7 @@ export async function createServerFunctionDirectivePlugins(options: Options): Pr "@vitejs/plugin-rsc/transforms", ); const rscRuntime = pathToFileURL( - resolvePluginRscModule(options.projectRoot, "@vitejs/plugin-rsc/react/rsc"), + resolvePluginRscModule(options.projectRoot, "@vitejs/plugin-rsc/react/rsc/server"), ).href; const browserRuntime = pathToFileURL( resolvePluginRscModule(options.projectRoot, "@vitejs/plugin-rsc/react/browser"), @@ -214,6 +268,8 @@ export async function createServerFunctionDirectivePlugins(options: Options): Pr const transforms: RscTransforms = await import(pathToFileURL(transformsPath).href); const { getPluginApi } = rscModule; let manager: RscPluginManager | undefined; + const ownedReferences = new Map(); + const serverReferenceOwnership = new Map(); const transformPlugin: Plugin = { name: "vinext:server-function-directives", @@ -232,13 +288,16 @@ export async function createServerFunctionDirectivePlugins(options: Options): Pr (!definition.filter || definition.filter(id)), ); const isServer = this.environment.name === options.serverEnvironmentName; - if (active.length === 0) { - if (isServer && manager) delete manager.serverReferenceMetaMap[id]; - return; - } if (!manager) { throw new Error("vinext: failed to access @vitejs/plugin-rsc through getPluginApi()."); } + if (active.length === 0) { + if (isServer) { + serverReferenceOwnership.set(id, false); + removeOwnedServerReferenceMetadata(manager, ownedReferences, id); + } + return; + } let ast = await parseProgram(code); const useServerBoundary = transforms.hasDirective(ast.body, "use server"); @@ -288,11 +347,15 @@ export async function createServerFunctionDirectivePlugins(options: Options): Pr `$$ReactClient.createServerReference(${JSON.stringify(`${normalizedId}#${name}`)},$$ReactClient.callServer,undefined,${this.environment.mode === "dev" ? "$$ReactClient.findSourceMapURL" : "undefined"},${JSON.stringify(name)})`, }); if (!result?.output.hasChanged()) return; - manager.serverReferenceMetaMap[id] = { - importId: id, - referenceKey: normalizedId, - exportNames: result.exportNames, - }; + if (serverReferenceOwnership.get(id) !== false) { + setOwnedServerReferenceMetadata( + manager, + ownedReferences, + id, + normalizedId, + result.exportNames, + ); + } result.output.prepend( `${SERVER_FUNCTION_DIRECTIVE_MARKER}\nimport * as $$ReactClient from ${JSON.stringify(this.environment.name === options.browserEnvironmentName ? browserRuntime : ssrRuntime)};\n`, ); @@ -404,16 +467,12 @@ export async function createServerFunctionDirectivePlugins(options: Options): Pr ast = await parseProgram(code); } - if (!useServerBoundary) { - if (exportNames.size === 0) { - delete manager.serverReferenceMetaMap[id]; - } else { - manager.serverReferenceMetaMap[id] = { - importId: id, - referenceKey: normalizedId, - exportNames: [...exportNames], - }; - } + if (!useServerBoundary && exportNames.size > 0) { + serverReferenceOwnership.set(id, true); + setOwnedServerReferenceMetadata(manager, ownedReferences, id, normalizedId, exportNames); + } else if (isServer) { + serverReferenceOwnership.set(id, false); + removeOwnedServerReferenceMetadata(manager, ownedReferences, id); } const imports = [ @@ -429,5 +488,17 @@ export async function createServerFunctionDirectivePlugins(options: Options): Pr }, }; - return [transformPlugin]; + const metadataPlugin: Plugin = { + name: "vinext:server-function-directive-metadata", + transform: { + handler(_code, id) { + if (!manager) return; + const owned = ownedReferences.get(id); + if (!owned) return; + mergeServerReferenceMetadata(manager, id, owned.referenceKey, owned.exportNames); + }, + }, + }; + + return [transformPlugin, metadataPlugin]; } diff --git a/packages/vinext/src/shims/cache-runtime.ts b/packages/vinext/src/shims/cache-runtime.ts index 5273118685..99575ab39b 100644 --- a/packages/vinext/src/shims/cache-runtime.ts +++ b/packages/vinext/src/shims/cache-runtime.ts @@ -170,7 +170,8 @@ type RscModule = { renderToReadableStream: (data: unknown, options?: object) => ReadableStream; createFromReadableStream: ( stream: ReadableStream, - options?: { serverReferences?: "resolve" | "preserve" }, + options?: object, + extraOptions?: { preserveServerReferences?: boolean }, ) => Promise; encodeReply: (v: unknown[], options?: unknown) => Promise; createTemporaryReferenceSet: () => unknown; @@ -555,9 +556,11 @@ export function registerCachedFunction( // RSC-serialized entry: base64 → bytes → stream → deserialize const bytes = base64ToUint8(existing.value.data.body); const stream = uint8ToStream(bytes); - const result = await rsc.createFromReadableStream(stream, { - serverReferences: "preserve", - }); + const result = await rsc.createFromReadableStream( + stream, + {}, + { preserveServerReferences: true }, + ); recordRequestScopedCacheControl(existing.cacheControl); return result; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f18a38b7f0..84e22e533e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -91,8 +91,8 @@ catalogs: specifier: https://pkg.pr.new/@vitejs/plugin-react@82d2c578 version: 6.0.2 '@vitejs/plugin-rsc': - specifier: https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578 - version: 0.5.27 + specifier: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75 + version: 0.5.28 '@vitest/coverage-istanbul': specifier: 4.1.6 version: 4.1.6 @@ -277,7 +277,7 @@ importers: version: 7.0.0-dev.20260217.1 '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.6(@voidzero-dev/vite-plus-test@0.1.24) @@ -365,7 +365,7 @@ importers: version: https://pkg.pr.new/@vitejs/plugin-react@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0)) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) drizzle-kit: specifier: 'catalog:' version: 0.31.10 @@ -408,7 +408,7 @@ importers: devDependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.24 version: '@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0)' @@ -426,7 +426,7 @@ importers: version: https://pkg.pr.new/@vitejs/plugin-react@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0)) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) react: specifier: 'catalog:' version: 19.2.7 @@ -558,7 +558,7 @@ importers: version: 19.2.3(@types/react@19.2.16) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) postcss: specifier: 'catalog:' version: 8.5.10 @@ -597,7 +597,7 @@ importers: version: https://pkg.pr.new/@vitejs/plugin-react@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0)) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) react: specifier: 'catalog:' version: 19.2.7 @@ -680,7 +680,7 @@ importers: version: https://pkg.pr.new/@vitejs/plugin-react@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0)) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) postcss: specifier: 'catalog:' version: 8.5.10 @@ -713,7 +713,7 @@ importers: version: https://pkg.pr.new/@vitejs/plugin-react@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0)) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) ms: specifier: 'catalog:' version: 3.0.0-canary.1 @@ -784,7 +784,7 @@ importers: version: 19.2.3(@types/react@19.2.16) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) typescript: specifier: 'catalog:' version: 5.9.3 @@ -876,7 +876,7 @@ importers: version: https://pkg.pr.new/@vitejs/plugin-react@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0)) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) react: specifier: 'catalog:' version: 19.2.7 @@ -922,7 +922,7 @@ importers: version: https://pkg.pr.new/@vitejs/plugin-react@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0)) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) react: specifier: 'catalog:' version: 19.2.7 @@ -1008,7 +1008,7 @@ importers: version: https://pkg.pr.new/@vitejs/plugin-react@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0)) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) react-server-dom-webpack: specifier: 'catalog:' version: 19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -1020,7 +1020,7 @@ importers: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) fake-context-lib: specifier: file:./__test_packages__/fake-context-lib version: file:tests/fixtures/app-basic/__test_packages__/fake-context-lib @@ -1054,7 +1054,7 @@ importers: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) react: specifier: 'catalog:' version: 19.2.7 @@ -1079,7 +1079,7 @@ importers: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) react: specifier: 'catalog:' version: 19.2.7 @@ -1104,7 +1104,7 @@ importers: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) react: specifier: 'catalog:' version: 19.2.7 @@ -1135,7 +1135,7 @@ importers: version: https://pkg.pr.new/@vitejs/plugin-react@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0)) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) react: specifier: 'catalog:' version: 19.2.7 @@ -1170,7 +1170,7 @@ importers: version: 1.9.1 '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) better-auth: specifier: 'catalog:' version: 1.5.6(@cloudflare/workers-types@4.20260313.1)(@opentelemetry/api@1.9.1)(@types/node@25.9.2)(@vitest/coverage-istanbul@4.1.6(@voidzero-dev/vite-plus-test@0.1.24))(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(better-sqlite3@12.6.2)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260313.1)(@opentelemetry/api@1.9.1)(better-sqlite3@12.6.2)(kysely@0.28.15))(esbuild@0.27.3)(jiti@2.7.0)(next@16.2.7(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.100.0))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0) @@ -1201,7 +1201,7 @@ importers: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) next-intl: specifier: 'catalog:' version: 4.11.1(next@16.2.7(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.100.0))(react@19.2.7)(typescript@5.9.3) @@ -1229,7 +1229,7 @@ importers: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) next-themes: specifier: 'catalog:' version: 0.4.6(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -1257,7 +1257,7 @@ importers: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) next-view-transitions: specifier: 'catalog:' version: 0.3.5(next@16.2.7(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.100.0))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -1285,7 +1285,7 @@ importers: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) nuqs: specifier: 'catalog:' version: 2.8.8(next@16.2.7(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.100.0))(react@19.2.7) @@ -1322,7 +1322,7 @@ importers: version: 1.2.4(@types/react@19.2.16)(react@19.2.7) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) class-variance-authority: specifier: 'catalog:' version: 0.7.1 @@ -1378,7 +1378,7 @@ importers: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) react: specifier: 'catalog:' version: 19.2.7 @@ -1403,7 +1403,7 @@ importers: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) react: specifier: 'catalog:' version: 19.2.7 @@ -1428,7 +1428,7 @@ importers: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) react: specifier: 'catalog:' version: 19.2.7 @@ -1472,7 +1472,7 @@ importers: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) react: specifier: 'catalog:' version: 19.2.7 @@ -1516,7 +1516,7 @@ importers: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) react: specifier: 'catalog:' version: 19.2.7 @@ -4394,9 +4394,9 @@ packages: babel-plugin-react-compiler: optional: true - '@vitejs/plugin-rsc@https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578': - resolution: {integrity: sha512-vos4mwhdaMBHkA1HND+rhs0GWlKTKnP0XrmADiBWRb3LmHdauz+KBQaNKuKuCEHcKkkpI56WZ8MCi8otkJtoIg==, tarball: https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578} - version: 0.5.27 + '@vitejs/plugin-rsc@https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75': + resolution: {integrity: sha512-Q4cHFFyarDlR6ZcMqlIg5XbGdxYfxgyee77hTPgq4RmT2fbu9sB503cgZ1A3DSzsykufBQB0Ix69fquE2CSRMA==, tarball: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75} + version: 0.5.28 peerDependencies: react: '*' react-dom: '*' @@ -5085,8 +5085,8 @@ packages: es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - es-module-lexer@2.1.0: - resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + es-module-lexer@2.3.1: + resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} esast-util-from-estree@2.0.0: resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} @@ -9414,10 +9414,10 @@ snapshots: '@rolldown/pluginutils': 1.0.1 vite: '@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0)' - '@vitejs/plugin-rsc@https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)': + '@vitejs/plugin-rsc@https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)': dependencies: '@rolldown/pluginutils': 1.0.1 - es-module-lexer: 2.1.0 + es-module-lexer: 2.3.1 estree-walker: 3.0.3 magic-string: 0.30.21 react: 19.2.7 @@ -9893,7 +9893,7 @@ snapshots: es-module-lexer@1.7.0: {} - es-module-lexer@2.1.0: {} + es-module-lexer@2.3.1: {} esast-util-from-estree@2.0.0: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 3458c4433e..90ee02a165 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -43,7 +43,7 @@ catalog: "@next/mdx": 16.2.7 recma-codehike: 0.0.1 remark-codehike: 0.0.1 - "@vitejs/plugin-rsc": https://pkg.pr.new/@vitejs/plugin-rsc@82d2c578 + "@vitejs/plugin-rsc": https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75 "@vitest/coverage-istanbul": 4.1.6 better-auth: ^1.5.6 better-sqlite3: ^12.0.0 diff --git a/tests/use-cache-transform.test.ts b/tests/use-cache-transform.test.ts index abd1fa7c7e..9a4b72d81b 100644 --- a/tests/use-cache-transform.test.ts +++ b/tests/use-cache-transform.test.ts @@ -1,8 +1,8 @@ /** * Tests the vinext user-land server function directive integration used for function-level * "use cache" directives. Vinext owns the directive plugin while plugin-rsc - * owns directive discovery, closure hoisting, encryption, reference ids, and - * server-reference manifest metadata. + * provides directive transforms and the shared reference metadata map. Vinext + * owns directive orchestration and merges its references after rsc:use-server. */ import path from "node:path"; import { createHash } from "node:crypto"; @@ -56,14 +56,18 @@ async function configurePluginRsc(plugins: Plugin[]) { } describe("plugin-rsc inline use-cache references", () => { - it("preserves user-land reference metadata through rsc:use-server", async () => { + it("restores user-land reference metadata after rsc:use-server", async () => { const plugins = await getPlugins(); const manager = await configurePluginRsc(plugins); const useCacheIndex = plugins.findIndex( (candidate) => candidate.name === "vinext:server-function-directives", ); const useServerIndex = plugins.findIndex((candidate) => candidate.name === "rsc:use-server"); + const metadataIndex = plugins.findIndex( + (candidate) => candidate.name === "vinext:server-function-directive-metadata", + ); expect(useCacheIndex).toBeLessThan(useServerIndex); + expect(metadataIndex).toBeGreaterThan(useServerIndex); const context = { environment: { name: "rsc", mode: "build" } }; const transformed = await unwrapHook(plugins[useCacheIndex]!.transform)!.call( @@ -78,6 +82,9 @@ describe("plugin-rsc inline use-cache references", () => { transformed!.code, moduleId, ); + expect(manager.serverReferenceMetaMap[moduleId]).toBeUndefined(); + + unwrapHook(plugins[metadataIndex]!.transform)!.call(context, transformed!.code, moduleId); expect(manager.serverReferenceMetaMap[moduleId]).toBeDefined(); const ssrContext = { environment: { name: "ssr", mode: "build" } }; @@ -87,12 +94,119 @@ describe("plugin-rsc inline use-cache references", () => { moduleId, ); await unwrapHook(plugins[useServerIndex]!.transform)!.call(ssrContext, proxied!.code, moduleId); + unwrapHook(plugins[metadataIndex]!.transform)!.call(ssrContext, proxied!.code, moduleId); expect(manager.serverReferenceMetaMap[moduleId]).toMatchObject({ importId: moduleId, - exportNames: ["getData"], + exportNames: expect.arrayContaining(["getData"]), }); }); + it("merges and deduplicates use-server and user-land reference metadata", async () => { + const plugins = await getPlugins(); + const manager = await configurePluginRsc(plugins); + const useCachePlugin = plugins.find( + (candidate) => candidate.name === "vinext:server-function-directives", + )!; + const useServerPlugin = plugins.find((candidate) => candidate.name === "rsc:use-server")!; + const metadataPlugin = plugins.find( + (candidate) => candidate.name === "vinext:server-function-directive-metadata", + )!; + const context = { environment: { name: "rsc", mode: "build" } }; + const source = [ + `export async function action() {`, + ` "use server";`, + `}`, + `export async function getData() {`, + ` "use cache";`, + ` return 1;`, + `}`, + ].join("\n"); + + const useCacheResult = await unwrapHook(useCachePlugin.transform)!.call( + context, + source, + moduleId, + ); + const useServerResult = await unwrapHook(useServerPlugin.transform)!.call( + context, + useCacheResult!.code, + moduleId, + ); + const upstreamMetadata = manager.serverReferenceMetaMap[moduleId]; + const upstreamExport = upstreamMetadata.exportNames[0]!; + upstreamMetadata.exportNames.push(upstreamExport); + + unwrapHook(metadataPlugin.transform)!.call(context, useServerResult!.code, moduleId); + + const merged = manager.serverReferenceMetaMap[moduleId]; + expect(merged.importId).toBe(upstreamMetadata.importId); + expect(merged.referenceKey).toBe(upstreamMetadata.referenceKey); + expect(merged.exportNames).toContain(upstreamExport); + expect(merged.exportNames).toContainEqual(expect.stringMatching(/getData/)); + expect(merged.exportNames).toHaveLength(new Set(merged.exportNames).size); + }); + + it("restores RSC-owned metadata after a non-owning proxy pass", async () => { + const plugins = await getPlugins(); + const manager = await configurePluginRsc(plugins); + const useCachePlugin = plugins.find( + (candidate) => candidate.name === "vinext:server-function-directives", + )!; + const useServerPlugin = plugins.find((candidate) => candidate.name === "rsc:use-server")!; + const metadataPlugin = plugins.find( + (candidate) => candidate.name === "vinext:server-function-directive-metadata", + )!; + const rscContext = { environment: { name: "rsc", mode: "build" } }; + const transformed = await unwrapHook(useCachePlugin.transform)!.call( + rscContext, + inlineCacheCode, + moduleId, + ); + const ownedExportNames = manager.serverReferenceMetaMap[moduleId].exportNames; + + const ssrContext = { environment: { name: "ssr", mode: "build" } }; + await unwrapHook(useCachePlugin.transform)!.call(ssrContext, transformed!.code, moduleId); + await unwrapHook(useServerPlugin.transform)!.call(ssrContext, transformed!.code, moduleId); + expect(manager.serverReferenceMetaMap[moduleId]).toBeUndefined(); + + unwrapHook(metadataPlugin.transform)!.call(ssrContext, transformed!.code, moduleId); + expect(manager.serverReferenceMetaMap[moduleId].exportNames).toEqual(ownedExportNames); + }); + + it("does not let a stale proxy transform resurrect RSC-removed metadata", async () => { + const plugins = await getPlugins(); + const manager = await configurePluginRsc(plugins); + const useCachePlugin = plugins.find( + (candidate) => candidate.name === "vinext:server-function-directives", + )!; + const metadataPlugin = plugins.find( + (candidate) => candidate.name === "vinext:server-function-directive-metadata", + )!; + const rscContext = { environment: { name: "rsc", mode: "build" } }; + const ssrContext = { environment: { name: "ssr", mode: "build" } }; + + await unwrapHook(useCachePlugin.transform)!.call(rscContext, fileCacheCode, moduleId); + expect(manager.serverReferenceMetaMap[moduleId]).toBeDefined(); + + await unwrapHook(useCachePlugin.transform)!.call( + rscContext, + `export async function getData() { return 1; }`, + moduleId, + ); + expect(manager.serverReferenceMetaMap[moduleId]).toBeUndefined(); + + const staleProxy = await unwrapHook(useCachePlugin.transform)!.call( + ssrContext, + fileCacheCode, + moduleId, + ); + unwrapHook(metadataPlugin.transform)!.call(ssrContext, staleProxy!.code, moduleId); + expect(manager.serverReferenceMetaMap[moduleId]).toBeUndefined(); + + await unwrapHook(useCachePlugin.transform)!.call(rscContext, fileCacheCode, moduleId); + expect(manager.serverReferenceMetaMap[moduleId]).toBeDefined(); + }); + it("matches Vite's dev reference key for files outside the project root", async () => { const plugins = await getPlugins(); const manager = await configurePluginRsc(plugins); @@ -172,24 +286,24 @@ describe("plugin-rsc inline use-cache references", () => { expect(getDataName(withUnrelated!.code)).toBe(getDataName(original!.code)); }); - it("removes owned reference metadata when the directive is removed", async () => { + it("leaves directive removal cleanup to the preceding use-server pass", async () => { const plugins = await getPlugins(); const manager = await configurePluginRsc(plugins); const plugin = plugins.find( (candidate) => candidate.name === "vinext:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; - await transform.call( - { environment: { name: "rsc", mode: "build" } }, - inlineCacheCode, - moduleId, - ); + const useServerPlugin = plugins.find((candidate) => candidate.name === "rsc:use-server")!; + const context = { environment: { name: "rsc", mode: "build" } }; + await transform.call(context, inlineCacheCode, moduleId); expect(manager.serverReferenceMetaMap[moduleId]).toBeDefined(); - await transform.call( - { environment: { name: "rsc", mode: "build" } }, - `export async function getData() { return 1; }`, + const source = `export async function getData() { return 1; }`; + const useServerResult = await unwrapHook(useServerPlugin.transform)!.call( + context, + source, moduleId, ); + await transform.call(context, useServerResult?.code ?? source, moduleId); expect(manager.serverReferenceMetaMap[moduleId]).toBeUndefined(); }); From 622f0b915156fbfcb03ca4f1596cc0d0f6725ebe Mon Sep 17 00:00:00 2001 From: James Date: Mon, 20 Jul 2026 23:43:35 +0100 Subject: [PATCH 6/8] chore(use-cache): keep directive type internal --- packages/vinext/src/plugins/server-function-directives.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vinext/src/plugins/server-function-directives.ts b/packages/vinext/src/plugins/server-function-directives.ts index 664499a17f..f0560696d0 100644 --- a/packages/vinext/src/plugins/server-function-directives.ts +++ b/packages/vinext/src/plugins/server-function-directives.ts @@ -34,7 +34,7 @@ export type ServerFunctionDirectiveContext = { meta?: ExportMeta; }; -export type ServerFunctionDirective = { +type ServerFunctionDirective = { directive: string | RegExp; test?: (code: string) => boolean; filter?: (id: string) => boolean; From 250707cbc35c009d5bd1243d67044a6fecbd66b5 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 24 Jul 2026 12:04:45 +0100 Subject: [PATCH 7/8] refactor(use-cache): adopt server reference claims --- packages/vinext/src/index.ts | 8 +- .../src/plugins/server-function-directives.ts | 279 +++++++----------- packages/vinext/src/shims/cache-runtime.ts | 15 +- pnpm-lock.yaml | 68 ++--- pnpm-workspace.yaml | 2 +- tests/use-cache-transform.test.ts | 140 ++++----- 6 files changed, 216 insertions(+), 296 deletions(-) diff --git a/packages/vinext/src/index.ts b/packages/vinext/src/index.ts index 2a4d3eba18..298e25bf26 100644 --- a/packages/vinext/src/index.ts +++ b/packages/vinext/src/index.ts @@ -118,7 +118,7 @@ import { createOptimizeImportsPlugin } from "./plugins/optimize-imports.js"; import { createDynamicPreloadMetadataPlugin } from "./plugins/dynamic-preload-metadata.js"; import { createOgInlineFetchAssetsPlugin, createOgAssetsPlugin } from "./plugins/og-assets.js"; import { - createServerFunctionDirectivePlugins, + createServerFunctionDirectivePlugin, type ServerFunctionDirectiveContext, } from "./plugins/server-function-directives.js"; import { generateRouteTypes } from "./typegen.js"; @@ -946,7 +946,7 @@ export default function vinext(options: VinextOptions = {}): PluginOption[] { client: VIRTUAL_APP_BROWSER_ENTRY, }, }); - const serverFunctionPlugins = await createServerFunctionDirectivePlugins({ + const serverFunctionPlugin = await createServerFunctionDirectivePlugin({ projectRoot: earlyBaseDir, definitions: [ { @@ -1011,13 +1011,11 @@ export default function vinext(options: VinextOptions = {}): PluginOption[] { serverEnvironmentName: "rsc", browserEnvironmentName: "client", }); - const [serverFunctionPlugin, serverFunctionMetadataPlugin] = serverFunctionPlugins; const useServerIndex = plugins.findIndex((plugin) => plugin.name === "rsc:use-server"); - if (useServerIndex === -1 || !serverFunctionPlugin || !serverFunctionMetadataPlugin) { + if (useServerIndex === -1) { throw new Error("vinext: Failed to locate @vitejs/plugin-rsc use-server plugin."); } plugins.splice(useServerIndex, 0, serverFunctionPlugin); - plugins.splice(useServerIndex + 2, 0, serverFunctionMetadataPlugin); return plugins; }) .catch((cause) => { diff --git a/packages/vinext/src/plugins/server-function-directives.ts b/packages/vinext/src/plugins/server-function-directives.ts index f0560696d0..2dd6982b21 100644 --- a/packages/vinext/src/plugins/server-function-directives.ts +++ b/packages/vinext/src/plugins/server-function-directives.ts @@ -1,23 +1,22 @@ -import { createHash } from "node:crypto"; import fs from "node:fs"; import { createRequire } from "node:module"; import path from "node:path"; import { pathToFileURL } from "node:url"; +import type { RscPluginManager } from "@vitejs/plugin-rsc"; import type { SourceMap } from "magic-string"; -import type { Plugin, Rollup, ViteDevServer } from "vite"; +import type { Plugin, Rollup } from "vite"; import { parseAstAsync, transformWithOxc } from "vite"; import { isUnknownRecord } from "../utils/record.js"; import { escapeRegExp } from "../utils/regex.js"; type RscTransforms = typeof import("@vitejs/plugin-rsc/transforms"); -type RscPluginManager = NonNullable< - ReturnType ->["manager"]; type Program = Parameters[0]; -type ModuleDirective = NonNullable< - Parameters[2]["moduleDirective"] -> & { start?: number }; -type StringDirective = ModuleDirective & { type: "Literal"; value: string }; +type ProgramExpressionStatement = Extract; +type StringDirective = Extract & { + value: string; + start: number; + end: number; +}; type ExportFilter = NonNullable[2]["filter"]>; type ExportMeta = Parameters[1]; type FunctionParameters = NonNullable; @@ -55,60 +54,8 @@ type Options = { }; const SERVER_FUNCTION_DIRECTIVE_MARKER = "/* __vinext_server_function_directives__ */"; - -type ServerReferenceMetadata = RscPluginManager["serverReferenceMetaMap"][string]; - -function mergeServerReferenceMetadata( - manager: RscPluginManager, - id: string, - referenceKey: string, - exportNames: Iterable, -): void { - const existing = manager.serverReferenceMetaMap[id]; - manager.serverReferenceMetaMap[id] = { - importId: existing?.importId ?? id, - referenceKey: existing?.referenceKey ?? referenceKey, - exportNames: [...new Set([...(existing?.exportNames ?? []), ...exportNames])], - }; -} - -function removeOwnedServerReferenceMetadata( - manager: RscPluginManager, - ownedReferences: Map, - id: string, -): void { - const owned = ownedReferences.get(id); - if (!owned) return; - ownedReferences.delete(id); - - const existing = manager.serverReferenceMetaMap[id]; - if (!existing) return; - - const ownedExportNames = new Set(owned.exportNames); - const exportNames = existing.exportNames.filter((name) => !ownedExportNames.has(name)); - if (exportNames.length === 0) { - delete manager.serverReferenceMetaMap[id]; - } else { - manager.serverReferenceMetaMap[id] = { ...existing, exportNames }; - } -} - -function setOwnedServerReferenceMetadata( - manager: RscPluginManager, - ownedReferences: Map, - id: string, - referenceKey: string, - exportNames: Iterable, -): void { - removeOwnedServerReferenceMetadata(manager, ownedReferences, id); - const metadata = { - importId: id, - referenceKey, - exportNames: [...new Set(exportNames)], - }; - ownedReferences.set(id, metadata); - mergeServerReferenceMetadata(manager, id, referenceKey, metadata.exportNames); -} +const SERVER_FUNCTION_DIRECTIVE_PLUGIN_NAME = "vinext:server-function-directives"; +const USE_SERVER_PLUGIN_NAME = "rsc:use-server"; function resolvePluginRscModule(projectRoot: string, specifier: string): string { try { @@ -136,7 +83,13 @@ function matchDirective(value: string, directive: string | RegExp): RegExpMatchA } function isStringLiteral(value: unknown): value is StringDirective { - return isUnknownRecord(value) && value.type === "Literal" && typeof value.value === "string"; + return ( + isUnknownRecord(value) && + value.type === "Literal" && + typeof value.value === "string" && + typeof value.start === "number" && + typeof value.end === "number" + ); } function isExpressionStatement( @@ -206,24 +159,6 @@ function findInlineDirective( return result; } -function hashString(value: string): string { - return createHash("sha256").update(value).digest("hex").slice(0, 12); -} - -function normalizeViteImportAnalysisUrl( - environment: ViteDevServer["environments"][string], - id: string, -): string { - const root = environment.config.root; - const rootPrefix = root.endsWith("/") ? root : `${root}/`; - if (id.startsWith(rootPrefix)) return id.slice(root.length); - - const cleanId = id.split("?", 1)[0] ?? id; - if (path.isAbsolute(cleanId) && fs.existsSync(cleanId)) return path.posix.join("/@fs/", id); - if (id.startsWith(".") || id.startsWith("/")) return id; - return `/@id/${id.replace("\0", "__x00__")}`; -} - async function expandExportAll( transforms: RscTransforms, context: Rollup.TransformPluginContext, @@ -244,7 +179,7 @@ async function expandExportAll( }); } -export async function createServerFunctionDirectivePlugins(options: Options): Promise { +export async function createServerFunctionDirectivePlugin(options: Options): Promise { const rscModulePath = resolvePluginRscModule(options.projectRoot, "@vitejs/plugin-rsc"); const transformsPath = resolvePluginRscModule( options.projectRoot, @@ -268,11 +203,9 @@ export async function createServerFunctionDirectivePlugins(options: Options): Pr const transforms: RscTransforms = await import(pathToFileURL(transformsPath).href); const { getPluginApi } = rscModule; let manager: RscPluginManager | undefined; - const ownedReferences = new Map(); - const serverReferenceOwnership = new Map(); const transformPlugin: Plugin = { - name: "vinext:server-function-directives", + name: SERVER_FUNCTION_DIRECTIVE_PLUGIN_NAME, configResolved(config) { manager = getPluginApi(config)?.manager; @@ -292,24 +225,17 @@ export async function createServerFunctionDirectivePlugins(options: Options): Pr throw new Error("vinext: failed to access @vitejs/plugin-rsc through getPluginApi()."); } if (active.length === 0) { - if (isServer) { - serverReferenceOwnership.set(id, false); - removeOwnedServerReferenceMetadata(manager, ownedReferences, id); - } + manager.serverReferences.deleteClaim(SERVER_FUNCTION_DIRECTIVE_PLUGIN_NAME, id); return; } let ast = await parseProgram(code); const useServerBoundary = transforms.hasDirective(ast.body, "use server"); - if (!isServer && useServerBoundary) return; - - const normalizedId = - manager.config.command === "build" - ? hashString(manager.toRelativeId(id)) - : normalizeViteImportAnalysisUrl( - manager.server.environments[options.serverEnvironmentName], - id, - ); + if (!isServer && useServerBoundary) { + manager.serverReferences.deleteClaim(SERVER_FUNCTION_DIRECTIVE_PLUGIN_NAME, id); + return; + } + const reference = manager.serverReferences.resolve(id, options.serverEnvironmentName); if (!isServer) { for (const definition of active) { @@ -327,7 +253,10 @@ export async function createServerFunctionDirectivePlugins(options: Options): Pr const moduleDirective = findModuleDirective(ast, definition.directive); if (moduleDirective) matches.push([definition, moduleDirective]); } - if (matches.length === 0) return; + if (matches.length === 0) { + manager.serverReferences.deleteClaim(SERVER_FUNCTION_DIRECTIVE_PLUGIN_NAME, id); + return; + } if (matches.length > 1) { throw Object.assign( new Error("Multiple server function directives match this module."), @@ -344,18 +273,17 @@ export async function createServerFunctionDirectivePlugins(options: Options): Pr code, directive: moduleDirective.value, runtime: (name) => - `$$ReactClient.createServerReference(${JSON.stringify(`${normalizedId}#${name}`)},$$ReactClient.callServer,undefined,${this.environment.mode === "dev" ? "$$ReactClient.findSourceMapURL" : "undefined"},${JSON.stringify(name)})`, + `$$ReactClient.createServerReference(${JSON.stringify(`${reference.referenceKey}#${name}`)},$$ReactClient.callServer,undefined,${this.environment.mode === "dev" ? "$$ReactClient.findSourceMapURL" : "undefined"},${JSON.stringify(name)})`, }); - if (!result?.output.hasChanged()) return; - if (serverReferenceOwnership.get(id) !== false) { - setOwnedServerReferenceMetadata( - manager, - ownedReferences, - id, - normalizedId, - result.exportNames, - ); + if (!result?.output.hasChanged()) { + manager.serverReferences.deleteClaim(SERVER_FUNCTION_DIRECTIVE_PLUGIN_NAME, id); + return; } + manager.serverReferences.deleteClaim(USE_SERVER_PLUGIN_NAME, id); + manager.serverReferences.replaceClaim(SERVER_FUNCTION_DIRECTIVE_PLUGIN_NAME, id, { + ...reference, + exportNames: result.exportNames, + }); result.output.prepend( `${SERVER_FUNCTION_DIRECTIVE_MARKER}\nimport * as $$ReactClient from ${JSON.stringify(this.environment.name === options.browserEnvironmentName ? browserRuntime : ssrRuntime)};\n`, ); @@ -370,9 +298,9 @@ export async function createServerFunctionDirectivePlugins(options: Options): Pr let needsEncryptionRuntime = false; let outputMap: SourceMap | undefined; - for (const definition of active) { + for (const [definitionIndex, definition] of active.entries()) { const runtimeName = definition.runtime - ? `$$server_function_directive_${hashString(definition.runtime)}` + ? `$$server_function_directive_${definitionIndex}` : undefined; let runtimeUsed = false; const getRuntime = () => { @@ -405,55 +333,61 @@ export async function createServerFunctionDirectivePlugins(options: Options): Pr definition.validate?.({ id, directive: moduleMatch[0], location: "module" }); } - const result = transforms.transformServerActionServer(code, ast, { - runtime: (value, name) => - `$$ReactServer.registerServerReference(${value}, ${JSON.stringify(normalizedId)}, ${JSON.stringify(name)})`, - directive: definition.directive, - moduleDirective, - moduleRuntime: (value, name, meta) => { - if (!moduleMatch) return value; - needsReactRuntime = true; - return `$$ReactServer.registerServerReference(${definition.wrap({ value, name, id, directiveMatch: moduleMatch, location: "module", hasBoundArgs: false, parameters: meta.parameters, runtime: getRuntime(), meta })}, ${JSON.stringify(normalizedId)}, ${JSON.stringify(name)})`; - }, - inlineRuntime: (value, name, meta) => { - definition.validate?.({ - id, - directive: meta.directiveMatch[0], - location: "inline", + const result = moduleMatch + ? transforms.transformWrapExport(code, ast, { + runtime: (value, name, meta) => { + needsReactRuntime = true; + return `$$VinextReactServer.registerServerReference(${definition.wrap({ value, name, id, directiveMatch: moduleMatch, location: "module", hasBoundArgs: false, parameters: meta.parameters, runtime: getRuntime(), meta })}, ${JSON.stringify(reference.referenceKey)}, ${JSON.stringify(name)})`; + }, + filter: (name, meta) => definition.filterExport?.({ name, id, meta }) ?? true, + rejectNonAsyncFunction: definition.rejectNonAsyncModule, + }) + : transforms.transformHoistInlineDirective(code, ast, { + directive: definition.directive, + runtime: (value, name, meta) => { + definition.validate?.({ + id, + directive: meta.directiveMatch[0], + location: "inline", + }); + const wrapped = definition.wrap({ + value, + name, + id, + directiveMatch: meta.directiveMatch, + location: "inline", + hasBoundArgs: meta.hasBoundArgs, + parameters: meta.parameters, + runtime: getRuntime(), + }); + if (useServerBoundary) return wrapped; + + needsReactRuntime = true; + if (meta.hasBoundArgs) { + needsEncryptionRuntime = true; + return `$$VinextReactServer.registerServerReference((($$wrapped) => async ($$encoded, ...$$args) => $$wrapped(...await __vite_rsc_encryption_runtime.decryptActionBoundArgs($$encoded), ...$$args))(${wrapped}), ${JSON.stringify(reference.referenceKey)}, ${JSON.stringify(name)})`; + } + return `$$VinextReactServer.registerServerReference(${wrapped}, ${JSON.stringify(reference.referenceKey)}, ${JSON.stringify(name)})`; + }, + rejectNonAsyncFunction: definition.rejectNonAsyncFunction, + encode: (value) => { + needsEncryptionRuntime = true; + return `__vite_rsc_encryption_runtime.encryptActionBoundArgs(${value})`; + }, + stableName: true, + exportWrappedHoist: !useServerBoundary, + rejectForbiddenExpressions: true, }); - const wrapped = definition.wrap({ - value, - name, - id, - directiveMatch: meta.directiveMatch, - location: "inline", - hasBoundArgs: meta.hasBoundArgs, - parameters: meta.parameters, - runtime: getRuntime(), - }); - if (useServerBoundary) return wrapped; - - needsReactRuntime = true; - if (meta.hasBoundArgs) { - needsEncryptionRuntime = true; - return `$$ReactServer.registerServerReference((($$wrapped) => async ($$encoded, ...$$args) => $$wrapped(...await __vite_rsc_encryption_runtime.decryptActionBoundArgs($$encoded), ...$$args))(${wrapped}), ${JSON.stringify(normalizedId)}, ${JSON.stringify(name)})`; - } - return `$$ReactServer.registerServerReference(${wrapped}, ${JSON.stringify(normalizedId)}, ${JSON.stringify(name)})`; - }, - filter: (name, meta) => definition.filterExport?.({ name, id, meta }) ?? true, - rejectNonAsyncFunction: definition.rejectNonAsyncFunction, - rejectNonAsyncModule: definition.rejectNonAsyncModule, - encode: (value) => { - needsEncryptionRuntime = true; - return `__vite_rsc_encryption_runtime.encryptActionBoundArgs(${value})`; - }, - stableName: true, - exportWrappedHoist: !useServerBoundary, - detectUseServerModule: false, - rejectForbiddenExpressions: true, - }); if (!result.output.hasChanged()) continue; + if (moduleDirective) { + result.output.overwrite( + moduleDirective.start, + moduleDirective.end, + `/* ${JSON.stringify(moduleDirective.value)} */`, + ); + } + if (runtimeUsed && definition.runtime && runtimeName) { result.output.prepend( `import * as ${runtimeName} from ${JSON.stringify(definition.runtime)};\n`, @@ -468,15 +402,18 @@ export async function createServerFunctionDirectivePlugins(options: Options): Pr } if (!useServerBoundary && exportNames.size > 0) { - serverReferenceOwnership.set(id, true); - setOwnedServerReferenceMetadata(manager, ownedReferences, id, normalizedId, exportNames); - } else if (isServer) { - serverReferenceOwnership.set(id, false); - removeOwnedServerReferenceMetadata(manager, ownedReferences, id); + manager.serverReferences.deleteClaim(USE_SERVER_PLUGIN_NAME, id); + manager.serverReferences.replaceClaim(SERVER_FUNCTION_DIRECTIVE_PLUGIN_NAME, id, { + ...reference, + exportNames: [...exportNames], + }); + } else { + manager.serverReferences.deleteClaim(SERVER_FUNCTION_DIRECTIVE_PLUGIN_NAME, id); } const imports = [ - needsReactRuntime && `import * as $$ReactServer from ${JSON.stringify(rscRuntime)};`, + needsReactRuntime && + `import * as $$VinextReactServer from ${JSON.stringify(rscRuntime)};`, needsEncryptionRuntime && `import * as __vite_rsc_encryption_runtime from ${JSON.stringify(encryptionRuntime)};`, ].filter(Boolean); @@ -488,17 +425,5 @@ export async function createServerFunctionDirectivePlugins(options: Options): Pr }, }; - const metadataPlugin: Plugin = { - name: "vinext:server-function-directive-metadata", - transform: { - handler(_code, id) { - if (!manager) return; - const owned = ownedReferences.get(id); - if (!owned) return; - mergeServerReferenceMetadata(manager, id, owned.referenceKey, owned.exportNames); - }, - }, - }; - - return [transformPlugin, metadataPlugin]; + return transformPlugin; } diff --git a/packages/vinext/src/shims/cache-runtime.ts b/packages/vinext/src/shims/cache-runtime.ts index 99575ab39b..f0940241f3 100644 --- a/packages/vinext/src/shims/cache-runtime.ts +++ b/packages/vinext/src/shims/cache-runtime.ts @@ -166,18 +166,7 @@ export function getCacheContext(): CacheContext | null { * (they depend on virtual modules set up by @vitejs/plugin-rsc). * In test environments, the import fails and we fall back to JSON. */ -type RscModule = { - renderToReadableStream: (data: unknown, options?: object) => ReadableStream; - createFromReadableStream: ( - stream: ReadableStream, - options?: object, - extraOptions?: { preserveServerReferences?: boolean }, - ) => Promise; - encodeReply: (v: unknown[], options?: unknown) => Promise; - createTemporaryReferenceSet: () => unknown; - createClientTemporaryReferenceSet: () => unknown; - decodeReply: (body: string | FormData, options?: unknown) => Promise; -}; +type RscModule = typeof import("@vitejs/plugin-rsc/react/rsc"); function getUseCacheDeploymentIdDefine(): string | undefined { try { @@ -216,7 +205,7 @@ let _rscModule: RscModule | null | typeof NOT_LOADED = NOT_LOADED; async function getRscModule(): Promise { if (_rscModule !== NOT_LOADED) return _rscModule; try { - _rscModule = (await import("@vitejs/plugin-rsc/react/rsc")) as RscModule; + _rscModule = await import("@vitejs/plugin-rsc/react/rsc"); } catch { _rscModule = null; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 84e22e533e..0269913e1e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -91,8 +91,8 @@ catalogs: specifier: https://pkg.pr.new/@vitejs/plugin-react@82d2c578 version: 6.0.2 '@vitejs/plugin-rsc': - specifier: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75 - version: 0.5.28 + specifier: https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476 + version: 0.5.30 '@vitest/coverage-istanbul': specifier: 4.1.6 version: 4.1.6 @@ -277,7 +277,7 @@ importers: version: 7.0.0-dev.20260217.1 '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.6(@voidzero-dev/vite-plus-test@0.1.24) @@ -365,7 +365,7 @@ importers: version: https://pkg.pr.new/@vitejs/plugin-react@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0)) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) drizzle-kit: specifier: 'catalog:' version: 0.31.10 @@ -408,7 +408,7 @@ importers: devDependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.24 version: '@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0)' @@ -426,7 +426,7 @@ importers: version: https://pkg.pr.new/@vitejs/plugin-react@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0)) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) react: specifier: 'catalog:' version: 19.2.7 @@ -558,7 +558,7 @@ importers: version: 19.2.3(@types/react@19.2.16) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) postcss: specifier: 'catalog:' version: 8.5.10 @@ -597,7 +597,7 @@ importers: version: https://pkg.pr.new/@vitejs/plugin-react@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0)) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) react: specifier: 'catalog:' version: 19.2.7 @@ -680,7 +680,7 @@ importers: version: https://pkg.pr.new/@vitejs/plugin-react@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0)) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) postcss: specifier: 'catalog:' version: 8.5.10 @@ -713,7 +713,7 @@ importers: version: https://pkg.pr.new/@vitejs/plugin-react@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0)) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) ms: specifier: 'catalog:' version: 3.0.0-canary.1 @@ -784,7 +784,7 @@ importers: version: 19.2.3(@types/react@19.2.16) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) typescript: specifier: 'catalog:' version: 5.9.3 @@ -876,7 +876,7 @@ importers: version: https://pkg.pr.new/@vitejs/plugin-react@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0)) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) react: specifier: 'catalog:' version: 19.2.7 @@ -922,7 +922,7 @@ importers: version: https://pkg.pr.new/@vitejs/plugin-react@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0)) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) react: specifier: 'catalog:' version: 19.2.7 @@ -1008,7 +1008,7 @@ importers: version: https://pkg.pr.new/@vitejs/plugin-react@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0)) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) react-server-dom-webpack: specifier: 'catalog:' version: 19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -1020,7 +1020,7 @@ importers: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) fake-context-lib: specifier: file:./__test_packages__/fake-context-lib version: file:tests/fixtures/app-basic/__test_packages__/fake-context-lib @@ -1054,7 +1054,7 @@ importers: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) react: specifier: 'catalog:' version: 19.2.7 @@ -1079,7 +1079,7 @@ importers: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) react: specifier: 'catalog:' version: 19.2.7 @@ -1104,7 +1104,7 @@ importers: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) react: specifier: 'catalog:' version: 19.2.7 @@ -1135,7 +1135,7 @@ importers: version: https://pkg.pr.new/@vitejs/plugin-react@82d2c578(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0)) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) react: specifier: 'catalog:' version: 19.2.7 @@ -1170,7 +1170,7 @@ importers: version: 1.9.1 '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) better-auth: specifier: 'catalog:' version: 1.5.6(@cloudflare/workers-types@4.20260313.1)(@opentelemetry/api@1.9.1)(@types/node@25.9.2)(@vitest/coverage-istanbul@4.1.6(@voidzero-dev/vite-plus-test@0.1.24))(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(better-sqlite3@12.6.2)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260313.1)(@opentelemetry/api@1.9.1)(better-sqlite3@12.6.2)(kysely@0.28.15))(esbuild@0.27.3)(jiti@2.7.0)(next@16.2.7(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.100.0))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0) @@ -1201,7 +1201,7 @@ importers: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) next-intl: specifier: 'catalog:' version: 4.11.1(next@16.2.7(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.100.0))(react@19.2.7)(typescript@5.9.3) @@ -1229,7 +1229,7 @@ importers: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) next-themes: specifier: 'catalog:' version: 0.4.6(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -1257,7 +1257,7 @@ importers: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) next-view-transitions: specifier: 'catalog:' version: 0.3.5(next@16.2.7(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.100.0))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -1285,7 +1285,7 @@ importers: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) nuqs: specifier: 'catalog:' version: 2.8.8(next@16.2.7(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.100.0))(react@19.2.7) @@ -1322,7 +1322,7 @@ importers: version: 1.2.4(@types/react@19.2.16)(react@19.2.7) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) class-variance-authority: specifier: 'catalog:' version: 0.7.1 @@ -1378,7 +1378,7 @@ importers: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) react: specifier: 'catalog:' version: 19.2.7 @@ -1403,7 +1403,7 @@ importers: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) react: specifier: 'catalog:' version: 19.2.7 @@ -1428,7 +1428,7 @@ importers: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) react: specifier: 'catalog:' version: 19.2.7 @@ -1472,7 +1472,7 @@ importers: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) react: specifier: 'catalog:' version: 19.2.7 @@ -1516,7 +1516,7 @@ importers: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) react: specifier: 'catalog:' version: 19.2.7 @@ -4394,9 +4394,9 @@ packages: babel-plugin-react-compiler: optional: true - '@vitejs/plugin-rsc@https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75': - resolution: {integrity: sha512-Q4cHFFyarDlR6ZcMqlIg5XbGdxYfxgyee77hTPgq4RmT2fbu9sB503cgZ1A3DSzsykufBQB0Ix69fquE2CSRMA==, tarball: https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75} - version: 0.5.28 + '@vitejs/plugin-rsc@https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476': + resolution: {integrity: sha512-hJz+rxnLJ6zSlHNJBZr097gF+3uzoOYLYgYVkwIpdl7ttO5UUjVOLXaomF4ZF0OVFde17uAfUyW2eX1e9s/+hQ==, tarball: https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476} + version: 0.5.30 peerDependencies: react: '*' react-dom: '*' @@ -9414,7 +9414,7 @@ snapshots: '@rolldown/pluginutils': 1.0.1 vite: '@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0)' - '@vitejs/plugin-rsc@https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)': + '@vitejs/plugin-rsc@https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476(@voidzero-dev/vite-plus-core@0.1.24(@types/node@25.9.2)(esbuild@0.27.3)(jiti@2.7.0)(sass@1.100.0)(tsx@4.21.1)(typescript@5.9.3)(yaml@2.9.0))(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)': dependencies: '@rolldown/pluginutils': 1.0.1 es-module-lexer: 2.3.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 90ee02a165..94ba817b31 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -43,7 +43,7 @@ catalog: "@next/mdx": 16.2.7 recma-codehike: 0.0.1 remark-codehike: 0.0.1 - "@vitejs/plugin-rsc": https://pkg.pr.new/@vitejs/plugin-rsc@5a2fd75 + "@vitejs/plugin-rsc": https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476 "@vitest/coverage-istanbul": 4.1.6 better-auth: ^1.5.6 better-sqlite3: ^12.0.0 diff --git a/tests/use-cache-transform.test.ts b/tests/use-cache-transform.test.ts index 9a4b72d81b..d5d961ceb1 100644 --- a/tests/use-cache-transform.test.ts +++ b/tests/use-cache-transform.test.ts @@ -1,8 +1,8 @@ /** * Tests the vinext user-land server function directive integration used for function-level * "use cache" directives. Vinext owns the directive plugin while plugin-rsc - * provides directive transforms and the shared reference metadata map. Vinext - * owns directive orchestration and merges its references after rsc:use-server. + * provides directive transforms and aggregates independently owned server + * reference claims. */ import path from "node:path"; import { createHash } from "node:crypto"; @@ -56,18 +56,14 @@ async function configurePluginRsc(plugins: Plugin[]) { } describe("plugin-rsc inline use-cache references", () => { - it("restores user-land reference metadata after rsc:use-server", async () => { + it("keeps the vinext claim when rsc:use-server removes its own claim", async () => { const plugins = await getPlugins(); const manager = await configurePluginRsc(plugins); const useCacheIndex = plugins.findIndex( (candidate) => candidate.name === "vinext:server-function-directives", ); const useServerIndex = plugins.findIndex((candidate) => candidate.name === "rsc:use-server"); - const metadataIndex = plugins.findIndex( - (candidate) => candidate.name === "vinext:server-function-directive-metadata", - ); expect(useCacheIndex).toBeLessThan(useServerIndex); - expect(metadataIndex).toBeGreaterThan(useServerIndex); const context = { environment: { name: "rsc", mode: "build" } }; const transformed = await unwrapHook(plugins[useCacheIndex]!.transform)!.call( @@ -75,17 +71,14 @@ describe("plugin-rsc inline use-cache references", () => { inlineCacheCode, moduleId, ); - expect(manager.serverReferenceMetaMap[moduleId]).toBeDefined(); + expect(manager.serverReferences.metaMap.get(moduleId)).toBeDefined(); await unwrapHook(plugins[useServerIndex]!.transform)!.call( context, transformed!.code, moduleId, ); - expect(manager.serverReferenceMetaMap[moduleId]).toBeUndefined(); - - unwrapHook(plugins[metadataIndex]!.transform)!.call(context, transformed!.code, moduleId); - expect(manager.serverReferenceMetaMap[moduleId]).toBeDefined(); + expect(manager.serverReferences.metaMap.get(moduleId)).toBeDefined(); const ssrContext = { environment: { name: "ssr", mode: "build" } }; const proxied = await unwrapHook(plugins[useCacheIndex]!.transform)!.call( @@ -94,23 +87,19 @@ describe("plugin-rsc inline use-cache references", () => { moduleId, ); await unwrapHook(plugins[useServerIndex]!.transform)!.call(ssrContext, proxied!.code, moduleId); - unwrapHook(plugins[metadataIndex]!.transform)!.call(ssrContext, proxied!.code, moduleId); - expect(manager.serverReferenceMetaMap[moduleId]).toMatchObject({ + expect(manager.serverReferences.metaMap.get(moduleId)).toMatchObject({ importId: moduleId, exportNames: expect.arrayContaining(["getData"]), }); }); - it("merges and deduplicates use-server and user-land reference metadata", async () => { + it("aggregates use-server and vinext claims", async () => { const plugins = await getPlugins(); const manager = await configurePluginRsc(plugins); const useCachePlugin = plugins.find( (candidate) => candidate.name === "vinext:server-function-directives", )!; const useServerPlugin = plugins.find((candidate) => candidate.name === "rsc:use-server")!; - const metadataPlugin = plugins.find( - (candidate) => candidate.name === "vinext:server-function-directive-metadata", - )!; const context = { environment: { name: "rsc", mode: "build" } }; const source = [ `export async function action() {`, @@ -132,79 +121,98 @@ describe("plugin-rsc inline use-cache references", () => { useCacheResult!.code, moduleId, ); - const upstreamMetadata = manager.serverReferenceMetaMap[moduleId]; - const upstreamExport = upstreamMetadata.exportNames[0]!; - upstreamMetadata.exportNames.push(upstreamExport); - - unwrapHook(metadataPlugin.transform)!.call(context, useServerResult!.code, moduleId); - - const merged = manager.serverReferenceMetaMap[moduleId]; - expect(merged.importId).toBe(upstreamMetadata.importId); - expect(merged.referenceKey).toBe(upstreamMetadata.referenceKey); - expect(merged.exportNames).toContain(upstreamExport); + expect(useServerResult!.code).toContain("$$VinextReactServer.registerServerReference"); + expect(() => parseAst(useServerResult!.code)).not.toThrow(); + const claims = manager.serverReferences.claimMap.get(moduleId); + expect([...claims.keys()]).toEqual(["vinext:server-function-directives", "rsc:use-server"]); + + const merged = manager.serverReferences.metaMap.get(moduleId)!; + expect(merged.importId).toBe(moduleId); + expect(merged.exportNames).toContainEqual(expect.stringMatching(/action/)); expect(merged.exportNames).toContainEqual(expect.stringMatching(/getData/)); expect(merged.exportNames).toHaveLength(new Set(merged.exportNames).size); }); - it("restores RSC-owned metadata after a non-owning proxy pass", async () => { + it("preserves the vinext claim when transformed code enters a proxy graph", async () => { const plugins = await getPlugins(); const manager = await configurePluginRsc(plugins); const useCachePlugin = plugins.find( (candidate) => candidate.name === "vinext:server-function-directives", )!; const useServerPlugin = plugins.find((candidate) => candidate.name === "rsc:use-server")!; - const metadataPlugin = plugins.find( - (candidate) => candidate.name === "vinext:server-function-directive-metadata", - )!; const rscContext = { environment: { name: "rsc", mode: "build" } }; const transformed = await unwrapHook(useCachePlugin.transform)!.call( rscContext, inlineCacheCode, moduleId, ); - const ownedExportNames = manager.serverReferenceMetaMap[moduleId].exportNames; + const ownedExportNames = manager.serverReferences.metaMap.get(moduleId)!.exportNames; const ssrContext = { environment: { name: "ssr", mode: "build" } }; await unwrapHook(useCachePlugin.transform)!.call(ssrContext, transformed!.code, moduleId); await unwrapHook(useServerPlugin.transform)!.call(ssrContext, transformed!.code, moduleId); - expect(manager.serverReferenceMetaMap[moduleId]).toBeUndefined(); - - unwrapHook(metadataPlugin.transform)!.call(ssrContext, transformed!.code, moduleId); - expect(manager.serverReferenceMetaMap[moduleId].exportNames).toEqual(ownedExportNames); + expect(manager.serverReferences.metaMap.get(moduleId)!.exportNames).toEqual(ownedExportNames); }); - it("does not let a stale proxy transform resurrect RSC-removed metadata", async () => { + it("removes the vinext claim when the directive is removed", async () => { const plugins = await getPlugins(); const manager = await configurePluginRsc(plugins); const useCachePlugin = plugins.find( (candidate) => candidate.name === "vinext:server-function-directives", )!; - const metadataPlugin = plugins.find( - (candidate) => candidate.name === "vinext:server-function-directive-metadata", - )!; const rscContext = { environment: { name: "rsc", mode: "build" } }; const ssrContext = { environment: { name: "ssr", mode: "build" } }; await unwrapHook(useCachePlugin.transform)!.call(rscContext, fileCacheCode, moduleId); - expect(manager.serverReferenceMetaMap[moduleId]).toBeDefined(); + await unwrapHook(useCachePlugin.transform)!.call(ssrContext, fileCacheCode, moduleId); + expect(manager.serverReferences.metaMap.get(moduleId)).toBeDefined(); - await unwrapHook(useCachePlugin.transform)!.call( - rscContext, - `export async function getData() { return 1; }`, - moduleId, - ); - expect(manager.serverReferenceMetaMap[moduleId]).toBeUndefined(); + const source = `export async function getData() { return 1; }`; + await unwrapHook(useCachePlugin.transform)!.call(rscContext, source, moduleId); + await unwrapHook(useCachePlugin.transform)!.call(ssrContext, source, moduleId); + expect(manager.serverReferences.metaMap.get(moduleId)).toBeUndefined(); + }); - const staleProxy = await unwrapHook(useCachePlugin.transform)!.call( - ssrContext, - fileCacheCode, - moduleId, - ); - unwrapHook(metadataPlugin.transform)!.call(ssrContext, staleProxy!.code, moduleId); - expect(manager.serverReferenceMetaMap[moduleId]).toBeUndefined(); + it("hands a file-level reference between vinext and rsc:use-server", async () => { + const plugins = await getPlugins(); + const manager = await configurePluginRsc(plugins); + const useCachePlugin = plugins.find( + (candidate) => candidate.name === "vinext:server-function-directives", + )!; + const useServerPlugin = plugins.find((candidate) => candidate.name === "rsc:use-server")!; + const context = { environment: { name: "rsc", mode: "build" } }; + const useServerCode = [ + `"use server";`, + `export async function getData() {`, + ` return 1;`, + `}`, + ].join("\n"); - await unwrapHook(useCachePlugin.transform)!.call(rscContext, fileCacheCode, moduleId); - expect(manager.serverReferenceMetaMap[moduleId]).toBeDefined(); + const transform = async (source: string) => { + const useCacheResult = await unwrapHook(useCachePlugin.transform)!.call( + context, + source, + moduleId, + ); + await unwrapHook(useServerPlugin.transform)!.call( + context, + useCacheResult?.code ?? source, + moduleId, + ); + }; + + await transform(fileCacheCode); + expect([...manager.serverReferences.claimMap.get(moduleId).keys()]).toEqual([ + "vinext:server-function-directives", + ]); + + await transform(useServerCode); + expect([...manager.serverReferences.claimMap.get(moduleId).keys()]).toEqual(["rsc:use-server"]); + + await transform(fileCacheCode); + expect([...manager.serverReferences.claimMap.get(moduleId).keys()]).toEqual([ + "vinext:server-function-directives", + ]); }); it("matches Vite's dev reference key for files outside the project root", async () => { @@ -230,7 +238,7 @@ describe("plugin-rsc inline use-cache references", () => { ); const expectedKey = path.posix.join("/@fs/", externalId); expect(result!.code).toContain(JSON.stringify(expectedKey)); - expect(manager.serverReferenceMetaMap[externalId].referenceKey).toBe(expectedKey); + expect(manager.serverReferences.metaMap.get(externalId)!.referenceKey).toBe(expectedKey); }); it("wraps and registers inline cache functions with plugin-rsc's build reference key", async () => { @@ -251,10 +259,10 @@ describe("plugin-rsc inline use-cache references", () => { .update(manager.toRelativeId(moduleId)) .digest("hex") .slice(0, 12); - expect(result!.code).toContain("$$ReactServer.registerServerReference"); + expect(result!.code).toContain("$$VinextReactServer.registerServerReference"); expect(result!.code).toContain("registerCachedFunction"); expect(result!.code).toContain(JSON.stringify(expectedKey)); - expect(manager.serverReferenceMetaMap[moduleId]).toEqual({ + expect(manager.serverReferences.metaMap.get(moduleId)).toEqual({ importId: moduleId, referenceKey: expectedKey, exportNames: [expect.stringMatching(/^\$\$hoist_[a-z0-9]+_0_getData$/)], @@ -286,7 +294,7 @@ describe("plugin-rsc inline use-cache references", () => { expect(getDataName(withUnrelated!.code)).toBe(getDataName(original!.code)); }); - it("leaves directive removal cleanup to the preceding use-server pass", async () => { + it("removes its claim when the directive is removed", async () => { const plugins = await getPlugins(); const manager = await configurePluginRsc(plugins); const plugin = plugins.find( @@ -296,7 +304,7 @@ describe("plugin-rsc inline use-cache references", () => { const useServerPlugin = plugins.find((candidate) => candidate.name === "rsc:use-server")!; const context = { environment: { name: "rsc", mode: "build" } }; await transform.call(context, inlineCacheCode, moduleId); - expect(manager.serverReferenceMetaMap[moduleId]).toBeDefined(); + expect(manager.serverReferences.metaMap.get(moduleId)).toBeDefined(); const source = `export async function getData() { return 1; }`; const useServerResult = await unwrapHook(useServerPlugin.transform)!.call( context, @@ -304,7 +312,7 @@ describe("plugin-rsc inline use-cache references", () => { moduleId, ); await transform.call(context, useServerResult?.code ?? source, moduleId); - expect(manager.serverReferenceMetaMap[moduleId]).toBeUndefined(); + expect(manager.serverReferences.metaMap.get(moduleId)).toBeUndefined(); }); it("encrypts closure captures and reports bound-argument metadata to vinext", async () => { @@ -428,7 +436,7 @@ describe("plugin-rsc inline use-cache references", () => { expect(result!.code).toContain("registerCachedFunction(alias"); expect(result!.code).toContain("registerCachedFunction(named"); expect(result!.code).toContain("registerCachedFunction(imported"); - expect(manager.serverReferenceMetaMap[moduleId].exportNames).toEqual( + expect(manager.serverReferences.metaMap.get(moduleId)!.exportNames).toEqual( expect.arrayContaining(["direct", "alias", "named", "renamed", "default"]), ); }); @@ -588,10 +596,10 @@ describe("plugin-rsc inline use-cache references", () => { moduleId, ); expect(result).not.toBeNull(); - expect(result!.code).toContain("$$ReactServer.registerServerReference"); + expect(result!.code).toContain("$$VinextReactServer.registerServerReference"); expect(result!.code).toContain("registerCachedFunction"); expect(result!.code).not.toContain('"use cache";'); - expect(manager.serverReferenceMetaMap[moduleId].exportNames).toEqual(["getData"]); + expect(manager.serverReferences.metaMap.get(moduleId)!.exportNames).toEqual(["getData"]); }); it.each(["ssr", "client"])( From a1c2e6740a78e80f3d126c77f94f37a8bd5e3bd6 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 24 Jul 2026 12:21:29 +0100 Subject: [PATCH 8/8] fix(init): install required plugin-rsc prerelease --- packages/vinext/src/deploy.ts | 5 ++++- packages/vinext/src/init.ts | 8 +++++++- .../vinext/src/plugins/server-function-directives.ts | 5 +++++ tests/deploy.test.ts | 5 ++++- tests/init.test.ts | 9 ++++++++- 5 files changed, 28 insertions(+), 4 deletions(-) diff --git a/packages/vinext/src/deploy.ts b/packages/vinext/src/deploy.ts index b74f98cb84..a11e5cc283 100644 --- a/packages/vinext/src/deploy.ts +++ b/packages/vinext/src/deploy.ts @@ -811,7 +811,10 @@ export function getMissingDeps( missing.push({ name: "@vitejs/plugin-react", version: "latest" }); } if (info.isAppRouter && !info.hasRscPlugin) { - missing.push({ name: "@vitejs/plugin-rsc", version: "latest" }); + missing.push({ + name: "@vitejs/plugin-rsc", + version: "https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476", + }); } if (info.isAppRouter) { // react-server-dom-webpack must be resolvable from the project root for Vite. diff --git a/packages/vinext/src/init.ts b/packages/vinext/src/init.ts index 937705022e..3cc4332ea9 100644 --- a/packages/vinext/src/init.ts +++ b/packages/vinext/src/init.ts @@ -121,6 +121,8 @@ export function addScripts(root: string, port: number): string[] { // ─── Dependency Installation ───────────────────────────────────────────────── +const PLUGIN_RSC_INSTALL_SPEC = "@vitejs/plugin-rsc@https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476"; + export function getInitDeps(isAppRouter: boolean): string[] { const deps = ["vinext", "vite", "@vitejs/plugin-react"]; if (isAppRouter) { @@ -308,7 +310,11 @@ export async function init(options: InitOptions): Promise { if (missingDeps.length > 0) { console.log(` Installing ${missingDeps.join(", ")}...`); - installDeps(root, missingDeps, exec); + installDeps( + root, + missingDeps.map((dep) => (dep === "@vitejs/plugin-rsc" ? PLUGIN_RSC_INSTALL_SPEC : dep)), + exec, + ); console.log(); } diff --git a/packages/vinext/src/plugins/server-function-directives.ts b/packages/vinext/src/plugins/server-function-directives.ts index 2dd6982b21..d9129ef89b 100644 --- a/packages/vinext/src/plugins/server-function-directives.ts +++ b/packages/vinext/src/plugins/server-function-directives.ts @@ -224,6 +224,11 @@ export async function createServerFunctionDirectivePlugin(options: Options): Pro if (!manager) { throw new Error("vinext: failed to access @vitejs/plugin-rsc through getPluginApi()."); } + if (!manager.serverReferences) { + throw new Error( + "vinext: Installed @vitejs/plugin-rsc does not support user-land server reference claims.", + ); + } if (active.length === 0) { manager.serverReferences.deleteClaim(SERVER_FUNCTION_DIRECTIVE_PLUGIN_NAME, id); return; diff --git a/tests/deploy.test.ts b/tests/deploy.test.ts index 704954c1c7..d94d41a5c8 100644 --- a/tests/deploy.test.ts +++ b/tests/deploy.test.ts @@ -1273,7 +1273,10 @@ describe("getMissingDeps", () => { info.hasRscPlugin = false; const missing = getMissingDeps(info); - expect(missing).toContainEqual(expect.objectContaining({ name: "@vitejs/plugin-rsc" })); + expect(missing).toContainEqual({ + name: "@vitejs/plugin-rsc", + version: "https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476", + }); }); it("does not require @vitejs/plugin-rsc for Pages Router", () => { diff --git a/tests/init.test.ts b/tests/init.test.ts index a55b64b51c..5b0edd2e04 100644 --- a/tests/init.test.ts +++ b/tests/init.test.ts @@ -517,10 +517,17 @@ describe("init — dependency installation", () => { it("detects missing @vitejs/plugin-rsc for App Router", async () => { setupProject(tmpDir, { router: "app" }); - const { result } = await runInit(tmpDir); + const { result, execCalls } = await runInit(tmpDir); expect(result.installedDeps).toContain("@vitejs/plugin-react"); expect(result.installedDeps).toContain("@vitejs/plugin-rsc"); + expect(execCalls).toContainEqual( + expect.objectContaining({ + cmd: expect.stringContaining( + "@vitejs/plugin-rsc@https://pkg.pr.new/@vitejs/plugin-rsc@50eaf476", + ), + }), + ); }); it("treats src/app projects as App Router", async () => {