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/index.ts b/packages/vinext/src/index.ts index 08d2e23a0c..298e25bf26 100644 --- a/packages/vinext/src/index.ts +++ b/packages/vinext/src/index.ts @@ -117,6 +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 { + createServerFunctionDirectivePlugin, + type ServerFunctionDirectiveContext, +} from "./plugins/server-function-directives.js"; import { generateRouteTypes } from "./typegen.js"; import { mergeOptimizeDepsExclude, @@ -187,16 +191,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 +937,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 = await createServerFunctionDirectivePlugin({ + projectRoot: earlyBaseDir, + definitions: [ { directive: /^use cache.*$/, test: (code: string) => code.includes("use cache"), @@ -1002,15 +999,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 +1008,15 @@ export default function vinext(options: VinextOptions = {}): PluginOption[] { }, }, ], + serverEnvironmentName: "rsc", + browserEnvironmentName: "client", }); + const useServerIndex = plugins.findIndex((plugin) => plugin.name === "rsc:use-server"); + if (useServerIndex === -1) { + throw new Error("vinext: Failed to locate @vitejs/plugin-rsc use-server plugin."); + } + plugins.splice(useServerIndex, 0, serverFunctionPlugin); + return plugins; }) .catch((cause) => { throw new Error("vinext: Failed to load @vitejs/plugin-rsc.", { 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 new file mode 100644 index 0000000000..d9129ef89b --- /dev/null +++ b/packages/vinext/src/plugins/server-function-directives.ts @@ -0,0 +1,434 @@ +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 } 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 ProgramExpressionStatement = Extract; +type StringDirective = Extract & { + value: string; + start: number; + end: number; +}; +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; +}; + +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; + definitions: ServerFunctionDirective[]; + serverEnvironmentName: string; + browserEnvironmentName: string; +}; + +const SERVER_FUNCTION_DIRECTIVE_MARKER = "/* __vinext_server_function_directives__ */"; +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 { + 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" && + typeof value.start === "number" && + typeof value.end === "number" + ); +} + +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; +} + +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 createServerFunctionDirectivePlugin(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/server"), + ).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 transformPlugin: Plugin = { + name: SERVER_FUNCTION_DIRECTIVE_PLUGIN_NAME, + + 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 (!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; + } + + let ast = await parseProgram(code); + const useServerBoundary = transforms.hasDirective(ast.body, "use server"); + 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) { + 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) { + 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."), + { + 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(`${reference.referenceKey}#${name}`)},$$ReactClient.callServer,undefined,${this.environment.mode === "dev" ? "$$ReactClient.findSourceMapURL" : "undefined"},${JSON.stringify(name)})`, + }); + 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`, + ); + 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 [definitionIndex, definition] of active.entries()) { + const runtimeName = definition.runtime + ? `$$server_function_directive_${definitionIndex}` + : 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 = 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, + }); + 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`, + ); + } + + 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 && exportNames.size > 0) { + 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 $$VinextReactServer 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, + }; + }, + }, + }; + + return transformPlugin; +} diff --git a/packages/vinext/src/shims/cache-runtime.ts b/packages/vinext/src/shims/cache-runtime.ts index 5273118685..f0940241f3 100644 --- a/packages/vinext/src/shims/cache-runtime.ts +++ b/packages/vinext/src/shims/cache-runtime.ts @@ -166,17 +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?: { serverReferences?: "resolve" | "preserve" }, - ) => 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 { @@ -215,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; } @@ -555,9 +545,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..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@82d2c578 - version: 0.5.27 + 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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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: '*' @@ -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@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.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..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@82d2c578 + "@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/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/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/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/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 () => { diff --git a/tests/use-cache-transform.test.ts b/tests/use-cache-transform.test.ts index 1b2455caf7..d5d961ceb1 100644 --- a/tests/use-cache-transform.test.ts +++ b/tests/use-cache-transform.test.ts @@ -1,8 +1,8 @@ /** - * Tests the plugin-rsc serverFunctionDirectives integration used for function-level - * "use cache" directives. Vinext supplies cache wrapper expressions; plugin-rsc - * owns directive discovery, closure hoisting, encryption, reference ids, and - * server-reference manifest metadata. + * 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 aggregates independently owned server + * reference claims. */ import path from "node:path"; import { createHash } from "node:crypto"; @@ -47,16 +47,205 @@ async function configurePluginRsc(plugins: Plugin[]) { rsc: { build: { outDir: path.join(APP_FIXTURE_DIR, "dist/rsc") } }, }, }); + const useCachePlugin = plugins.find( + (plugin) => plugin.name === "vinext:server-function-directives", + )!; + 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("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"); + expect(useCacheIndex).toBeLessThan(useServerIndex); + + const context = { environment: { name: "rsc", mode: "build" } }; + const transformed = await unwrapHook(plugins[useCacheIndex]!.transform)!.call( + context, + inlineCacheCode, + moduleId, + ); + expect(manager.serverReferences.metaMap.get(moduleId)).toBeDefined(); + + await unwrapHook(plugins[useServerIndex]!.transform)!.call( + context, + transformed!.code, + moduleId, + ); + expect(manager.serverReferences.metaMap.get(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.serverReferences.metaMap.get(moduleId)).toMatchObject({ + importId: moduleId, + exportNames: expect.arrayContaining(["getData"]), + }); + }); + + 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 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, + ); + 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("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 rscContext = { environment: { name: "rsc", mode: "build" } }; + const transformed = await unwrapHook(useCachePlugin.transform)!.call( + rscContext, + inlineCacheCode, + moduleId, + ); + 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.serverReferences.metaMap.get(moduleId)!.exportNames).toEqual(ownedExportNames); + }); + + 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 rscContext = { environment: { name: "rsc", mode: "build" } }; + const ssrContext = { environment: { name: "ssr", mode: "build" } }; + + await unwrapHook(useCachePlugin.transform)!.call(rscContext, fileCacheCode, moduleId); + await unwrapHook(useCachePlugin.transform)!.call(ssrContext, fileCacheCode, moduleId); + expect(manager.serverReferences.metaMap.get(moduleId)).toBeDefined(); + + 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(); + }); + + 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"); + + 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 () => { + 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:server-function-directives", + )!; + 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.serverReferences.metaMap.get(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:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; const result = await transform.call( @@ -70,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$/)], @@ -84,7 +273,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:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; const original = await transform.call( @@ -105,32 +294,32 @@ 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("removes its claim when the directive is removed", 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:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; - await transform.call( - { environment: { name: "rsc", mode: "build" } }, - inlineCacheCode, - moduleId, - ); - expect(manager.serverReferenceMetaMap[moduleId]).toBeDefined(); - await transform.call( - { environment: { name: "rsc", mode: "build" } }, - `export async function getData() { return 1; }`, + 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.serverReferences.metaMap.get(moduleId)).toBeDefined(); + const source = `export async function getData() { return 1; }`; + const useServerResult = await unwrapHook(useServerPlugin.transform)!.call( + context, + source, moduleId, ); - expect(manager.serverReferenceMetaMap[moduleId]).toBeUndefined(); + await transform.call(context, useServerResult?.code ?? source, moduleId); + expect(manager.serverReferences.metaMap.get(moduleId)).toBeUndefined(); }); it("encrypts closure captures and reports bound-argument metadata to vinext", async () => { const plugins = await getPlugins(); await configurePluginRsc(plugins); const plugin = plugins.find( - (candidate) => candidate.name === "rsc:server-function-directives", + (candidate) => candidate.name === "vinext:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; const closureCode = [ @@ -164,7 +353,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:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; @@ -182,7 +371,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:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; const result = await transform.call( @@ -197,7 +386,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:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; const result = await transform.call( @@ -212,7 +401,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:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; const result = await transform.call( @@ -227,7 +416,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:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; const result = await transform.call( @@ -247,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"]), ); }); @@ -256,7 +445,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:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; await expect( @@ -274,7 +463,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:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; await expect( @@ -315,7 +504,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:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; const result = await transform.call( @@ -331,7 +520,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:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; await expect( @@ -349,7 +538,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:server-function-directives", )!; const context = { environment: { name: "rsc", mode: "build" } }; const source = [ @@ -368,7 +557,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:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; await expect( @@ -384,7 +573,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:server-function-directives", )!; const result = await unwrapHook(plugin.transform)!.call( { environment: { name: "rsc", mode: "build" } }, @@ -398,7 +587,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:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; const result = await transform.call( @@ -407,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"])( @@ -419,7 +608,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:server-function-directives", )!; const transform = unwrapHook(plugin.transform)!; const result = await transform.call(