Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4412003
feat(rsc): add module export transform
hi-ogawa Jul 30, 2026
e18a148
refactor(rsc): migrate module export transforms
hi-ogawa Jul 30, 2026
6360351
refactor(rsc): clarify module export transform types
hi-ogawa Jul 30, 2026
7c52056
docs(rsc): explain module export transform
hi-ogawa Jul 30, 2026
4d0b874
feat(rsc): return module export contexts
hi-ogawa Jul 30, 2026
fa56085
Merge branch 'main' into fix/issue-1350
hi-ogawa Jul 30, 2026
1ad2fae
refactor(rsc): simplify multi-declarator exports
hi-ogawa Jul 30, 2026
e5354dd
Merge branch 'main' into fix/issue-1350
hi-ogawa Jul 31, 2026
ec8275e
refactor(rsc): split module export models
hi-ogawa Jul 31, 2026
50ecb7d
test(rsc): compare module export source maps
hi-ogawa Jul 31, 2026
62ab0cc
refactor(rsc): simplify module export names
hi-ogawa Jul 31, 2026
d904947
test(rsc): group module export source maps
hi-ogawa Jul 31, 2026
14afa60
Merge branch 'main' into fix/issue-1350
hi-ogawa Jul 31, 2026
3c7641e
refactor(rsc): allow mutable module exports
hi-ogawa Jul 31, 2026
9bb4d95
test(rsc): compare module export coverage
hi-ogawa Jul 31, 2026
18c328f
fix(rsc): preserve module export effect mappings
hi-ogawa Jul 31, 2026
b5f3300
Merge branch 'main' into fix/issue-1350
hi-ogawa Jul 31, 2026
406ae52
refactor(rsc): narrow module export transform
hi-ogawa Jul 31, 2026
714d08a
test(rsc): compare module export mappings
hi-ogawa Jul 31, 2026
be2b47d
docs(rsc): explain module export source mapping
hi-ogawa Jul 31, 2026
d894c62
test(rsc): cover module export semantics
hi-ogawa Jul 31, 2026
16e44fa
test(rsc): migrate hoist source map snapshots
hi-ogawa Jul 31, 2026
235da5d
docs(rsc): remove stale inline mapping todo
hi-ogawa Jul 31, 2026
1ccaa5b
test(rsc): update Vite 7 source map expectations
hi-ogawa Jul 31, 2026
3711be3
nit
hi-ogawa Jul 31, 2026
5ea878d
nit
hi-ogawa Jul 31, 2026
f923975
nit
hi-ogawa Jul 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 6 additions & 13 deletions packages/plugin-rsc/e2e/source-map.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,40 +49,33 @@ const serverReferenceCases: {
route: '/defaults',
references: [
{
// Currently resolves to the end of the declaration.
name: 'default-named-function',
rolldown: '/src/features/defaults/named.ts:4:0',
rolldown: '/src/features/defaults/named.ts:2:0',
},
{
// Currently resolves to the end of the declaration.
name: 'default-anonymous-function',
rolldown: '/src/features/defaults/anonymous.ts:4:0',
rolldown: '/src/features/defaults/anonymous.ts:2:0',
},
{
name: 'default-identifier',
rolldown: '/src/features/defaults/identifier.ts:6:15',
rolldown: '/src/features/defaults/identifier.ts:6:0',
},
],
},
{
route: '/specifiers',
references: [
{
// Currently resolves to the end of the local declaration.
name: 'local-alias',
rolldown: '/src/features/specifiers/local-alias.ts:4:0',
rolldown: '/src/features/specifiers/local-alias.ts:6:0',
},
{
// Rolldown resolves to the directive while Rollup has no mapping.
name: 're-export',
rolldown: '/src/features/specifiers/reexport.ts:0:0',
rollup: null,
rolldown: '/src/features/specifiers/reexport.ts:2:0',
},
{
// Rolldown resolves to the directive while Rollup has no mapping.
name: 'export-all',
rolldown: '/src/features/specifiers/export-all.ts:0:0',
rollup: null,
rolldown: '/src/features/specifiers/export-all.ts:2:0',
},
],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
hasDirective,
transformDirectiveProxyExport,
transformHoistInlineDirective,
transformWrapExport,
transformModuleExportEffect,
} from '@vitejs/plugin-rsc/transforms'
import { parseAstAsync, type Plugin } from 'vite'

