diff --git a/packages/plugin-rsc/e2e/use-cache-callable.test.ts b/packages/plugin-rsc/e2e/use-cache-callable.test.ts
index 5a2ae9be7..2344927e0 100644
--- a/packages/plugin-rsc/e2e/use-cache-callable.test.ts
+++ b/packages/plugin-rsc/e2e/use-cache-callable.test.ts
@@ -103,7 +103,7 @@ function defineTests(f: Fixture) {
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(ordinaryExports).toHaveText('object: array')
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 a9f4728f7..8a1f0b624 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
@@ -74,6 +74,9 @@ export function callableCachePlugin(): Plugin {
const result = transformDirectiveProxyExport(ast, {
code,
directive,
+ filter: (_name, meta) =>
+ meta.valueNode?.type !== 'ObjectExpression' &&
+ meta.valueNode?.type !== 'ArrayExpression',
rejectNonAsyncFunction: true,
runtime: (name) =>
`$$ReactClient.createServerReference(` +
diff --git a/packages/plugin-rsc/examples/use-cache-callable/src/features/file-directive-from-client/action.ts b/packages/plugin-rsc/examples/use-cache-callable/src/features/file-directive-from-client/action.ts
index 90dc25108..04aaa4b72 100644
--- a/packages/plugin-rsc/examples/use-cache-callable/src/features/file-directive-from-client/action.ts
+++ b/packages/plugin-rsc/examples/use-cache-callable/src/features/file-directive-from-client/action.ts
@@ -2,6 +2,11 @@
import { state } from './state'
+// Next.js excludes statically known object and array exports from "use cache"
+// server-reference handling. The transform filter mirrors that narrow case.
+export const objectValue = { text: 'object' }
+export const arrayValue = ['array']
+
export async function cachedFromClient(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/action.ts b/packages/plugin-rsc/examples/use-cache-callable/src/features/file-directive-from-server/action.ts
index 2de9df9ca..0573bd770 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,10 +2,10 @@
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']
+// Next.js excludes statically known object and array exports from "use cache"
+// server-reference handling. The transform filter mirrors that narrow case.
+export const objectValue = { text: 'object' }
+export const arrayValue = ['array']
export async function cachedFromServer(formData: FormData) {
const argument = String(formData.get('argument'))
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 3f09befef..394509bef 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, metadata, tags } from './action'
+import { arrayValue, cachedFromServer, objectValue } from './action'
import { FileDirectiveFromServerClient } from './client'
import { resetAction } from './reset'
import { state } from './state'
@@ -8,7 +8,7 @@ export function FileDirectiveFromServer() {
diff --git a/packages/plugin-rsc/src/transforms/fixtures/source-map/proxy-export/filter-value-node.js b/packages/plugin-rsc/src/transforms/fixtures/source-map/proxy-export/filter-value-node.js
new file mode 100644
index 000000000..01f36df69
--- /dev/null
+++ b/packages/plugin-rsc/src/transforms/fixtures/source-map/proxy-export/filter-value-node.js
@@ -0,0 +1,7 @@
+'use cache'
+
+export const cached = async () => {},
+ objectValue = {},
+ arrayValue = []
+export const unknown = createCached()
+export const primitive = 0
diff --git a/packages/plugin-rsc/src/transforms/fixtures/source-map/proxy-export/filter-value-node.js.map.snap.md b/packages/plugin-rsc/src/transforms/fixtures/source-map/proxy-export/filter-value-node.js.map.snap.md
new file mode 100644
index 000000000..81729a733
--- /dev/null
+++ b/packages/plugin-rsc/src/transforms/fixtures/source-map/proxy-export/filter-value-node.js.map.snap.md
@@ -0,0 +1,7 @@
+## proxy-export-filtered
+
+```txt
+(2:0) "export const cached = async () => {},\n" --> (2:0) "export const cached = /* #__PURE__ */ createServerReference(\"cached\");\n"
+(5:0) "export const unknown = createCached()\n" --> (4:0) "export const unknown = /* #__PURE__ */ createServerReference(\"unknown\");\n"
+(6:0) "export const primitive = 0\n" --> (6:0) "export const primitive = /* #__PURE__ */ createServerReference(\"primitive\");\n"
+```
diff --git a/packages/plugin-rsc/src/transforms/fixtures/source-map/proxy-export/filter-value-node.js.snap.md b/packages/plugin-rsc/src/transforms/fixtures/source-map/proxy-export/filter-value-node.js.snap.md
new file mode 100644
index 000000000..84f060c5d
--- /dev/null
+++ b/packages/plugin-rsc/src/transforms/fixtures/source-map/proxy-export/filter-value-node.js.snap.md
@@ -0,0 +1,30 @@
+## Input
+
+```js
+'use cache'
+
+export const cached = async () => {},
+ objectValue = {},
+ arrayValue = []
+export const unknown = createCached()
+export const primitive = 0
+```
+
+## proxy-export-filtered
+
+**Status:** transformed
+
+**References:** cached, unknown, primitive
+
+[Source map visualization](https://evanw.github.io/source-map-visualization/#MjI2AAoKZXhwb3J0IGNvbnN0IGNhY2hlZCA9IC8qICNfX1BVUkVfXyAqLyBjcmVhdGVTZXJ2ZXJSZWZlcmVuY2UoImNhY2hlZCIpOwoKZXhwb3J0IGNvbnN0IHVua25vd24gPSAvKiAjX19QVVJFX18gKi8gY3JlYXRlU2VydmVyUmVmZXJlbmNlKCJ1bmtub3duIik7CgpleHBvcnQgY29uc3QgcHJpbWl0aXZlID0gLyogI19fUFVSRV9fICovIGNyZWF0ZVNlcnZlclJlZmVyZW5jZSgicHJpbWl0aXZlIik7CgoyNTYAeyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIiJdLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIGNhY2hlJ1xuXG5leHBvcnQgY29uc3QgY2FjaGVkID0gYXN5bmMgKCkgPT4ge30sXG4gIG9iamVjdFZhbHVlID0ge30sXG4gIGFycmF5VmFsdWUgPSBbXVxuZXhwb3J0IGNvbnN0IHVua25vd24gPSBjcmVhdGVDYWNoZWQoKVxuZXhwb3J0IGNvbnN0IHByaW1pdGl2ZSA9IDBcbiJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUVBOztBQUdBOztBQUNBOzsifQ==)
+
+```js
+
+
+export const cached = /* #__PURE__ */ createServerReference("cached");
+
+export const unknown = /* #__PURE__ */ createServerReference("unknown");
+
+export const primitive = /* #__PURE__ */ createServerReference("primitive");
+
+```
diff --git a/packages/plugin-rsc/src/transforms/module-export-scan.ts b/packages/plugin-rsc/src/transforms/module-export-scan.ts
index 92b3b3bac..fa2084fb3 100644
--- a/packages/plugin-rsc/src/transforms/module-export-scan.ts
+++ b/packages/plugin-rsc/src/transforms/module-export-scan.ts
@@ -150,6 +150,11 @@ export function scanModuleExports(
// uniformly handle destructured exports such as
// export const { foo, bar } = ...
// even though associated `meta` doesn't make sense anymore
+ // TODO: Treat destructured bindings as unknown for both
+ // "use server" and "use cache" instead of using the container
+ // initializer as each binding's `valueNode`.
+ // See the destructured-binding proxy export regression test.
+ // https://github.com/vercel/next.js/blob/aae4179ac628e55483b62cd023a7e1827dcef122/crates/next-custom-transforms/src/transforms/server_actions.rs#L1787-L1815
exports: extractNames(declarator.id).map((name) => ({
localName: name,
exportName: name,
diff --git a/packages/plugin-rsc/src/transforms/proxy-export.test.ts b/packages/plugin-rsc/src/transforms/proxy-export.test.ts
index cca6086c6..8702ee797 100644
--- a/packages/plugin-rsc/src/transforms/proxy-export.test.ts
+++ b/packages/plugin-rsc/src/transforms/proxy-export.test.ts
@@ -1,17 +1,72 @@
import { parseAstAsync } from 'vite'
import { describe, expect, test } from 'vitest'
-import { transformProxyExport } from './proxy-export'
+import {
+ transformProxyExport,
+ type TransformProxyExportOptions,
+} from './proxy-export'
-async function testTransform(input: string) {
+async function testTransform(
+ input: string,
+ options?: Partial,
+) {
const ast = await parseAstAsync(input)
const result = transformProxyExport(ast, {
code: input,
runtime: (name) => `$$proxy("", ${JSON.stringify(name)})`,
+ ...options,
})
return { ...result, output: result.output.toString() }
}
describe(transformProxyExport, () => {
+ test('filter runs before validation', async () => {
+ const input = `export const cached = async () => {}, objectValue = {}`
+ const ast = await parseAstAsync(input)
+ const options: TransformProxyExportOptions = {
+ code: input,
+ runtime: (name) => `$$proxy(${JSON.stringify(name)})`,
+ rejectNonAsyncFunction: true,
+ filter: (_name, meta) => meta.valueNode?.type !== 'ObjectExpression',
+ }
+
+ expect(() => transformProxyExport(ast, options)).not.toThrow()
+
+ const invalidInput = `${input}, primitive = 0`
+ const invalidAst = await parseAstAsync(invalidInput)
+ expect(() =>
+ transformProxyExport(invalidAst, { ...options, code: invalidInput }),
+ ).toThrow('unsupported non async function')
+ })
+
+ test('filter classifies destructured bindings from their container', async () => {
+ const input = `export const { cached } = { cached: async () => {} }`
+ const result = await testTransform(input, {
+ rejectNonAsyncFunction: true,
+ filter: (_name, meta) => meta.valueNode?.type !== 'ObjectExpression',
+ })
+
+ // TODO: A destructured binding should have no `valueNode` because the
+ // container is not its value. The filter should therefore conservatively
+ // select `cached` without validating the object initializer, resulting in
+ // `exportNames: ['cached']`.
+ // https://github.com/vercel/next.js/tree/aae4179ac628e55483b62cd023a7e1827dcef122/crates/next-custom-transforms/tests/fixture/server-actions/client-graph/14
+ expect(result.exportNames).toEqual([])
+ })
+
+ test.each([
+ ['{}', undefined],
+ ['[]', undefined],
+ ['{}', () => true],
+ ['[]', () => true],
+ ])('validates empty binding %s with filter %s', async (id, filter) => {
+ await expect(
+ testTransform(`export const ${id} = ${id}`, {
+ rejectNonAsyncFunction: true,
+ filter,
+ }),
+ ).rejects.toThrow('unsupported non async function')
+ })
+
test('export string name throws', async () => {
const input = `
const x = 0;
@@ -27,4 +82,13 @@ export { x as "my thing" }
'unsupported ExportAllDeclaration',
)
})
+
+ test('filter with keep throws', async () => {
+ await expect(
+ testTransform(`export const action = () => {}`, {
+ keep: true,
+ filter: () => true,
+ }),
+ ).rejects.toThrow('`filter` option is not supported with `keep`')
+ })
})
diff --git a/packages/plugin-rsc/src/transforms/proxy-export.ts b/packages/plugin-rsc/src/transforms/proxy-export.ts
index aeebb583a..b7daf4834 100644
--- a/packages/plugin-rsc/src/transforms/proxy-export.ts
+++ b/packages/plugin-rsc/src/transforms/proxy-export.ts
@@ -1,52 +1,118 @@
import type { Node, Program } from 'estree'
import MagicString from 'magic-string'
import type { ESTree } from 'vite'
-import { extractNames, hasDirective, validateNonAsyncFunction } from './utils'
+import { scanModuleExports, type ModuleExportMeta } from './module-export-scan'
+import { hasDirective, validateNonAsyncFunction } from './utils'
+
+/** Selects which statically discovered exports become proxies. */
+export type TransformProxyExportFilter = (
+ name: string,
+ meta: ModuleExportMeta,
+) => boolean
export type TransformProxyExportOptions = {
- /** Required for source map and `keep` options */
+ /**
+ * Original module source used to preserve source mappings and initializer
+ * text. Required by `keep`.
+ */
code?: string
+ /**
+ * Returns the proxy expression for an export name. In `keep` mode, `value`
+ * contains the original initializer for a single identifier declaration.
+ */
runtime: (name: string, meta?: { value: string }) => string
+ /**
+ * Removes a bare `export *` instead of rejecting it. With `keep`, the
+ * declaration is retained.
+ * @default false
+ */
ignoreExportAllDeclaration?: boolean
+ /** Rejects statically known values that are not async functions. */
rejectNonAsyncFunction?: boolean
/**
- * escape hatch for Waku's `allowServer`
+ * Selects exports before validation and proxy generation. Filtered exports
+ * are omitted from both the generated module and `exportNames`.
+ *
+ * Cannot be combined with `keep`.
+ * @default () => true
+ */
+ filter?: TransformProxyExportFilter
+ /**
+ * Retains non-export implementation code and passes a single identifier
+ * declaration's initializer to `runtime`. This is an escape hatch for Waku's
+ * `allowServer` transform.
+ *
+ * Requires `code` and cannot be combined with `filter`.
* @default false
*/
keep?: boolean
}
+export type TransformProxyExportResult = {
+ exportNames: string[]
+ output: MagicString
+}
+
export function transformDirectiveProxyExport(
ast: ESTree.Program,
options: {
directive: string
} & TransformProxyExportOptions,
-):
- | {
- exportNames: string[]
- output: MagicString
- }
- | undefined {
+): TransformProxyExportResult | undefined {
if (!hasDirective(ast.body, options.directive)) {
return
}
return transformProxyExport(ast, options)
}
+/**
+ * Replaces selected exports with proxies created by `runtime` and removes the
+ * original module implementation.
+ *
+ * Conceptually, with a filter that excludes `objectValue`:
+ *
+ * ```js
+ * import { dependency } from './dep'
+ * export async function action() {}
+ * export const objectValue = {}
+ * export { dependency as renamed }
+ * export default async () => {}
+ * ```
+ *
+ * becomes:
+ *
+ * ```js
+ * export const action = __PROXY__('action')
+ * export const renamed = __PROXY__('renamed')
+ * export default __PROXY__('default')
+ * ```
+ *
+ * Unlike `transformWrapExport`, this transform does not evaluate the original
+ * exports. Unknown values are represented only by their export names, so the
+ * caller must filter solely from the available static `ModuleExportMeta`.
+ * `keep` is a specialized mode that retains non-export implementation code and
+ * passes a single variable initializer to `runtime`.
+ */
export function transformProxyExport(
viteAst: ESTree.Program,
options: TransformProxyExportOptions,
-): {
- exportNames: string[]
- output: MagicString
-} {
+): TransformProxyExportResult {
const ast = viteAst as unknown as Program
if (options.keep && typeof options.code !== 'string') {
throw new Error('`keep` option requires `code`')
}
+ if (options.keep && options.filter) {
+ throw new Error('`filter` option is not supported with `keep`')
+ }
const output = new MagicString(options.code ?? ' '.repeat(ast.end))
const exportNames: string[] = []
+ const filter = options.filter ?? (() => true)
+ function removeNode(node: Node) {
+ output.remove(node.start, node.end)
+ }
+
+ /** Replaces one complete export statement with the selected proxy exports. */
function createExport(node: Node, names: string[]) {
exportNames.push(...names)
const newCode = names
@@ -59,97 +125,111 @@ export function transformProxyExport(
output.update(node.start, node.end, newCode)
}
- for (const node of ast.body) {
- if (node.type === 'ExportNamedDeclaration') {
- if (node.declaration) {
+ const exportNodes = new Set()
+ for (const group of scanModuleExports(viteAst)) {
+ const node = group.node as Node
+ exportNodes.add(node)
+
+ if (group.type === 'declaration') {
+ // export function action() {}
+ // -> export const action = __PROXY__('action')
+ const entry = group.export
+ if (filter(entry.exportName, entry.meta)) {
+ validateNonAsyncFunction(options, group.declaration)
+ createExport(node, [entry.exportName])
+ } else {
+ removeNode(node)
+ }
+ } else if (group.type === 'variable-declaration') {
+ // export const selected = init(), skipped = {}
+ // -> export const selected = __PROXY__('selected')
+ const selectedNames: string[] = []
+ for (const declarator of group.declarators) {
+ const names = declarator.exports
+ .filter((entry) => filter(entry.exportName, entry.meta))
+ .map((entry) => entry.exportName)
if (
- node.declaration.type === 'FunctionDeclaration' ||
- node.declaration.type === 'ClassDeclaration'
+ declarator.node.init &&
+ (!options.filter ||
+ declarator.exports.length === 0 ||
+ names.length > 0)
) {
- /**
- * export function foo() {}
- */
- validateNonAsyncFunction(options, node.declaration)
- createExport(node, [node.declaration.id.name])
- } else if (node.declaration.type === 'VariableDeclaration') {
- /**
- * export const foo = 1, bar = 2
- */
- for (const decl of node.declaration.declarations) {
- if (decl.init) validateNonAsyncFunction(options, decl.init)
- }
- if (options.keep && options.code) {
- if (node.declaration.declarations.length === 1) {
- const decl = node.declaration.declarations[0]!
- if (decl.id.type === 'Identifier' && decl.init) {
- const name = decl.id.name
- const value = options.code.slice(decl.init.start, decl.init.end)
- const newCode = `export const ${name} = /* #__PURE__ */ ${options.runtime(
- name,
- { value },
- )};`
- output.update(node.start, node.end, newCode)
- exportNames.push(name)
- continue
- }
- }
- }
- const names = node.declaration.declarations.flatMap((decl) =>
- extractNames(decl.id),
- )
- createExport(node, names)
- } else {
- node.declaration satisfies never
+ validateNonAsyncFunction(options, declarator.node.init)
}
- } else {
- /**
- * export { foo, bar as car } from './foo'
- * export { foo, bar as car }
- */
- const names: string[] = []
- for (const spec of node.specifiers) {
- if (spec.exported.type !== 'Identifier') {
+ selectedNames.push(...names)
+ }
+ if (options.keep && group.declaration.declarations.length === 1) {
+ // Waku's `keep` mode retains the initializer as the proxy value:
+ // export const value = init()
+ // -> export const value = __PROXY__(init(), 'value')
+ const decl = group.declaration.declarations[0]!
+ if (decl.id.type === 'Identifier' && decl.init) {
+ const name = decl.id.name
+ const value = options.code!.slice(decl.init.start, decl.init.end)
+ const newCode = `export const ${name} = /* #__PURE__ */ ${options.runtime(
+ name,
+ { value },
+ )};`
+ output.update(node.start, node.end, newCode)
+ exportNames.push(name)
+ continue
+ }
+ }
+ createExport(node, selectedNames)
+ } else if (group.type === 'specifiers') {
+ // export { local as renamed } from './dep'
+ // -> export const renamed = __PROXY__('renamed')
+ const names = group.exports
+ .filter((entry) => {
+ if (entry.node.exported.type !== 'Identifier') {
throw Object.assign(
new Error('unsupported string literal export name'),
- { pos: spec.exported.start },
+ { pos: entry.node.exported.start },
)
}
- names.push(spec.exported.name)
+ return filter(entry.exportName, entry.meta)
+ })
+ .map((entry) => entry.exportName)
+ createExport(node, names)
+ } else if (group.type === 'export-all') {
+ // A namespace re-export has one known name. A bare export-all cannot be
+ // represented without resolving the dependency's export names.
+ // TODO: Reject `export * as "name"` as an unsupported string literal
+ // export name instead of handling it like a bare `export *`.
+ if (group.node.exported?.type === 'Identifier') {
+ // export * as dep from './dep'
+ // -> export const dep = __PROXY__('dep')
+ const name = group.node.exported.name
+ if (filter(name, {})) {
+ createExport(node, [name])
+ } else {
+ removeNode(node)
}
- createExport(node, names)
- }
- continue
- }
-
- /**
- * export * as ns from './foo'
- * export * from './foo'
- */
- if (node.type === 'ExportAllDeclaration') {
- if (node.exported?.type === 'Identifier') {
- createExport(node, [node.exported.name])
- continue
- }
- if (!options.ignoreExportAllDeclaration) {
+ } else if (!options.ignoreExportAllDeclaration) {
throw new Error('unsupported ExportAllDeclaration')
+ } else if (!options.keep) {
+ removeNode(node)
+ }
+ } else if (group.type === 'default') {
+ // export default async () => {}
+ // -> export default __PROXY__('default')
+ if (filter('default', group.meta)) {
+ validateNonAsyncFunction(options, group.node.declaration)
+ createExport(node, ['default'])
+ } else {
+ removeNode(node)
}
}
+ }
- /**
- * export default function foo() {}
- * export default class Foo {}
- * export default () => {}
- */
- if (node.type === 'ExportDefaultDeclaration') {
- validateNonAsyncFunction(options, node.declaration)
- createExport(node, ['default'])
- continue
+ if (!options.keep) {
+ // Imports, directives, and implementation statements must not execute in
+ // the graph that consumes these proxies.
+ for (const node of ast.body) {
+ if (!exportNodes.has(node)) {
+ removeNode(node)
+ }
}
-
- if (options.keep) continue
-
- // remove all other nodes
- output.remove(node.start, node.end)
}
return { exportNames, output }
diff --git a/packages/plugin-rsc/src/transforms/source-map.test.ts b/packages/plugin-rsc/src/transforms/source-map.test.ts
index 71664fa11..32aa8a028 100644
--- a/packages/plugin-rsc/src/transforms/source-map.test.ts
+++ b/packages/plugin-rsc/src/transforms/source-map.test.ts
@@ -4,7 +4,10 @@ import { describe, expect, test } from 'vitest'
import { transformCjsToEsm } from './cjs'
import { transformHoistInlineDirective } from './hoist'
import { transformModuleExportEffect } from './module-export-effect'
-import { transformProxyExport } from './proxy-export'
+import {
+ transformProxyExport,
+ type TransformProxyExportOptions,
+} from './proxy-export'
import {
formatDecodedSourceMapMarkdown,
formatSourceMapMarkdownFixture,
@@ -89,16 +92,28 @@ describe('source map fixtures', () => {
string,
{
name: string
- keep?: boolean
- ignoreExportAllDeclaration?: boolean
+ options?: Partial
}[]
> = {
'./fixtures/source-map/proxy-export/export-all-ignore.js': [
- { name: 'proxy-export', ignoreExportAllDeclaration: true },
+ {
+ name: 'proxy-export',
+ options: { ignoreExportAllDeclaration: true },
+ },
],
'./fixtures/source-map/proxy-export/keep.js': [
{ name: 'proxy-export' },
- { name: 'proxy-export-keep', keep: true },
+ { name: 'proxy-export-keep', options: { keep: true } },
+ ],
+ './fixtures/source-map/proxy-export/filter-value-node.js': [
+ {
+ name: 'proxy-export-filtered',
+ options: {
+ filter: (_name, meta) =>
+ meta.valueNode?.type !== 'ObjectExpression' &&
+ meta.valueNode?.type !== 'ArrayExpression',
+ },
+ },
],
}
for (const [file, load] of Object.entries(proxyExportFixtures)) {
@@ -108,7 +123,7 @@ describe('source map fixtures', () => {
const variants = proxyExportFixtureVariants[file] ?? [
{ name: 'proxy-export' },
]
- const outputs = variants.map(({ name, ...options }) => {
+ const outputs = variants.map(({ name, options }) => {
const result = transformProxyExport(ast, {
code: input,
...options,
diff --git a/packages/plugin-rsc/src/transforms/wrap-export.test.ts b/packages/plugin-rsc/src/transforms/wrap-export.test.ts
index 58d01d77c..0d745600c 100644
--- a/packages/plugin-rsc/src/transforms/wrap-export.test.ts
+++ b/packages/plugin-rsc/src/transforms/wrap-export.test.ts
@@ -369,8 +369,8 @@ export default Page;
test('filter value node', async () => {
const input = `\
export const action = async () => {}
-export const metadata = {}
-export const tags = []
+export const objectValue = {}
+export const arrayValue = []
`
const ast = await parseAstAsync(input)
const result = transformWrapExport(input, ast, {
@@ -384,8 +384,8 @@ export const tags = []
expect(result.exportNames).toEqual(['action'])
expect(result.output.toString()).toMatchInlineSnapshot(`
"let action = async () => {}
- export const metadata = {}
- export const tags = []
+ export const objectValue = {}
+ export const arrayValue = []
action = /* #__PURE__ */ $$wrap(action, "action");
export { action };
"