@@ -8,13 +8,7 @@ use next_binding::swc::core::{
88 BytePos , FileName , DUMMY_SP ,
99 } ,
1010 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:: * ,
1812 atoms:: JsWord ,
1913 utils:: { private_ident, quote_ident, ExprFactory } ,
2014 visit:: { as_folder, noop_visit_mut_type, Fold , VisitMut , VisitMutWith } ,
@@ -40,6 +34,7 @@ pub fn server_actions<C: Comments>(
4034 start_pos : BytePos ( 0 ) ,
4135 in_action_file : false ,
4236 in_export_decl : false ,
37+ in_prepass : false ,
4338 has_action : false ,
4439 top_level : false ,
4540
@@ -48,6 +43,8 @@ pub fn server_actions<C: Comments>(
4843 should_add_name : false ,
4944 closure_idents : Default :: default ( ) ,
5045 action_idents : Default :: default ( ) ,
46+ async_fn_idents : Default :: default ( ) ,
47+ exported_idents : Default :: default ( ) ,
5148
5249 annotations : Default :: default ( ) ,
5350 extra_items : Default :: default ( ) ,
@@ -64,6 +61,7 @@ struct ServerActions<C: Comments> {
6461 start_pos : BytePos ,
6562 in_action_file : bool ,
6663 in_export_decl : bool ,
64+ in_prepass : bool ,
6765 has_action : bool ,
6866 top_level : bool ,
6967
@@ -72,6 +70,8 @@ struct ServerActions<C: Comments> {
7270 should_add_name : bool ,
7371 closure_idents : Vec < Id > ,
7472 action_idents : Vec < Name > ,
73+ async_fn_idents : Vec < Id > ,
74+ exported_idents : Vec < Id > ,
7575
7676 annotations : Vec < Stmt > ,
7777 extra_items : Vec < ModuleItem > ,
@@ -87,28 +87,44 @@ impl<C: Comments> VisitMut for ServerActions<C> {
8787 }
8888
8989 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 ;
91101
92102 if self . in_action_file && self . in_export_decl {
93103 // All export functions in a server file are actions
94- in_action_fn = true ;
104+ is_action_fn = true ;
95105 } else {
96106 // Check if the function has `"use server"`
97107 if let Some ( body) = & mut f. function . body {
98108 let directive_index = get_server_directive_index_in_fn ( & body. stmts ) ;
99109 if directive_index >= 0 {
100- in_action_fn = true ;
110+ is_action_fn = true ;
101111 body. stmts . remove ( directive_index. try_into ( ) . unwrap ( ) ) ;
102112 }
103113 }
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+ }
104120 }
105121
106122 {
107123 // Visit children
108124 let old_in_action_fn = self . in_action_fn ;
109125 let old_in_module = self . in_module ;
110126 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 ;
112128 self . in_module = false ;
113129 self . should_add_name = true ;
114130 f. visit_mut_children_with ( self ) ;
@@ -117,7 +133,7 @@ impl<C: Comments> VisitMut for ServerActions<C> {
117133 self . should_add_name = old_should_add_name;
118134 }
119135
120- if !in_action_fn {
136+ if !is_action_fn {
121137 return ;
122138 }
123139
@@ -129,7 +145,8 @@ impl<C: Comments> VisitMut for ServerActions<C> {
129145 } ) ;
130146 }
131147
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 {
133150 f. ident . sym . clone ( )
134151 } else {
135152 format ! ( "$ACTION_{}" , f. ident. sym) . into ( )
@@ -177,7 +194,7 @@ impl<C: Comments> VisitMut for ServerActions<C> {
177194 . into ( ) ,
178195 ) ) ;
179196
180- if !( self . in_action_file && self . in_export_decl ) {
197+ if !need_rename_export {
181198 // export const $ACTION_myAction = myAction;
182199 self . extra_items
183200 . push ( ModuleItem :: ModuleDecl ( ModuleDecl :: ExportDecl ( ExportDecl {
@@ -277,7 +294,7 @@ impl<C: Comments> VisitMut for ServerActions<C> {
277294 fn visit_mut_stmt ( & mut self , n : & mut Stmt ) {
278295 n. visit_mut_children_with ( self ) ;
279296
280- if self . in_module {
297+ if self . in_module || self . in_prepass {
281298 return ;
282299 }
283300
@@ -290,6 +307,10 @@ impl<C: Comments> VisitMut for ServerActions<C> {
290307 fn visit_mut_param ( & mut self , n : & mut Param ) {
291308 n. visit_mut_children_with ( self ) ;
292309
310+ if self . in_prepass {
311+ return ;
312+ }
313+
293314 if !self . in_action_fn && !self . in_action_file {
294315 match & n. pat {
295316 Pat :: Ident ( ident) => {
@@ -317,7 +338,9 @@ impl<C: Comments> VisitMut for ServerActions<C> {
317338 if self . in_action_fn && self . should_add_name {
318339 if let Ok ( name) = Name :: try_from ( & * n) {
319340 self . should_add_name = false ;
320- self . action_idents . push ( name) ;
341+ if !self . in_prepass {
342+ self . action_idents . push ( name) ;
343+ }
321344 n. visit_mut_children_with ( self ) ;
322345 self . should_add_name = true ;
323346 return ;
@@ -338,22 +361,89 @@ impl<C: Comments> VisitMut for ServerActions<C> {
338361 let old_annotations = self . annotations . take ( ) ;
339362
340363 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+
341398 for mut stmt in stmts. take ( ) {
342399 self . top_level = true ;
343400
344401 // For action file, it's not allowed to export things other than async
345402 // functions.
346403 if self . in_action_file {
347404 let mut disallowed_export_span = DUMMY_SP ;
405+
406+ // Currrently only function exports are allowed.
348407 match & mut stmt {
349408 ModuleItem :: ModuleDecl ( ModuleDecl :: ExportDecl ( ExportDecl { decl, span } ) ) => {
350409 match decl {
351410 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+ }
352423 _ => {
353424 disallowed_export_span = * span;
354425 }
355426 }
356427 }
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+ }
357447 ModuleItem :: ModuleDecl ( ModuleDecl :: ExportDefaultDecl ( ExportDefaultDecl {
358448 decl,
359449 span,
@@ -364,6 +454,16 @@ impl<C: Comments> VisitMut for ServerActions<C> {
364454 disallowed_export_span = * span;
365455 }
366456 } ,
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+ } ,
367467 _ => { }
368468 }
369469
0 commit comments