From 601d3e402828c648af16e2090837dcad12ca63fb Mon Sep 17 00:00:00 2001
From: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com>
Date: Tue, 4 Aug 2026 15:23:14 +0900
Subject: [PATCH 1/5] refactor(rsc): expose export value nodes
Co-authored-by: OpenCode
---
.oxfmtrc.json | 1 +
.../plugin-rsc/e2e/use-cache-callable.test.ts | 2 +
.../callable-cache-plugin.ts | 5 ++
.../file-directive-from-server/action.ts | 5 ++
.../file-directive-from-server/client.tsx | 6 ++
.../file-directive-from-server/server.tsx | 3 +-
.../source-map/wrap-export/classes.js | 7 +++
.../wrap-export/classes.js.map.snap.md | 34 ++++++++++
.../source-map/wrap-export/classes.js.snap.md | 63 +++++++++++++++++++
.../wrap-export/default-anonymous-class.js | 3 +
.../default-anonymous-class.js.map.snap.md | 21 +++++++
.../default-anonymous-class.js.snap.md | 41 ++++++++++++
.../wrap-export/default-comments.js | 5 ++
.../default-comments.js.map.snap.md | 25 ++++++++
.../wrap-export/default-comments.js.snap.md | 47 ++++++++++++++
.../wrap-export/local-binding-preservation.js | 6 ++
.../local-binding-preservation.js.map.snap.md | 25 ++++++++
.../local-binding-preservation.js.snap.md | 54 ++++++++++++++++
.../wrap-export/multiple-aliases.js | 7 +++
.../multiple-aliases.js.map.snap.md | 25 ++++++++
.../wrap-export/multiple-aliases.js.snap.md | 55 ++++++++++++++++
.../source-map/wrap-export/recursive.js | 6 ++
.../wrap-export/recursive.js.map.snap.md | 24 +++++++
.../wrap-export/recursive.js.snap.md | 49 +++++++++++++++
.../transforms/module-export-effect.test.ts | 4 +-
.../src/transforms/module-export-scan.test.ts | 9 ++-
.../src/transforms/module-export-scan.ts | 23 ++++++-
.../src/transforms/wrap-export.test.ts | 35 +++++++++--
.../plugin-rsc/src/transforms/wrap-export.ts | 9 +++
29 files changed, 589 insertions(+), 10 deletions(-)
create mode 100644 packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/classes.js
create mode 100644 packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/classes.js.map.snap.md
create mode 100644 packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/classes.js.snap.md
create mode 100644 packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/default-anonymous-class.js
create mode 100644 packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/default-anonymous-class.js.map.snap.md
create mode 100644 packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/default-anonymous-class.js.snap.md
create mode 100644 packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/default-comments.js
create mode 100644 packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/default-comments.js.map.snap.md
create mode 100644 packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/default-comments.js.snap.md
create mode 100644 packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/local-binding-preservation.js
create mode 100644 packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/local-binding-preservation.js.map.snap.md
create mode 100644 packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/local-binding-preservation.js.snap.md
create mode 100644 packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/multiple-aliases.js
create mode 100644 packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/multiple-aliases.js.map.snap.md
create mode 100644 packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/multiple-aliases.js.snap.md
create mode 100644 packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/recursive.js
create mode 100644 packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/recursive.js.map.snap.md
create mode 100644 packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/recursive.js.snap.md
diff --git a/.oxfmtrc.json b/.oxfmtrc.json
index ec2b1779f..60b34effb 100644
--- a/.oxfmtrc.json
+++ b/.oxfmtrc.json
@@ -10,6 +10,7 @@
},
"ignorePatterns": [
"*.snap.*",
+ "packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/default-comments.js",
"typescript-eslint/",
"packages/*/CHANGELOG.md",
"playground-temp/",
diff --git a/packages/plugin-rsc/e2e/use-cache-callable.test.ts b/packages/plugin-rsc/e2e/use-cache-callable.test.ts
index e30eb24a6..5a2ae9be7 100644
--- a/packages/plugin-rsc/e2e/use-cache-callable.test.ts
+++ b/packages/plugin-rsc/e2e/use-cache-callable.test.ts
@@ -97,11 +97,13 @@ function defineTests(f: Fixture) {
const example = page.getByTestId('file-directive-from-server')
const submissionCount = example.getByTestId('submission-count')
const executionCount = example.getByTestId('execution-count')
+ const ordinaryExports = example.getByTestId('ordinary-exports')
const result = example.getByTestId('result')
const argument = example.getByRole('textbox', { name: 'Cache key' })
await page.getByRole('button', { name: 'Reset' }).click()
await expect(submissionCount).toHaveText('0')
await expect(executionCount).toHaveText('0')
+ await expect(ordinaryExports).toHaveText('cached metadata: cache')
await expect(result).toHaveText('not called')
// The wrapped export is passed from a Server Component to a Client Component.
diff --git a/packages/plugin-rsc/examples/use-cache-callable/callable-cache-plugin.ts b/packages/plugin-rsc/examples/use-cache-callable/callable-cache-plugin.ts
index f84717491..b2b6da65e 100644
--- a/packages/plugin-rsc/examples/use-cache-callable/callable-cache-plugin.ts
+++ b/packages/plugin-rsc/examples/use-cache-callable/callable-cache-plugin.ts
@@ -37,6 +37,11 @@ export function callableCachePlugin(): Plugin {
const result = hasDirective(ast.body, directive)
? transformWrapExport(code, ast, {
runtime,
+ // Next.js permits object and array exports for metadata and
+ // viewport values in "use cache" modules.
+ filter: (_name, meta) =>
+ meta.valueNode?.type !== 'ObjectExpression' &&
+ meta.valueNode?.type !== 'ArrayExpression',
rejectNonAsyncFunction: true,
})
: transformHoistInlineDirective(code, ast, {
diff --git a/packages/plugin-rsc/examples/use-cache-callable/src/features/file-directive-from-server/action.ts b/packages/plugin-rsc/examples/use-cache-callable/src/features/file-directive-from-server/action.ts
index c912d165f..2de9df9ca 100644
--- a/packages/plugin-rsc/examples/use-cache-callable/src/features/file-directive-from-server/action.ts
+++ b/packages/plugin-rsc/examples/use-cache-callable/src/features/file-directive-from-server/action.ts
@@ -2,6 +2,11 @@
import { state } from './state'
+// Ordinary values remain available from "use cache" modules without becoming
+// callable server references.
+export const metadata = { title: 'cached metadata' }
+export const tags = ['cache']
+
export async function cachedFromServer(formData: FormData) {
const argument = String(formData.get('argument'))
state.executionCount++
diff --git a/packages/plugin-rsc/examples/use-cache-callable/src/features/file-directive-from-server/client.tsx b/packages/plugin-rsc/examples/use-cache-callable/src/features/file-directive-from-server/client.tsx
index 9ffa05f25..ff58bf808 100644
--- a/packages/plugin-rsc/examples/use-cache-callable/src/features/file-directive-from-server/client.tsx
+++ b/packages/plugin-rsc/examples/use-cache-callable/src/features/file-directive-from-server/client.tsx
@@ -5,6 +5,7 @@ import { useState } from 'react'
export function FileDirectiveFromServerClient(props: {
action: (formData: FormData) => Promise
executionCount: number
+ ordinaryExports: string
resetAction: () => Promise
result: string
}) {
@@ -35,6 +36,11 @@ export function FileDirectiveFromServerClient(props: {
{props.executionCount}
+ Ordinary exports:{' '}
+
+
Result:
diff --git a/packages/plugin-rsc/examples/use-cache-callable/src/features/file-directive-from-server/server.tsx b/packages/plugin-rsc/examples/use-cache-callable/src/features/file-directive-from-server/server.tsx
index e6c340114..3f09befef 100644
--- a/packages/plugin-rsc/examples/use-cache-callable/src/features/file-directive-from-server/server.tsx
+++ b/packages/plugin-rsc/examples/use-cache-callable/src/features/file-directive-from-server/server.tsx
@@ -1,4 +1,4 @@
-import { cachedFromServer } from './action'
+import { cachedFromServer, metadata, tags } from './action'
import { FileDirectiveFromServerClient } from './client'
import { resetAction } from './reset'
import { state } from './state'
@@ -8,6 +8,7 @@ export function FileDirectiveFromServer() {
diff --git a/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/classes.js b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/classes.js
new file mode 100644
index 000000000..8f55ccbeb
--- /dev/null
+++ b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/classes.js
@@ -0,0 +1,7 @@
+'use server'
+
+export class NamedClass {}
+
+export const ClassExpression = class InnerClass {}
+
+export default class DefaultClass {}
diff --git a/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/classes.js.map.snap.md b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/classes.js.map.snap.md
new file mode 100644
index 000000000..4667dfeec
--- /dev/null
+++ b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/classes.js.map.snap.md
@@ -0,0 +1,34 @@
+## wrap-export
+
+```txt
+(0:0) "'use server'\n" --> (0:0) "'use server'\n"
+(2:7) "class NamedClass {}\n" --> (2:0) "class NamedClass {}\n"
+(4:7) "const" --> (4:0) "let"
+(4:12) " ClassExpression = class InnerClass {}\n" --> (4:3) " ClassExpression = class InnerClass {}\n"
+(6:15) "class DefaultClass {}\n" --> (6:0) "class DefaultClass {}\n"
+(2:0) "export class NamedClass {}\n" --> (7:0) "NamedClass = /* #__PURE__ */ registerServerReference(NamedClass, \"NamedClass\");\n"
+(2:0) "export class NamedClass {}\n" --> (8:0) "export { NamedClass };\n"
+(4:0) "export const ClassExpression = class InnerClass {}\n" --> (9:0) "ClassExpression = /* #__PURE__ */ registerServerReference(ClassExpression, \"ClassExpression\");\n"
+(4:0) "export const ClassExpression = class InnerClass {}\n" --> (10:0) "export { ClassExpression };\n"
+[unmapped] --> (11:0) ";\n"
+[unmapped] --> (12:0) "const $$wrap_DefaultClass = /* #__PURE__ */ registerServerReference(DefaultClass, \"default\");\n"
+[unmapped] --> (13:0) "export { $$wrap_DefaultClass as default };\n"
+```
+
+## module-export-effect
+
+```txt
+(0:0) "'use server'\n" --> (0:0) "'use server'\n"
+(2:7) "class NamedClass {}\n" --> (2:0) "class NamedClass {}\n"
+(4:7) "const ClassExpression = class InnerClass {}\n" --> (4:0) "const ClassExpression = class InnerClass {}\n"
+(6:15) "class DefaultClass {}\n" --> (6:0) "class DefaultClass {}\n"
+(2:0) "export class NamedClass {}\n" --> (7:0) "\n"
+(2:0) "export class NamedClass {}\n" --> (8:0) "registerServerReference(NamedClass, \"NamedClass\");\n"
+(2:0) "export class NamedClass {}\n" --> (9:0) "export { NamedClass };\n"
+(4:0) "export const ClassExpression = class InnerClass {}\n" --> (10:0) "\n"
+(4:0) "export const ClassExpression = class InnerClass {}\n" --> (11:0) "registerServerReference(ClassExpression, \"ClassExpression\");\n"
+(4:0) "export const ClassExpression = class InnerClass {}\n" --> (12:0) "export { ClassExpression };\n"
+(6:0) "export default class DefaultClass {}\n" --> (13:0) "\n"
+(6:0) "export default class DefaultClass {}\n" --> (14:0) "registerServerReference(DefaultClass, \"default\");\n"
+(6:0) "export default class DefaultClass {}\n" --> (15:0) "export default DefaultClass;\n"
+```
diff --git a/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/classes.js.snap.md b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/classes.js.snap.md
new file mode 100644
index 000000000..358c7c1b2
--- /dev/null
+++ b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/classes.js.snap.md
@@ -0,0 +1,63 @@
+## Input
+
+```js
+'use server'
+
+export class NamedClass {}
+
+export const ClassExpression = class InnerClass {}
+
+export default class DefaultClass {}
+```
+
+## wrap-export
+
+**Status:** transformed
+
+**References:** NamedClass, ClassExpression, default
+
+[Source map visualization](https://evanw.github.io/source-map-visualization/#NDY1ACd1c2Ugc2VydmVyJwoKY2xhc3MgTmFtZWRDbGFzcyB7fQoKbGV0IENsYXNzRXhwcmVzc2lvbiA9IGNsYXNzIElubmVyQ2xhc3Mge30KCmNsYXNzIERlZmF1bHRDbGFzcyB7fQpOYW1lZENsYXNzID0gLyogI19fUFVSRV9fICovIHJlZ2lzdGVyU2VydmVyUmVmZXJlbmNlKE5hbWVkQ2xhc3MsICJOYW1lZENsYXNzIik7CmV4cG9ydCB7IE5hbWVkQ2xhc3MgfTsKQ2xhc3NFeHByZXNzaW9uID0gLyogI19fUFVSRV9fICovIHJlZ2lzdGVyU2VydmVyUmVmZXJlbmNlKENsYXNzRXhwcmVzc2lvbiwgIkNsYXNzRXhwcmVzc2lvbiIpOwpleHBvcnQgeyBDbGFzc0V4cHJlc3Npb24gfTsKOwpjb25zdCAkJHdyYXBfRGVmYXVsdENsYXNzID0gLyogI19fUFVSRV9fICovIHJlZ2lzdGVyU2VydmVyUmVmZXJlbmNlKERlZmF1bHRDbGFzcywgImRlZmF1bHQiKTsKZXhwb3J0IHsgJCR3cmFwX0RlZmF1bHRDbGFzcyBhcyBkZWZhdWx0IH07CjM4NwB7InZlcnNpb24iOjMsInNvdXJjZXMiOlsiIl0sInNvdXJjZXNDb250ZW50IjpbIid1c2Ugc2VydmVyJ1xuXG5leHBvcnQgY2xhc3MgTmFtZWRDbGFzcyB7fVxuXG5leHBvcnQgY29uc3QgQ2xhc3NFeHByZXNzaW9uID0gY2xhc3MgSW5uZXJDbGFzcyB7fVxuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBEZWZhdWx0Q2xhc3Mge31cbiJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxDQUFDLEdBQUcsQ0FBQyxNQUFNOztBQUVKLEtBQUssQ0FBQyxVQUFVLENBQUMsQ0FBQzs7QUFFbEIsR0FBSyxDQUFDLGVBQWUsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLFVBQVUsQ0FBQyxDQUFDOztBQUVsQyxLQUFLLENBQUMsWUFBWSxDQUFDLENBQUM7QUFKbkM7QUFBQTtBQUVBO0FBQUE7Ozs7In0=)
+
+```js
+'use server'
+
+class NamedClass {}
+
+let ClassExpression = class InnerClass {}
+
+class DefaultClass {}
+NamedClass = /* #__PURE__ */ registerServerReference(NamedClass, "NamedClass");
+export { NamedClass };
+ClassExpression = /* #__PURE__ */ registerServerReference(ClassExpression, "ClassExpression");
+export { ClassExpression };
+;
+const $$wrap_DefaultClass = /* #__PURE__ */ registerServerReference(DefaultClass, "default");
+export { $$wrap_DefaultClass as default };
+```
+
+## module-export-effect
+
+**Status:** transformed
+
+**References:** NamedClass, ClassExpression, default
+
+[Source map visualization](https://evanw.github.io/source-map-visualization/#MzQ3ACd1c2Ugc2VydmVyJwoKY2xhc3MgTmFtZWRDbGFzcyB7fQoKY29uc3QgQ2xhc3NFeHByZXNzaW9uID0gY2xhc3MgSW5uZXJDbGFzcyB7fQoKY2xhc3MgRGVmYXVsdENsYXNzIHt9CgpyZWdpc3RlclNlcnZlclJlZmVyZW5jZShOYW1lZENsYXNzLCAiTmFtZWRDbGFzcyIpOwpleHBvcnQgeyBOYW1lZENsYXNzIH07CgpyZWdpc3RlclNlcnZlclJlZmVyZW5jZShDbGFzc0V4cHJlc3Npb24sICJDbGFzc0V4cHJlc3Npb24iKTsKZXhwb3J0IHsgQ2xhc3NFeHByZXNzaW9uIH07CgpyZWdpc3RlclNlcnZlclJlZmVyZW5jZShEZWZhdWx0Q2xhc3MsICJkZWZhdWx0Iik7CmV4cG9ydCBkZWZhdWx0IERlZmF1bHRDbGFzczsKNDA5AHsidmVyc2lvbiI6Mywic291cmNlcyI6WyIiXSwic291cmNlc0NvbnRlbnQiOlsiJ3VzZSBzZXJ2ZXInXG5cbmV4cG9ydCBjbGFzcyBOYW1lZENsYXNzIHt9XG5cbmV4cG9ydCBjb25zdCBDbGFzc0V4cHJlc3Npb24gPSBjbGFzcyBJbm5lckNsYXNzIHt9XG5cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIERlZmF1bHRDbGFzcyB7fVxuIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLENBQUMsR0FBRyxDQUFDLE1BQU07O0FBRUosS0FBSyxDQUFDLFVBQVUsQ0FBQyxDQUFDOztBQUVsQixLQUFLLENBQUMsZUFBZSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsVUFBVSxDQUFDLENBQUM7O0FBRWxDLEtBQUssQ0FBQyxZQUFZLENBQUMsQ0FBQztBQUpuQztBQUFBO0FBQUE7QUFFQTtBQUFBO0FBQUE7QUFFQTtBQUFBO0FBQUE7In0=)
+
+```js
+'use server'
+
+class NamedClass {}
+
+const ClassExpression = class InnerClass {}
+
+class DefaultClass {}
+
+registerServerReference(NamedClass, "NamedClass");
+export { NamedClass };
+
+registerServerReference(ClassExpression, "ClassExpression");
+export { ClassExpression };
+
+registerServerReference(DefaultClass, "default");
+export default DefaultClass;
+```
diff --git a/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/default-anonymous-class.js b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/default-anonymous-class.js
new file mode 100644
index 000000000..4bd0d1182
--- /dev/null
+++ b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/default-anonymous-class.js
@@ -0,0 +1,3 @@
+'use server'
+
+export default class {}
diff --git a/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/default-anonymous-class.js.map.snap.md b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/default-anonymous-class.js.map.snap.md
new file mode 100644
index 000000000..0ecfabcc0
--- /dev/null
+++ b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/default-anonymous-class.js.map.snap.md
@@ -0,0 +1,21 @@
+## wrap-export
+
+```txt
+(0:0) "'use server'\n" --> (0:0) "'use server'\n"
+(2:0) "export default " --> (2:0) "const $$default = "
+(2:15) "class {}\n" --> (2:18) "class {}\n"
+[unmapped] --> (3:0) ";\n"
+[unmapped] --> (4:0) "const $$wrap_$$default = /* #__PURE__ */ registerServerReference($$default, \"default\");\n"
+[unmapped] --> (5:0) "export { $$wrap_$$default as default };\n"
+```
+
+## module-export-effect
+
+```txt
+(0:0) "'use server'\n" --> (0:0) "'use server'\n"
+(2:6) " default " --> (2:0) "const $$effect_default = "
+(2:15) "class {}\n" --> (2:25) "class {}\n"
+(2:0) "export default class {}\n" --> (3:0) "\n"
+(2:0) "export default class {}\n" --> (4:0) "registerServerReference($$effect_default, \"default\");\n"
+(2:0) "export default class {}\n" --> (5:0) "export default $$effect_default;\n"
+```
diff --git a/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/default-anonymous-class.js.snap.md b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/default-anonymous-class.js.snap.md
new file mode 100644
index 000000000..7b7f84fb7
--- /dev/null
+++ b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/default-anonymous-class.js.snap.md
@@ -0,0 +1,41 @@
+## Input
+
+```js
+'use server'
+
+export default class {}
+```
+
+## wrap-export
+
+**Status:** transformed
+
+**References:** default
+
+[Source map visualization](https://evanw.github.io/source-map-visualization/#MTcxACd1c2Ugc2VydmVyJwoKY29uc3QgJCRkZWZhdWx0ID0gY2xhc3Mge30KOwpjb25zdCAkJHdyYXBfJCRkZWZhdWx0ID0gLyogI19fUFVSRV9fICovIHJlZ2lzdGVyU2VydmVyUmVmZXJlbmNlKCQkZGVmYXVsdCwgImRlZmF1bHQiKTsKZXhwb3J0IHsgJCR3cmFwXyQkZGVmYXVsdCBhcyBkZWZhdWx0IH07CjE3MQB7InZlcnNpb24iOjMsInNvdXJjZXMiOlsiIl0sInNvdXJjZXNDb250ZW50IjpbIid1c2Ugc2VydmVyJ1xuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyB7fVxuIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLENBQUMsR0FBRyxDQUFDLE1BQU07O0FBRVgsa0JBQWUsS0FBSyxDQUFDLENBQUM7Ozs7In0=)
+
+```js
+'use server'
+
+const $$default = class {}
+;
+const $$wrap_$$default = /* #__PURE__ */ registerServerReference($$default, "default");
+export { $$wrap_$$default as default };
+```
+
+## module-export-effect
+
+**Status:** transformed
+
+**References:** default
+
+[Source map visualization](https://evanw.github.io/source-map-visualization/#MTM2ACd1c2Ugc2VydmVyJwoKY29uc3QgJCRlZmZlY3RfZGVmYXVsdCA9IGNsYXNzIHt9CgpyZWdpc3RlclNlcnZlclJlZmVyZW5jZSgkJGVmZmVjdF9kZWZhdWx0LCAiZGVmYXVsdCIpOwpleHBvcnQgZGVmYXVsdCAkJGVmZmVjdF9kZWZhdWx0OwoxODQAeyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIiJdLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHNlcnZlcidcblxuZXhwb3J0IGRlZmF1bHQgY2xhc3Mge31cbiJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxDQUFDLEdBQUcsQ0FBQyxNQUFNOztBQUVMLHlCQUFTLEtBQUssQ0FBQyxDQUFDO0FBQXRCO0FBQUE7QUFBQTsifQ==)
+
+```js
+'use server'
+
+const $$effect_default = class {}
+
+registerServerReference($$effect_default, "default");
+export default $$effect_default;
+```
diff --git a/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/default-comments.js b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/default-comments.js
new file mode 100644
index 000000000..b4c2fe4b9
--- /dev/null
+++ b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/default-comments.js
@@ -0,0 +1,5 @@
+'use server'
+
+export /* before */ default /* after */ async function () {
+ return 'default comments called'
+}
diff --git a/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/default-comments.js.map.snap.md b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/default-comments.js.map.snap.md
new file mode 100644
index 000000000..50dc072a3
--- /dev/null
+++ b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/default-comments.js.map.snap.md
@@ -0,0 +1,25 @@
+## wrap-export
+
+```txt
+(0:0) "'use server'\n" --> (0:0) "'use server'\n"
+(2:0) "export /* before */ default /* after */ " --> (2:0) "const $$default = "
+(2:40) "async function () {\n" --> (2:18) "async function () {\n"
+(3:0) " return 'default comments called'\n" --> (3:0) " return 'default comments called'\n"
+(4:0) "}\n" --> (4:0) "}\n"
+[unmapped] --> (5:0) ";\n"
+[unmapped] --> (6:0) "const $$wrap_$$default = /* #__PURE__ */ registerServerReference($$default, \"default\");\n"
+[unmapped] --> (7:0) "export { $$wrap_$$default as default };\n"
+```
+
+## module-export-effect
+
+```txt
+(0:0) "'use server'\n" --> (0:0) "'use server'\n"
+(2:6) " /* before */ default /* after */ " --> (2:0) "const $$effect_default = "
+(2:40) "async function () {\n" --> (2:25) "async function () {\n"
+(3:0) " return 'default comments called'\n" --> (3:0) " return 'default comments called'\n"
+(4:0) "}\n" --> (4:0) "}\n"
+(2:0) "export /* before */ default /* after */ async function () {\n" --> (5:0) "\n"
+(2:0) "export /* before */ default /* after */ async function () {\n" --> (6:0) "registerServerReference($$effect_default, \"default\");\n"
+(2:0) "export /* before */ default /* after */ async function () {\n" --> (7:0) "export default $$effect_default;\n"
+```
diff --git a/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/default-comments.js.snap.md b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/default-comments.js.snap.md
new file mode 100644
index 000000000..32756539d
--- /dev/null
+++ b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/default-comments.js.snap.md
@@ -0,0 +1,47 @@
+## Input
+
+```js
+'use server'
+
+export /* before */ default /* after */ async function () {
+ return 'default comments called'
+}
+```
+
+## wrap-export
+
+**Status:** transformed
+
+**References:** default
+
+[Source map visualization](https://evanw.github.io/source-map-visualization/#MjE5ACd1c2Ugc2VydmVyJwoKY29uc3QgJCRkZWZhdWx0ID0gYXN5bmMgZnVuY3Rpb24gKCkgewogIHJldHVybiAnZGVmYXVsdCBjb21tZW50cyBjYWxsZWQnCn0KOwpjb25zdCAkJHdyYXBfJCRkZWZhdWx0ID0gLyogI19fUFVSRV9fICovIHJlZ2lzdGVyU2VydmVyUmVmZXJlbmNlKCQkZGVmYXVsdCwgImRlZmF1bHQiKTsKZXhwb3J0IHsgJCR3cmFwXyQkZGVmYXVsdCBhcyBkZWZhdWx0IH07CjMyOQB7InZlcnNpb24iOjMsInNvdXJjZXMiOlsiIl0sInNvdXJjZXNDb250ZW50IjpbIid1c2Ugc2VydmVyJ1xuXG5leHBvcnQgLyogYmVmb3JlICovIGRlZmF1bHQgLyogYWZ0ZXIgKi8gYXN5bmMgZnVuY3Rpb24gKCkge1xuICByZXR1cm4gJ2RlZmF1bHQgY29tbWVudHMgY2FsbGVkJ1xufVxuIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLENBQUMsR0FBRyxDQUFDLE1BQU07O0FBRVgsa0JBQXdDLEtBQUssQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDMUQsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsTUFBTTtBQUNqQzs7OzsifQ==)
+
+```js
+'use server'
+
+const $$default = async function () {
+ return 'default comments called'
+}
+;
+const $$wrap_$$default = /* #__PURE__ */ registerServerReference($$default, "default");
+export { $$wrap_$$default as default };
+```
+
+## module-export-effect
+
+**Status:** transformed
+
+**References:** default
+
+[Source map visualization](https://evanw.github.io/source-map-visualization/#MTg0ACd1c2Ugc2VydmVyJwoKY29uc3QgJCRlZmZlY3RfZGVmYXVsdCA9IGFzeW5jIGZ1bmN0aW9uICgpIHsKICByZXR1cm4gJ2RlZmF1bHQgY29tbWVudHMgY2FsbGVkJwp9CgpyZWdpc3RlclNlcnZlclJlZmVyZW5jZSgkJGVmZmVjdF9kZWZhdWx0LCAiZGVmYXVsdCIpOwpleHBvcnQgZGVmYXVsdCAkJGVmZmVjdF9kZWZhdWx0OwozNDEAeyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIiJdLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHNlcnZlcidcblxuZXhwb3J0IC8qIGJlZm9yZSAqLyBkZWZhdWx0IC8qIGFmdGVyICovIGFzeW5jIGZ1bmN0aW9uICgpIHtcbiAgcmV0dXJuICdkZWZhdWx0IGNvbW1lbnRzIGNhbGxlZCdcbn1cbiJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxDQUFDLEdBQUcsQ0FBQyxNQUFNOztBQUVMLHlCQUFrQyxLQUFLLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQzFELENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLE1BQU07QUFDakM7QUFGQTtBQUFBO0FBQUE7In0=)
+
+```js
+'use server'
+
+const $$effect_default = async function () {
+ return 'default comments called'
+}
+
+registerServerReference($$effect_default, "default");
+export default $$effect_default;
+```
diff --git a/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/local-binding-preservation.js b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/local-binding-preservation.js
new file mode 100644
index 000000000..06e0bcbf2
--- /dev/null
+++ b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/local-binding-preservation.js
@@ -0,0 +1,6 @@
+export async function direct() {}
+
+const indirect = async () => {}
+export { indirect }
+
+consume(direct, indirect)
diff --git a/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/local-binding-preservation.js.map.snap.md b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/local-binding-preservation.js.map.snap.md
new file mode 100644
index 000000000..34b62c715
--- /dev/null
+++ b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/local-binding-preservation.js.map.snap.md
@@ -0,0 +1,25 @@
+## wrap-export
+
+```txt
+(0:7) "async function direct() {}\n" --> (0:0) "async function direct() {}\n"
+(2:0) "const indirect = async () => {}\n" --> (2:0) "const indirect = async () => {}\n"
+(5:0) "consume(direct, indirect)\n" --> (5:0) "consume(direct, indirect)\n"
+(0:0) "export async function direct() {}\n" --> (6:0) "direct = /* #__PURE__ */ registerServerReference(direct, \"direct\");\n"
+(0:0) "export async function direct() {}\n" --> (7:0) "export { direct };\n"
+[unmapped] --> (8:0) ";\n"
+[unmapped] --> (9:0) "const $$wrap_indirect = /* #__PURE__ */ registerServerReference(indirect, \"indirect\");\n"
+[unmapped] --> (10:0) "export { $$wrap_indirect as indirect };\n"
+```
+
+## module-export-effect
+
+```txt
+(0:7) "async function direct() {}\n" --> (0:0) "async function direct() {}\n"
+(2:0) "const indirect = async () => {}\n" --> (2:0) "const indirect = async () => {}\n"
+(3:0) "export { indirect }\n" --> (3:0) "export { indirect }\n"
+(5:0) "consume(direct, indirect)\n" --> (5:0) "consume(direct, indirect)\n"
+(0:0) "export async function direct() {}\n" --> (6:0) "\n"
+(0:0) "export async function direct() {}\n" --> (7:0) "registerServerReference(direct, \"direct\");\n"
+(0:0) "export async function direct() {}\n" --> (8:0) "export { direct };\n"
+[unmapped] --> (10:0) "registerServerReference(indirect, \"indirect\");"
+```
diff --git a/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/local-binding-preservation.js.snap.md b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/local-binding-preservation.js.snap.md
new file mode 100644
index 000000000..a095b3ac4
--- /dev/null
+++ b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/local-binding-preservation.js.snap.md
@@ -0,0 +1,54 @@
+## Input
+
+```js
+export async function direct() {}
+
+const indirect = async () => {}
+export { indirect }
+
+consume(direct, indirect)
+```
+
+## wrap-export
+
+**Status:** transformed
+
+**References:** direct, indirect
+
+[Source map visualization](https://evanw.github.io/source-map-visualization/#MzA0AGFzeW5jIGZ1bmN0aW9uIGRpcmVjdCgpIHt9Cgpjb25zdCBpbmRpcmVjdCA9IGFzeW5jICgpID0+IHt9CgoKY29uc3VtZShkaXJlY3QsIGluZGlyZWN0KQpkaXJlY3QgPSAvKiAjX19QVVJFX18gKi8gcmVnaXN0ZXJTZXJ2ZXJSZWZlcmVuY2UoZGlyZWN0LCAiZGlyZWN0Iik7CmV4cG9ydCB7IGRpcmVjdCB9Owo7CmNvbnN0ICQkd3JhcF9pbmRpcmVjdCA9IC8qICNfX1BVUkVfXyAqLyByZWdpc3RlclNlcnZlclJlZmVyZW5jZShpbmRpcmVjdCwgImluZGlyZWN0Iik7CmV4cG9ydCB7ICQkd3JhcF9pbmRpcmVjdCBhcyBpbmRpcmVjdCB9OwozNzkAeyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIiJdLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgYXN5bmMgZnVuY3Rpb24gZGlyZWN0KCkge31cblxuY29uc3QgaW5kaXJlY3QgPSBhc3luYyAoKSA9PiB7fVxuZXhwb3J0IHsgaW5kaXJlY3QgfVxuXG5jb25zdW1lKGRpcmVjdCwgaW5kaXJlY3QpXG4iXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQU8sS0FBSyxDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7O0FBRWhDLEtBQUssQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDOzs7QUFHOUIsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUFDLFFBQVE7QUFMeEI7QUFBQTs7OzsifQ==)
+
+```js
+async function direct() {}
+
+const indirect = async () => {}
+
+
+consume(direct, indirect)
+direct = /* #__PURE__ */ registerServerReference(direct, "direct");
+export { direct };
+;
+const $$wrap_indirect = /* #__PURE__ */ registerServerReference(indirect, "indirect");
+export { $$wrap_indirect as indirect };
+```
+
+## module-export-effect
+
+**Status:** transformed
+
+**References:** direct, indirect
+
+[Source map visualization](https://evanw.github.io/source-map-visualization/#MjE3AGFzeW5jIGZ1bmN0aW9uIGRpcmVjdCgpIHt9Cgpjb25zdCBpbmRpcmVjdCA9IGFzeW5jICgpID0+IHt9CmV4cG9ydCB7IGluZGlyZWN0IH0KCmNvbnN1bWUoZGlyZWN0LCBpbmRpcmVjdCkKCnJlZ2lzdGVyU2VydmVyUmVmZXJlbmNlKGRpcmVjdCwgImRpcmVjdCIpOwpleHBvcnQgeyBkaXJlY3QgfTsKCnJlZ2lzdGVyU2VydmVyUmVmZXJlbmNlKGluZGlyZWN0LCAiaW5kaXJlY3QiKTs0MTcAeyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIiJdLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgYXN5bmMgZnVuY3Rpb24gZGlyZWN0KCkge31cblxuY29uc3QgaW5kaXJlY3QgPSBhc3luYyAoKSA9PiB7fVxuZXhwb3J0IHsgaW5kaXJlY3QgfVxuXG5jb25zdW1lKGRpcmVjdCwgaW5kaXJlY3QpXG4iXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQU8sS0FBSyxDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7O0FBRWhDLEtBQUssQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQzlCLE1BQU0sQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDOztBQUVsQixPQUFPLENBQUMsTUFBTSxDQUFDLENBQUMsUUFBUTtBQUx4QjtBQUFBO0FBQUE7OyJ9)
+
+```js
+async function direct() {}
+
+const indirect = async () => {}
+export { indirect }
+
+consume(direct, indirect)
+
+registerServerReference(direct, "direct");
+export { direct };
+
+registerServerReference(indirect, "indirect");
+```
diff --git a/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/multiple-aliases.js b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/multiple-aliases.js
new file mode 100644
index 000000000..babb53a27
--- /dev/null
+++ b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/multiple-aliases.js
@@ -0,0 +1,7 @@
+'use server'
+
+async function value() {
+ return 'multiple aliases called'
+}
+
+export { value as first, value as second }
diff --git a/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/multiple-aliases.js.map.snap.md b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/multiple-aliases.js.map.snap.md
new file mode 100644
index 000000000..101621d42
--- /dev/null
+++ b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/multiple-aliases.js.map.snap.md
@@ -0,0 +1,25 @@
+## wrap-export
+
+```txt
+(0:0) "'use server'\n" --> (0:0) "'use server'\n"
+(2:0) "async function value() {\n" --> (2:0) "async function value() {\n"
+(3:0) " return 'multiple aliases called'\n" --> (3:0) " return 'multiple aliases called'\n"
+(4:0) "}\n" --> (4:0) "}\n"
+[unmapped] --> (7:0) ";\n"
+[unmapped] --> (8:0) "const $$wrap_value = /* #__PURE__ */ registerServerReference(value, \"first\");\n"
+[unmapped] --> (9:0) "export { $$wrap_value as first };\n"
+[unmapped] --> (10:0) "const $$wrap_value = /* #__PURE__ */ registerServerReference(value, \"second\");\n"
+[unmapped] --> (11:0) "export { $$wrap_value as second };\n"
+```
+
+## module-export-effect
+
+```txt
+(0:0) "'use server'\n" --> (0:0) "'use server'\n"
+(2:0) "async function value() {\n" --> (2:0) "async function value() {\n"
+(3:0) " return 'multiple aliases called'\n" --> (3:0) " return 'multiple aliases called'\n"
+(4:0) "}\n" --> (4:0) "}\n"
+(6:0) "export { value as first, value as second }\n" --> (6:0) "export { value as first, value as second }\n"
+[unmapped] --> (8:0) "registerServerReference(value, \"first\");\n"
+[unmapped] --> (9:0) "registerServerReference(value, \"second\");"
+```
diff --git a/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/multiple-aliases.js.snap.md b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/multiple-aliases.js.snap.md
new file mode 100644
index 000000000..88cc01b87
--- /dev/null
+++ b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/multiple-aliases.js.snap.md
@@ -0,0 +1,55 @@
+## Input
+
+```js
+'use server'
+
+async function value() {
+ return 'multiple aliases called'
+}
+
+export { value as first, value as second }
+```
+
+## wrap-export
+
+**Status:** transformed
+
+**References:** first, second
+
+[Source map visualization](https://evanw.github.io/source-map-visualization/#MzA2ACd1c2Ugc2VydmVyJwoKYXN5bmMgZnVuY3Rpb24gdmFsdWUoKSB7CiAgcmV0dXJuICdtdWx0aXBsZSBhbGlhc2VzIGNhbGxlZCcKfQoKCjsKY29uc3QgJCR3cmFwX3ZhbHVlID0gLyogI19fUFVSRV9fICovIHJlZ2lzdGVyU2VydmVyUmVmZXJlbmNlKHZhbHVlLCAiZmlyc3QiKTsKZXhwb3J0IHsgJCR3cmFwX3ZhbHVlIGFzIGZpcnN0IH07CmNvbnN0ICQkd3JhcF92YWx1ZSA9IC8qICNfX1BVUkVfXyAqLyByZWdpc3RlclNlcnZlclJlZmVyZW5jZSh2YWx1ZSwgInNlY29uZCIpOwpleHBvcnQgeyAkJHdyYXBfdmFsdWUgYXMgc2Vjb25kIH07CjM0MgB7InZlcnNpb24iOjMsInNvdXJjZXMiOlsiIl0sInNvdXJjZXNDb250ZW50IjpbIid1c2Ugc2VydmVyJ1xuXG5hc3luYyBmdW5jdGlvbiB2YWx1ZSgpIHtcbiAgcmV0dXJuICdtdWx0aXBsZSBhbGlhc2VzIGNhbGxlZCdcbn1cblxuZXhwb3J0IHsgdmFsdWUgYXMgZmlyc3QsIHZhbHVlIGFzIHNlY29uZCB9XG4iXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsQ0FBQyxHQUFHLENBQUMsTUFBTTs7QUFFWCxLQUFLLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7QUFDdkIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsTUFBTTtBQUNqQzs7Ozs7Ozs7In0=)
+
+```js
+'use server'
+
+async function value() {
+ return 'multiple aliases called'
+}
+
+
+;
+const $$wrap_value = /* #__PURE__ */ registerServerReference(value, "first");
+export { $$wrap_value as first };
+const $$wrap_value = /* #__PURE__ */ registerServerReference(value, "second");
+export { $$wrap_value as second };
+```
+
+## module-export-effect
+
+**Status:** transformed
+
+**References:** first, second
+
+[Source map visualization](https://evanw.github.io/source-map-visualization/#MjAzACd1c2Ugc2VydmVyJwoKYXN5bmMgZnVuY3Rpb24gdmFsdWUoKSB7CiAgcmV0dXJuICdtdWx0aXBsZSBhbGlhc2VzIGNhbGxlZCcKfQoKZXhwb3J0IHsgdmFsdWUgYXMgZmlyc3QsIHZhbHVlIGFzIHNlY29uZCB9CgpyZWdpc3RlclNlcnZlclJlZmVyZW5jZSh2YWx1ZSwgImZpcnN0Iik7CnJlZ2lzdGVyU2VydmVyUmVmZXJlbmNlKHZhbHVlLCAic2Vjb25kIik7NDI4AHsidmVyc2lvbiI6Mywic291cmNlcyI6WyIiXSwic291cmNlc0NvbnRlbnQiOlsiJ3VzZSBzZXJ2ZXInXG5cbmFzeW5jIGZ1bmN0aW9uIHZhbHVlKCkge1xuICByZXR1cm4gJ211bHRpcGxlIGFsaWFzZXMgY2FsbGVkJ1xufVxuXG5leHBvcnQgeyB2YWx1ZSBhcyBmaXJzdCwgdmFsdWUgYXMgc2Vjb25kIH1cbiJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxDQUFDLEdBQUcsQ0FBQyxNQUFNOztBQUVYLEtBQUssQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQztBQUN2QixDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxNQUFNO0FBQ2pDOztBQUVBLE1BQU0sQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLE1BQU0sQ0FBQzs7OyJ9)
+
+```js
+'use server'
+
+async function value() {
+ return 'multiple aliases called'
+}
+
+export { value as first, value as second }
+
+registerServerReference(value, "first");
+registerServerReference(value, "second");
+```
diff --git a/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/recursive.js b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/recursive.js
new file mode 100644
index 000000000..f9953526b
--- /dev/null
+++ b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/recursive.js
@@ -0,0 +1,6 @@
+'use server'
+
+export function recursive(depth) {
+ if (depth > 0) return recursive(depth - 1)
+ return recursive.marker
+}
diff --git a/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/recursive.js.map.snap.md b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/recursive.js.map.snap.md
new file mode 100644
index 000000000..608b8a776
--- /dev/null
+++ b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/recursive.js.map.snap.md
@@ -0,0 +1,24 @@
+## wrap-export
+
+```txt
+(0:0) "'use server'\n" --> (0:0) "'use server'\n"
+(2:7) "function recursive(depth) {\n" --> (2:0) "function recursive(depth) {\n"
+(3:0) " if (depth > 0) return recursive(depth - 1)\n" --> (3:0) " if (depth > 0) return recursive(depth - 1)\n"
+(4:0) " return recursive.marker\n" --> (4:0) " return recursive.marker\n"
+(5:0) "}\n" --> (5:0) "}\n"
+(2:0) "export function recursive(depth) {\n" --> (6:0) "recursive = /* #__PURE__ */ registerServerReference(recursive, \"recursive\");\n"
+(2:0) "export function recursive(depth) {\n" --> (7:0) "export { recursive };\n"
+```
+
+## module-export-effect
+
+```txt
+(0:0) "'use server'\n" --> (0:0) "'use server'\n"
+(2:7) "function recursive(depth) {\n" --> (2:0) "function recursive(depth) {\n"
+(3:0) " if (depth > 0) return recursive(depth - 1)\n" --> (3:0) " if (depth > 0) return recursive(depth - 1)\n"
+(4:0) " return recursive.marker\n" --> (4:0) " return recursive.marker\n"
+(5:0) "}\n" --> (5:0) "}\n"
+(2:0) "export function recursive(depth) {\n" --> (6:0) "\n"
+(2:0) "export function recursive(depth) {\n" --> (7:0) "registerServerReference(recursive, \"recursive\");\n"
+(2:0) "export function recursive(depth) {\n" --> (8:0) "export { recursive };\n"
+```
diff --git a/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/recursive.js.snap.md b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/recursive.js.snap.md
new file mode 100644
index 000000000..5248f4be4
--- /dev/null
+++ b/packages/plugin-rsc/src/transforms/fixtures/source-map/wrap-export/recursive.js.snap.md
@@ -0,0 +1,49 @@
+## Input
+
+```js
+'use server'
+
+export function recursive(depth) {
+ if (depth > 0) return recursive(depth - 1)
+ return recursive.marker
+}
+```
+
+## wrap-export
+
+**Status:** transformed
+
+**References:** recursive
+
+[Source map visualization](https://evanw.github.io/source-map-visualization/#MjE0ACd1c2Ugc2VydmVyJwoKZnVuY3Rpb24gcmVjdXJzaXZlKGRlcHRoKSB7CiAgaWYgKGRlcHRoID4gMCkgcmV0dXJuIHJlY3Vyc2l2ZShkZXB0aCAtIDEpCiAgcmV0dXJuIHJlY3Vyc2l2ZS5tYXJrZXIKfQpyZWN1cnNpdmUgPSAvKiAjX19QVVJFX18gKi8gcmVnaXN0ZXJTZXJ2ZXJSZWZlcmVuY2UocmVjdXJzaXZlLCAicmVjdXJzaXZlIik7CmV4cG9ydCB7IHJlY3Vyc2l2ZSB9Owo0MzIAeyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIiJdLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHNlcnZlcidcblxuZXhwb3J0IGZ1bmN0aW9uIHJlY3Vyc2l2ZShkZXB0aCkge1xuICBpZiAoZGVwdGggPiAwKSByZXR1cm4gcmVjdXJzaXZlKGRlcHRoIC0gMSlcbiAgcmV0dXJuIHJlY3Vyc2l2ZS5tYXJrZXJcbn1cbiJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxDQUFDLEdBQUcsQ0FBQyxNQUFNOztBQUVKLFFBQVEsQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLENBQUM7QUFDakMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDM0MsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUM7QUFDbkI7QUFIQTtBQUFBOyJ9)
+
+```js
+'use server'
+
+function recursive(depth) {
+ if (depth > 0) return recursive(depth - 1)
+ return recursive.marker
+}
+recursive = /* #__PURE__ */ registerServerReference(recursive, "recursive");
+export { recursive };
+```
+
+## module-export-effect
+
+**Status:** transformed
+
+**References:** recursive
+
+[Source map visualization](https://evanw.github.io/source-map-visualization/#MTg3ACd1c2Ugc2VydmVyJwoKZnVuY3Rpb24gcmVjdXJzaXZlKGRlcHRoKSB7CiAgaWYgKGRlcHRoID4gMCkgcmV0dXJuIHJlY3Vyc2l2ZShkZXB0aCAtIDEpCiAgcmV0dXJuIHJlY3Vyc2l2ZS5tYXJrZXIKfQoKcmVnaXN0ZXJTZXJ2ZXJSZWZlcmVuY2UocmVjdXJzaXZlLCAicmVjdXJzaXZlIik7CmV4cG9ydCB7IHJlY3Vyc2l2ZSB9Owo0MzcAeyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIiJdLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHNlcnZlcidcblxuZXhwb3J0IGZ1bmN0aW9uIHJlY3Vyc2l2ZShkZXB0aCkge1xuICBpZiAoZGVwdGggPiAwKSByZXR1cm4gcmVjdXJzaXZlKGRlcHRoIC0gMSlcbiAgcmV0dXJuIHJlY3Vyc2l2ZS5tYXJrZXJcbn1cbiJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxDQUFDLEdBQUcsQ0FBQyxNQUFNOztBQUVKLFFBQVEsQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLENBQUM7QUFDakMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDM0MsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUM7QUFDbkI7QUFIQTtBQUFBO0FBQUE7In0=)
+
+```js
+'use server'
+
+function recursive(depth) {
+ if (depth > 0) return recursive(depth - 1)
+ return recursive.marker
+}
+
+registerServerReference(recursive, "recursive");
+export { recursive };
+```
diff --git a/packages/plugin-rsc/src/transforms/module-export-effect.test.ts b/packages/plugin-rsc/src/transforms/module-export-effect.test.ts
index cace6e975..93642475d 100644
--- a/packages/plugin-rsc/src/transforms/module-export-effect.test.ts
+++ b/packages/plugin-rsc/src/transforms/module-export-effect.test.ts
@@ -243,7 +243,9 @@ export default async function Page() {}
for (const [input, expected] of examples) {
const result = await transform(input)
- expect(result.references.map((context) => context.meta)).toEqual(expected)
+ expect(
+ result.references.map(({ meta: { valueNode: _, ...meta } }) => meta),
+ ).toEqual(expected)
}
})
})
diff --git a/packages/plugin-rsc/src/transforms/module-export-scan.test.ts b/packages/plugin-rsc/src/transforms/module-export-scan.test.ts
index aae47872b..c38487d9b 100644
--- a/packages/plugin-rsc/src/transforms/module-export-scan.test.ts
+++ b/packages/plugin-rsc/src/transforms/module-export-scan.test.ts
@@ -26,6 +26,7 @@ export * from './all'
meta: {
localName: 'action',
isFunction: true,
+ valueNode: { type: 'FunctionDeclaration' },
},
},
],
@@ -42,6 +43,7 @@ export * from './all'
meta: {
localName: 'loader',
isFunction: true,
+ valueNode: { type: 'ArrowFunctionExpression' },
},
},
],
@@ -54,6 +56,7 @@ export * from './all'
meta: {
localName: 'value',
isFunction: false,
+ valueNode: { type: 'Literal' },
},
},
],
@@ -71,6 +74,7 @@ export * from './all'
meta: {
localName: 'item',
isFunction: undefined,
+ valueNode: { type: 'Identifier', name: 'source' },
},
},
],
@@ -91,7 +95,10 @@ export * from './all'
type: 'default',
kind: 'identifier',
localName: undefined,
- meta: { defaultExportIdentifierName: 'action' },
+ meta: {
+ defaultExportIdentifierName: 'action',
+ valueNode: { type: 'Identifier', name: 'action' },
+ },
})
expect(groups[6]).toMatchObject({ type: 'export-all' })
})
diff --git a/packages/plugin-rsc/src/transforms/module-export-scan.ts b/packages/plugin-rsc/src/transforms/module-export-scan.ts
index c3b8ccf2a..30e5d19fa 100644
--- a/packages/plugin-rsc/src/transforms/module-export-scan.ts
+++ b/packages/plugin-rsc/src/transforms/module-export-scan.ts
@@ -15,6 +15,16 @@ import type { ESTree } from 'vite'
import { extractNames } from './utils'
export type ModuleExportMeta = {
+ /**
+ * The source node that evaluates to the exported value when directly
+ * available.
+ *
+ * - The declaration for a function or class export.
+ * - The initializer for a variable export.
+ * - The declaration expression for a default export.
+ * - `undefined` for export specifiers and re-exports.
+ */
+ valueNode?: Node | ExportDefaultDeclaration['declaration']
/**
* The local declaration name when statically available.
*
@@ -141,6 +151,7 @@ export function scanModuleExports(
meta: {
localName: name,
isFunction,
+ valueNode: declarator.init ?? undefined,
},
})),
}
@@ -162,6 +173,7 @@ export function scanModuleExports(
meta: {
localName: name,
isFunction: getIsFunction(node.declaration),
+ valueNode: node.declaration,
},
},
],
@@ -211,15 +223,22 @@ export function scanModuleExports(
meta = {
localName: node.declaration.id.name,
isFunction: getIsFunction(node.declaration),
+ valueNode: node.declaration,
}
} else if (node.declaration.type === 'Identifier') {
kind = 'identifier'
- meta = { defaultExportIdentifierName: node.declaration.name }
+ meta = {
+ defaultExportIdentifierName: node.declaration.name,
+ valueNode: node.declaration,
+ }
} else {
// export default function () {}
// export default () => {}
kind = 'other'
- meta = { isFunction: getIsFunction(node.declaration) }
+ meta = {
+ isFunction: getIsFunction(node.declaration),
+ valueNode: node.declaration,
+ }
}
groups.push({ type: 'default', kind, node, localName, meta })
}
diff --git a/packages/plugin-rsc/src/transforms/wrap-export.test.ts b/packages/plugin-rsc/src/transforms/wrap-export.test.ts
index 69a726c0d..b2e349c4f 100644
--- a/packages/plugin-rsc/src/transforms/wrap-export.test.ts
+++ b/packages/plugin-rsc/src/transforms/wrap-export.test.ts
@@ -259,11 +259,10 @@ export default function d() {}
})
expect(result).toMatchInlineSnapshot(`
"
- let a = 0;
+ export const a = 0;
let b = function() {}
let c = () => {}
function d() {}
- export { a };
b = /* #__PURE__ */ $$wrap(b, "", "b");
export { b };
c = /* #__PURE__ */ $$wrap(c, "", "c");
@@ -311,6 +310,32 @@ export default Page;
`)
})
+ test('filter value node', async () => {
+ const input = `\
+export const action = async () => {}
+export const metadata = {}
+export const tags = []
+`
+ const ast = await parseAstAsync(input)
+ const result = transformWrapExport(input, ast, {
+ runtime: (value, name) => `$$wrap(${value}, ${JSON.stringify(name)})`,
+ rejectNonAsyncFunction: true,
+ filter: (_name, meta) =>
+ meta.valueNode?.type !== 'ObjectExpression' &&
+ meta.valueNode?.type !== 'ArrayExpression',
+ })
+
+ expect(result.exportNames).toEqual(['action'])
+ expect(result.output.toString()).toMatchInlineSnapshot(`
+ "let action = async () => {}
+ export const metadata = {}
+ export const tags = []
+ action = /* #__PURE__ */ $$wrap(action, \"action\");
+ export { action };
+ "
+ `)
+ })
+
test('filtered exports are not validated or reported', async () => {
const input = `
export const revalidate = 1;
@@ -325,9 +350,8 @@ export default async function Page() {}
expect(result.exportNames).toEqual(['default'])
expect(result.output.toString()).toMatchInlineSnapshot(`
"
- let revalidate = 1;
+ export const revalidate = 1;
async function Page() {}
- export { revalidate };
;
const $$wrap_Page = /* #__PURE__ */ $$wrap(Page, "default");
export { $$wrap_Page as default };
@@ -465,7 +489,8 @@ export default cached;
const ast = await parseAstAsync(input)
transformWrapExport(input, ast, {
runtime(value, _name, meta) {
- actual.push(meta)
+ const { valueNode: _, ...rest } = meta
+ actual.push(rest)
return value
},
})
diff --git a/packages/plugin-rsc/src/transforms/wrap-export.ts b/packages/plugin-rsc/src/transforms/wrap-export.ts
index aaa9fcee8..fe7b608d4 100644
--- a/packages/plugin-rsc/src/transforms/wrap-export.ts
+++ b/packages/plugin-rsc/src/transforms/wrap-export.ts
@@ -9,6 +9,8 @@ import {
import { validateNonAsyncFunction } from './utils'
type ExportMeta = {
+ /** The directly exported declaration or value expression, when available. */
+ valueNode?: ModuleExportMeta['valueNode']
/**
* The local declaration name when statically available.
*
@@ -133,6 +135,13 @@ export function transformWrapExport(
}
wrapSimple(group.node.start, group.declaration.start, group.exports)
} else if (group.type === 'variable-declaration') {
+ const shouldWrap = group.declarators.some((declarator) =>
+ declarator.exports.some(({ exportName, meta }) =>
+ filter(exportName, getExportMeta(meta)),
+ ),
+ )
+ if (!shouldWrap) continue
+
if (group.declaration.kind === 'const') {
output.update(
group.declaration.start,
From b809b88cde02b592fbe830197663bd81313d1e14 Mon Sep 17 00:00:00 2001
From: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com>
Date: Tue, 4 Aug 2026 15:27:09 +0900
Subject: [PATCH 2/5] refactor(rsc): unify module export metadata
Co-authored-by: OpenCode
---
.../transforms/module-export-effect.test.ts | 32 ++++-----
.../src/transforms/module-export-scan.test.ts | 8 +--
.../src/transforms/module-export-scan.ts | 8 +--
.../plugin-rsc/src/transforms/wrap-export.ts | 67 ++++---------------
4 files changed, 38 insertions(+), 77 deletions(-)
diff --git a/packages/plugin-rsc/src/transforms/module-export-effect.test.ts b/packages/plugin-rsc/src/transforms/module-export-effect.test.ts
index 93642475d..2ad104d7d 100644
--- a/packages/plugin-rsc/src/transforms/module-export-effect.test.ts
+++ b/packages/plugin-rsc/src/transforms/module-export-effect.test.ts
@@ -126,7 +126,7 @@ export default async function Page() {}
`export function Fn() {}`,
[
{
- localName: 'Fn',
+ declName: 'Fn',
isFunction: true,
},
],
@@ -135,7 +135,7 @@ export default async function Page() {}
`export class Cls {}`,
[
{
- localName: 'Cls',
+ declName: 'Cls',
isFunction: false,
},
],
@@ -144,7 +144,7 @@ export default async function Page() {}
`export const Arrow = () => {}`,
[
{
- localName: 'Arrow',
+ declName: 'Arrow',
isFunction: true,
},
],
@@ -153,7 +153,7 @@ export default async function Page() {}
`export const FnExpression = function () {}`,
[
{
- localName: 'FnExpression',
+ declName: 'FnExpression',
isFunction: true,
},
],
@@ -162,7 +162,7 @@ export default async function Page() {}
`export const Literal = 1`,
[
{
- localName: 'Literal',
+ declName: 'Literal',
isFunction: false,
},
],
@@ -171,7 +171,7 @@ export default async function Page() {}
`export const ObjectValue = {}`,
[
{
- localName: 'ObjectValue',
+ declName: 'ObjectValue',
isFunction: false,
},
],
@@ -180,7 +180,7 @@ export default async function Page() {}
`export const ArrayValue = []`,
[
{
- localName: 'ArrayValue',
+ declName: 'ArrayValue',
isFunction: false,
},
],
@@ -189,33 +189,33 @@ export default async function Page() {}
`export const ClassValue = class {}`,
[
{
- localName: 'ClassValue',
+ declName: 'ClassValue',
isFunction: false,
},
],
],
- [`export const Unknown = getValue()`, [{ localName: 'Unknown' }]],
- [`export const { id } = getValue()`, [{ localName: 'id' }]],
- [`export const [a, b] = []`, [{ localName: 'a' }, { localName: 'b' }]],
+ [`export const Unknown = getValue()`, [{ declName: 'Unknown' }]],
+ [`export const { id } = getValue()`, [{ declName: 'id' }]],
+ [`export const [a, b] = []`, [{ declName: 'a' }, { declName: 'b' }]],
[
`export const MultiFn = () => {}, MultiValue = 1, MultiUnknown = getValue()`,
[
{
- localName: 'MultiFn',
+ declName: 'MultiFn',
isFunction: true,
},
{
- localName: 'MultiValue',
+ declName: 'MultiValue',
isFunction: false,
},
- { localName: 'MultiUnknown' },
+ { declName: 'MultiUnknown' },
],
],
[
`export default function Page() {}`,
[
{
- localName: 'Page',
+ declName: 'Page',
isFunction: true,
},
],
@@ -225,7 +225,7 @@ export default async function Page() {}
`export default class Page {}`,
[
{
- localName: 'Page',
+ declName: 'Page',
isFunction: false,
},
],
diff --git a/packages/plugin-rsc/src/transforms/module-export-scan.test.ts b/packages/plugin-rsc/src/transforms/module-export-scan.test.ts
index c38487d9b..c40a3c915 100644
--- a/packages/plugin-rsc/src/transforms/module-export-scan.test.ts
+++ b/packages/plugin-rsc/src/transforms/module-export-scan.test.ts
@@ -24,7 +24,7 @@ export * from './all'
localName: 'action',
exportName: 'action',
meta: {
- localName: 'action',
+ declName: 'action',
isFunction: true,
valueNode: { type: 'FunctionDeclaration' },
},
@@ -41,7 +41,7 @@ export * from './all'
localName: 'loader',
exportName: 'loader',
meta: {
- localName: 'loader',
+ declName: 'loader',
isFunction: true,
valueNode: { type: 'ArrowFunctionExpression' },
},
@@ -54,7 +54,7 @@ export * from './all'
localName: 'value',
exportName: 'value',
meta: {
- localName: 'value',
+ declName: 'value',
isFunction: false,
valueNode: { type: 'Literal' },
},
@@ -72,7 +72,7 @@ export * from './all'
localName: 'item',
exportName: 'item',
meta: {
- localName: 'item',
+ declName: 'item',
isFunction: undefined,
valueNode: { type: 'Identifier', name: 'source' },
},
diff --git a/packages/plugin-rsc/src/transforms/module-export-scan.ts b/packages/plugin-rsc/src/transforms/module-export-scan.ts
index 30e5d19fa..73f506c3b 100644
--- a/packages/plugin-rsc/src/transforms/module-export-scan.ts
+++ b/packages/plugin-rsc/src/transforms/module-export-scan.ts
@@ -33,7 +33,7 @@ export type ModuleExportMeta = {
* - `undefined` for `export default () => {}`
* - `undefined` for `export { Page }`
*/
- localName?: string
+ declName?: string
/**
* Whether the exported value is statically known to be a function.
*
@@ -149,7 +149,7 @@ export function scanModuleExports(
localName: name,
exportName: name,
meta: {
- localName: name,
+ declName: name,
isFunction,
valueNode: declarator.init ?? undefined,
},
@@ -171,7 +171,7 @@ export function scanModuleExports(
localName: name,
exportName: name,
meta: {
- localName: name,
+ declName: name,
isFunction: getIsFunction(node.declaration),
valueNode: node.declaration,
},
@@ -221,7 +221,7 @@ export function scanModuleExports(
kind = 'named-declaration'
localName = node.declaration.id.name
meta = {
- localName: node.declaration.id.name,
+ declName: node.declaration.id.name,
isFunction: getIsFunction(node.declaration),
valueNode: node.declaration,
}
diff --git a/packages/plugin-rsc/src/transforms/wrap-export.ts b/packages/plugin-rsc/src/transforms/wrap-export.ts
index fe7b608d4..5d65b2338 100644
--- a/packages/plugin-rsc/src/transforms/wrap-export.ts
+++ b/packages/plugin-rsc/src/transforms/wrap-export.ts
@@ -8,46 +8,13 @@ import {
} from './module-export-scan'
import { validateNonAsyncFunction } from './utils'
-type ExportMeta = {
- /** The directly exported declaration or value expression, when available. */
- valueNode?: ModuleExportMeta['valueNode']
- /**
- * The local declaration name when statically available.
- *
- * - `"Page"` for `export function Page() {}`
- * - `"Page"` for `export const Page = () => {}`
- * - `undefined` for `export default () => {}`
- * - `undefined` for `export { Page }`
- */
- declName?: string
- /**
- * Whether the exported value is statically known to be a function.
- *
- * - `true` for `export const Page = () => {}`
- * - `false` for `export const value = 1`
- * - `undefined` for `export const value = getValue()`
- * - `undefined` for `export default Page`
- */
- isFunction?: boolean
- /**
- * The local identifier referenced by a default export.
- * The RSC CSS transform uses this to detect a component by its capitalized
- * local name and preserve that name on the generated wrapper.
- *
- * - `"Page"` for `const Page = () => {}; export default Page`
- * - `undefined` for `export default function Page() {}`
- * - `undefined` for `export default () => {}`
- */
- defaultExportIdentifierName?: string
-}
-
export type TransformWrapExportFilter = (
name: string,
- meta: ExportMeta,
+ meta: ModuleExportMeta,
) => boolean
export type TransformWrapExportOptions = {
- runtime: (value: string, name: string, meta: ExportMeta) => string
+ runtime: (value: string, name: string, meta: ModuleExportMeta) => string
ignoreExportAllDeclaration?: boolean
rejectNonAsyncFunction?: boolean
filter?: TransformWrapExportFilter
@@ -72,11 +39,9 @@ export function transformWrapExport(
exports: ModuleExportEntry[],
) {
const filteredExports = exports.map((item) => {
- const meta = getExportMeta(item.meta)
return {
...item,
- meta,
- shouldWrap: filter(item.exportName, meta),
+ shouldWrap: filter(item.exportName, item.meta),
}
})
exportNames.push(
@@ -110,7 +75,11 @@ export function transformWrapExport(
output.move(start, end, input.length)
}
- function wrapExport(name: string, exportName: string, meta: ExportMeta = {}) {
+ function wrapExport(
+ name: string,
+ exportName: string,
+ meta: ModuleExportMeta = {},
+ ) {
if (!filter(exportName, meta)) {
toAppend.push(`export { ${name} as ${exportName} }`)
return
@@ -130,14 +99,14 @@ export function transformWrapExport(
for (const group of scanModuleExports(viteAst)) {
if (group.type === 'declaration') {
const [entry] = group.exports
- if (filter(entry.exportName, getExportMeta(entry.meta))) {
+ if (filter(entry.exportName, entry.meta)) {
validateNonAsyncFunction(options, group.declaration)
}
wrapSimple(group.node.start, group.declaration.start, group.exports)
} else if (group.type === 'variable-declaration') {
const shouldWrap = group.declarators.some((declarator) =>
declarator.exports.some(({ exportName, meta }) =>
- filter(exportName, getExportMeta(meta)),
+ filter(exportName, meta),
),
)
if (!shouldWrap) continue
@@ -155,7 +124,7 @@ export function transformWrapExport(
if (
declarator.node.init &&
declarator.exports.some(({ exportName, meta }) =>
- filter(exportName, getExportMeta(meta)),
+ filter(exportName, meta),
)
) {
validateNonAsyncFunction(options, declarator.node.init)
@@ -179,7 +148,7 @@ export function transformWrapExport(
wrapExport(
`$$import_${entry.localName}`,
entry.exportName,
- getExportMeta(entry.meta),
+ entry.meta,
)
}
} else {
@@ -192,11 +161,7 @@ export function transformWrapExport(
{ pos: entry.node.exported.start },
)
}
- wrapExport(
- entry.localName,
- entry.exportName,
- getExportMeta(entry.meta),
- )
+ wrapExport(entry.localName, entry.exportName, entry.meta)
}
}
} else if (group.type === 'export-all') {
@@ -232,7 +197,7 @@ export function transformWrapExport(
'const $$default = ',
)
}
- const meta = getExportMeta(group.meta)
+ const meta = group.meta
if (filter('default', meta)) {
validateNonAsyncFunction(options, group.node.declaration)
}
@@ -246,7 +211,3 @@ export function transformWrapExport(
return { exportNames, output }
}
-
-function getExportMeta({ localName, ...meta }: ModuleExportMeta): ExportMeta {
- return { ...meta, ...(localName && { declName: localName }) }
-}
From bfe0f7caa108949ff88844ed08aa67024ca3de7f Mon Sep 17 00:00:00 2001
From: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com>
Date: Tue, 4 Aug 2026 15:29:11 +0900
Subject: [PATCH 3/5] refactor(rsc): defer filtered declaration preservation
Co-authored-by: OpenCode
---
.../plugin-rsc/src/transforms/wrap-export.test.ts | 14 +++++++++-----
packages/plugin-rsc/src/transforms/wrap-export.ts | 7 -------
2 files changed, 9 insertions(+), 12 deletions(-)
diff --git a/packages/plugin-rsc/src/transforms/wrap-export.test.ts b/packages/plugin-rsc/src/transforms/wrap-export.test.ts
index b2e349c4f..976d511a9 100644
--- a/packages/plugin-rsc/src/transforms/wrap-export.test.ts
+++ b/packages/plugin-rsc/src/transforms/wrap-export.test.ts
@@ -259,10 +259,11 @@ export default function d() {}
})
expect(result).toMatchInlineSnapshot(`
"
- export const a = 0;
+ let a = 0;
let b = function() {}
let c = () => {}
function d() {}
+ export { a };
b = /* #__PURE__ */ $$wrap(b, "", "b");
export { b };
c = /* #__PURE__ */ $$wrap(c, "", "c");
@@ -328,10 +329,12 @@ export const tags = []
expect(result.exportNames).toEqual(['action'])
expect(result.output.toString()).toMatchInlineSnapshot(`
"let action = async () => {}
- export const metadata = {}
- export const tags = []
- action = /* #__PURE__ */ $$wrap(action, \"action\");
+ let metadata = {}
+ let tags = []
+ action = /* #__PURE__ */ $$wrap(action, "action");
export { action };
+ export { metadata };
+ export { tags };
"
`)
})
@@ -350,8 +353,9 @@ export default async function Page() {}
expect(result.exportNames).toEqual(['default'])
expect(result.output.toString()).toMatchInlineSnapshot(`
"
- export const revalidate = 1;
+ let revalidate = 1;
async function Page() {}
+ export { revalidate };
;
const $$wrap_Page = /* #__PURE__ */ $$wrap(Page, "default");
export { $$wrap_Page as default };
diff --git a/packages/plugin-rsc/src/transforms/wrap-export.ts b/packages/plugin-rsc/src/transforms/wrap-export.ts
index 5d65b2338..25b0320d4 100644
--- a/packages/plugin-rsc/src/transforms/wrap-export.ts
+++ b/packages/plugin-rsc/src/transforms/wrap-export.ts
@@ -104,13 +104,6 @@ export function transformWrapExport(
}
wrapSimple(group.node.start, group.declaration.start, group.exports)
} else if (group.type === 'variable-declaration') {
- const shouldWrap = group.declarators.some((declarator) =>
- declarator.exports.some(({ exportName, meta }) =>
- filter(exportName, meta),
- ),
- )
- if (!shouldWrap) continue
-
if (group.declaration.kind === 'const') {
output.update(
group.declaration.start,
From 3ecc96742e1b7f2c74a911d3db523c156cb230c6 Mon Sep 17 00:00:00 2001
From: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com>
Date: Tue, 4 Aug 2026 15:30:46 +0900
Subject: [PATCH 4/5] docs(rsc): note export metadata cleanup
Co-authored-by: OpenCode
---
packages/plugin-rsc/src/transforms/module-export-scan.ts | 2 ++
1 file changed, 2 insertions(+)
diff --git a/packages/plugin-rsc/src/transforms/module-export-scan.ts b/packages/plugin-rsc/src/transforms/module-export-scan.ts
index 73f506c3b..ce7b9dbea 100644
--- a/packages/plugin-rsc/src/transforms/module-export-scan.ts
+++ b/packages/plugin-rsc/src/transforms/module-export-scan.ts
@@ -25,6 +25,8 @@ export type ModuleExportMeta = {
* - `undefined` for export specifiers and re-exports.
*/
valueNode?: Node | ExportDefaultDeclaration['declaration']
+ // TODO: followings are used only for internal `transformRscCssExport`.
+ // should probably simplify to use `valueNode` directly and remove these.
/**
* The local declaration name when statically available.
*
From f224ca4432871419b13a8461132d53f3beb3e5c0 Mon Sep 17 00:00:00 2001
From: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com>
Date: Tue, 4 Aug 2026 15:32:00 +0900
Subject: [PATCH 5/5] docs(rsc): explain callable cache export filter
Co-authored-by: OpenCode
---
.../examples/use-cache-callable/callable-cache-plugin.ts | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/packages/plugin-rsc/examples/use-cache-callable/callable-cache-plugin.ts b/packages/plugin-rsc/examples/use-cache-callable/callable-cache-plugin.ts
index b2b6da65e..a9f4728f7 100644
--- a/packages/plugin-rsc/examples/use-cache-callable/callable-cache-plugin.ts
+++ b/packages/plugin-rsc/examples/use-cache-callable/callable-cache-plugin.ts
@@ -37,8 +37,10 @@ export function callableCachePlugin(): Plugin {
const result = hasDirective(ast.body, directive)
? transformWrapExport(code, ast, {
runtime,
- // Next.js permits object and array exports for metadata and
- // viewport values in "use cache" modules.
+ // Next.js calls rejecting primitive literals while permitting
+ // objects and arrays arbitrary, but keeps the latter for metadata
+ // and viewport exports.
+ // https://github.com/vercel/next.js/blob/aae4179ac628e55483b62cd023a7e1827dcef122/crates/next-custom-transforms/src/transforms/server_actions.rs#L1914-L1919
filter: (_name, meta) =>
meta.valueNode?.type !== 'ObjectExpression' &&
meta.valueNode?.type !== 'ArrayExpression',