11import type { Rollup } from 'vite'
22import { describe , expect , it , vi } from 'vitest'
33import {
4- SERVER_FUNCTION_DIRECTIVE_MARKER ,
54 vitePluginServerFunctionDirectives ,
65 type ServerFunctionDirective ,
76} from './server-function-directives'
@@ -75,6 +74,8 @@ function cacheDirective(
7574 directive : / ^ u s e c a c h e (?: : .+ ) ? $ / ,
7675 test : ( code ) => code . includes ( 'use cache' ) ,
7776 rejectNonAsyncFunction : true ,
77+ clientError : ( { id, environment } ) =>
78+ `inline use cache is not allowed in ${ environment } : ${ id } ` ,
7879 wrap : ( { value, directiveMatch, location } ) =>
7980 `cache(${ value } , ${ JSON . stringify ( directiveMatch [ 0 ] ) } , ${ JSON . stringify ( location ) } )` ,
8081 ...overrides ,
@@ -90,10 +91,19 @@ export async function getData() {
9091 return 1;
9192}
9293` )
93- expect ( result ?. code ) . toContain ( SERVER_FUNCTION_DIRECTIVE_MARKER )
94- expect ( result ?. code ) . toContain ( 'cache($$hoist_' )
95- expect ( result ?. code ) . toContain ( '$$ReactServer.registerServerReference' )
96- expect ( result ?. code ) . toContain ( '/rsc-runtime.js' )
94+ expect ( result ?. code ) . toMatchInlineSnapshot ( `
95+ "/* __vite_rsc_server_function_directives__ */
96+ import * as $$ReactServer from "/rsc-runtime.js";
97+
98+ export const getData = /* #__PURE__ */ $$ReactServer.registerServerReference(cache($$hoist_e9c2205b6101_0_getData, "use cache", "inline"), "53eb073e2100", "$$hoist_e9c2205b6101_0_getData");
99+
100+ ;export async function $$hoist_e9c2205b6101_0_getData() {
101+ "use cache";
102+ return 1;
103+ };
104+ /* #__PURE__ */ Object.defineProperty($$hoist_e9c2205b6101_0_getData, "name", { value: "getData" });
105+ "
106+ ` )
97107 expect (
98108 manager . serverReferenceMetaMap [ '/src/example.ts' ] ?. exportNames ,
99109 ) . toEqual ( [ expect . stringMatching ( / ^ \$ \$ h o i s t _ / ) ] )
@@ -110,9 +120,22 @@ export async function outer(value) {
110120 };
111121}
112122` )
113- expect ( result ?. code ) . toContain ( 'encryptActionBoundArgs([value])' )
114- expect ( result ?. code ) . toContain ( 'decryptActionBoundArgs($$encoded)' )
115- expect ( result ?. code ) . toContain ( '/encryption-runtime.js' )
123+ expect ( result ?. code ) . toMatchInlineSnapshot ( `
124+ "/* __vite_rsc_server_function_directives__ */
125+ import * as $$ReactServer from "/rsc-runtime.js";
126+ import * as __vite_rsc_encryption_runtime from "/encryption-runtime.js";
127+
128+ export async function outer(value) {
129+ return /* #__PURE__ */ $$ReactServer.registerServerReference((($$wrapped) => async ($$encoded, ...$$args) => $$wrapped(...await __vite_rsc_encryption_runtime.decryptActionBoundArgs($$encoded), ...$$args))(cache($$hoist_ab3ae7af371a_0_cached)), "53eb073e2100", "$$hoist_ab3ae7af371a_0_cached").bind(null, __vite_rsc_encryption_runtime.encryptActionBoundArgs([value]));
130+ }
131+
132+ ;export async function $$hoist_ab3ae7af371a_0_cached(value) {
133+ "use cache";
134+ return value;
135+ };
136+ /* #__PURE__ */ Object.defineProperty($$hoist_ab3ae7af371a_0_cached, "name", { value: "cached" });
137+ "
138+ ` )
116139 expect ( wrap ) . toHaveBeenCalledWith (
117140 expect . objectContaining ( { location : 'inline' , hasBoundArgs : true } ) ,
118141 )
@@ -131,8 +154,18 @@ export async function getData() { return 1 }
131154export const metadata = { title: "test" };
132155` )
133156 expect ( expandExportAll ) . toHaveBeenCalledOnce ( )
134- expect ( result ?. code ) . toContain ( 'cache(getData, "use cache", "module")' )
135- expect ( result ?. code ) . not . toContain ( 'cache(metadata' )
157+ expect ( result ?. code ) . toMatchInlineSnapshot ( `
158+ "/* __vite_rsc_server_function_directives__ */
159+
160+
161+ /* "use cache" */;
162+ async function getData() { return 1 }
163+ let metadata = { title: "test" };
164+ getData = /* #__PURE__ */ $$ReactServer.registerServerReference(cache(getData, "use cache", "module"), "53eb073e2100", "getData");
165+ export { getData };
166+ export { metadata };
167+ "
168+ ` )
136169 expect (
137170 manager . serverReferenceMetaMap [ '/src/example.ts' ] ?. exportNames ,
138171 ) . toEqual ( [ 'getData' ] )
@@ -141,36 +174,63 @@ export const metadata = { title: "test" };
141174 )
142175 } )
143176
144- it . each ( [
145- [ 'client' , '/browser-runtime.js' ] ,
146- [ 'ssr' , '/ssr-runtime.js' ] ,
147- ] as const ) ( 'creates module proxies in %s' , async ( environment , runtime ) => {
177+ it ( 'creates module proxies in client' , async ( ) => {
178+ const { run } = createHarness ( [ cacheDirective ( ) ] )
179+ const result = await run (
180+ `"use cache"; export async function getData() { return 1 }` ,
181+ 'client' ,
182+ )
183+ expect ( result ?. code ) . toMatchInlineSnapshot ( `
184+ "import * as $$ReactClient from "/browser-runtime.js";
185+ export const getData = /* #__PURE__ */ $$ReactClient.createServerReference("53eb073e2100#getData",$$ReactClient.callServer,undefined,undefined,"getData");
186+ "
187+ ` )
188+ } )
189+
190+ it ( 'creates module proxies in SSR' , async ( ) => {
148191 const { run } = createHarness ( [ cacheDirective ( ) ] )
149192 const result = await run (
150193 `"use cache"; export async function getData() { return 1 }` ,
151- environment ,
194+ 'ssr' ,
152195 )
153- expect ( result ?. code ) . toContain ( runtime )
154- expect ( result ?. code ) . toContain ( '$$ReactClient.createServerReference' )
155- expect ( result ?. code ) . toContain ( '#getData' )
196+ expect ( result ?. code ) . toMatchInlineSnapshot ( `
197+ "import * as $$ReactClient from "/ssr-runtime.js";
198+ export const getData = /* #__PURE__ */ $$ReactClient.createServerReference("53eb073e2100#getData",$$ReactClient.callServer,undefined,undefined,"getData");
199+ "
200+ ` )
156201 } )
157202
158- it ( 'uses clientError for non-RSC module boundaries ' , async ( ) => {
203+ it ( 'uses clientError for non-RSC inline directives ' , async ( ) => {
159204 const { run } = createHarness ( [
160205 cacheDirective ( {
161206 clientError : ( { id, environment } ) => `${ environment } :${ id } ` ,
162207 } ) ,
163208 ] )
164209 await expect (
165- run ( `"use cache"; export async function getData() {}` , 'client' ) ,
210+ run ( `export async function getData() { "use cache" }` , 'client' ) ,
166211 ) . rejects . toThrow ( 'client:/src/example.ts' )
167212 } )
168213
169- it ( 'leaves non-server inline directives untouched' , async ( ) => {
214+ it . each ( [ 'client' , 'ssr' ] as const ) (
215+ 'rejects inline directives in %s when clientError is configured' ,
216+ async ( environment ) => {
217+ const { run } = createHarness ( [ cacheDirective ( ) ] )
218+ const code = `export async function getData() { "use cache" }`
219+ await expect ( run ( code , environment ) ) . rejects . toThrow (
220+ `inline use cache is not allowed in ${ environment } : /src/example.ts` ,
221+ )
222+ } ,
223+ )
224+
225+ it ( 'leaves non-server inline directives untouched without clientError' , async ( ) => {
170226 const { run } = createHarness ( [ cacheDirective ( ) ] )
227+ const { run : runWithoutError } = createHarness ( [
228+ cacheDirective ( { clientError : undefined } ) ,
229+ ] )
171230 const code = `export async function getData() { "use cache" }`
172- await expect ( run ( code , 'client' ) ) . resolves . toBeUndefined ( )
173- await expect ( run ( code , 'ssr' ) ) . resolves . toBeUndefined ( )
231+ await expect ( run ( code , 'client' ) ) . rejects . toThrow ( )
232+ await expect ( runWithoutError ( code , 'client' ) ) . resolves . toBeUndefined ( )
233+ await expect ( runWithoutError ( code , 'ssr' ) ) . resolves . toBeUndefined ( )
174234 } )
175235
176236 it ( 'wraps inline directives inside use-server modules without owning metadata' , async ( ) => {
@@ -187,8 +247,20 @@ export async function action() {
187247 return cached();
188248}
189249` )
190- expect ( result ?. code ) . toContain ( 'cache($$hoist_' )
191- expect ( result ?. code ) . not . toContain ( '$$ReactServer.registerServerReference' )
250+ expect ( result ?. code ) . toMatchInlineSnapshot ( `
251+ "/* __vite_rsc_server_function_directives__ */
252+
253+
254+ "use server";
255+ export async function action() {
256+ const cached = /* #__PURE__ */ cache($$hoist_bf311121ee97_0_cached, "use cache", "inline");
257+ return cached();
258+ }
259+
260+ ;export async function $$hoist_bf311121ee97_0_cached() { "use cache"; return 1 };
261+ /* #__PURE__ */ Object.defineProperty($$hoist_bf311121ee97_0_cached, "name", { value: "cached" });
262+ "
263+ ` )
192264 expect ( manager . serverReferenceMetaMap [ '/src/example.ts' ] ) . toEqual ( {
193265 importId : '/src/example.ts' ,
194266 referenceKey : 'existing' ,
@@ -226,6 +298,20 @@ export async function action() {
226298 ) . rejects . toThrow ( 'non async function' )
227299 } )
228300
301+ it . each ( [ 'this' , 'super' , 'arguments' ] as const ) (
302+ 'rejects %s inside inline directive functions' ,
303+ async ( expression ) => {
304+ const { run } = createHarness ( [ cacheDirective ( ) ] )
305+ const code =
306+ expression === 'super'
307+ ? `class Base { value() {} } class Test extends Base { static async value() { "use cache"; return super.value() } }`
308+ : `export async function getData() { "use cache"; return ${ expression } }`
309+ await expect ( run ( code ) ) . rejects . toThrow (
310+ `"use cache" functions cannot use ${ JSON . stringify ( expression ) } ` ,
311+ )
312+ } ,
313+ )
314+
229315 it ( 'respects source and id prefilters and clears stale metadata' , async ( ) => {
230316 const test = vi . fn ( ( ) => false )
231317 const filter = vi . fn ( ( ) => false )
0 commit comments