diff --git a/packages/plugin-rsc/e2e/basic.test.ts b/packages/plugin-rsc/e2e/basic.test.ts index f5bb8aa62..01cadf3b0 100644 --- a/packages/plugin-rsc/e2e/basic.test.ts +++ b/packages/plugin-rsc/e2e/basic.test.ts @@ -376,6 +376,27 @@ function defineTest(f: Fixture) { ) }) + test('use server export all @js', async ({ page }) => { + await page.goto(f.url()) + await waitForHydration(page) + + await expect(page.getByTestId('test-action-export-all-server')).toHaveText( + 'export-all-server-to-server: 0', + ) + await page.getByTestId('test-action-export-all-server').click() + await expect(page.getByTestId('test-action-export-all-server')).toHaveText( + 'export-all-server-to-server: 1', + ) + + await expect(page.getByTestId('test-action-export-all-client')).toHaveText( + 'export-all-server-to-client: ?', + ) + await page.getByTestId('test-action-export-all-client').click() + await expect(page.getByTestId('test-action-export-all-client')).toHaveText( + 'export-all-server-to-client: export-all-client', + ) + }) + test('useActionState with jsx @js', async ({ page }) => { await page.goto(f.url()) await waitForHydration(page) @@ -1874,7 +1895,7 @@ function defineTest(f: Fixture) { ) }) - test('export *', async ({ page }) => { + test('export all', async ({ page }) => { await page.goto(f.url()) await waitForHydration(page) await expect(page.getByTestId('test-export-all')).toHaveText( diff --git a/packages/plugin-rsc/examples/basic/src/routes/action-export-all/action-impl.ts b/packages/plugin-rsc/examples/basic/src/routes/action-export-all/action-impl.ts new file mode 100644 index 000000000..b14fd254c --- /dev/null +++ b/packages/plugin-rsc/examples/basic/src/routes/action-export-all/action-impl.ts @@ -0,0 +1,13 @@ +let serverValue = 0 + +export async function readExportAllServerValue() { + return serverValue +} + +export async function incrementExportAllServerValue() { + serverValue++ +} + +export async function getExportAllClientValue() { + return 'export-all-client' +} diff --git a/packages/plugin-rsc/examples/basic/src/routes/action-export-all/actions.ts b/packages/plugin-rsc/examples/basic/src/routes/action-export-all/actions.ts new file mode 100644 index 000000000..bf9797022 --- /dev/null +++ b/packages/plugin-rsc/examples/basic/src/routes/action-export-all/actions.ts @@ -0,0 +1,3 @@ +'use server' + +export * from './action-impl' diff --git a/packages/plugin-rsc/examples/basic/src/routes/action-export-all/client.tsx b/packages/plugin-rsc/examples/basic/src/routes/action-export-all/client.tsx new file mode 100644 index 000000000..716cb9568 --- /dev/null +++ b/packages/plugin-rsc/examples/basic/src/routes/action-export-all/client.tsx @@ -0,0 +1,19 @@ +'use client' + +import React from 'react' +import { getExportAllClientValue } from './actions' + +export function TestActionExportAllClient() { + const [result, setResult] = React.useState('?') + + return ( + + ) +} diff --git a/packages/plugin-rsc/examples/basic/src/routes/action-export-all/server.tsx b/packages/plugin-rsc/examples/basic/src/routes/action-export-all/server.tsx new file mode 100644 index 000000000..243d3c35e --- /dev/null +++ b/packages/plugin-rsc/examples/basic/src/routes/action-export-all/server.tsx @@ -0,0 +1,18 @@ +import { + incrementExportAllServerValue, + readExportAllServerValue, +} from './actions' +import { TestActionExportAllClient } from './client' + +export function TestActionExportAll() { + return ( + <> +
+ +
+ + + ) +} diff --git a/packages/plugin-rsc/examples/basic/src/routes/root.tsx b/packages/plugin-rsc/examples/basic/src/routes/root.tsx index 5192bca23..ee7718b9a 100644 --- a/packages/plugin-rsc/examples/basic/src/routes/root.tsx +++ b/packages/plugin-rsc/examples/basic/src/routes/root.tsx @@ -9,6 +9,7 @@ import { TestServerActionBindMember, } from './action-bind/server' import { TestServerActionError } from './action-error/server' +import { TestActionExportAll } from './action-export-all/server' import { TestActionFromClient, TestNonFormActionArgs, @@ -101,6 +102,7 @@ export function Root(props: { url: URL }) { + diff --git a/packages/plugin-rsc/src/plugin.ts b/packages/plugin-rsc/src/plugin.ts index 023729878..01ec7b083 100644 --- a/packages/plugin-rsc/src/plugin.ts +++ b/packages/plugin-rsc/src/plugin.ts @@ -65,12 +65,13 @@ import { } from './plugins/vite-utils' import { type TransformWrapExportFilter, - extractNames, hasDirective, transformDirectiveProxyExport, + transformExpandExportAll, transformServerActionServer, transformWrapExport, findDirectives, + type TransformExpandExportAllContext, } from './transforms' import { generateEncryptionKey, toBase64 } from './utils/encryption-utils' import { createRpcServer } from './utils/rpc' @@ -1376,141 +1377,26 @@ function globalAsyncLocalStoragePlugin(): Plugin[] { ] } -// Strip TS/JSX so `parseAstAsync` can read the result. Prefer oxc when -// available (Vite 8+); fall back to esbuild for older Vite versions. -async function transformSourceForExportScan( - code: string, - filename: string, -): Promise { - const v = vite as Partial<{ - transformWithOxc: ( - code: string, - filename: string, - options?: { sourcemap?: boolean }, - ) => Promise<{ code: string }> - transformWithEsbuild: ( - code: string, - filename: string, - options?: { sourcemap?: boolean }, - ) => Promise<{ code: string }> - }> - const transform = v.transformWithOxc ?? v.transformWithEsbuild - if (!transform) return undefined - const result = await transform(code, filename, { sourcemap: false }) - return result.code -} - -// Recursively collect the named exports of a module (following `export * from` -// chains), so that the RSC `use client`/`use server` proxy transforms can -// expand bare `export *` re-exports into explicit named re-exports before -// proxy generation. The pure proxy transform cannot do this on its own because -// the names live in another file. -async function collectExportNames( +function createTransformExpandExportAllContext( ctx: Rollup.TransformPluginContext, - resolvedId: string, - seen: Set, -): Promise { - if (seen.has(resolvedId)) return [] - seen.add(resolvedId) - - // Read the source from disk and strip TS/JSX so the AST walk below sees - // standard ESM exports. We don't go through `this.load` / - // `transformRequest` here — in dev they return module-runner output - // (`__vite_ssr_exportName__(...)`) the walker can't read, and on build - // there's no practical benefit over reading the source directly for the - // simple TS/JSX modules we care about. - let moduleCode: string | undefined - try { - const raw = await fs.promises.readFile(resolvedId, 'utf-8') - moduleCode = await transformSourceForExportScan(raw, resolvedId) - } catch { - return [] - } - if (!moduleCode) return [] - - let ast: Awaited> - try { - ast = await parseAstAsync(moduleCode) - } catch { - return [] - } - - const names: string[] = [] - for (const node of ast.body) { - if (node.type === 'ExportNamedDeclaration') { - if (node.declaration) { - if ( - node.declaration.type === 'FunctionDeclaration' || - node.declaration.type === 'ClassDeclaration' - ) { - if (node.declaration.id) names.push(node.declaration.id.name) - } else if (node.declaration.type === 'VariableDeclaration') { - for (const decl of node.declaration.declarations) { - names.push(...extractNames(decl.id)) - } - } - } else { - for (const spec of node.specifiers) { - if ( - spec.exported.type === 'Identifier' && - spec.exported.name !== 'default' - ) { - names.push(spec.exported.name) - } - } - } - } else if (node.type === 'ExportAllDeclaration') { - if (node.exported?.type === 'Identifier') { - names.push(node.exported.name) - } else if (node.source) { - const subResolved = await ctx.resolve( - node.source.value as string, - resolvedId, - ) - if (subResolved) { - names.push(...(await collectExportNames(ctx, subResolved.id, seen))) - } - } - } - } - return names -} - -async function expandExportAllDeclarations( - ctx: Rollup.TransformPluginContext, - ast: Awaited>, - code: string, - id: string, -): Promise<{ - code: string - ast: Awaited> -} | null> { - const targets = ast.body.filter( - (n) => n.type === 'ExportAllDeclaration' && !n.exported, - ) - if (targets.length === 0) return null - - const output = new MagicString(code) - for (const node of targets) { - if (node.type !== 'ExportAllDeclaration') continue - const source = node.source.value as string - const resolved = await ctx.resolve(source, id) - if (!resolved) continue - const names = await collectExportNames(ctx, resolved.id, new Set()) - if (names.length === 0) { - output.remove(node.start, node.end) - } else { - output.update( - node.start, - node.end, - `export { ${names.join(', ')} } from ${JSON.stringify(source)};`, - ) - } +): TransformExpandExportAllContext { + return { + resolve: async (source, importer) => { + return (await ctx.resolve(source, importer))?.id + }, + load: async (id) => { + // Read the source from disk and strip TS/JSX so the AST walk sees + // standard ESM exports. We don't go through `this.load` / + // `transformRequest` here — in dev they return module-runner output + // (`__vite_ssr_exportName__(...)`) the walker can't read, and on build + // there's no practical benefit over reading the source directly for the + // simple TS/JSX modules we care about. + const raw = await fs.promises.readFile(id, 'utf-8') + const transform = vite.transformWithOxc ?? vite.transformWithEsbuild + const result = await transform(raw, id, { sourcemap: false }) + return parseAstAsync(result.code) + }, } - if (!output.hasChanged()) return null - const newCode = output.toString() - const newAst = await parseAstAsync(newCode) - return { code: newCode, ast: newAst } } function vitePluginUseClient( @@ -1580,15 +1466,15 @@ function vitePluginUseClient( } } - const expanded = await expandExportAllDeclarations( - this, - ast, + const expanded = await transformExpandExportAll({ code, - id, - ) + ast, + importer: id, + ...createTransformExpandExportAllContext(this), + }) if (expanded) { code = expanded.code - ast = expanded.ast + ast = await parseAstAsync(code) } let importId: string @@ -2065,15 +1951,15 @@ function vitePluginUseServer( } let ast = await parseAstAsync(code) if (hasDirective(ast.body, 'use server')) { - const expanded = await expandExportAllDeclarations( - this, - ast, + const expanded = await transformExpandExportAll({ code, - id, - ) + ast, + importer: id, + ...createTransformExpandExportAllContext(this), + }) if (expanded) { code = expanded.code - ast = expanded.ast + ast = await parseAstAsync(code) } } diff --git a/packages/plugin-rsc/src/transforms/expand-export-all.test.ts b/packages/plugin-rsc/src/transforms/expand-export-all.test.ts new file mode 100644 index 000000000..ae083c26d --- /dev/null +++ b/packages/plugin-rsc/src/transforms/expand-export-all.test.ts @@ -0,0 +1,72 @@ +import fs from 'node:fs' +import path from 'node:path' +import { parseAstAsync } from 'vite' +import { describe, expect, it } from 'vitest' +import { transformExpandExportAll } from './expand-export-all' + +describe('fixtures', () => { + const fixtures = import.meta.glob( + ['./fixtures/expand-export-all/**/entry.js', '!**/*.snap.*'], + { + query: 'raw', + }, + ) + async function transformFixture(input: string, importer: string) { + const ast = await parseAstAsync(input) + const result = await transformExpandExportAll({ + code: input, + ast, + importer, + resolve: async (source, importer) => { + if (importer.includes('/bad-resolve/')) return + return path.join(path.dirname(importer), source) + }, + load: async (id) => { + if (!fs.existsSync(id)) { + throw new Error(`failed to load ${JSON.stringify(path.basename(id))}`) + } + const code = fs.readFileSync(id, 'utf-8') + return parseAstAsync(code) + }, + }) + if (!result) { + return '/* NO CHANGE */\n' + } + try { + await parseAstAsync(result.code) + } catch (e) { + return `\ +${result.code} + +/* PARSE ERROR + +${(e as Error).message} + +*/ +` + } + return result.code.trim() + '\n' + } + + for (const [file, mod] of Object.entries(fixtures)) { + it(path.basename(path.dirname(file)), async () => { + const input = ((await mod()) as any).default as string + let output: string + try { + output = await transformFixture( + input, + path.join(import.meta.dirname, file), + ) + } catch (e) { + output = `\ +/* ERROR + +${(e as Error).message} + +*/ +` + } + await expect(output).toMatchFileSnapshot(file + '.snap.js') + }) + } +}) diff --git a/packages/plugin-rsc/src/transforms/expand-export-all.ts b/packages/plugin-rsc/src/transforms/expand-export-all.ts new file mode 100644 index 000000000..6799744d8 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/expand-export-all.ts @@ -0,0 +1,217 @@ +import type { ExportAllDeclaration, Program } from 'estree' +import MagicString from 'magic-string' +import { extractNames } from './utils' + +export interface TransformExpandExportAllContext { + resolve: (source: string, importer: string) => Promise + load: (id: string) => Promise +} + +export interface TransformExpandExportAllOptions extends TransformExpandExportAllContext { + code: string + ast: Program + importer: string +} + +type ModuleExportScan = { + explicitNames: Set + starSources: StarExportSource[] +} + +type StarExportSource = { + node: ExportAllDeclaration + scan: ExportNameScan +} + +// `names` and `ambiguousNames` describe this module's star-visible export +// state. A name in `names` can be safely re-exported by a parent. A name in +// `ambiguousNames` must be propagated even though it is not visible, because a +// parent star export must also treat that name as ambiguous unless the parent +// defines it explicitly. +type ExportNameScan = { + names: string[] + ambiguousNames: Set +} + +type StarExportResolution = { + ambiguousNames: Set + plans: StarExportRewritePlan[] +} + +type StarExportRewritePlan = { + node: ExportAllDeclaration + names: string[] +} + +export async function transformExpandExportAll( + options: TransformExpandExportAllOptions, +): Promise<{ code: string } | undefined> { + const scan = await scanModuleExports(options.ast, options.importer, options) + const { plans } = resolveStarExports(scan) + if (plans.length === 0) { + return + } + const output = new MagicString(options.code) + for (const item of plans) { + const newExport = `export {${item.names.join(', ')}} from ${JSON.stringify(item.node.source.value)};` + output.update(item.node.start, item.node.end, newExport) + } + // TODO: return a sourcemap so callers can compose this pre-rewrite with + // their follow-up proxy/wrap transform maps. + return { code: output.toString() } +} + +// Scan a module into local explicit exports and recursively scanned direct +// `export *` sources. This does not decide conflicts; resolveStarExports does. +async function scanModuleExports( + ast: Program, + importer: string, + context: TransformExpandExportAllContext, + seen = new Set(), +): Promise { + const starSources: StarExportSource[] = [] + const bareStars = ast.body.filter( + (n): n is ExportAllDeclaration => + n.type === 'ExportAllDeclaration' && !n.exported, + ) + + for (const node of bareStars) { + const source = node.source.value as string + const resolved = await context.resolve(source, importer) + if (!resolved) { + throw Object.assign( + new Error( + `failed to resolve export-all source ${JSON.stringify(source)}`, + ), + { pos: node.start }, + ) + } + starSources.push({ + node, + scan: await collectExportScan(resolved, context, new Set(seen)), + }) + } + + const explicitNames = collectExplicitExportNames(ast) + return { explicitNames, starSources } +} + +// Return the names that a resolved dependency visibly exports, plus names whose +// resolution is ambiguous and must continue to poison parent star resolution. +async function collectExportScan( + resolvedId: string, + context: TransformExpandExportAllContext, + seen: Set, +): Promise { + if (seen.has(resolvedId)) { + // TODO: This module-level bailout is only a termination guard. A fully + // spec-accurate resolver would track per-name resolution state and binding + // identity, so cyclic paths that resolve to the same binding can survive. + return { names: [], ambiguousNames: new Set() } + } + seen.add(resolvedId) + + const ast = await context.load(resolvedId) + const scan = await scanModuleExports(ast, resolvedId, context, seen) + const resolved = resolveStarExports(scan) + const names = [ + ...scan.explicitNames, + ...resolved.plans.flatMap((item) => item.names), + ] + return { + names, + ambiguousNames: resolved.ambiguousNames, + } +} + +// Collect names declared directly by the module, including namespace re-exports. +// These names shadow star exports at this module boundary. +function collectExplicitExportNames(ast: Program): Set { + const names = new Set() + for (const node of ast.body) { + if (node.type === 'ExportNamedDeclaration') { + if (node.declaration) { + if ( + node.declaration.type === 'FunctionDeclaration' || + node.declaration.type === 'ClassDeclaration' + ) { + if (node.declaration.id) names.add(node.declaration.id.name) + } else if (node.declaration.type === 'VariableDeclaration') { + for (const decl of node.declaration.declarations) { + for (const name of extractNames(decl.id)) { + names.add(name) + } + } + } + } else { + for (const spec of node.specifiers) { + if (spec.exported.type === 'Identifier') { + names.add(spec.exported.name) + } else { + throw Object.assign( + new Error('unsupported string literal export name'), + { pos: spec.exported.start }, + ) + } + } + } + } else if (node.type === 'ExportDefaultDeclaration') { + names.add('default') + } else if (node.type === 'ExportAllDeclaration') { + if (node.exported?.type === 'Identifier') { + names.add(node.exported.name) + } + } + } + return names +} + +// Apply ESM export-star conflict rules for one module boundary and build the +// rewrite for each direct `export *`. Ambiguity from child modules is preserved +// so a parent does not accidentally make an invalid name explicit. +function resolveStarExports(scan: ModuleExportScan): StarExportResolution { + // find multiple re-exported names through "export *" + // and treat as ambiguous + const seenStarNames = new Set() + const ambiguousNames = new Set() + for (const source of scan.starSources) { + for (const name of source.scan.names) { + if (name === 'default' || scan.explicitNames.has(name)) { + continue + } + if (seenStarNames.has(name)) { + ambiguousNames.add(name) + } else { + seenStarNames.add(name) + } + } + } + + // propagate ambiguous star re-exports to the parent + for (const source of scan.starSources) { + for (const name of source.scan.ambiguousNames) { + if (!scan.explicitNames.has(name)) { + ambiguousNames.add(name) + } + } + } + + const plans: StarExportRewritePlan[] = [] + for (const source of scan.starSources) { + const names = source.scan.names.filter( + (name) => + name !== 'default' && + !scan.explicitNames.has(name) && + !ambiguousNames.has(name), + ) + plans.push({ + node: source.node, + names, + }) + } + + return { + ambiguousNames, + plans, + } +} diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/bad-load/entry.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/bad-load/entry.js new file mode 100644 index 000000000..4bae3c345 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/bad-load/entry.js @@ -0,0 +1 @@ +export * from './missing.js' diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/bad-load/entry.js.snap.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/bad-load/entry.js.snap.js new file mode 100644 index 000000000..73ec11e53 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/bad-load/entry.js.snap.js @@ -0,0 +1,5 @@ +/* ERROR + +failed to load "missing.js" + +*/ diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/bad-resolve/entry.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/bad-resolve/entry.js new file mode 100644 index 000000000..e5d44c46a --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/bad-resolve/entry.js @@ -0,0 +1 @@ +export * from './dep.js' diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/bad-resolve/entry.js.snap.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/bad-resolve/entry.js.snap.js new file mode 100644 index 000000000..ba74313e2 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/bad-resolve/entry.js.snap.js @@ -0,0 +1,5 @@ +/* ERROR + +failed to resolve export-all source "./dep.js" + +*/ diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/basic/default.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/basic/default.js new file mode 100644 index 000000000..bc40bc291 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/basic/default.js @@ -0,0 +1 @@ +export default function Default() {} diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/basic/dep.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/basic/dep.js new file mode 100644 index 000000000..fbbbd8789 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/basic/dep.js @@ -0,0 +1,5 @@ +export function a() {} +export const b = 1 +export { default } from './default.js' +export * from './nested.js' +export * as ns from './nested.js' diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/basic/entry.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/basic/entry.js new file mode 100644 index 000000000..0b5a53754 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/basic/entry.js @@ -0,0 +1,2 @@ +export * from './dep.js' +export const local = 1 diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/basic/entry.js.snap.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/basic/entry.js.snap.js new file mode 100644 index 000000000..9bf9a0427 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/basic/entry.js.snap.js @@ -0,0 +1,2 @@ +export {a, b, ns, C} from "./dep.js"; +export const local = 1 diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/basic/nested.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/basic/nested.js new file mode 100644 index 000000000..1ec0ebf40 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/basic/nested.js @@ -0,0 +1 @@ +export class C {} diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/circular-same-binding/a.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/circular-same-binding/a.js new file mode 100644 index 000000000..80fd85b47 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/circular-same-binding/a.js @@ -0,0 +1 @@ +export * from './b.js' diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/circular-same-binding/b.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/circular-same-binding/b.js new file mode 100644 index 000000000..d1a3afc49 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/circular-same-binding/b.js @@ -0,0 +1,2 @@ +export * from './a.js' +export const value = 1 diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/circular-same-binding/entry.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/circular-same-binding/entry.js new file mode 100644 index 000000000..64c1f05bc --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/circular-same-binding/entry.js @@ -0,0 +1,3 @@ +// TODO: support cyclic star exports that resolve to the same binding. +export * from './a.js' +export * from './b.js' diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/circular-same-binding/entry.js.snap.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/circular-same-binding/entry.js.snap.js new file mode 100644 index 000000000..498b8d1f4 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/circular-same-binding/entry.js.snap.js @@ -0,0 +1,3 @@ +// TODO: support cyclic star exports that resolve to the same binding. +export {} from "./a.js"; +export {} from "./b.js"; diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/circular/dep1.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/circular/dep1.js new file mode 100644 index 000000000..3e5f5ce9c --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/circular/dep1.js @@ -0,0 +1,2 @@ +export * from './dep2.js' +export const a = 1 diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/circular/dep2.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/circular/dep2.js new file mode 100644 index 000000000..392cf2b68 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/circular/dep2.js @@ -0,0 +1,2 @@ +export * from './dep1.js' +export const b = 2 diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/circular/entry.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/circular/entry.js new file mode 100644 index 000000000..14fdb1a99 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/circular/entry.js @@ -0,0 +1 @@ +export * from './dep1.js' diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/circular/entry.js.snap.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/circular/entry.js.snap.js new file mode 100644 index 000000000..1ee19e2d3 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/circular/entry.js.snap.js @@ -0,0 +1 @@ +export {a, b} from "./dep1.js"; diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/conflict-star/a.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/conflict-star/a.js new file mode 100644 index 000000000..605a790bb --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/conflict-star/a.js @@ -0,0 +1,2 @@ +export const shared = 1 +export const onlyA = 1 diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/conflict-star/b.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/conflict-star/b.js new file mode 100644 index 000000000..73366dadc --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/conflict-star/b.js @@ -0,0 +1,2 @@ +export const shared = 2 +export const onlyB = 2 diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/conflict-star/entry.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/conflict-star/entry.js new file mode 100644 index 000000000..2f940ab3d --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/conflict-star/entry.js @@ -0,0 +1,2 @@ +export * from './a.js' +export * from './b.js' diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/conflict-star/entry.js.snap.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/conflict-star/entry.js.snap.js new file mode 100644 index 000000000..dcabdda5c --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/conflict-star/entry.js.snap.js @@ -0,0 +1,2 @@ +export {onlyA} from "./a.js"; +export {onlyB} from "./b.js"; diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/default-only/dep.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/default-only/dep.js new file mode 100644 index 000000000..33c69bb17 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/default-only/dep.js @@ -0,0 +1,2 @@ +console.log('dep') +export default 1 diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/default-only/entry.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/default-only/entry.js new file mode 100644 index 000000000..e5d44c46a --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/default-only/entry.js @@ -0,0 +1 @@ +export * from './dep.js' diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/default-only/entry.js.snap.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/default-only/entry.js.snap.js new file mode 100644 index 000000000..686a3c75a --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/default-only/entry.js.snap.js @@ -0,0 +1 @@ +export {} from "./dep.js"; diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/duplicate-star-same-source/dep.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/duplicate-star-same-source/dep.js new file mode 100644 index 000000000..f5e5bd3f1 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/duplicate-star-same-source/dep.js @@ -0,0 +1,2 @@ +export const a = 1 +export const b = 2 diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/duplicate-star-same-source/entry.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/duplicate-star-same-source/entry.js new file mode 100644 index 000000000..19c5db7b5 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/duplicate-star-same-source/entry.js @@ -0,0 +1,4 @@ +// TODO: support duplicate star exports that resolve to the same bindings. +export * from './dep.js' +export * from './dep.js' +export * from './dep.js' diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/duplicate-star-same-source/entry.js.snap.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/duplicate-star-same-source/entry.js.snap.js new file mode 100644 index 000000000..fd6f94519 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/duplicate-star-same-source/entry.js.snap.js @@ -0,0 +1,4 @@ +// TODO: support duplicate star exports that resolve to the same bindings. +export {} from "./dep.js"; +export {} from "./dep.js"; +export {} from "./dep.js"; diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/explicit-wins/dep.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/explicit-wins/dep.js new file mode 100644 index 000000000..07d338c59 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/explicit-wins/dep.js @@ -0,0 +1,2 @@ +export const shared = 2 +export const fromDep = 2 diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/explicit-wins/entry.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/explicit-wins/entry.js new file mode 100644 index 000000000..c7b86f1c8 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/explicit-wins/entry.js @@ -0,0 +1,2 @@ +export * from './dep.js' +export const shared = 1 diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/explicit-wins/entry.js.snap.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/explicit-wins/entry.js.snap.js new file mode 100644 index 000000000..f6a87d7a2 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/explicit-wins/entry.js.snap.js @@ -0,0 +1,2 @@ +export {fromDep} from "./dep.js"; +export const shared = 1 diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/namespace-same-binding/dep-target.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/namespace-same-binding/dep-target.js new file mode 100644 index 000000000..1d772ee2c --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/namespace-same-binding/dep-target.js @@ -0,0 +1 @@ +export const value = 1 diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/namespace-same-binding/dep1.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/namespace-same-binding/dep1.js new file mode 100644 index 000000000..f989d83ec --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/namespace-same-binding/dep1.js @@ -0,0 +1 @@ +export * as target from './dep-target.js' diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/namespace-same-binding/dep2.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/namespace-same-binding/dep2.js new file mode 100644 index 000000000..f989d83ec --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/namespace-same-binding/dep2.js @@ -0,0 +1 @@ +export * as target from './dep-target.js' diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/namespace-same-binding/entry.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/namespace-same-binding/entry.js new file mode 100644 index 000000000..97b6b3db2 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/namespace-same-binding/entry.js @@ -0,0 +1,4 @@ +// TODO: support duplicate star exports that resolve to the same namespace binding. +// This is also currently broken in Node 24.16.0 due to a V8 bug. +export * from './dep1.js' +export * from './dep2.js' diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/namespace-same-binding/entry.js.snap.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/namespace-same-binding/entry.js.snap.js new file mode 100644 index 000000000..a6c8b0741 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/namespace-same-binding/entry.js.snap.js @@ -0,0 +1,4 @@ +// TODO: support duplicate star exports that resolve to the same namespace binding. +// This is also currently broken in Node 24.16.0 due to a V8 bug. +export {} from "./dep1.js"; +export {} from "./dep2.js"; diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-explicit-reexport-wins/a.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-explicit-reexport-wins/a.js new file mode 100644 index 000000000..605a790bb --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-explicit-reexport-wins/a.js @@ -0,0 +1,2 @@ +export const shared = 1 +export const onlyA = 1 diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-explicit-reexport-wins/b.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-explicit-reexport-wins/b.js new file mode 100644 index 000000000..73366dadc --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-explicit-reexport-wins/b.js @@ -0,0 +1,2 @@ +export const shared = 2 +export const onlyB = 2 diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-explicit-reexport-wins/dep.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-explicit-reexport-wins/dep.js new file mode 100644 index 000000000..2f940ab3d --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-explicit-reexport-wins/dep.js @@ -0,0 +1,2 @@ +export * from './a.js' +export * from './b.js' diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-explicit-reexport-wins/entry.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-explicit-reexport-wins/entry.js new file mode 100644 index 000000000..ef6ac0100 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-explicit-reexport-wins/entry.js @@ -0,0 +1 @@ +export * from './middle.js' diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-explicit-reexport-wins/entry.js.snap.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-explicit-reexport-wins/entry.js.snap.js new file mode 100644 index 000000000..14f241bfd --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-explicit-reexport-wins/entry.js.snap.js @@ -0,0 +1 @@ +export {shared, onlyA, onlyB} from "./middle.js"; diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-explicit-reexport-wins/middle.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-explicit-reexport-wins/middle.js new file mode 100644 index 000000000..402b3fd36 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-explicit-reexport-wins/middle.js @@ -0,0 +1,2 @@ +export * from './dep.js' +export { shared } from './override.js' diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-explicit-reexport-wins/override.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-explicit-reexport-wins/override.js new file mode 100644 index 000000000..9b592ef09 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-explicit-reexport-wins/override.js @@ -0,0 +1 @@ +export const shared = 3 diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-star/a.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-star/a.js new file mode 100644 index 000000000..605a790bb --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-star/a.js @@ -0,0 +1,2 @@ +export const shared = 1 +export const onlyA = 1 diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-star/b.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-star/b.js new file mode 100644 index 000000000..73366dadc --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-star/b.js @@ -0,0 +1,2 @@ +export const shared = 2 +export const onlyB = 2 diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-star/dep.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-star/dep.js new file mode 100644 index 000000000..2f940ab3d --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-star/dep.js @@ -0,0 +1,2 @@ +export * from './a.js' +export * from './b.js' diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-star/entry.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-star/entry.js new file mode 100644 index 000000000..e5d44c46a --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-star/entry.js @@ -0,0 +1 @@ +export * from './dep.js' diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-star/entry.js.snap.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-star/entry.js.snap.js new file mode 100644 index 000000000..82914593b --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-conflict-star/entry.js.snap.js @@ -0,0 +1 @@ +export {onlyA, onlyB} from "./dep.js"; diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-filtered-conflict/dep1.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-filtered-conflict/dep1.js new file mode 100644 index 000000000..257d79057 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-filtered-conflict/dep1.js @@ -0,0 +1,3 @@ +export const a = 1 +export const b = 1 +export const c = 1 diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-filtered-conflict/dep2-1.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-filtered-conflict/dep2-1.js new file mode 100644 index 000000000..6fd2947a5 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-filtered-conflict/dep2-1.js @@ -0,0 +1,3 @@ +export const a = 1 +export const b = 1 +export const x = 1 diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-filtered-conflict/dep2-2.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-filtered-conflict/dep2-2.js new file mode 100644 index 000000000..6cf48bbbb --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-filtered-conflict/dep2-2.js @@ -0,0 +1,2 @@ +export const a = 2 +export const y = 2 diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-filtered-conflict/dep2.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-filtered-conflict/dep2.js new file mode 100644 index 000000000..f9b593c3a --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-filtered-conflict/dep2.js @@ -0,0 +1,2 @@ +export * from './dep2-1.js' +export * from './dep2-2.js' diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-filtered-conflict/entry.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-filtered-conflict/entry.js new file mode 100644 index 000000000..bcbfe36b7 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-filtered-conflict/entry.js @@ -0,0 +1,2 @@ +export * from './dep1.js' +export * from './dep2.js' diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-filtered-conflict/entry.js.snap.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-filtered-conflict/entry.js.snap.js new file mode 100644 index 000000000..a72df6029 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-filtered-conflict/entry.js.snap.js @@ -0,0 +1,2 @@ +export {c} from "./dep1.js"; +export {x, y} from "./dep2.js"; diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-filtered-conflict/repro1.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-filtered-conflict/repro1.js new file mode 100644 index 000000000..328601cb2 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-filtered-conflict/repro1.js @@ -0,0 +1,7 @@ +// this doesn't fail but it should. potentially node/v8 bug. +// node packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-filtered-conflict/repro1.js +// similar class of issue but different +// https://github.com/nodejs/node/issues/53707 +// https://chromium.googlesource.com/v8/v8/+/88760dadfba1457aea76e50e6ead3b87f868c7fb +import * as ns from './entry.js' +console.log(ns.a) diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-filtered-conflict/repro2.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-filtered-conflict/repro2.js new file mode 100644 index 000000000..c45ffb94f --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-filtered-conflict/repro2.js @@ -0,0 +1,4 @@ +// this fails as expected. +// node packages/plugin-rsc/src/transforms/fixtures/expand-export-all/nested-filtered-conflict/repro2.js +import { a } from './entry.js' +console.log(a) diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/no-export-star/dep.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/no-export-star/dep.js new file mode 100644 index 000000000..1d772ee2c --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/no-export-star/dep.js @@ -0,0 +1 @@ +export const value = 1 diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/no-export-star/entry.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/no-export-star/entry.js new file mode 100644 index 000000000..d16b69db0 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/no-export-star/entry.js @@ -0,0 +1,2 @@ +export * as ns from './dep.js' +export const local = 1 diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/no-export-star/entry.js.snap.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/no-export-star/entry.js.snap.js new file mode 100644 index 000000000..aed6a1387 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/no-export-star/entry.js.snap.js @@ -0,0 +1 @@ +/* NO CHANGE */ diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/string-export-name/dep.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/string-export-name/dep.js new file mode 100644 index 000000000..6a37bac3a --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/string-export-name/dep.js @@ -0,0 +1,2 @@ +const x = 1 +export { x as 'my thing' } diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/string-export-name/entry.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/string-export-name/entry.js new file mode 100644 index 000000000..3a374f6b3 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/string-export-name/entry.js @@ -0,0 +1,2 @@ +// TODO: support string literal export names. +export * from './dep.js' diff --git a/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/string-export-name/entry.js.snap.js b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/string-export-name/entry.js.snap.js new file mode 100644 index 000000000..3ff9186b4 --- /dev/null +++ b/packages/plugin-rsc/src/transforms/fixtures/expand-export-all/string-export-name/entry.js.snap.js @@ -0,0 +1,5 @@ +/* ERROR + +unsupported string literal export name + +*/ diff --git a/packages/plugin-rsc/src/transforms/index.ts b/packages/plugin-rsc/src/transforms/index.ts index 55b054c08..b37480dae 100644 --- a/packages/plugin-rsc/src/transforms/index.ts +++ b/packages/plugin-rsc/src/transforms/index.ts @@ -3,3 +3,4 @@ export * from './wrap-export' export * from './proxy-export' export * from './utils' export * from './server-action' +export * from './expand-export-all' diff --git a/packages/plugin-rsc/src/transforms/proxy-export.test.ts b/packages/plugin-rsc/src/transforms/proxy-export.test.ts index 4d5ce63d2..4530d237a 100644 --- a/packages/plugin-rsc/src/transforms/proxy-export.test.ts +++ b/packages/plugin-rsc/src/transforms/proxy-export.test.ts @@ -171,6 +171,16 @@ export { x as y } `) }) + test('export string name throws', async () => { + const input = ` +const x = 0; +export { x as "my thing" } +` + await expect(testTransform(input)).rejects.toThrow( + 'unsupported string literal export name', + ) + }) + test('re-export simple', async () => { const input = `export { x } from "./dep"` expect(await testTransform(input)).toMatchInlineSnapshot(` @@ -210,23 +220,6 @@ export { x as y } `) }) - test('re-export all (resolved)', async () => { - // when caller resolves names ahead of time, the source is rewritten so - // the transform never sees a bare `export *`. - const input = `export { x, y } from "./dep"` - expect(await testTransform(input)).toMatchInlineSnapshot(` - { - "exportNames": [ - "x", - "y", - ], - "output": "export const x = /* #__PURE__ */ $$proxy("", "x"); - export const y = /* #__PURE__ */ $$proxy("", "y"); - ", - } - `) - }) - test('re-export all (ignoreExportAllDeclaration)', async () => { const input = `export * from "./dep"` expect(await testTransform(input, { ignoreExportAllDeclaration: true })) diff --git a/packages/plugin-rsc/src/transforms/proxy-export.ts b/packages/plugin-rsc/src/transforms/proxy-export.ts index 9ba0e9cc3..026b6da71 100644 --- a/packages/plugin-rsc/src/transforms/proxy-export.ts +++ b/packages/plugin-rsc/src/transforms/proxy-export.ts @@ -1,4 +1,3 @@ -import { tinyassert } from '@hiogawa/utils' import type { Node, Program } from 'estree' import MagicString from 'magic-string' import { extractNames, hasDirective, validateNonAsyncFunction } from './utils' @@ -107,7 +106,12 @@ export function transformProxyExport( */ const names: string[] = [] for (const spec of node.specifiers) { - tinyassert(spec.exported.type === 'Identifier') + if (spec.exported.type !== 'Identifier') { + throw Object.assign( + new Error('unsupported string literal export name'), + { pos: spec.exported.start }, + ) + } names.push(spec.exported.name) } createExport(node, names) diff --git a/packages/plugin-rsc/src/transforms/utils.ts b/packages/plugin-rsc/src/transforms/utils.ts index 4fd949359..a0769edfe 100644 --- a/packages/plugin-rsc/src/transforms/utils.ts +++ b/packages/plugin-rsc/src/transforms/utils.ts @@ -1,4 +1,3 @@ -import { tinyassert } from '@hiogawa/utils' import type { ExportDefaultDeclaration } from 'estree' import type { Identifier, Node, Pattern, Program } from 'estree' @@ -15,72 +14,6 @@ export function hasDirective( ) } -export function getExportNames( - ast: Program, - options: { - ignoreExportAllDeclaration?: boolean - }, -): { - exportNames: string[] -} { - const exportNames: string[] = [] - - for (const node of ast.body) { - if (node.type === 'ExportNamedDeclaration') { - if (node.declaration) { - if ( - node.declaration.type === 'FunctionDeclaration' || - node.declaration.type === 'ClassDeclaration' - ) { - /** - * export function foo() {} - */ - exportNames.push(node.declaration.id.name) - } else if (node.declaration.type === 'VariableDeclaration') { - /** - * export const foo = 1, bar = 2 - */ - for (const decl of node.declaration.declarations) { - exportNames.push(...extractNames(decl.id)) - } - } else { - node.declaration satisfies never - } - } else { - /** - * export { foo, bar as car } from './foo' - * export { foo, bar as car } - */ - for (const spec of node.specifiers) { - tinyassert(spec.exported.type === 'Identifier') - exportNames.push(spec.exported.name) - } - } - } - - /** - * export * from './foo' - */ - if ( - !options.ignoreExportAllDeclaration && - node.type === 'ExportAllDeclaration' - ) { - throw new Error('unsupported ExportAllDeclaration') - } - - /** - * export default function foo() {} - * export default class Foo {} - * export default () => {} - */ - if (node.type === 'ExportDefaultDeclaration') { - exportNames.push('default') - } - } - - return { exportNames } -} - // Copied from periscopic `extract_names` / `extract_identifiers` export function extractNames(param: Pattern): string[] { return extractIdentifiers(param).map((n) => n.name) diff --git a/packages/plugin-rsc/src/transforms/wrap-export.test.ts b/packages/plugin-rsc/src/transforms/wrap-export.test.ts index 97d9f4f29..c3cb7473a 100644 --- a/packages/plugin-rsc/src/transforms/wrap-export.test.ts +++ b/packages/plugin-rsc/src/transforms/wrap-export.test.ts @@ -178,6 +178,16 @@ export { x as y } `) }) + test('export string name throws', async () => { + const input = ` +const x = 0; +export { x as "my thing" } +` + await expect(testTransform(input)).rejects.toThrow( + 'unsupported string literal export name', + ) + }) + test('re-export simple', async () => { const input = `export { x } from "./dep"` expect(await testTransform(input)).toMatchInlineSnapshot(` @@ -207,13 +217,7 @@ export { x as y } test('re-export all rename', async () => { const input = `export * as all from "./dep"` - expect(await testTransform(input)).toMatchInlineSnapshot(` - "; - import * as $$import_all from "./dep"; - const $$wrap_$$import_all = /* #__PURE__ */ $$wrap($$import_all, "", "all"); - export { $$wrap_$$import_all as all }; - " - `) + expect(await testTransform(input)).toMatchInlineSnapshot(`false`) }) test('filter', async () => { diff --git a/packages/plugin-rsc/src/transforms/wrap-export.ts b/packages/plugin-rsc/src/transforms/wrap-export.ts index 26113c2f9..f5328e967 100644 --- a/packages/plugin-rsc/src/transforms/wrap-export.ts +++ b/packages/plugin-rsc/src/transforms/wrap-export.ts @@ -146,7 +146,12 @@ export function transformWrapExport( output.remove(node.start, node.end) for (const spec of node.specifiers) { tinyassert(spec.local.type === 'Identifier') - tinyassert(spec.exported.type === 'Identifier') + if (spec.exported.type !== 'Identifier') { + throw Object.assign( + new Error('unsupported string literal export name'), + { pos: spec.exported.start }, + ) + } const name = spec.local.name toAppend.push( `import { ${name} as $$import_${name} } from ${node.source.raw}`, @@ -160,7 +165,12 @@ export function transformWrapExport( output.remove(node.start, node.end) for (const spec of node.specifiers) { tinyassert(spec.local.type === 'Identifier') - tinyassert(spec.exported.type === 'Identifier') + if (spec.exported.type !== 'Identifier') { + throw Object.assign( + new Error('unsupported string literal export name'), + { pos: spec.exported.start }, + ) + } wrapExport(spec.local.name, spec.exported.name) } } @@ -175,14 +185,7 @@ export function transformWrapExport( // for now we just give an option to not throw for this case. // https://github.com/vitejs/vite-plugin-vue/blob/30a97c1ddbdfb0e23b7dc14a1d2fb609668b9987/packages/plugin-vue/src/main.ts#L372 if (node.type === 'ExportAllDeclaration') { - if (node.exported?.type === 'Identifier') { - tinyassert(node.source.type === 'Literal') - const exportedName = node.exported.name - const localName = `$$import_${exportedName}` - output.remove(node.start, node.end) - toAppend.push(`import * as ${localName} from ${node.source.raw}`) - wrapExport(localName, exportedName) - } else if (!options.ignoreExportAllDeclaration) { + if (!options.ignoreExportAllDeclaration) { throw Object.assign(new Error('unsupported ExportAllDeclaration'), { pos: node.start, })