Skip to content

Commit 06e5841

Browse files
committed
fix(rsc): preserve custom directive references
1 parent 2ae42d1 commit 06e5841

3 files changed

Lines changed: 56 additions & 7 deletions

File tree

packages/plugin-rsc/src/plugins/server-function-directives.test.ts

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Rollup } from 'vite'
22
import { describe, expect, it, vi } from 'vitest'
33
import {
4+
SERVER_FUNCTION_DIRECTIVE_MARKER,
45
vitePluginServerFunctionDirectives,
56
type ServerFunctionDirective,
67
} from './server-function-directives'
@@ -181,7 +182,7 @@ export const metadata = { title: "test" };
181182
expect(expandExportAll).toHaveBeenCalledOnce()
182183
expect(result?.code).toMatchInlineSnapshot(`
183184
"/* __vite_rsc_server_function_directives__ */
184-
185+
import * as $$ReactServer from "/rsc-runtime.js";
185186
186187
/* "use cache" */;
187188
async function getData() { return 1 }
@@ -199,17 +200,38 @@ export const metadata = { title: "test" };
199200
)
200201
})
201202

202-
it('creates module proxies in client', async () => {
203+
it('ignores jsxDEV source metadata while rejecting user this expressions', async () => {
203204
const { run } = createHarness([cacheDirective()])
205+
await expect(
206+
run(`
207+
export async function Page() {
208+
"use cache";
209+
return _jsxDEV("p", { children: "test" }, void 0, false, { fileName: "page.tsx" }, this);
210+
}
211+
`),
212+
).resolves.toBeDefined()
213+
await expect(
214+
run(`export async function getData() { "use cache"; return this.value }`),
215+
).rejects.toThrow('"use cache" functions cannot use "this"')
216+
})
217+
218+
it('creates module proxies in client', async () => {
219+
const { manager, run } = createHarness([cacheDirective()])
204220
const result = await run(
205221
`"use cache"; export async function getData() { return 1 }`,
206222
'client',
207223
)
208224
expect(result?.code).toMatchInlineSnapshot(`
209-
"import * as $$ReactClient from "/browser-runtime.js";
225+
"/* __vite_rsc_server_function_directives__ */
226+
import * as $$ReactClient from "/browser-runtime.js";
210227
export const getData = /* #__PURE__ */ $$ReactClient.createServerReference("53eb073e2100#getData",$$ReactClient.callServer,undefined,undefined,"getData");
211228
"
212229
`)
230+
expect(manager.serverReferenceMetaMap['/src/example.ts']).toEqual({
231+
importId: '/src/example.ts',
232+
referenceKey: '53eb073e2100',
233+
exportNames: ['getData'],
234+
})
213235
})
214236

215237
it('creates module proxies in SSR', async () => {
@@ -219,7 +241,8 @@ export const metadata = { title: "test" };
219241
'ssr',
220242
)
221243
expect(result?.code).toMatchInlineSnapshot(`
222-
"import * as $$ReactClient from "/ssr-runtime.js";
244+
"/* __vite_rsc_server_function_directives__ */
245+
import * as $$ReactClient from "/ssr-runtime.js";
223246
export const getData = /* #__PURE__ */ $$ReactClient.createServerReference("53eb073e2100#getData",$$ReactClient.callServer,undefined,undefined,"getData");
224247
"
225248
`)
@@ -386,6 +409,18 @@ export class CacheClass {
386409
expect(manager.serverReferenceMetaMap['/src/example.ts']).toBeUndefined()
387410
})
388411

412+
it('preserves metadata when transformed output is processed again', async () => {
413+
const { manager, run } = createHarness([cacheDirective()])
414+
const result = await run(
415+
`"use cache"; export async function getData() { return 1 }`,
416+
)
417+
expect(result?.code).toContain(SERVER_FUNCTION_DIRECTIVE_MARKER)
418+
const metadata = manager.serverReferenceMetaMap['/src/example.ts']
419+
420+
await expect(run(result!.code!)).resolves.toBeUndefined()
421+
expect(manager.serverReferenceMetaMap['/src/example.ts']).toEqual(metadata)
422+
})
423+
389424
it('rejects overlapping module directive definitions', async () => {
390425
const { run } = createHarness([
391426
cacheDirective({ directive: /^use cache/ }),

packages/plugin-rsc/src/plugins/server-function-directives.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ export function vitePluginServerFunctionDirectives(options: Options): Plugin {
167167
name: 'rsc:server-function-directives',
168168
transform: {
169169
async handler(code, id) {
170+
if (code.includes(SERVER_FUNCTION_DIRECTIVE_MARKER)) return
171+
170172
const active = definitions.filter(
171173
(definition) =>
172174
(definition.test?.(code) ?? code.includes('use ')) &&
@@ -244,8 +246,13 @@ export function vitePluginServerFunctionDirectives(options: Options): Plugin {
244246
`$$ReactClient.createServerReference(${JSON.stringify(normalizedId + '#' + name)},$$ReactClient.callServer,undefined,${this.environment.mode === 'dev' ? '$$ReactClient.findSourceMapURL' : 'undefined'},${JSON.stringify(name)})`,
245247
})
246248
if (!result?.output.hasChanged()) return
249+
manager.serverReferenceMetaMap[id] = {
250+
importId: id,
251+
referenceKey: normalizedId,
252+
exportNames: result.exportNames,
253+
}
247254
result.output.prepend(
248-
`import * as $$ReactClient from ${JSON.stringify(this.environment.name === options.browserEnvironmentName ? options.browserRuntime : options.ssrRuntime)};\n`,
255+
`${SERVER_FUNCTION_DIRECTIVE_MARKER}\nimport * as $$ReactClient from ${JSON.stringify(this.environment.name === options.browserEnvironmentName ? options.browserRuntime : options.ssrRuntime)};\n`,
249256
)
250257
return {
251258
code: result.output.toString(),
@@ -309,6 +316,7 @@ export function vitePluginServerFunctionDirectives(options: Options): Plugin {
309316
moduleDirective,
310317
moduleRuntime: (value, name, meta) => {
311318
if (!moduleMatch) return value
319+
needsReactRuntime = true
312320
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)})`
313321
},
314322
inlineRuntime: (value, name, meta) => {

packages/plugin-rsc/src/transforms/hoist.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ export function transformHoistInlineDirective(
237237

238238
function validateForbiddenExpressions(body: BlockStatement, directive: string) {
239239
walk(body, {
240-
enter(node) {
240+
enter(node, parent) {
241241
if (
242242
node !== body &&
243243
(node.type === 'FunctionDeclaration' ||
@@ -246,8 +246,14 @@ function validateForbiddenExpressions(body: BlockStatement, directive: string) {
246246
this.skip()
247247
return
248248
}
249+
const isJsxDevSourceThis =
250+
node.type === 'ThisExpression' &&
251+
parent?.type === 'CallExpression' &&
252+
parent.arguments.at(-1) === node &&
253+
parent.callee.type === 'Identifier' &&
254+
/(?:^|_)jsxDEV$/.test(parent.callee.name)
249255
const expression =
250-
node.type === 'ThisExpression'
256+
node.type === 'ThisExpression' && !isJsxDevSourceThis
251257
? 'this'
252258
: node.type === 'Super'
253259
? 'super'

0 commit comments

Comments
 (0)