11import { tinyassert } from '@hiogawa/utils'
2- import type { Program } from 'estree'
2+ import type { ExportDefaultDeclaration , Node , Program } from 'estree'
33import MagicString from 'magic-string'
44import { extractNames , validateNonAsyncFunction } from './utils'
55
66type ExportMeta = {
7+ /**
8+ * The local declaration name when statically available.
9+ *
10+ * - `"Page"` for `export function Page() {}`
11+ * - `"Page"` for `export const Page = () => {}`
12+ * - `undefined` for `export default () => {}`
13+ * - `undefined` for `export { Page }`
14+ */
715 declName ?: string
16+ /**
17+ * Whether the exported value is statically known to be a function.
18+ *
19+ * - `true` for `export const Page = () => {}`
20+ * - `false` for `export const value = 1`
21+ * - `undefined` for `export const value = getValue()`
22+ * - `undefined` for `export default Page`
23+ */
824 isFunction ?: boolean
25+ /**
26+ * The local identifier referenced by a default export.
27+ * The RSC CSS transform uses this to detect a component by its capitalized
28+ * local name and preserve that name on the generated wrapper.
29+ *
30+ * - `"Page"` for `const Page = () => {}; export default Page`
31+ * - `undefined` for `export default function Page() {}`
32+ * - `undefined` for `export default () => {}`
33+ */
934 defaultExportIdentifierName ?: string
1035}
1136
37+ type ExportWithMeta = { name : string ; meta : ExportMeta }
38+
1239export type TransformWrapExportFilter = (
1340 name : string ,
1441 meta : ExportMeta ,
@@ -34,12 +61,16 @@ export function transformWrapExport(
3461 const toAppend : string [ ] = [ ]
3562 const filter = options . filter ?? ( ( ) => true )
3663
37- function wrapSimple (
38- start : number ,
39- end : number ,
40- exports : { name : string ; meta : ExportMeta } [ ] ,
41- ) {
42- exportNames . push ( ...exports . map ( ( e ) => e . name ) )
64+ function wrapSimple ( start : number , end : number , exports : ExportWithMeta [ ] ) {
65+ const filteredExports = exports . map ( ( item ) => ( {
66+ ...item ,
67+ shouldWrap : filter ( item . name , item . meta ) ,
68+ } ) )
69+ exportNames . push (
70+ ...filteredExports
71+ . filter ( ( item ) => item . shouldWrap )
72+ . map ( ( item ) => item . name ) ,
73+ )
4374 // update code and move to preserve `registerServerReference` position
4475 // e.g.
4576 // input
@@ -49,9 +80,9 @@ export function transformWrapExport(
4980 // async function f() {}
5081 // f = registerServerReference(f, ...) << maps to original "export" token
5182 // export { f } <<
52- const newCode = exports
83+ const newCode = filteredExports
5384 . map ( ( e ) => [
54- filter ( e . name , e . meta ) &&
85+ e . shouldWrap &&
5586 `${ e . name } = /* #__PURE__ */ ${ options . runtime (
5687 e . name ,
5788 e . name ,
@@ -67,11 +98,11 @@ export function transformWrapExport(
6798 }
6899
69100 function wrapExport ( name : string , exportName : string , meta : ExportMeta = { } ) {
70- exportNames . push ( exportName )
71101 if ( ! filter ( exportName , meta ) ) {
72102 toAppend . push ( `export { ${ name } as ${ exportName } }` )
73103 return
74104 }
105+ exportNames . push ( exportName )
75106
76107 toAppend . push (
77108 `const $$wrap_${ name } = /* #__PURE__ */ ${ options . runtime (
@@ -94,47 +125,47 @@ export function transformWrapExport(
94125 /**
95126 * export function foo() {}
96127 */
97- validateNonAsyncFunction ( options , node . declaration )
98128 const name = node . declaration . id . name
99- wrapSimple ( node . start , node . declaration . start , [
100- { name, meta : { isFunction : true , declName : name } } ,
101- ] )
129+ const meta : ExportMeta = {
130+ isFunction : getIsFunction ( node . declaration ) ,
131+ declName : name ,
132+ }
133+ if ( filter ( name , meta ) ) {
134+ validateNonAsyncFunction ( options , node . declaration )
135+ }
136+ wrapSimple ( node . start , node . declaration . start , [ { name, meta } ] )
102137 } else if ( node . declaration . type === 'VariableDeclaration' ) {
103138 /**
104139 * export const foo = 1, bar = 2
105140 */
106- for ( const decl of node . declaration . declarations ) {
107- if ( decl . init ) {
108- validateNonAsyncFunction ( options , decl . init )
109- }
110- }
111141 if ( node . declaration . kind === 'const' ) {
112142 output . update (
113143 node . declaration . start ,
114144 node . declaration . start + 5 ,
115145 'let' ,
116146 )
117147 }
118- const names = node . declaration . declarations . flatMap ( ( decl ) =>
119- extractNames ( decl . id ) ,
120- )
121- // treat only simple single decl as function
122- let isFunction = false
123- if ( node . declaration . declarations . length === 1 ) {
124- const decl = node . declaration . declarations [ 0 ] !
125- isFunction =
126- decl . id . type === 'Identifier' &&
127- ( decl . init ?. type === 'ArrowFunctionExpression' ||
128- decl . init ?. type === 'FunctionExpression' )
129- }
130- wrapSimple (
131- node . start ,
132- node . declaration . start ,
133- names . map ( ( name ) => ( {
148+ const exports : ExportWithMeta [ ] = [ ]
149+ for ( const decl of node . declaration . declarations ) {
150+ const isFunction =
151+ decl . id . type === 'Identifier' && decl . init
152+ ? getIsFunction ( decl . init )
153+ : undefined
154+ const declarationExports : ExportWithMeta [ ] = extractNames (
155+ decl . id ,
156+ ) . map ( ( name ) => ( {
134157 name,
135158 meta : { isFunction, declName : name } ,
136- } ) ) ,
137- )
159+ } ) )
160+ exports . push ( ...declarationExports )
161+ if (
162+ decl . init &&
163+ declarationExports . some ( ( { name, meta } ) => filter ( name , meta ) )
164+ ) {
165+ validateNonAsyncFunction ( options , decl . init )
166+ }
167+ }
168+ wrapSimple ( node . start , node . declaration . start , exports )
138169 } else {
139170 node . declaration satisfies never
140171 }
@@ -198,9 +229,8 @@ export function transformWrapExport(
198229 * export default () => {}
199230 */
200231 if ( node . type === 'ExportDefaultDeclaration' ) {
201- validateNonAsyncFunction ( options , node . declaration )
202232 let localName : string
203- let isFunction = false
233+ let isFunction : boolean | undefined
204234 let declName : string | undefined
205235 let defaultExportIdentifierName : string | undefined
206236 if (
@@ -211,21 +241,27 @@ export function transformWrapExport(
211241 // preserve name scope for `function foo() {}` and `class Foo {}`
212242 localName = node . declaration . id . name
213243 output . remove ( node . start , node . declaration . start )
214- isFunction = node . declaration . type === 'FunctionDeclaration'
244+ isFunction = getIsFunction ( node . declaration )
215245 declName = node . declaration . id . name
216246 } else {
217247 // otherwise we can introduce new variable
218248 localName = '$$default'
219249 output . update ( node . start , node . declaration . start , 'const $$default = ' )
220250 if ( node . declaration . type === 'Identifier' ) {
221251 defaultExportIdentifierName = node . declaration . name
252+ } else {
253+ isFunction = getIsFunction ( node . declaration )
222254 }
223255 }
224- wrapExport ( localName , 'default' , {
256+ const defaultMeta : ExportMeta = {
225257 isFunction,
226258 declName,
227259 defaultExportIdentifierName,
228- } )
260+ }
261+ if ( filter ( 'default' , defaultMeta ) ) {
262+ validateNonAsyncFunction ( options , node . declaration )
263+ }
264+ wrapExport ( localName , 'default' , defaultMeta )
229265 }
230266 }
231267
@@ -235,3 +271,24 @@ export function transformWrapExport(
235271
236272 return { exportNames, output }
237273}
274+
275+ function getIsFunction (
276+ node : Node | ExportDefaultDeclaration [ 'declaration' ] ,
277+ ) : boolean | undefined {
278+ if (
279+ node . type === 'FunctionDeclaration' ||
280+ node . type === 'ArrowFunctionExpression' ||
281+ node . type === 'FunctionExpression'
282+ ) {
283+ return true
284+ }
285+ if (
286+ node . type === 'ClassDeclaration' ||
287+ node . type === 'Literal' ||
288+ node . type === 'ObjectExpression' ||
289+ node . type === 'ArrayExpression' ||
290+ node . type === 'ClassExpression'
291+ ) {
292+ return false
293+ }
294+ }
0 commit comments