Skip to content
46 changes: 23 additions & 23 deletions packages/plugin-rsc/src/transforms/wrap-export.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, "<id>", "b");
export { b };
c = /* #__PURE__ */ $$wrap(c, "<id>", "c");
Expand All @@ -282,13 +281,7 @@ export default () => {}
const result = await testTransform(input, {
filter: (_name, meta) => !!(meta.isFunction && meta.declName),
})
expect(result).toMatchInlineSnapshot(`
"
const $$default = () => {}
;
export { $$default as default };
"
`)
expect(result).toMatchInlineSnapshot(`false`)
})

test('filter defaultExportIdentifierName', async () => {
Expand Down Expand Up @@ -329,12 +322,10 @@ export const tags = []
expect(result.exportNames).toEqual(['action'])
expect(result.output.toString()).toMatchInlineSnapshot(`
"let action = async () => {}
let metadata = {}
let tags = []
export const metadata = {}
export const tags = []
action = /* #__PURE__ */ $$wrap(action, "action");
export { action };
export { metadata };
export { tags };
"
`)
})
Expand All @@ -353,30 +344,39 @@ 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 };
"
`)
})

test('filtered default exports are not validated or reported', async () => {
const input = `export default 1;`
test.each([
`export function action() {}`,
`export class Action {}`,
`export const action = 1`,
`const action = 1; export { action }`,
`export { action } from './dep'`,
`export default function action() {}`,
`export default function () {}`,
`export default class {}`,
`export default action`,
`export default 1`,
])('fully filtered export is unchanged: %s', async (input) => {
const ast = await parseAstAsync(input)
const result = transformWrapExport(input, ast, {
runtime: (value, name) => `$$wrap(${value}, ${JSON.stringify(name)})`,
runtime: () => {
throw new Error('runtime called for filtered export')
},
rejectNonAsyncFunction: true,
filter: () => false,
})

expect(result.exportNames).toEqual([])
expect(result.output.toString()).toMatchInlineSnapshot(`
"const $$default = 1;;
export { $$default as default };
"
`)
expect(result.output.hasChanged()).toBe(false)
expect(result.output.toString()).toBe(input)
})

test('unknown identifier exports remain eligible for wrapping', async () => {
Expand Down
84 changes: 50 additions & 34 deletions packages/plugin-rsc/src/transforms/wrap-export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,18 @@ export function transformWrapExport(
const toAppend: string[] = []
const filter = options.filter ?? (() => true)

function filterExports<T extends ModuleExportEntry>(exports: T[]) {
return exports.map((item) => ({
...item,
shouldWrap: filter(item.exportName, item.meta),
}))
}

function wrapSimple(
start: number,
end: number,
exports: ModuleExportEntry[],
filteredExports: (ModuleExportEntry & { shouldWrap: boolean })[],
) {
const filteredExports = exports.map((item) => {
return {
...item,
shouldWrap: filter(item.exportName, item.meta),
}
})
exportNames.push(
...filteredExports
.filter((item) => item.shouldWrap)
Expand Down Expand Up @@ -78,9 +79,10 @@ export function transformWrapExport(
function wrapExport(
name: string,
exportName: string,
meta: ModuleExportMeta = {},
meta: ModuleExportMeta,
shouldWrap: boolean,
) {
if (!filter(exportName, meta)) {
if (!shouldWrap) {
toAppend.push(`export { ${name} as ${exportName} }`)
return
}
Expand All @@ -98,36 +100,44 @@ export function transformWrapExport(

for (const group of scanModuleExports(viteAst)) {
if (group.type === 'declaration') {
const [entry] = group.exports
if (filter(entry.exportName, entry.meta)) {
validateNonAsyncFunction(options, group.declaration)
}
wrapSimple(group.node.start, group.declaration.start, group.exports)
const exports = filterExports(group.exports)
if (!exports.some((entry) => entry.shouldWrap)) continue

validateNonAsyncFunction(options, group.declaration)
wrapSimple(group.node.start, group.declaration.start, exports)
} else if (group.type === 'variable-declaration') {
const exports: (ModuleExportEntry & { shouldWrap: boolean })[] = []
let shouldWrap = false
for (const declarator of group.declarators) {
const declaratorExports = filterExports(declarator.exports)
exports.push(...declaratorExports)
const shouldWrapDeclarator = declaratorExports.some(
(entry) => entry.shouldWrap,
)
if (shouldWrapDeclarator) {
shouldWrap = true
}
if (declarator.node.init && shouldWrapDeclarator) {
validateNonAsyncFunction(options, declarator.node.init)
}
}
if (!shouldWrap) continue

if (group.declaration.kind === 'const') {
output.update(
group.declaration.start,
group.declaration.start + 5,
'let',
)
}
const exports: ModuleExportEntry[] = []
for (const declarator of group.declarators) {
exports.push(...declarator.exports)
if (
declarator.node.init &&
declarator.exports.some(({ exportName, meta }) =>
filter(exportName, meta),
)
) {
validateNonAsyncFunction(options, declarator.node.init)
}
}
wrapSimple(group.node.start, group.declaration.start, exports)
} else if (group.type === 'specifiers') {
const exports = filterExports(group.exports)
if (!exports.some((entry) => entry.shouldWrap)) continue

if (group.node.source) {
output.remove(group.node.start, group.node.end)
for (const entry of group.exports) {
for (const entry of exports) {
tinyassert(entry.node.local.type === 'Identifier')
if (entry.node.exported.type !== 'Identifier') {
throw Object.assign(
Expand All @@ -142,19 +152,25 @@ export function transformWrapExport(
`$$import_${entry.localName}`,
entry.exportName,
entry.meta,
entry.shouldWrap,
)
}
} else {
output.remove(group.node.start, group.node.end)
for (const entry of group.exports) {
for (const entry of exports) {
tinyassert(entry.node.local.type === 'Identifier')
if (entry.node.exported.type !== 'Identifier') {
throw Object.assign(
new Error('unsupported string literal export name'),
{ pos: entry.node.exported.start },
)
}
wrapExport(entry.localName, entry.exportName, entry.meta)
wrapExport(
entry.localName,
entry.exportName,
entry.meta,
entry.shouldWrap,
)
}
}
} else if (group.type === 'export-all') {
Expand All @@ -167,6 +183,9 @@ export function transformWrapExport(
})
}
} else if (group.type === 'default') {
const meta = group.meta
if (!filter('default', meta)) continue

const localName = group.localName ?? '$$default'
if (group.kind === 'named-declaration') {
// preserve name scope for `function foo() {}` and `class Foo {}`
Expand All @@ -190,11 +209,8 @@ export function transformWrapExport(
'const $$default = ',
)
}
const meta = group.meta
if (filter('default', meta)) {
validateNonAsyncFunction(options, group.node.declaration)
}
wrapExport(localName, 'default', meta)
validateNonAsyncFunction(options, group.node.declaration)
wrapExport(localName, 'default', meta, true)
}
}

Expand Down
Loading