@@ -8,13 +8,7 @@ use next_binding::swc::core::{
8
8
BytePos , FileName , DUMMY_SP ,
9
9
} ,
10
10
ecma:: {
11
- ast:: {
12
- op, ArrayLit , AssignExpr , AssignPatProp , BlockStmt , CallExpr , ComputedPropName , Decl ,
13
- DefaultDecl , ExportDecl , ExportDefaultDecl , Expr , ExprStmt , FnDecl , Function , Id ,
14
- Ident , KeyValuePatProp , KeyValueProp , Lit , MemberExpr , MemberProp , Module , ModuleDecl ,
15
- ModuleItem , ObjectPatProp , OptChainBase , OptChainExpr , Param , Pat , PatOrExpr , Prop ,
16
- PropName , RestPat , ReturnStmt , Stmt , Str , VarDecl , VarDeclKind , VarDeclarator ,
17
- } ,
11
+ ast:: * ,
18
12
atoms:: JsWord ,
19
13
utils:: { private_ident, quote_ident, ExprFactory } ,
20
14
visit:: { as_folder, noop_visit_mut_type, Fold , VisitMut , VisitMutWith } ,
@@ -40,6 +34,7 @@ pub fn server_actions<C: Comments>(
40
34
start_pos : BytePos ( 0 ) ,
41
35
in_action_file : false ,
42
36
in_export_decl : false ,
37
+ in_prepass : false ,
43
38
has_action : false ,
44
39
top_level : false ,
45
40
@@ -48,6 +43,8 @@ pub fn server_actions<C: Comments>(
48
43
should_add_name : false ,
49
44
closure_idents : Default :: default ( ) ,
50
45
action_idents : Default :: default ( ) ,
46
+ async_fn_idents : Default :: default ( ) ,
47
+ exported_idents : Default :: default ( ) ,
51
48
52
49
annotations : Default :: default ( ) ,
53
50
extra_items : Default :: default ( ) ,
@@ -64,6 +61,7 @@ struct ServerActions<C: Comments> {
64
61
start_pos : BytePos ,
65
62
in_action_file : bool ,
66
63
in_export_decl : bool ,
64
+ in_prepass : bool ,
67
65
has_action : bool ,
68
66
top_level : bool ,
69
67
@@ -72,6 +70,8 @@ struct ServerActions<C: Comments> {
72
70
should_add_name : bool ,
73
71
closure_idents : Vec < Id > ,
74
72
action_idents : Vec < Name > ,
73
+ async_fn_idents : Vec < Id > ,
74
+ exported_idents : Vec < Id > ,
75
75
76
76
annotations : Vec < Stmt > ,
77
77
extra_items : Vec < ModuleItem > ,
@@ -87,28 +87,44 @@ impl<C: Comments> VisitMut for ServerActions<C> {
87
87
}
88
88
89
89
fn visit_mut_fn_decl ( & mut self , f : & mut FnDecl ) {
90
- let mut in_action_fn = false ;
90
+ // Need to collect all async function identifiers if we are in a server
91
+ // file, because it can be exported later.
92
+ if self . in_action_file && self . in_prepass {
93
+ if f. function . is_async {
94
+ self . async_fn_idents . push ( f. ident . to_id ( ) ) ;
95
+ }
96
+ return ;
97
+ }
98
+
99
+ let mut is_action_fn = false ;
100
+ let mut is_exported = false ;
91
101
92
102
if self . in_action_file && self . in_export_decl {
93
103
// All export functions in a server file are actions
94
- in_action_fn = true ;
104
+ is_action_fn = true ;
95
105
} else {
96
106
// Check if the function has `"use server"`
97
107
if let Some ( body) = & mut f. function . body {
98
108
let directive_index = get_server_directive_index_in_fn ( & body. stmts ) ;
99
109
if directive_index >= 0 {
100
- in_action_fn = true ;
110
+ is_action_fn = true ;
101
111
body. stmts . remove ( directive_index. try_into ( ) . unwrap ( ) ) ;
102
112
}
103
113
}
114
+
115
+ // If it's exported via named export, it's a valid action.
116
+ if !is_action_fn && self . exported_idents . contains ( & f. ident . to_id ( ) ) {
117
+ is_action_fn = true ;
118
+ is_exported = true ;
119
+ }
104
120
}
105
121
106
122
{
107
123
// Visit children
108
124
let old_in_action_fn = self . in_action_fn ;
109
125
let old_in_module = self . in_module ;
110
126
let old_should_add_name = self . should_add_name ;
111
- self . in_action_fn = in_action_fn ;
127
+ self . in_action_fn = is_action_fn ;
112
128
self . in_module = false ;
113
129
self . should_add_name = true ;
114
130
f. visit_mut_children_with ( self ) ;
@@ -117,7 +133,7 @@ impl<C: Comments> VisitMut for ServerActions<C> {
117
133
self . should_add_name = old_should_add_name;
118
134
}
119
135
120
- if !in_action_fn {
136
+ if !is_action_fn {
121
137
return ;
122
138
}
123
139
@@ -129,7 +145,8 @@ impl<C: Comments> VisitMut for ServerActions<C> {
129
145
} ) ;
130
146
}
131
147
132
- let action_name: JsWord = if self . in_action_file && self . in_export_decl {
148
+ let need_rename_export = self . in_action_file && ( self . in_export_decl || is_exported) ;
149
+ let action_name: JsWord = if need_rename_export {
133
150
f. ident . sym . clone ( )
134
151
} else {
135
152
format ! ( "$ACTION_{}" , f. ident. sym) . into ( )
@@ -177,7 +194,7 @@ impl<C: Comments> VisitMut for ServerActions<C> {
177
194
. into ( ) ,
178
195
) ) ;
179
196
180
- if !( self . in_action_file && self . in_export_decl ) {
197
+ if !need_rename_export {
181
198
// export const $ACTION_myAction = myAction;
182
199
self . extra_items
183
200
. push ( ModuleItem :: ModuleDecl ( ModuleDecl :: ExportDecl ( ExportDecl {
@@ -277,7 +294,7 @@ impl<C: Comments> VisitMut for ServerActions<C> {
277
294
fn visit_mut_stmt ( & mut self , n : & mut Stmt ) {
278
295
n. visit_mut_children_with ( self ) ;
279
296
280
- if self . in_module {
297
+ if self . in_module || self . in_prepass {
281
298
return ;
282
299
}
283
300
@@ -290,6 +307,10 @@ impl<C: Comments> VisitMut for ServerActions<C> {
290
307
fn visit_mut_param ( & mut self , n : & mut Param ) {
291
308
n. visit_mut_children_with ( self ) ;
292
309
310
+ if self . in_prepass {
311
+ return ;
312
+ }
313
+
293
314
if !self . in_action_fn && !self . in_action_file {
294
315
match & n. pat {
295
316
Pat :: Ident ( ident) => {
@@ -317,7 +338,9 @@ impl<C: Comments> VisitMut for ServerActions<C> {
317
338
if self . in_action_fn && self . should_add_name {
318
339
if let Ok ( name) = Name :: try_from ( & * n) {
319
340
self . should_add_name = false ;
320
- self . action_idents . push ( name) ;
341
+ if !self . in_prepass {
342
+ self . action_idents . push ( name) ;
343
+ }
321
344
n. visit_mut_children_with ( self ) ;
322
345
self . should_add_name = true ;
323
346
return ;
@@ -338,22 +361,89 @@ impl<C: Comments> VisitMut for ServerActions<C> {
338
361
let old_annotations = self . annotations . take ( ) ;
339
362
340
363
let mut new = Vec :: with_capacity ( stmts. len ( ) ) ;
364
+
365
+ // We need a second pass to collect all async function idents and exports
366
+ // so we can handle the named export cases if it's in the "use server" file.
367
+ if self . in_action_file {
368
+ self . in_prepass = true ;
369
+ for stmt in stmts. iter_mut ( ) {
370
+ match & * stmt {
371
+ ModuleItem :: ModuleDecl ( ModuleDecl :: ExportDecl ( ExportDecl {
372
+ decl : Decl :: Var ( var) ,
373
+ ..
374
+ } ) ) => {
375
+ let ids: Vec < Id > = collect_idents_in_var_decls ( & var. decls ) ;
376
+ self . exported_idents . extend ( ids) ;
377
+ }
378
+ ModuleItem :: ModuleDecl ( ModuleDecl :: ExportNamed ( named) ) => {
379
+ for spec in & named. specifiers {
380
+ if let ExportSpecifier :: Named ( ExportNamedSpecifier {
381
+ orig : ModuleExportName :: Ident ( ident) ,
382
+ ..
383
+ } ) = spec
384
+ {
385
+ // export { foo, foo as bar }
386
+ self . exported_idents . push ( ident. to_id ( ) ) ;
387
+ }
388
+ }
389
+ }
390
+ _ => { }
391
+ }
392
+
393
+ stmt. visit_mut_with ( self ) ;
394
+ }
395
+ self . in_prepass = false ;
396
+ }
397
+
341
398
for mut stmt in stmts. take ( ) {
342
399
self . top_level = true ;
343
400
344
401
// For action file, it's not allowed to export things other than async
345
402
// functions.
346
403
if self . in_action_file {
347
404
let mut disallowed_export_span = DUMMY_SP ;
405
+
406
+ // Currrently only function exports are allowed.
348
407
match & mut stmt {
349
408
ModuleItem :: ModuleDecl ( ModuleDecl :: ExportDecl ( ExportDecl { decl, span } ) ) => {
350
409
match decl {
351
410
Decl :: Fn ( _f) => { }
411
+ Decl :: Var ( var) => {
412
+ for decl in & mut var. decls {
413
+ if let Some ( init) = & decl. init {
414
+ match & * * init {
415
+ Expr :: Fn ( _f) => { }
416
+ _ => {
417
+ disallowed_export_span = * span;
418
+ }
419
+ }
420
+ }
421
+ }
422
+ }
352
423
_ => {
353
424
disallowed_export_span = * span;
354
425
}
355
426
}
356
427
}
428
+ ModuleItem :: ModuleDecl ( ModuleDecl :: ExportNamed ( named) ) => {
429
+ if named. src . is_some ( ) {
430
+ disallowed_export_span = named. span ;
431
+ } else {
432
+ for spec in & mut named. specifiers {
433
+ if let ExportSpecifier :: Named ( ExportNamedSpecifier {
434
+ orig : ModuleExportName :: Ident ( ident) ,
435
+ ..
436
+ } ) = spec
437
+ {
438
+ if !self . async_fn_idents . contains ( & ident. to_id ( ) ) {
439
+ disallowed_export_span = named. span ;
440
+ }
441
+ } else {
442
+ disallowed_export_span = named. span ;
443
+ }
444
+ }
445
+ }
446
+ }
357
447
ModuleItem :: ModuleDecl ( ModuleDecl :: ExportDefaultDecl ( ExportDefaultDecl {
358
448
decl,
359
449
span,
@@ -364,6 +454,16 @@ impl<C: Comments> VisitMut for ServerActions<C> {
364
454
disallowed_export_span = * span;
365
455
}
366
456
} ,
457
+ ModuleItem :: ModuleDecl ( ModuleDecl :: ExportDefaultExpr ( ExportDefaultExpr {
458
+ expr,
459
+ span,
460
+ ..
461
+ } ) ) => match & * * expr {
462
+ Expr :: Fn ( _f) => { }
463
+ _ => {
464
+ disallowed_export_span = * span;
465
+ }
466
+ } ,
367
467
_ => { }
368
468
}
369
469
0 commit comments