@@ -65,6 +65,7 @@ import {
6565} from './plugins/vite-utils'
6666import {
6767 type TransformWrapExportFilter ,
68+ extractNames ,
6869 hasDirective ,
6970 transformDirectiveProxyExport ,
7071 transformServerActionServer ,
@@ -1375,6 +1376,143 @@ function globalAsyncLocalStoragePlugin(): Plugin[] {
13751376 ]
13761377}
13771378
1379+ // Strip TS/JSX so `parseAstAsync` can read the result. Prefer oxc when
1380+ // available (Vite 8+); fall back to esbuild for older Vite versions.
1381+ async function transformSourceForExportScan (
1382+ code : string ,
1383+ filename : string ,
1384+ ) : Promise < string | undefined > {
1385+ const v = vite as Partial < {
1386+ transformWithOxc : (
1387+ code : string ,
1388+ filename : string ,
1389+ options ?: { sourcemap ?: boolean } ,
1390+ ) => Promise < { code : string } >
1391+ transformWithEsbuild : (
1392+ code : string ,
1393+ filename : string ,
1394+ options ?: { sourcemap ?: boolean } ,
1395+ ) => Promise < { code : string } >
1396+ } >
1397+ const transform = v . transformWithOxc ?? v . transformWithEsbuild
1398+ if ( ! transform ) return undefined
1399+ const result = await transform ( code , filename , { sourcemap : false } )
1400+ return result . code
1401+ }
1402+
1403+ // Recursively collect the named exports of a module (following `export * from`
1404+ // chains), so that the RSC `use client`/`use server` proxy transforms can
1405+ // expand bare `export *` re-exports into explicit named re-exports before
1406+ // proxy generation. The pure proxy transform cannot do this on its own because
1407+ // the names live in another file.
1408+ async function collectExportNames (
1409+ ctx : Rollup . TransformPluginContext ,
1410+ resolvedId : string ,
1411+ seen : Set < string > ,
1412+ ) : Promise < string [ ] > {
1413+ if ( seen . has ( resolvedId ) ) return [ ]
1414+ seen . add ( resolvedId )
1415+
1416+ // Read the source from disk and strip TS/JSX so the AST walk below sees
1417+ // standard ESM exports. We don't go through `this.load` /
1418+ // `transformRequest` here — in dev they return module-runner output
1419+ // (`__vite_ssr_exportName__(...)`) the walker can't read, and on build
1420+ // there's no practical benefit over reading the source directly for the
1421+ // simple TS/JSX modules we care about.
1422+ let moduleCode : string | undefined
1423+ try {
1424+ const raw = await fs . promises . readFile ( resolvedId , 'utf-8' )
1425+ moduleCode = await transformSourceForExportScan ( raw , resolvedId )
1426+ } catch {
1427+ return [ ]
1428+ }
1429+ if ( ! moduleCode ) return [ ]
1430+
1431+ let ast : Awaited < ReturnType < typeof parseAstAsync > >
1432+ try {
1433+ ast = await parseAstAsync ( moduleCode )
1434+ } catch {
1435+ return [ ]
1436+ }
1437+
1438+ const names : string [ ] = [ ]
1439+ for ( const node of ast . body ) {
1440+ if ( node . type === 'ExportNamedDeclaration' ) {
1441+ if ( node . declaration ) {
1442+ if (
1443+ node . declaration . type === 'FunctionDeclaration' ||
1444+ node . declaration . type === 'ClassDeclaration'
1445+ ) {
1446+ if ( node . declaration . id ) names . push ( node . declaration . id . name )
1447+ } else if ( node . declaration . type === 'VariableDeclaration' ) {
1448+ for ( const decl of node . declaration . declarations ) {
1449+ names . push ( ...extractNames ( decl . id ) )
1450+ }
1451+ }
1452+ } else {
1453+ for ( const spec of node . specifiers ) {
1454+ if (
1455+ spec . exported . type === 'Identifier' &&
1456+ spec . exported . name !== 'default'
1457+ ) {
1458+ names . push ( spec . exported . name )
1459+ }
1460+ }
1461+ }
1462+ } else if ( node . type === 'ExportAllDeclaration' ) {
1463+ if ( node . exported ?. type === 'Identifier' ) {
1464+ names . push ( node . exported . name )
1465+ } else if ( node . source ) {
1466+ const subResolved = await ctx . resolve (
1467+ node . source . value as string ,
1468+ resolvedId ,
1469+ )
1470+ if ( subResolved ) {
1471+ names . push ( ...( await collectExportNames ( ctx , subResolved . id , seen ) ) )
1472+ }
1473+ }
1474+ }
1475+ }
1476+ return names
1477+ }
1478+
1479+ async function expandExportAllDeclarations (
1480+ ctx : Rollup . TransformPluginContext ,
1481+ ast : Awaited < ReturnType < typeof parseAstAsync > > ,
1482+ code : string ,
1483+ id : string ,
1484+ ) : Promise < {
1485+ code : string
1486+ ast : Awaited < ReturnType < typeof parseAstAsync > >
1487+ } | null > {
1488+ const targets = ast . body . filter (
1489+ ( n ) => n . type === 'ExportAllDeclaration' && ! n . exported ,
1490+ )
1491+ if ( targets . length === 0 ) return null
1492+
1493+ const output = new MagicString ( code )
1494+ for ( const node of targets ) {
1495+ if ( node . type !== 'ExportAllDeclaration' ) continue
1496+ const source = node . source . value as string
1497+ const resolved = await ctx . resolve ( source , id )
1498+ if ( ! resolved ) continue
1499+ const names = await collectExportNames ( ctx , resolved . id , new Set ( ) )
1500+ if ( names . length === 0 ) {
1501+ output . remove ( node . start , node . end )
1502+ } else {
1503+ output . update (
1504+ node . start ,
1505+ node . end ,
1506+ `export { ${ names . join ( ', ' ) } } from ${ JSON . stringify ( source ) } ;` ,
1507+ )
1508+ }
1509+ }
1510+ if ( ! output . hasChanged ( ) ) return null
1511+ const newCode = output . toString ( )
1512+ const newAst = await parseAstAsync ( newCode )
1513+ return { code : newCode , ast : newAst }
1514+ }
1515+
13781516function vitePluginUseClient (
13791517 useClientPluginOptions : Pick <
13801518 RscPluginOptions ,
@@ -1426,7 +1564,7 @@ function vitePluginUseClient(
14261564 return
14271565 }
14281566
1429- const ast = await parseAstAsync ( code )
1567+ let ast = await parseAstAsync ( code )
14301568 if ( ! hasDirective ( ast . body , 'use client' ) ) {
14311569 delete manager . clientReferenceMetaMap [ id ]
14321570 return
@@ -1442,6 +1580,17 @@ function vitePluginUseClient(
14421580 }
14431581 }
14441582
1583+ const expanded = await expandExportAllDeclarations (
1584+ this ,
1585+ ast ,
1586+ code ,
1587+ id ,
1588+ )
1589+ if ( expanded ) {
1590+ code = expanded . code
1591+ ast = expanded . ast
1592+ }
1593+
14451594 let importId : string
14461595 let referenceKey : string
14471596 const packageSource = packageSources . get ( id )
@@ -1914,7 +2063,19 @@ function vitePluginUseServer(
19142063 delete manager . serverReferenceMetaMap [ id ]
19152064 return
19162065 }
1917- const ast = await parseAstAsync ( code )
2066+ let ast = await parseAstAsync ( code )
2067+ if ( hasDirective ( ast . body , 'use server' ) ) {
2068+ const expanded = await expandExportAllDeclarations (
2069+ this ,
2070+ ast ,
2071+ code ,
2072+ id ,
2073+ )
2074+ if ( expanded ) {
2075+ code = expanded . code
2076+ ast = expanded . ast
2077+ }
2078+ }
19182079
19192080 let normalizedId_ : string | undefined
19202081 const getNormalizedId = ( ) => {
0 commit comments