Expand Down Expand Up @@ -37,9 +37,10 @@ export function customServerFunctionPlugin(): Plugin {
const runtime = (value: string, name: string) =>
`$$CustomReactServer.registerServerReference(${value}, ${JSON.stringify(reference.referenceKey)}, ${JSON.stringify(name)})`
const result = hasDirective(ast.body, directive)
? transformWrapExport(code, ast, {
runtime,
? transformModuleExportEffect(code, ast, {
rejectNonAsyncFunction: true,
runtime: ({ binding, exportName }) =>
runtime(binding, exportName),
})
: transformHoistInlineDirective(code, ast, {
directive,
Expand All @@ -53,7 +54,7 @@ export function customServerFunctionPlugin(): Plugin {

manager.serverReferences.replaceClaim(pluginName, id, {
...reference,
exportNames: 'names' in result ? result.names : result.exportNames,
exportNames: 'names' in result ? result.names : result.referenceNames,
})
result.output.prepend(
`import * as $$CustomReactServer from "@vitejs/plugin-rsc/react/rsc/server";\n`,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## hoist

```txt
(0:0) "export function InlineDirective() {\n" --> (0:0) "export function InlineDirective() {\n"
(1:0) " const captured = 'captured'\n" --> (1:0) " const captured = 'captured'\n"
(3:0) " async function inlineAction() {\n" --> (3:0) " const inlineAction = /* #__PURE__ */ registerServerReference($$hoist_0_inlineAction, \"$$hoist_0_inlineAction\");\n"
Expand Down Expand Up @@ -27,3 +30,4 @@
(15:0) " return 'inline function expression called'\n" --> (25:0) " return 'inline function expression called'\n"
(16:0) " })\n" --> (26:0) " };\n"
[unmapped] --> (27:0) "/* #__PURE__ */ Object.defineProperty($$hoist_2_anonymous_server_function, \"name\", { value: \"anonymous_server_function\" });\n"
```

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
## Input

```js
export function InlineDirective() {
const captured = 'captured'

async function inlineAction() {
'use server'
return 'inline directive called'
}

const inlineArrow = async (suffix = 'arrow') => {
'use server'
return `${captured} ${suffix} called`
}

consume(inlineAction, inlineArrow, async function () {
'use server'
return 'inline function expression called'
})
}
```

## hoist

**Status:** transformed

**References:** $$hoist_0_inlineAction, $$hoist_1_inlineArrow, $$hoist_2_anonymous_server_function

[Source map visualization](https://evanw.github.io/source-map-visualization/#MTI0MABleHBvcnQgZnVuY3Rpb24gSW5saW5lRGlyZWN0aXZlKCkgewogIGNvbnN0IGNhcHR1cmVkID0gJ2NhcHR1cmVkJwoKICBjb25zdCBpbmxpbmVBY3Rpb24gPSAvKiAjX19QVVJFX18gKi8gcmVnaXN0ZXJTZXJ2ZXJSZWZlcmVuY2UoJCRob2lzdF8wX2lubGluZUFjdGlvbiwgIiQkaG9pc3RfMF9pbmxpbmVBY3Rpb24iKTsKCiAgY29uc3QgaW5saW5lQXJyb3cgPSAvKiAjX19QVVJFX18gKi8gcmVnaXN0ZXJTZXJ2ZXJSZWZlcmVuY2UoJCRob2lzdF8xX2lubGluZUFycm93LCAiJCRob2lzdF8xX2lubGluZUFycm93IikuYmluZChudWxsLCBlbmNyeXB0KFtjYXB0dXJlZF0pKQoKICBjb25zdW1lKGlubGluZUFjdGlvbiwgaW5saW5lQXJyb3csIC8qICNfX1BVUkVfXyAqLyByZWdpc3RlclNlcnZlclJlZmVyZW5jZSgkJGhvaXN0XzJfYW5vbnltb3VzX3NlcnZlcl9mdW5jdGlvbiwgIiQkaG9pc3RfMl9hbm9ueW1vdXNfc2VydmVyX2Z1bmN0aW9uIikpCn0KCjtleHBvcnQgYXN5bmMgZnVuY3Rpb24gJCRob2lzdF8wX2lubGluZUFjdGlvbigpIHsKICAgICd1c2Ugc2VydmVyJwogICAgcmV0dXJuICdpbmxpbmUgZGlyZWN0aXZlIGNhbGxlZCcKICB9OwovKiAjX19QVVJFX18gKi8gT2JqZWN0LmRlZmluZVByb3BlcnR5KCQkaG9pc3RfMF9pbmxpbmVBY3Rpb24sICJuYW1lIiwgeyB2YWx1ZTogImlubGluZUFjdGlvbiIgfSk7Cgo7ZXhwb3J0IGFzeW5jIGZ1bmN0aW9uICQkaG9pc3RfMV9pbmxpbmVBcnJvdygkJGhvaXN0X2VuY29kZWQsIHN1ZmZpeCA9ICdhcnJvdycpIHsKICAgIGNvbnN0IFtjYXB0dXJlZF0gPSBhd2FpdCBkZWNyeXB0KCQkaG9pc3RfZW5jb2RlZCk7Cid1c2Ugc2VydmVyJwogICAgcmV0dXJuIGAke2NhcHR1cmVkfSAke3N1ZmZpeH0gY2FsbGVkYAogIH07Ci8qICNfX1BVUkVfXyAqLyBPYmplY3QuZGVmaW5lUHJvcGVydHkoJCRob2lzdF8xX2lubGluZUFycm93LCAibmFtZSIsIHsgdmFsdWU6ICJpbmxpbmVBcnJvdyIgfSk7Cgo7ZXhwb3J0IGFzeW5jIGZ1bmN0aW9uICQkaG9pc3RfMl9hbm9ueW1vdXNfc2VydmVyX2Z1bmN0aW9uKCkgewogICAgJ3VzZSBzZXJ2ZXInCiAgICByZXR1cm4gJ2lubGluZSBmdW5jdGlvbiBleHByZXNzaW9uIGNhbGxlZCcKICB9OwovKiAjX19QVVJFX18gKi8gT2JqZWN0LmRlZmluZVByb3BlcnR5KCQkaG9pc3RfMl9hbm9ueW1vdXNfc2VydmVyX2Z1bmN0aW9uLCAibmFtZSIsIHsgdmFsdWU6ICJhbm9ueW1vdXNfc2VydmVyX2Z1bmN0aW9uIiB9KTsKMTE5MgB7InZlcnNpb24iOjMsInNvdXJjZXMiOlsiIl0sInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCBmdW5jdGlvbiBJbmxpbmVEaXJlY3RpdmUoKSB7XG4gIGNvbnN0IGNhcHR1cmVkID0gJ2NhcHR1cmVkJ1xuXG4gIGFzeW5jIGZ1bmN0aW9uIGlubGluZUFjdGlvbigpIHtcbiAgICAndXNlIHNlcnZlcidcbiAgICByZXR1cm4gJ2lubGluZSBkaXJlY3RpdmUgY2FsbGVkJ1xuICB9XG5cbiAgY29uc3QgaW5saW5lQXJyb3cgPSBhc3luYyAoc3VmZml4ID0gJ2Fycm93JykgPT4ge1xuICAgICd1c2Ugc2VydmVyJ1xuICAgIHJldHVybiBgJHtjYXB0dXJlZH0gJHtzdWZmaXh9IGNhbGxlZGBcbiAgfVxuXG4gIGNvbnN1bWUoaW5saW5lQWN0aW9uLCBpbmxpbmVBcnJvdywgYXN5bmMgZnVuY3Rpb24gKCkge1xuICAgICd1c2Ugc2VydmVyJ1xuICAgIHJldHVybiAnaW5saW5lIGZ1bmN0aW9uIGV4cHJlc3Npb24gY2FsbGVkJ1xuICB9KVxufVxuIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE1BQU0sQ0FBQyxRQUFRLENBQUMsZUFBZSxDQUFDLENBQUMsQ0FBQztBQUNsQyxDQUFDLENBQUMsS0FBSyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxRQUFROztBQUU1QixDQUFDOztBQUtELENBQUMsQ0FBQyxLQUFLLENBQUMsV0FBVyxDQUFDLENBQUM7O0FBS3JCLENBQUMsQ0FBQyxPQUFPLENBQUMsWUFBWSxDQUFDLENBQUMsV0FBVyxDQUFDLG9IQUdqQztBQUNIO0FBZEU7QUFBQSxnREFBOEI7QUFDaEMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxNQUFNO0FBQ2YsQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxNQUFNLENBQUMsU0FBUyxDQUFDLE1BQU07QUFDbkMsQ0FBQyxDQUFDOztBQUVvQjtBQUFBLGdGQUE0QjtBQUNsRCxDQUFDLENBQUMsQ0FBQztBQUFDLENBQUMsR0FBRyxDQUFDLE1BQU07QUFDZixDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsTUFBTTtBQUN4QyxDQUFDLENBQUM7O0FBRW1DO0FBQUEsNkRBQWtCO0FBQ3ZELENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsTUFBTTtBQUNmLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxVQUFVLENBQUMsTUFBTTtBQUM3QyxDQUFDLENBQUM7OyJ9)

```js
export function InlineDirective() {
const captured = 'captured'

const inlineAction = /* #__PURE__ */ registerServerReference($$hoist_0_inlineAction, "$$hoist_0_inlineAction");

const inlineArrow = /* #__PURE__ */ registerServerReference($$hoist_1_inlineArrow, "$$hoist_1_inlineArrow").bind(null, encrypt([captured]))

consume(inlineAction, inlineArrow, /* #__PURE__ */ registerServerReference($$hoist_2_anonymous_server_function, "$$hoist_2_anonymous_server_function"))
}

;export async function $$hoist_0_inlineAction() {
'use server'
return 'inline directive called'
};
/* #__PURE__ */ Object.defineProperty($$hoist_0_inlineAction, "name", { value: "inlineAction" });

;export async function $$hoist_1_inlineArrow($$hoist_encoded, suffix = 'arrow') {
const [captured] = await decrypt($$hoist_encoded);
'use server'
return `${captured} ${suffix} called`
};
/* #__PURE__ */ Object.defineProperty($$hoist_1_inlineArrow, "name", { value: "inlineArrow" });

;export async function $$hoist_2_anonymous_server_function() {
'use server'
return 'inline function expression called'
};
/* #__PURE__ */ Object.defineProperty($$hoist_2_anonymous_server_function, "name", { value: "anonymous_server_function" });
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
## wrap-export

```txt
(0:0) "'use server'\n" --> (0:0) "'use server'\n"
(2:0) "export default " --> (2:0) "const $$default = "
(2:15) "async function () {\n" --> (2:18) "async function () {\n"
(3:0) " return 'default anonymous function called'\n" --> (3:0) " return 'default anonymous function 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) " default " --> (2:0) "const $$effect_default = "
(2:15) "async function () {\n" --> (2:25) "async function () {\n"
(3:0) " return 'default anonymous function called'\n" --> (3:0) " return 'default anonymous function called'\n"
(4:0) "}\n" --> (4:0) "}\n"
(2:0) "export default async function () {\n" --> (5:0) "\n"
(2:0) "export default async function () {\n" --> (6:0) "registerServerReference($$effect_default, \"default\");\n"
(2:0) "export default async function () {\n" --> (7:0) "export default $$effect_default;\n"
```

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
## Input

```js
'use server'

export default async function () {
return 'default anonymous function called'
}
```

## wrap-export

**Status:** transformed

**References:** default

[Source map visualization](https://evanw.github.io/source-map-visualization/#MjI5ACd1c2Ugc2VydmVyJwoKY29uc3QgJCRkZWZhdWx0ID0gYXN5bmMgZnVuY3Rpb24gKCkgewogIHJldHVybiAnZGVmYXVsdCBhbm9ueW1vdXMgZnVuY3Rpb24gY2FsbGVkJwp9CjsKY29uc3QgJCR3cmFwXyQkZGVmYXVsdCA9IC8qICNfX1BVUkVfXyAqLyByZWdpc3RlclNlcnZlclJlZmVyZW5jZSgkJGRlZmF1bHQsICJkZWZhdWx0Iik7CmV4cG9ydCB7ICQkd3JhcF8kJGRlZmF1bHQgYXMgZGVmYXVsdCB9OwozMjMAeyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIiJdLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHNlcnZlcidcblxuZXhwb3J0IGRlZmF1bHQgYXN5bmMgZnVuY3Rpb24gKCkge1xuICByZXR1cm4gJ2RlZmF1bHQgYW5vbnltb3VzIGZ1bmN0aW9uIGNhbGxlZCdcbn1cbiJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxDQUFDLEdBQUcsQ0FBQyxNQUFNOztBQUVYLGtCQUFlLEtBQUssQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDakMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsUUFBUSxDQUFDLE1BQU07QUFDM0M7Ozs7In0=)

```js
'use server'

const $$default = async function () {
return 'default anonymous function 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/#MTk0ACd1c2Ugc2VydmVyJwoKY29uc3QgJCRlZmZlY3RfZGVmYXVsdCA9IGFzeW5jIGZ1bmN0aW9uICgpIHsKICByZXR1cm4gJ2RlZmF1bHQgYW5vbnltb3VzIGZ1bmN0aW9uIGNhbGxlZCcKfQoKcmVnaXN0ZXJTZXJ2ZXJSZWZlcmVuY2UoJCRlZmZlY3RfZGVmYXVsdCwgImRlZmF1bHQiKTsKZXhwb3J0IGRlZmF1bHQgJCRlZmZlY3RfZGVmYXVsdDsKMzM1AHsidmVyc2lvbiI6Mywic291cmNlcyI6WyIiXSwic291cmNlc0NvbnRlbnQiOlsiJ3VzZSBzZXJ2ZXInXG5cbmV4cG9ydCBkZWZhdWx0IGFzeW5jIGZ1bmN0aW9uICgpIHtcbiAgcmV0dXJuICdkZWZhdWx0IGFub255bW91cyBmdW5jdGlvbiBjYWxsZWQnXG59XG4iXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsQ0FBQyxHQUFHLENBQUMsTUFBTTs7QUFFTCx5QkFBUyxLQUFLLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ2pDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLFFBQVEsQ0FBQyxNQUFNO0FBQzNDO0FBRkE7QUFBQTtBQUFBOyJ9)

```js
'use server'

const $$effect_default = async function () {
return 'default anonymous function called'
}

registerServerReference($$effect_default, "default");
export default $$effect_default;
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
## wrap-export

```txt
(0:0) "'use server'\n" --> (0:0) "'use server'\n"
(2:0) "async function defaultIdentifier() {\n" --> (2:0) "async function defaultIdentifier() {\n"
(3:0) " return 'default identifier called'\n" --> (3:0) " return 'default identifier called'\n"
(4:0) "}\n" --> (4:0) "}\n"
(6:0) "export default " --> (6:0) "const $$default = "
(6:15) "defaultIdentifier\n" --> (6:18) "defaultIdentifier\n"
[unmapped] --> (7:0) ";\n"
[unmapped] --> (8:0) "const $$wrap_$$default = /* #__PURE__ */ registerServerReference($$default, \"default\");\n"
[unmapped] --> (9:0) "export { $$wrap_$$default as default };\n"
```

## module-export-effect

```txt
(0:0) "'use server'\n" --> (0:0) "'use server'\n"
(2:0) "async function defaultIdentifier() {\n" --> (2:0) "async function defaultIdentifier() {\n"
(3:0) " return 'default identifier called'\n" --> (3:0) " return 'default identifier called'\n"
(4:0) "}\n" --> (4:0) "}\n"
(6:6) " default defaultIdentifier\n" --> (6:0) "const $$effect_default = defaultIdentifier;\n"
(6:0) "export default defaultIdentifier\n" --> (7:0) "\n"
(6:0) "export default defaultIdentifier\n" --> (8:0) "registerServerReference($$effect_default, \"default\");\n"
(6:0) "export default defaultIdentifier\n" --> (9:0) "export default $$effect_default;\n"
```
Loading
Loading