1use std::{
2 cell::RefCell,
3 collections::{hash_map, BTreeMap},
4 convert::{TryFrom, TryInto},
5 mem::{replace, take},
6 path::{Path, PathBuf},
7 rc::Rc,
8 sync::Arc,
9};
10
11use base64::{display::Base64Display, prelude::BASE64_STANDARD};
12use hex::encode as hex_encode;
13use indoc::formatdoc;
14use pathdiff::diff_paths;
15use rustc_hash::{FxHashMap, FxHashSet};
16use serde::Deserialize;
17use sha1::{Digest, Sha1};
18use swc_core::{
19 atoms::{atom, Atom},
20 common::{
21 comments::{Comment, CommentKind, Comments, SingleThreadedComments},
22 errors::HANDLER,
23 source_map::{SourceMapGenConfig, PURE_SP},
24 util::take::Take,
25 BytePos, FileName, Mark, SourceMap, Span, SyntaxContext, DUMMY_SP,
26 },
27 ecma::{
28 ast::*,
29 codegen::{self, text_writer::JsWriter, Emitter},
30 utils::{private_ident, quote_ident, ExprFactory},
31 visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith},
32 },
33 quote,
34};
35use turbo_rcstr::{rcstr, RcStr};
36
37use crate::FxIndexMap;
38
39#[derive(Clone, Copy, Debug, Deserialize)]
40pub enum ServerActionsMode {
41 Webpack,
42 Turbopack,
43}
44
45#[derive(Clone, Debug, Deserialize)]
46#[serde(deny_unknown_fields, rename_all = "camelCase")]
47pub struct Config {
48 pub is_react_server_layer: bool,
49 pub is_development: bool,
50 pub use_cache_enabled: bool,
51 pub hash_salt: String,
52 pub cache_kinds: FxHashSet<RcStr>,
53}
54
55#[derive(Clone, Debug)]
56enum Directive {
57 UseServer,
58 UseCache { cache_kind: RcStr },
59}
60
61#[derive(Clone, Debug)]
62enum DirectiveLocation {
63 Module,
64 FunctionBody,
65}
66
67#[derive(Clone, Debug)]
68enum ThisStatus {
69 Allowed,
70 Forbidden { directive: Directive },
71}
72
73#[derive(Clone, Debug)]
74enum ServerActionsErrorKind {
75 ExportedSyncFunction {
76 span: Span,
77 in_action_file: bool,
78 },
79 ForbiddenExpression {
80 span: Span,
81 expr: String,
82 directive: Directive,
83 },
84 InlineSyncFunction {
85 span: Span,
86 directive: Directive,
87 },
88 InlineUseCacheInClassInstanceMethod {
89 span: Span,
90 },
91 InlineUseCacheInClientComponent {
92 span: Span,
93 },
94 InlineUseServerInClassInstanceMethod {
95 span: Span,
96 },
97 InlineUseServerInClientComponent {
98 span: Span,
99 },
100 MisplacedDirective {
101 span: Span,
102 directive: String,
103 location: DirectiveLocation,
104 },
105 MisplacedWrappedDirective {
106 span: Span,
107 directive: String,
108 location: DirectiveLocation,
109 },
110 MisspelledDirective {
111 span: Span,
112 directive: String,
113 expected_directive: String,
114 },
115 MultipleDirectives {
116 span: Span,
117 location: DirectiveLocation,
118 },
119 UnknownCacheKind {
120 span: Span,
121 cache_kind: RcStr,
122 },
123 UseCacheWithoutCacheComponents {
124 span: Span,
125 directive: String,
126 },
127 WrappedDirective {
128 span: Span,
129 directive: String,
130 },
131}
132
133pub type ActionsMap = BTreeMap<Atom, Atom>;
136
137#[tracing::instrument(level = tracing::Level::TRACE, skip_all)]
138pub fn server_actions<C: Comments>(
139 file_name: &FileName,
140 file_query: Option<RcStr>,
141 config: Config,
142 comments: C,
143 cm: Arc<SourceMap>,
144 use_cache_telemetry_tracker: Rc<RefCell<FxHashMap<String, usize>>>,
145 mode: ServerActionsMode,
146) -> impl Pass {
147 visit_mut_pass(ServerActions {
148 config,
149 mode,
150 comments,
151 cm,
152 file_name: file_name.to_string(),
153 file_query,
154 start_pos: BytePos(0),
155 file_directive: None,
156 in_exported_expr: false,
157 in_default_export_decl: false,
158 fn_decl_ident: None,
159 in_callee: false,
160 has_action: false,
161 has_cache: false,
162 this_status: ThisStatus::Allowed,
163
164 reference_index: 0,
165 in_module_level: true,
166 should_track_names: false,
167
168 names: Default::default(),
169 declared_idents: Default::default(),
170
171 exported_idents: Default::default(),
172
173 rewrite_fn_decl_to_proxy_decl: None,
175 rewrite_default_fn_expr_to_proxy_expr: None,
176 rewrite_expr_to_proxy_expr: None,
177
178 annotations: Default::default(),
179 extra_items: Default::default(),
180 hoisted_extra_items: Default::default(),
181 export_actions: Default::default(),
182
183 private_ctxt: SyntaxContext::empty().apply_mark(Mark::new()),
184
185 arrow_or_fn_expr_ident: None,
186 exported_local_ids: FxHashSet::default(),
187
188 use_cache_telemetry_tracker,
189 })
190}
191
192fn generate_server_actions_comment(
195 actions: &ActionsMap,
196 entry_path_query: Option<(&str, &str)>,
197) -> String {
198 format!(
199 " __next_internal_action_entry_do_not_use__ {} ",
200 if let Some(entry_path_query) = entry_path_query {
201 serde_json::to_string(&(actions, entry_path_query.0, entry_path_query.1))
202 } else {
203 serde_json::to_string(&actions)
204 }
205 .unwrap()
206 )
207}
208
209struct ServerActions<C: Comments> {
210 #[allow(unused)]
211 config: Config,
212 file_name: String,
213 file_query: Option<RcStr>,
214 comments: C,
215 cm: Arc<SourceMap>,
216 mode: ServerActionsMode,
217
218 start_pos: BytePos,
219 file_directive: Option<Directive>,
220 in_exported_expr: bool,
221 in_default_export_decl: bool,
222 fn_decl_ident: Option<Ident>,
223 in_callee: bool,
224 has_action: bool,
225 has_cache: bool,
226 this_status: ThisStatus,
227
228 reference_index: u32,
229 in_module_level: bool,
230 should_track_names: bool,
231
232 names: Vec<Name>,
233 declared_idents: Vec<Ident>,
234
235 rewrite_fn_decl_to_proxy_decl: Option<VarDecl>,
237 rewrite_default_fn_expr_to_proxy_expr: Option<Box<Expr>>,
238 rewrite_expr_to_proxy_expr: Option<Box<Expr>>,
239
240 exported_idents: Vec<(
241 Ident,
242 Atom,
243 Atom,
244 )>,
245
246 annotations: Vec<Stmt>,
247 extra_items: Vec<ModuleItem>,
248 hoisted_extra_items: Vec<ModuleItem>,
249 export_actions: Vec<(Atom, Atom)>,
250
251 private_ctxt: SyntaxContext,
252
253 arrow_or_fn_expr_ident: Option<Ident>,
254 exported_local_ids: FxHashSet<Id>,
255
256 use_cache_telemetry_tracker: Rc<RefCell<FxHashMap<String, usize>>>,
257}
258
259impl<C: Comments> ServerActions<C> {
260 fn generate_server_reference_id(
261 &self,
262 export_name: &str,
263 is_cache: bool,
264 params: Option<&Vec<Param>>,
265 ) -> Atom {
266 let mut hasher = Sha1::new();
271 hasher.update(self.config.hash_salt.as_bytes());
272 hasher.update(self.file_name.as_bytes());
273 hasher.update(b":");
274 hasher.update(export_name.as_bytes());
275 let mut result = hasher.finalize().to_vec();
276
277 let type_bit = if is_cache { 1u8 } else { 0u8 };
305 let mut arg_mask = 0u8;
306 let mut rest_args = 0u8;
307
308 if let Some(params) = params {
309 for (i, param) in params.iter().enumerate() {
314 if let Pat::Rest(_) = param.pat {
315 arg_mask = 0b111111;
318 rest_args = 0b1;
319 break;
320 }
321 if i < 6 {
322 arg_mask |= 0b1 << (5 - i);
323 } else {
324 rest_args = 0b1;
327 break;
328 }
329 }
330 } else {
331 arg_mask = 0b111111;
334 rest_args = 0b1;
335 }
336
337 result.push((type_bit << 7) | (arg_mask << 1) | rest_args);
338 result.rotate_right(1);
339
340 Atom::from(hex_encode(result))
341 }
342
343 fn gen_action_ident(&mut self) -> Atom {
344 let id: Atom = format!("$$RSC_SERVER_ACTION_{0}", self.reference_index).into();
345 self.reference_index += 1;
346 id
347 }
348
349 fn gen_cache_ident(&mut self) -> Atom {
350 let id: Atom = format!("$$RSC_SERVER_CACHE_{0}", self.reference_index).into();
351 self.reference_index += 1;
352 id
353 }
354
355 fn create_bound_action_args_array_pat(&mut self, arg_len: usize) -> Pat {
356 Pat::Array(ArrayPat {
357 span: DUMMY_SP,
358 elems: (0..arg_len)
359 .map(|i| {
360 Some(Pat::Ident(
361 Ident::new(
362 format!("$$ACTION_ARG_{i}").into(),
363 DUMMY_SP,
364 self.private_ctxt,
365 )
366 .into(),
367 ))
368 })
369 .collect(),
370 optional: false,
371 type_ann: None,
372 })
373 }
374
375 fn get_directive_for_function(
378 &mut self,
379 maybe_body: Option<&mut BlockStmt>,
380 ) -> Option<Directive> {
381 let mut directive: Option<Directive> = None;
382
383 if let Some(body) = maybe_body {
386 let directive_visitor = &mut DirectiveVisitor {
387 config: &self.config,
388 directive: None,
389 has_file_directive: self.file_directive.is_some(),
390 is_allowed_position: true,
391 location: DirectiveLocation::FunctionBody,
392 use_cache_telemetry_tracker: self.use_cache_telemetry_tracker.clone(),
393 };
394
395 body.stmts.retain(|stmt| {
396 let has_directive = directive_visitor.visit_stmt(stmt);
397
398 !has_directive
399 });
400
401 directive = directive_visitor.directive.clone();
402 }
403
404 if self.in_exported_expr && directive.is_none() && self.file_directive.is_some() {
406 return self.file_directive.clone();
407 }
408
409 directive
410 }
411
412 fn get_directive_for_module(&mut self, stmts: &mut Vec<ModuleItem>) -> Option<Directive> {
413 let directive_visitor = &mut DirectiveVisitor {
414 config: &self.config,
415 directive: None,
416 has_file_directive: false,
417 is_allowed_position: true,
418 location: DirectiveLocation::Module,
419 use_cache_telemetry_tracker: self.use_cache_telemetry_tracker.clone(),
420 };
421
422 stmts.retain(|item| {
423 if let ModuleItem::Stmt(stmt) = item {
424 let has_directive = directive_visitor.visit_stmt(stmt);
425
426 !has_directive
427 } else {
428 directive_visitor.is_allowed_position = false;
429 true
430 }
431 });
432
433 directive_visitor.directive.clone()
434 }
435
436 fn maybe_hoist_and_create_proxy_for_server_action_arrow_expr(
437 &mut self,
438 ids_from_closure: Vec<Name>,
439 arrow: &mut ArrowExpr,
440 ) -> Box<Expr> {
441 let mut new_params: Vec<Param> = vec![];
442
443 if !ids_from_closure.is_empty() {
444 new_params.push(Param {
446 span: DUMMY_SP,
447 decorators: vec![],
448 pat: Pat::Ident(IdentName::new(atom!("$$ACTION_CLOSURE_BOUND"), DUMMY_SP).into()),
449 });
450 }
451
452 for p in arrow.params.iter() {
453 new_params.push(Param::from(p.clone()));
454 }
455
456 let action_name = self.gen_action_ident();
457 let action_ident = Ident::new(action_name.clone(), arrow.span, self.private_ctxt);
458 let action_id = self.generate_server_reference_id(&action_name, false, Some(&new_params));
459
460 self.has_action = true;
461 self.export_actions
462 .push((action_name.clone(), action_id.clone()));
463
464 if let BlockStmtOrExpr::BlockStmt(block) = &mut *arrow.body {
465 block.visit_mut_with(&mut ClosureReplacer {
466 used_ids: &ids_from_closure,
467 private_ctxt: self.private_ctxt,
468 });
469 }
470
471 let mut new_body: BlockStmtOrExpr = *arrow.body.clone();
472
473 if !ids_from_closure.is_empty() {
474 let decryption_decl = VarDecl {
478 span: DUMMY_SP,
479 kind: VarDeclKind::Var,
480 declare: false,
481 decls: vec![VarDeclarator {
482 span: DUMMY_SP,
483 name: self.create_bound_action_args_array_pat(ids_from_closure.len()),
484 init: Some(Box::new(Expr::Await(AwaitExpr {
485 span: DUMMY_SP,
486 arg: Box::new(Expr::Call(CallExpr {
487 span: DUMMY_SP,
488 callee: quote_ident!("decryptActionBoundArgs").as_callee(),
489 args: vec![
490 action_id.clone().as_arg(),
491 quote_ident!("$$ACTION_CLOSURE_BOUND").as_arg(),
492 ],
493 ..Default::default()
494 })),
495 }))),
496 definite: Default::default(),
497 }],
498 ..Default::default()
499 };
500
501 match &mut new_body {
502 BlockStmtOrExpr::BlockStmt(body) => {
503 body.stmts.insert(0, decryption_decl.into());
504 }
505 BlockStmtOrExpr::Expr(body_expr) => {
506 new_body = BlockStmtOrExpr::BlockStmt(BlockStmt {
507 span: DUMMY_SP,
508 stmts: vec![
509 decryption_decl.into(),
510 Stmt::Return(ReturnStmt {
511 span: DUMMY_SP,
512 arg: Some(body_expr.take()),
513 }),
514 ],
515 ..Default::default()
516 });
517 }
518 }
519 }
520
521 self.hoisted_extra_items
524 .push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
525 span: DUMMY_SP,
526 decl: VarDecl {
527 kind: VarDeclKind::Const,
528 span: DUMMY_SP,
529 decls: vec![VarDeclarator {
530 span: DUMMY_SP,
531 name: Pat::Ident(action_ident.clone().into()),
532 definite: false,
533 init: Some(Box::new(Expr::Fn(FnExpr {
534 ident: self.arrow_or_fn_expr_ident.clone(),
535 function: Box::new(Function {
536 params: new_params,
537 body: match new_body {
538 BlockStmtOrExpr::BlockStmt(body) => Some(body),
539 BlockStmtOrExpr::Expr(expr) => Some(BlockStmt {
540 span: DUMMY_SP,
541 stmts: vec![Stmt::Return(ReturnStmt {
542 span: DUMMY_SP,
543 arg: Some(expr),
544 })],
545 ..Default::default()
546 }),
547 },
548 decorators: vec![],
549 span: DUMMY_SP,
550 is_generator: false,
551 is_async: true,
552 ..Default::default()
553 }),
554 }))),
555 }],
556 declare: Default::default(),
557 ctxt: self.private_ctxt,
558 }
559 .into(),
560 })));
561
562 self.hoisted_extra_items
563 .push(ModuleItem::Stmt(Stmt::Expr(ExprStmt {
564 span: DUMMY_SP,
565 expr: Box::new(annotate_ident_as_server_reference(
566 action_ident.clone(),
567 action_id.clone(),
568 arrow.span,
569 )),
570 })));
571
572 if ids_from_closure.is_empty() {
573 Box::new(action_ident.clone().into())
574 } else {
575 Box::new(bind_args_to_ident(
576 action_ident.clone(),
577 ids_from_closure
578 .iter()
579 .cloned()
580 .map(|id| Some(id.as_arg()))
581 .collect(),
582 action_id.clone(),
583 ))
584 }
585 }
586
587 fn maybe_hoist_and_create_proxy_for_server_action_function(
588 &mut self,
589 ids_from_closure: Vec<Name>,
590 function: &mut Function,
591 fn_name: Option<Ident>,
592 ) -> Box<Expr> {
593 let mut new_params: Vec<Param> = vec![];
594
595 if !ids_from_closure.is_empty() {
596 new_params.push(Param {
598 span: DUMMY_SP,
599 decorators: vec![],
600 pat: Pat::Ident(IdentName::new(atom!("$$ACTION_CLOSURE_BOUND"), DUMMY_SP).into()),
601 });
602 }
603
604 new_params.append(&mut function.params);
605
606 let action_name: Atom = self.gen_action_ident();
607 let mut action_ident = Ident::new(action_name.clone(), function.span, self.private_ctxt);
608 if action_ident.span.lo == self.start_pos {
609 action_ident.span = Span::dummy_with_cmt();
610 }
611
612 let action_id = self.generate_server_reference_id(&action_name, false, Some(&new_params));
613
614 self.has_action = true;
615 self.export_actions
616 .push((action_name.clone(), action_id.clone()));
617
618 function.body.visit_mut_with(&mut ClosureReplacer {
619 used_ids: &ids_from_closure,
620 private_ctxt: self.private_ctxt,
621 });
622
623 let mut new_body: Option<BlockStmt> = function.body.clone();
624
625 if !ids_from_closure.is_empty() {
626 let decryption_decl = VarDecl {
630 span: DUMMY_SP,
631 kind: VarDeclKind::Var,
632 decls: vec![VarDeclarator {
633 span: DUMMY_SP,
634 name: self.create_bound_action_args_array_pat(ids_from_closure.len()),
635 init: Some(Box::new(Expr::Await(AwaitExpr {
636 span: DUMMY_SP,
637 arg: Box::new(Expr::Call(CallExpr {
638 span: DUMMY_SP,
639 callee: quote_ident!("decryptActionBoundArgs").as_callee(),
640 args: vec![
641 action_id.clone().as_arg(),
642 quote_ident!("$$ACTION_CLOSURE_BOUND").as_arg(),
643 ],
644 ..Default::default()
645 })),
646 }))),
647 definite: Default::default(),
648 }],
649 ..Default::default()
650 };
651
652 if let Some(body) = &mut new_body {
653 body.stmts.insert(0, decryption_decl.into());
654 } else {
655 new_body = Some(BlockStmt {
656 span: DUMMY_SP,
657 stmts: vec![decryption_decl.into()],
658 ..Default::default()
659 });
660 }
661 }
662
663 self.hoisted_extra_items
666 .push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
667 span: DUMMY_SP,
668 decl: VarDecl {
669 kind: VarDeclKind::Const,
670 span: DUMMY_SP,
671 decls: vec![VarDeclarator {
672 span: DUMMY_SP, name: Pat::Ident(action_ident.clone().into()),
674 definite: false,
675 init: Some(Box::new(Expr::Fn(FnExpr {
676 ident: fn_name,
677 function: Box::new(Function {
678 params: new_params,
679 body: new_body,
680 ..function.take()
681 }),
682 }))),
683 }],
684 declare: Default::default(),
685 ctxt: self.private_ctxt,
686 }
687 .into(),
688 })));
689
690 self.hoisted_extra_items
691 .push(ModuleItem::Stmt(Stmt::Expr(ExprStmt {
692 span: DUMMY_SP,
693 expr: Box::new(annotate_ident_as_server_reference(
694 action_ident.clone(),
695 action_id.clone(),
696 function.span,
697 )),
698 })));
699
700 if ids_from_closure.is_empty() {
701 Box::new(action_ident.clone().into())
702 } else {
703 Box::new(bind_args_to_ident(
704 action_ident.clone(),
705 ids_from_closure
706 .iter()
707 .cloned()
708 .map(|id| Some(id.as_arg()))
709 .collect(),
710 action_id.clone(),
711 ))
712 }
713 }
714
715 fn maybe_hoist_and_create_proxy_for_cache_arrow_expr(
716 &mut self,
717 ids_from_closure: Vec<Name>,
718 cache_kind: RcStr,
719 arrow: &mut ArrowExpr,
720 ) -> Box<Expr> {
721 let mut new_params: Vec<Param> = vec![];
722
723 if !ids_from_closure.is_empty() {
727 new_params.push(Param {
728 span: DUMMY_SP,
729 decorators: vec![],
730 pat: self.create_bound_action_args_array_pat(ids_from_closure.len()),
731 });
732 }
733
734 for p in arrow.params.iter() {
735 new_params.push(Param::from(p.clone()));
736 }
737
738 let cache_name: Atom = self.gen_cache_ident();
739 let cache_ident = private_ident!(Span::dummy_with_cmt(), cache_name.clone());
740 let export_name: Atom = cache_name;
741
742 let reference_id = self.generate_server_reference_id(&export_name, true, Some(&new_params));
743
744 self.has_cache = true;
745 self.export_actions
746 .push((export_name.clone(), reference_id.clone()));
747
748 if let BlockStmtOrExpr::BlockStmt(block) = &mut *arrow.body {
749 block.visit_mut_with(&mut ClosureReplacer {
750 used_ids: &ids_from_closure,
751 private_ctxt: self.private_ctxt,
752 });
753 }
754
755 self.hoisted_extra_items
758 .push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
759 span: DUMMY_SP,
760 decl: VarDecl {
761 span: DUMMY_SP,
762 kind: VarDeclKind::Var,
763 decls: vec![VarDeclarator {
764 span: arrow.span,
765 name: Pat::Ident(cache_ident.clone().into()),
766 init: Some(wrap_cache_expr(
767 Box::new(Expr::Fn(FnExpr {
768 ident: None,
769 function: Box::new(Function {
770 params: new_params,
771 body: match *arrow.body.take() {
772 BlockStmtOrExpr::BlockStmt(body) => Some(body),
773 BlockStmtOrExpr::Expr(expr) => Some(BlockStmt {
774 span: DUMMY_SP,
775 stmts: vec![Stmt::Return(ReturnStmt {
776 span: DUMMY_SP,
777 arg: Some(expr),
778 })],
779 ..Default::default()
780 }),
781 },
782 decorators: vec![],
783 span: DUMMY_SP,
784 is_generator: false,
785 is_async: true,
786 ..Default::default()
787 }),
788 })),
789 &cache_kind,
790 &reference_id,
791 ids_from_closure.len(),
792 )),
793 definite: false,
794 }],
795 ..Default::default()
796 }
797 .into(),
798 })));
799
800 self.hoisted_extra_items
801 .push(ModuleItem::Stmt(Stmt::Expr(ExprStmt {
802 span: DUMMY_SP,
803 expr: Box::new(annotate_ident_as_server_reference(
804 cache_ident.clone(),
805 reference_id.clone(),
806 arrow.span,
807 )),
808 })));
809
810 if let Some(Ident { sym, .. }) = &self.arrow_or_fn_expr_ident {
811 assign_name_to_ident(&cache_ident, sym.as_str(), &mut self.hoisted_extra_items);
812 }
813
814 let bound_args: Vec<_> = ids_from_closure
815 .iter()
816 .cloned()
817 .map(|id| Some(id.as_arg()))
818 .collect();
819
820 if bound_args.is_empty() {
821 Box::new(cache_ident.clone().into())
822 } else {
823 Box::new(bind_args_to_ident(
824 cache_ident.clone(),
825 bound_args,
826 reference_id.clone(),
827 ))
828 }
829 }
830
831 fn maybe_hoist_and_create_proxy_for_cache_function(
832 &mut self,
833 ids_from_closure: Vec<Name>,
834 fn_name: Option<Ident>,
835 cache_kind: RcStr,
836 function: &mut Function,
837 ) -> Box<Expr> {
838 let mut new_params: Vec<Param> = vec![];
839
840 if !ids_from_closure.is_empty() {
844 new_params.push(Param {
845 span: DUMMY_SP,
846 decorators: vec![],
847 pat: self.create_bound_action_args_array_pat(ids_from_closure.len()),
848 });
849 }
850
851 for p in function.params.iter() {
852 new_params.push(p.clone());
853 }
854
855 let cache_name: Atom = self.gen_cache_ident();
856 let cache_ident = private_ident!(Span::dummy_with_cmt(), cache_name.clone());
857
858 let reference_id = self.generate_server_reference_id(&cache_name, true, Some(&new_params));
859
860 self.has_cache = true;
861 self.export_actions
862 .push((cache_name.clone(), reference_id.clone()));
863
864 function.body.visit_mut_with(&mut ClosureReplacer {
865 used_ids: &ids_from_closure,
866 private_ctxt: self.private_ctxt,
867 });
868
869 self.hoisted_extra_items
871 .push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
872 span: DUMMY_SP,
873 decl: VarDecl {
874 span: DUMMY_SP,
875 kind: VarDeclKind::Var,
876 decls: vec![VarDeclarator {
877 span: function.span,
878 name: Pat::Ident(cache_ident.clone().into()),
879 init: Some(wrap_cache_expr(
880 Box::new(Expr::Fn(FnExpr {
881 ident: fn_name.clone(),
882 function: Box::new(Function {
883 params: new_params,
884 ..function.take()
885 }),
886 })),
887 &cache_kind,
888 &reference_id,
889 ids_from_closure.len(),
890 )),
891 definite: false,
892 }],
893 ..Default::default()
894 }
895 .into(),
896 })));
897
898 self.hoisted_extra_items
899 .push(ModuleItem::Stmt(Stmt::Expr(ExprStmt {
900 span: DUMMY_SP,
901 expr: Box::new(annotate_ident_as_server_reference(
902 cache_ident.clone(),
903 reference_id.clone(),
904 function.span,
905 )),
906 })));
907
908 if let Some(Ident { sym, .. }) = fn_name {
909 assign_name_to_ident(&cache_ident, sym.as_str(), &mut self.hoisted_extra_items);
910 } else if self.in_default_export_decl {
911 assign_name_to_ident(&cache_ident, "default", &mut self.hoisted_extra_items);
912 }
913
914 let bound_args: Vec<_> = ids_from_closure
915 .iter()
916 .cloned()
917 .map(|id| Some(id.as_arg()))
918 .collect();
919
920 if bound_args.is_empty() {
921 Box::new(cache_ident.clone().into())
922 } else {
923 Box::new(bind_args_to_ident(
924 cache_ident.clone(),
925 bound_args,
926 reference_id.clone(),
927 ))
928 }
929 }
930}
931
932impl<C: Comments> VisitMut for ServerActions<C> {
933 fn visit_mut_export_decl(&mut self, decl: &mut ExportDecl) {
934 let old_in_exported_expr = replace(&mut self.in_exported_expr, true);
935 decl.decl.visit_mut_with(self);
936 self.in_exported_expr = old_in_exported_expr;
937 }
938
939 fn visit_mut_export_default_decl(&mut self, decl: &mut ExportDefaultDecl) {
940 let old_in_exported_expr = replace(&mut self.in_exported_expr, true);
941 let old_in_default_export_decl = replace(&mut self.in_default_export_decl, true);
942 self.rewrite_default_fn_expr_to_proxy_expr = None;
943 decl.decl.visit_mut_with(self);
944 self.in_exported_expr = old_in_exported_expr;
945 self.in_default_export_decl = old_in_default_export_decl;
946 }
947
948 fn visit_mut_export_default_expr(&mut self, expr: &mut ExportDefaultExpr) {
949 let old_in_exported_expr = replace(&mut self.in_exported_expr, true);
950 let old_in_default_export_decl = replace(&mut self.in_default_export_decl, true);
951 expr.expr.visit_mut_with(self);
952 self.in_exported_expr = old_in_exported_expr;
953 self.in_default_export_decl = old_in_default_export_decl;
954 }
955
956 fn visit_mut_fn_expr(&mut self, f: &mut FnExpr) {
957 let old_this_status = replace(&mut self.this_status, ThisStatus::Allowed);
958 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.clone();
959 if let Some(ident) = &f.ident {
960 self.arrow_or_fn_expr_ident = Some(ident.clone());
961 }
962 f.visit_mut_children_with(self);
963 self.this_status = old_this_status;
964 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
965 }
966
967 fn visit_mut_function(&mut self, f: &mut Function) {
968 let directive = self.get_directive_for_function(f.body.as_mut());
969 let declared_idents_until = self.declared_idents.len();
970 let old_names = take(&mut self.names);
971
972 if let Some(directive) = &directive {
973 self.this_status = ThisStatus::Forbidden {
974 directive: directive.clone(),
975 };
976 }
977
978 {
980 let old_in_module = replace(&mut self.in_module_level, false);
981 let should_track_names = directive.is_some() || self.should_track_names;
982 let old_should_track_names = replace(&mut self.should_track_names, should_track_names);
983 let old_in_exported_expr = replace(&mut self.in_exported_expr, false);
984 let old_in_default_export_decl = replace(&mut self.in_default_export_decl, false);
985 let old_fn_decl_ident = self.fn_decl_ident.take();
986 f.visit_mut_children_with(self);
987 self.in_module_level = old_in_module;
988 self.should_track_names = old_should_track_names;
989 self.in_exported_expr = old_in_exported_expr;
990 self.in_default_export_decl = old_in_default_export_decl;
991 self.fn_decl_ident = old_fn_decl_ident;
992 }
993
994 let mut child_names = take(&mut self.names);
995
996 if self.should_track_names {
997 self.names = [old_names, child_names.clone()].concat();
998 }
999
1000 if let Some(directive) = directive {
1001 let fn_name = self
1002 .fn_decl_ident
1003 .clone()
1004 .or(self.arrow_or_fn_expr_ident.clone());
1005
1006 if !f.is_async {
1007 emit_error(ServerActionsErrorKind::InlineSyncFunction {
1008 span: fn_name.as_ref().map_or(f.span, |ident| ident.span),
1009 directive,
1010 });
1011
1012 return;
1013 }
1014
1015 let has_errors = HANDLER.with(|handler| handler.has_errors());
1016
1017 if has_errors || !self.config.is_react_server_layer {
1019 return;
1020 }
1021
1022 if let Directive::UseCache { cache_kind } = directive {
1023 retain_names_from_declared_idents(
1026 &mut child_names,
1027 &self.declared_idents[..declared_idents_until],
1028 );
1029
1030 let new_expr = self.maybe_hoist_and_create_proxy_for_cache_function(
1031 child_names.clone(),
1032 self.fn_decl_ident
1033 .clone()
1034 .or(self.arrow_or_fn_expr_ident.clone()),
1035 cache_kind,
1036 f,
1037 );
1038
1039 if self.in_default_export_decl {
1040 self.rewrite_default_fn_expr_to_proxy_expr = Some(new_expr);
1045 } else if let Some(ident) = &self.fn_decl_ident {
1046 self.rewrite_fn_decl_to_proxy_decl = Some(VarDecl {
1048 span: DUMMY_SP,
1049 kind: VarDeclKind::Var,
1050 decls: vec![VarDeclarator {
1051 span: DUMMY_SP,
1052 name: Pat::Ident(ident.clone().into()),
1053 init: Some(new_expr),
1054 definite: false,
1055 }],
1056 ..Default::default()
1057 });
1058 } else {
1059 self.rewrite_expr_to_proxy_expr = Some(new_expr);
1060 }
1061 } else if !(matches!(self.file_directive, Some(Directive::UseServer))
1062 && self.in_exported_expr)
1063 {
1064 retain_names_from_declared_idents(
1067 &mut child_names,
1068 &self.declared_idents[..declared_idents_until],
1069 );
1070
1071 let new_expr = self.maybe_hoist_and_create_proxy_for_server_action_function(
1072 child_names,
1073 f,
1074 fn_name,
1075 );
1076
1077 if self.in_default_export_decl {
1078 self.rewrite_default_fn_expr_to_proxy_expr = Some(new_expr);
1083 } else if let Some(ident) = &self.fn_decl_ident {
1084 self.rewrite_fn_decl_to_proxy_decl = Some(VarDecl {
1087 span: DUMMY_SP,
1088 kind: VarDeclKind::Var,
1089 decls: vec![VarDeclarator {
1090 span: DUMMY_SP,
1091 name: Pat::Ident(ident.clone().into()),
1092 init: Some(new_expr),
1093 definite: false,
1094 }],
1095 ..Default::default()
1096 });
1097 } else {
1098 self.rewrite_expr_to_proxy_expr = Some(new_expr);
1099 }
1100 }
1101 }
1102 }
1103
1104 fn visit_mut_decl(&mut self, d: &mut Decl) {
1105 self.rewrite_fn_decl_to_proxy_decl = None;
1106 d.visit_mut_children_with(self);
1107
1108 if let Some(decl) = &self.rewrite_fn_decl_to_proxy_decl {
1109 *d = (*decl).clone().into();
1110 }
1111
1112 self.rewrite_fn_decl_to_proxy_decl = None;
1113 }
1114
1115 fn visit_mut_fn_decl(&mut self, f: &mut FnDecl) {
1116 let old_this_status = replace(&mut self.this_status, ThisStatus::Allowed);
1117 let old_in_exported_expr = self.in_exported_expr;
1118 if self.in_module_level && self.exported_local_ids.contains(&f.ident.to_id()) {
1119 self.in_exported_expr = true
1120 }
1121 let old_fn_decl_ident = self.fn_decl_ident.replace(f.ident.clone());
1122 f.visit_mut_children_with(self);
1123 self.this_status = old_this_status;
1124 self.in_exported_expr = old_in_exported_expr;
1125 self.fn_decl_ident = old_fn_decl_ident;
1126 }
1127
1128 fn visit_mut_arrow_expr(&mut self, a: &mut ArrowExpr) {
1129 let directive = self.get_directive_for_function(
1132 if let BlockStmtOrExpr::BlockStmt(block) = &mut *a.body {
1133 Some(block)
1134 } else {
1135 None
1136 },
1137 );
1138
1139 if let Some(directive) = &directive {
1140 self.this_status = ThisStatus::Forbidden {
1141 directive: directive.clone(),
1142 };
1143 }
1144
1145 let declared_idents_until = self.declared_idents.len();
1146 let old_names = take(&mut self.names);
1147
1148 {
1149 let old_in_module = replace(&mut self.in_module_level, false);
1151 let should_track_names = directive.is_some() || self.should_track_names;
1152 let old_should_track_names = replace(&mut self.should_track_names, should_track_names);
1153 let old_in_exported_expr = replace(&mut self.in_exported_expr, false);
1154 let old_in_default_export_decl = replace(&mut self.in_default_export_decl, false);
1155 {
1156 for n in &mut a.params {
1157 collect_idents_in_pat(n, &mut self.declared_idents);
1158 }
1159 }
1160 a.visit_mut_children_with(self);
1161 self.in_module_level = old_in_module;
1162 self.should_track_names = old_should_track_names;
1163 self.in_exported_expr = old_in_exported_expr;
1164 self.in_default_export_decl = old_in_default_export_decl;
1165 }
1166
1167 let mut child_names = take(&mut self.names);
1168
1169 if self.should_track_names {
1170 self.names = [old_names, child_names.clone()].concat();
1171 }
1172
1173 if let Some(directive) = directive {
1174 if !a.is_async {
1175 emit_error(ServerActionsErrorKind::InlineSyncFunction {
1176 span: self
1177 .arrow_or_fn_expr_ident
1178 .as_ref()
1179 .map_or(a.span, |ident| ident.span),
1180 directive,
1181 });
1182
1183 return;
1184 }
1185
1186 let has_errors = HANDLER.with(|handler| handler.has_errors());
1187
1188 if has_errors || !self.config.is_react_server_layer {
1191 return;
1192 }
1193
1194 retain_names_from_declared_idents(
1197 &mut child_names,
1198 &self.declared_idents[..declared_idents_until],
1199 );
1200
1201 if let Directive::UseCache { cache_kind } = directive {
1202 self.rewrite_expr_to_proxy_expr =
1203 Some(self.maybe_hoist_and_create_proxy_for_cache_arrow_expr(
1204 child_names,
1205 cache_kind,
1206 a,
1207 ));
1208 } else if !matches!(self.file_directive, Some(Directive::UseServer)) {
1209 self.rewrite_expr_to_proxy_expr = Some(
1210 self.maybe_hoist_and_create_proxy_for_server_action_arrow_expr(child_names, a),
1211 );
1212 }
1213 }
1214 }
1215
1216 fn visit_mut_module(&mut self, m: &mut Module) {
1217 self.start_pos = m.span.lo;
1218 m.visit_mut_children_with(self);
1219 }
1220
1221 fn visit_mut_stmt(&mut self, n: &mut Stmt) {
1222 n.visit_mut_children_with(self);
1223
1224 if self.in_module_level {
1225 return;
1226 }
1227
1228 collect_decl_idents_in_stmt(n, &mut self.declared_idents);
1231 }
1232
1233 fn visit_mut_param(&mut self, n: &mut Param) {
1234 n.visit_mut_children_with(self);
1235
1236 if self.in_module_level {
1237 return;
1238 }
1239
1240 collect_idents_in_pat(&n.pat, &mut self.declared_idents);
1241 }
1242
1243 fn visit_mut_prop_or_spread(&mut self, n: &mut PropOrSpread) {
1244 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.clone();
1245 let old_in_exported_expr = self.in_exported_expr;
1246
1247 match n {
1248 PropOrSpread::Prop(box Prop::KeyValue(KeyValueProp {
1249 key: PropName::Ident(ident_name),
1250 value: box Expr::Arrow(_) | box Expr::Fn(_),
1251 ..
1252 })) => {
1253 self.in_exported_expr = false;
1254 self.arrow_or_fn_expr_ident = Some(ident_name.clone().into());
1255 }
1256 PropOrSpread::Prop(box Prop::Method(MethodProp { key, .. })) => {
1257 let key = key.clone();
1258
1259 if let PropName::Ident(ident_name) = &key {
1260 self.arrow_or_fn_expr_ident = Some(ident_name.clone().into());
1261 }
1262
1263 let old_this_status = replace(&mut self.this_status, ThisStatus::Allowed);
1264 self.rewrite_expr_to_proxy_expr = None;
1265 self.in_exported_expr = false;
1266 n.visit_mut_children_with(self);
1267 self.in_exported_expr = old_in_exported_expr;
1268 self.this_status = old_this_status;
1269
1270 if let Some(expr) = self.rewrite_expr_to_proxy_expr.take() {
1271 *n = PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
1272 key,
1273 value: expr,
1274 })));
1275 }
1276
1277 return;
1278 }
1279 _ => {}
1280 }
1281
1282 if !self.in_module_level && self.should_track_names {
1283 if let PropOrSpread::Prop(box Prop::Shorthand(i)) = n {
1284 self.names.push(Name::from(&*i));
1285 self.should_track_names = false;
1286 n.visit_mut_children_with(self);
1287 self.should_track_names = true;
1288 return;
1289 }
1290 }
1291
1292 n.visit_mut_children_with(self);
1293 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
1294 self.in_exported_expr = old_in_exported_expr;
1295 }
1296
1297 fn visit_mut_class(&mut self, n: &mut Class) {
1298 let old_this_status = replace(&mut self.this_status, ThisStatus::Allowed);
1299 n.visit_mut_children_with(self);
1300 self.this_status = old_this_status;
1301 }
1302
1303 fn visit_mut_class_member(&mut self, n: &mut ClassMember) {
1304 if let ClassMember::Method(ClassMethod {
1305 is_abstract: false,
1306 is_static: true,
1307 kind: MethodKind::Method,
1308 key,
1309 span,
1310 accessibility: None | Some(Accessibility::Public),
1311 ..
1312 }) = n
1313 {
1314 let key = key.clone();
1315 let span = *span;
1316 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.clone();
1317
1318 if let PropName::Ident(ident_name) = &key {
1319 self.arrow_or_fn_expr_ident = Some(ident_name.clone().into());
1320 }
1321
1322 let old_this_status = replace(&mut self.this_status, ThisStatus::Allowed);
1323 self.rewrite_expr_to_proxy_expr = None;
1324 self.in_exported_expr = false;
1325 n.visit_mut_children_with(self);
1326 self.this_status = old_this_status;
1327 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
1328
1329 if let Some(expr) = self.rewrite_expr_to_proxy_expr.take() {
1330 *n = ClassMember::ClassProp(ClassProp {
1331 span,
1332 key,
1333 value: Some(expr),
1334 is_static: true,
1335 ..Default::default()
1336 });
1337 }
1338 } else {
1339 n.visit_mut_children_with(self);
1340 }
1341 }
1342
1343 fn visit_mut_class_method(&mut self, n: &mut ClassMethod) {
1344 if n.is_static {
1345 n.visit_mut_children_with(self);
1346 } else {
1347 let (is_action_fn, is_cache_fn) = has_body_directive(&n.function.body);
1348
1349 if is_action_fn {
1350 emit_error(
1351 ServerActionsErrorKind::InlineUseServerInClassInstanceMethod { span: n.span },
1352 );
1353 } else if is_cache_fn {
1354 emit_error(
1355 ServerActionsErrorKind::InlineUseCacheInClassInstanceMethod { span: n.span },
1356 );
1357 } else {
1358 n.visit_mut_children_with(self);
1359 }
1360 }
1361 }
1362
1363 fn visit_mut_call_expr(&mut self, n: &mut CallExpr) {
1364 if let Callee::Expr(box Expr::Ident(Ident { sym, .. })) = &mut n.callee {
1365 if sym == "jsxDEV" || sym == "_jsxDEV" {
1366 if n.args.len() > 4 {
1370 for arg in &mut n.args[0..4] {
1371 arg.visit_mut_with(self);
1372 }
1373 return;
1374 }
1375 }
1376 }
1377
1378 n.visit_mut_children_with(self);
1379 }
1380
1381 fn visit_mut_callee(&mut self, n: &mut Callee) {
1382 let old_in_callee = replace(&mut self.in_callee, true);
1383 n.visit_mut_children_with(self);
1384 self.in_callee = old_in_callee;
1385 }
1386
1387 fn visit_mut_expr(&mut self, n: &mut Expr) {
1388 if !self.in_module_level && self.should_track_names {
1389 if let Ok(mut name) = Name::try_from(&*n) {
1390 if self.in_callee {
1391 if !name.1.is_empty() {
1394 name.1.pop();
1395 }
1396 }
1397
1398 self.names.push(name);
1399 self.should_track_names = false;
1400 n.visit_mut_children_with(self);
1401 self.should_track_names = true;
1402 return;
1403 }
1404 }
1405
1406 self.rewrite_expr_to_proxy_expr = None;
1407 n.visit_mut_children_with(self);
1408 if let Some(expr) = self.rewrite_expr_to_proxy_expr.take() {
1409 *n = *expr;
1410 }
1411 }
1412
1413 fn visit_mut_module_items(&mut self, stmts: &mut Vec<ModuleItem>) {
1414 self.file_directive = self.get_directive_for_module(stmts);
1415
1416 let in_cache_file = matches!(self.file_directive, Some(Directive::UseCache { .. }));
1417 let in_action_file = matches!(self.file_directive, Some(Directive::UseServer));
1418
1419 if in_cache_file {
1420 for stmt in stmts.iter() {
1431 match stmt {
1432 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(export_default_expr)) => {
1433 if let Expr::Ident(ident) = &*export_default_expr.expr {
1434 self.exported_local_ids.insert(ident.to_id());
1435 }
1436 }
1437 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(named_export)) => {
1438 if named_export.src.is_none() {
1439 for spec in &named_export.specifiers {
1440 if let ExportSpecifier::Named(ExportNamedSpecifier {
1441 orig: ModuleExportName::Ident(ident),
1442 ..
1443 }) = spec
1444 {
1445 self.exported_local_ids.insert(ident.to_id());
1446 }
1447 }
1448 }
1449 }
1450 _ => {}
1451 }
1452 }
1453 }
1454
1455 let should_track_exports = self.file_directive.is_some();
1457
1458 let old_annotations = self.annotations.take();
1459 let mut new = Vec::with_capacity(stmts.len());
1460
1461 for mut stmt in stmts.take() {
1462 if should_track_exports {
1465 let mut disallowed_export_span = DUMMY_SP;
1466
1467 match &mut stmt {
1469 ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { decl, span })) => {
1470 match decl {
1471 Decl::Fn(f) => {
1472 let (is_action_fn, is_cache_fn) =
1475 has_body_directive(&f.function.body);
1476
1477 let ref_id = if is_action_fn {
1478 false
1479 } else if is_cache_fn {
1480 true
1481 } else {
1482 in_cache_file
1483 };
1484
1485 if !(is_cache_fn && self.config.is_react_server_layer) {
1491 self.exported_idents.push((
1492 f.ident.clone(),
1493 f.ident.sym.clone(),
1494 self.generate_server_reference_id(
1495 f.ident.sym.as_ref(),
1496 ref_id,
1497 Some(&f.function.params),
1498 ),
1499 ));
1500 }
1501 }
1502 Decl::Var(var) => {
1503 let mut idents: Vec<Ident> = Vec::new();
1505 collect_idents_in_var_decls(&var.decls, &mut idents);
1506
1507 for ident in &idents {
1508 self.exported_idents.push((
1509 ident.clone(),
1510 ident.sym.clone(),
1511 self.generate_server_reference_id(
1512 ident.sym.as_ref(),
1513 in_cache_file,
1514 None,
1515 ),
1516 ));
1517 }
1518
1519 for decl in &mut var.decls {
1520 if let Some(init) = &decl.init {
1521 if let Expr::Lit(_) = &**init {
1522 disallowed_export_span = *span;
1524 }
1525 }
1526 }
1527 }
1528 Decl::TsInterface(_) => {}
1529 Decl::TsTypeAlias(_) => {}
1530 Decl::TsEnum(_) => {}
1531 _ => {
1532 disallowed_export_span = *span;
1533 }
1534 }
1535 }
1536 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(named)) => {
1537 if !named.type_only {
1538 if named.src.is_some() {
1539 if named.specifiers.iter().any(|s| match s {
1540 ExportSpecifier::Namespace(_) | ExportSpecifier::Default(_) => {
1541 true
1542 }
1543 ExportSpecifier::Named(s) => !s.is_type_only,
1544 }) {
1545 disallowed_export_span = named.span;
1546 }
1547 } else {
1548 for spec in &mut named.specifiers {
1549 if let ExportSpecifier::Named(ExportNamedSpecifier {
1550 orig: ModuleExportName::Ident(ident),
1551 exported,
1552 is_type_only,
1553 ..
1554 }) = spec
1555 {
1556 if !*is_type_only {
1557 if let Some(export_name) = exported {
1558 if let ModuleExportName::Ident(Ident {
1559 sym, ..
1560 }) = export_name
1561 {
1562 self.exported_idents.push((
1564 ident.clone(),
1565 sym.clone(),
1566 self.generate_server_reference_id(
1567 sym.as_ref(),
1568 in_cache_file,
1569 None,
1570 ),
1571 ));
1572 } else if let ModuleExportName::Str(str) =
1573 export_name
1574 {
1575 self.exported_idents.push((
1577 ident.clone(),
1578 str.value.clone(),
1579 self.generate_server_reference_id(
1580 str.value.as_ref(),
1581 in_cache_file,
1582 None,
1583 ),
1584 ));
1585 }
1586 } else {
1587 self.exported_idents.push((
1589 ident.clone(),
1590 ident.sym.clone(),
1591 self.generate_server_reference_id(
1592 ident.sym.as_ref(),
1593 in_cache_file,
1594 None,
1595 ),
1596 ));
1597 }
1598 }
1599 } else {
1600 disallowed_export_span = named.span;
1601 }
1602 }
1603 }
1604 }
1605 }
1606 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(ExportDefaultDecl {
1607 decl,
1608 span,
1609 })) => match decl {
1610 DefaultDecl::Fn(f) => {
1611 let (is_action_fn, is_cache_fn) = has_body_directive(&f.function.body);
1612
1613 let is_cache = if is_action_fn {
1614 false
1615 } else if is_cache_fn {
1616 true
1617 } else {
1618 in_cache_file
1619 };
1620
1621 if !(is_cache_fn && self.config.is_react_server_layer) {
1627 let ref_id = self.generate_server_reference_id(
1628 "default",
1629 is_cache,
1630 Some(&f.function.params),
1631 );
1632
1633 if let Some(ident) = &f.ident {
1634 self.exported_idents.push((
1636 ident.clone(),
1637 atom!("default"),
1638 ref_id,
1639 ));
1640 } else {
1641 let span = f.function.span;
1644
1645 let new_ident = Ident::new(
1646 self.gen_action_ident(),
1647 span,
1648 self.private_ctxt,
1649 );
1650
1651 f.ident = Some(new_ident.clone());
1652
1653 self.exported_idents.push((
1654 new_ident.clone(),
1655 atom!("default"),
1656 ref_id,
1657 ));
1658
1659 assign_name_to_ident(
1660 &new_ident,
1661 "default",
1662 &mut self.extra_items,
1663 );
1664 }
1665 }
1666 }
1667 DefaultDecl::TsInterfaceDecl(_) => {}
1668 _ => {
1669 disallowed_export_span = *span;
1670 }
1671 },
1672 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(default_expr)) => {
1673 match &mut *default_expr.expr {
1674 Expr::Fn(_f) => {}
1675 Expr::Arrow(arrow) => {
1676 let span = arrow.span;
1679
1680 let (is_action_fn, is_cache_fn) =
1681 has_body_directive(&if let BlockStmtOrExpr::BlockStmt(block) =
1682 &*arrow.body
1683 {
1684 Some(block.clone())
1685 } else {
1686 None
1687 });
1688
1689 let is_cache = if is_action_fn {
1690 false
1691 } else if is_cache_fn {
1692 true
1693 } else {
1694 in_cache_file
1695 };
1696
1697 if !(is_cache_fn && self.config.is_react_server_layer) {
1703 let new_ident = Ident::new(
1704 self.gen_action_ident(),
1705 span,
1706 self.private_ctxt,
1707 );
1708
1709 self.exported_idents.push((
1710 new_ident.clone(),
1711 atom!("default"),
1712 self.generate_server_reference_id(
1713 "default",
1714 is_cache,
1715 Some(
1716 &arrow
1717 .params
1718 .iter()
1719 .map(|p| Param::from(p.clone()))
1720 .collect(),
1721 ),
1722 ),
1723 ));
1724
1725 create_var_declarator(&new_ident, &mut self.extra_items);
1726 assign_name_to_ident(
1727 &new_ident,
1728 "default",
1729 &mut self.extra_items,
1730 );
1731
1732 *default_expr.expr =
1733 assign_arrow_expr(&new_ident, Expr::Arrow(arrow.clone()));
1734 }
1735 }
1736 Expr::Ident(ident) => {
1737 self.exported_idents.push((
1739 ident.clone(),
1740 atom!("default"),
1741 self.generate_server_reference_id(
1742 "default",
1743 in_cache_file,
1744 None,
1745 ),
1746 ));
1747 }
1748 Expr::Call(call) => {
1749 let span = call.span;
1752
1753 let new_ident =
1754 Ident::new(self.gen_action_ident(), span, self.private_ctxt);
1755
1756 self.exported_idents.push((
1757 new_ident.clone(),
1758 atom!("default"),
1759 self.generate_server_reference_id(
1760 "default",
1761 in_cache_file,
1762 None,
1763 ),
1764 ));
1765
1766 create_var_declarator(&new_ident, &mut self.extra_items);
1767 assign_name_to_ident(&new_ident, "default", &mut self.extra_items);
1768
1769 *default_expr.expr =
1770 assign_arrow_expr(&new_ident, Expr::Call(call.clone()));
1771 }
1772 _ => {
1773 disallowed_export_span = default_expr.span;
1774 }
1775 }
1776 }
1777 ModuleItem::ModuleDecl(ModuleDecl::ExportAll(ExportAll {
1778 span,
1779 type_only,
1780 ..
1781 })) => {
1782 if !*type_only {
1783 disallowed_export_span = *span;
1784 }
1785 }
1786 _ => {}
1787 }
1788
1789 if disallowed_export_span != DUMMY_SP {
1790 emit_error(ServerActionsErrorKind::ExportedSyncFunction {
1791 span: disallowed_export_span,
1792 in_action_file,
1793 });
1794
1795 return;
1796 }
1797 }
1798
1799 stmt.visit_mut_with(self);
1800
1801 let mut new_stmt = stmt;
1802
1803 if let Some(expr) = &self.rewrite_default_fn_expr_to_proxy_expr {
1804 new_stmt =
1806 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(ExportDefaultExpr {
1807 span: DUMMY_SP,
1808 expr: expr.clone(),
1809 }));
1810 self.rewrite_default_fn_expr_to_proxy_expr = None;
1811 }
1812
1813 if self.config.is_react_server_layer || self.file_directive.is_none() {
1814 new.append(&mut self.hoisted_extra_items);
1815 new.push(new_stmt);
1816 new.extend(self.annotations.drain(..).map(ModuleItem::Stmt));
1817 new.append(&mut self.extra_items);
1818 }
1819 }
1820
1821 let mut actions = self.export_actions.take();
1822
1823 if in_action_file || in_cache_file && !self.config.is_react_server_layer {
1824 actions.extend(
1825 self.exported_idents
1826 .iter()
1827 .map(|e| (e.1.clone(), e.2.clone())),
1828 );
1829
1830 if !actions.is_empty() {
1831 self.has_action |= in_action_file;
1832 self.has_cache |= in_cache_file;
1833 }
1834 };
1835
1836 let actions = actions
1838 .into_iter()
1839 .map(|a| (a.1, a.0))
1840 .collect::<ActionsMap>();
1841
1842 let create_ref_ident = private_ident!("createServerReference");
1845 let call_server_ident = private_ident!("callServer");
1846 let find_source_map_url_ident = private_ident!("findSourceMapURL");
1847
1848 let client_layer_import = ((self.has_action || self.has_cache)
1849 && !self.config.is_react_server_layer)
1850 .then(|| {
1851 ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
1858 span: DUMMY_SP,
1859 specifiers: vec![
1860 ImportSpecifier::Named(ImportNamedSpecifier {
1861 span: DUMMY_SP,
1862 local: create_ref_ident.clone(),
1863 imported: None,
1864 is_type_only: false,
1865 }),
1866 ImportSpecifier::Named(ImportNamedSpecifier {
1867 span: DUMMY_SP,
1868 local: call_server_ident.clone(),
1869 imported: None,
1870 is_type_only: false,
1871 }),
1872 ImportSpecifier::Named(ImportNamedSpecifier {
1873 span: DUMMY_SP,
1874 local: find_source_map_url_ident.clone(),
1875 imported: None,
1876 is_type_only: false,
1877 }),
1878 ],
1879 src: Box::new(Str {
1880 span: DUMMY_SP,
1881 value: atom!("private-next-rsc-action-client-wrapper"),
1882 raw: None,
1883 }),
1884 type_only: false,
1885 with: None,
1886 phase: Default::default(),
1887 }))
1888 });
1889
1890 let mut client_layer_exports = FxIndexMap::default();
1891
1892 if should_track_exports {
1894 for (ident, export_name, ref_id) in self.exported_idents.iter() {
1895 if !self.config.is_react_server_layer {
1896 if export_name == "default" {
1897 let export_expr = ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(
1898 ExportDefaultExpr {
1899 span: DUMMY_SP,
1900 expr: Box::new(Expr::Call(CallExpr {
1901 span: if self.config.is_react_server_layer
1906 || self.config.is_development
1907 {
1908 self.comments.add_pure_comment(ident.span.lo);
1909 ident.span
1910 } else {
1911 PURE_SP
1912 },
1913 callee: Callee::Expr(Box::new(Expr::Ident(
1914 create_ref_ident.clone(),
1915 ))),
1916 args: vec![
1917 ref_id.clone().as_arg(),
1918 call_server_ident.clone().as_arg(),
1919 Expr::undefined(DUMMY_SP).as_arg(),
1920 find_source_map_url_ident.clone().as_arg(),
1921 "default".as_arg(),
1922 ],
1923 ..Default::default()
1924 })),
1925 },
1926 ));
1927 client_layer_exports
1928 .insert(atom!("default"), (export_expr, ref_id.clone()));
1929 } else {
1930 let export_expr =
1931 ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
1932 span: DUMMY_SP,
1933 decl: Decl::Var(Box::new(VarDecl {
1934 span: DUMMY_SP,
1935 kind: VarDeclKind::Var,
1936 decls: vec![VarDeclarator {
1937 span: DUMMY_SP,
1938 name: Pat::Ident(
1939 IdentName::new(
1940 export_name.clone(),
1941 if self.config.is_react_server_layer
1947 || self.config.is_development
1948 {
1949 ident.span
1950 } else {
1951 DUMMY_SP
1952 },
1953 )
1954 .into(),
1955 ),
1956 init: Some(Box::new(Expr::Call(CallExpr {
1957 span: PURE_SP,
1958 callee: Callee::Expr(Box::new(Expr::Ident(
1959 create_ref_ident.clone(),
1960 ))),
1961 args: vec![
1962 ref_id.clone().as_arg(),
1963 call_server_ident.clone().as_arg(),
1964 Expr::undefined(DUMMY_SP).as_arg(),
1965 find_source_map_url_ident.clone().as_arg(),
1966 export_name.clone().as_arg(),
1967 ],
1968 ..Default::default()
1969 }))),
1970 definite: false,
1971 }],
1972 ..Default::default()
1973 })),
1974 }));
1975 client_layer_exports
1976 .insert(export_name.clone(), (export_expr, ref_id.clone()));
1977 }
1978 } else if !in_cache_file {
1979 self.annotations.push(Stmt::Expr(ExprStmt {
1980 span: DUMMY_SP,
1981 expr: Box::new(annotate_ident_as_server_reference(
1982 ident.clone(),
1983 ref_id.clone(),
1984 ident.span,
1985 )),
1986 }));
1987 }
1988 }
1989
1990 if (self.has_action || self.has_cache) && self.config.is_react_server_layer {
1998 new.append(&mut self.extra_items);
1999
2000 if !in_cache_file && !self.exported_idents.is_empty() {
2002 let ensure_ident = private_ident!("ensureServerEntryExports");
2003 new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
2004 span: DUMMY_SP,
2005 specifiers: vec![ImportSpecifier::Named(ImportNamedSpecifier {
2006 span: DUMMY_SP,
2007 local: ensure_ident.clone(),
2008 imported: None,
2009 is_type_only: false,
2010 })],
2011 src: Box::new(Str {
2012 span: DUMMY_SP,
2013 value: atom!("private-next-rsc-action-validate"),
2014 raw: None,
2015 }),
2016 type_only: false,
2017 with: None,
2018 phase: Default::default(),
2019 })));
2020 new.push(ModuleItem::Stmt(Stmt::Expr(ExprStmt {
2021 span: DUMMY_SP,
2022 expr: Box::new(Expr::Call(CallExpr {
2023 span: DUMMY_SP,
2024 callee: Callee::Expr(Box::new(Expr::Ident(ensure_ident))),
2025 args: vec![ExprOrSpread {
2026 spread: None,
2027 expr: Box::new(Expr::Array(ArrayLit {
2028 span: DUMMY_SP,
2029 elems: self
2030 .exported_idents
2031 .iter()
2032 .map(|(ident, _, _)| {
2033 Some(ExprOrSpread {
2034 spread: None,
2035 expr: Box::new(Expr::Ident(ident.clone())),
2036 })
2037 })
2038 .collect(),
2039 })),
2040 }],
2041 ..Default::default()
2042 })),
2043 })));
2044 }
2045
2046 new.extend(self.annotations.drain(..).map(ModuleItem::Stmt));
2048 }
2049 }
2050
2051 if self.has_cache && self.config.is_react_server_layer {
2053 new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
2054 span: DUMMY_SP,
2055 specifiers: vec![ImportSpecifier::Named(ImportNamedSpecifier {
2056 span: DUMMY_SP,
2057 local: quote_ident!("$$cache__").into(),
2058 imported: Some(quote_ident!("cache").into()),
2059 is_type_only: false,
2060 })],
2061 src: Box::new(Str {
2062 span: DUMMY_SP,
2063 value: atom!("private-next-rsc-cache-wrapper"),
2064 raw: None,
2065 }),
2066 type_only: false,
2067 with: None,
2068 phase: Default::default(),
2069 })));
2070
2071 new.rotate_right(1);
2073 }
2074
2075 if (self.has_action || self.has_cache) && self.config.is_react_server_layer {
2076 new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
2079 span: DUMMY_SP,
2080 specifiers: vec![ImportSpecifier::Named(ImportNamedSpecifier {
2081 span: DUMMY_SP,
2082 local: quote_ident!("registerServerReference").into(),
2083 imported: None,
2084 is_type_only: false,
2085 })],
2086 src: Box::new(Str {
2087 span: DUMMY_SP,
2088 value: atom!("private-next-rsc-server-reference"),
2089 raw: None,
2090 }),
2091 type_only: false,
2092 with: None,
2093 phase: Default::default(),
2094 })));
2095
2096 new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
2100 span: DUMMY_SP,
2101 specifiers: vec![
2102 ImportSpecifier::Named(ImportNamedSpecifier {
2103 span: DUMMY_SP,
2104 local: quote_ident!("encryptActionBoundArgs").into(),
2105 imported: None,
2106 is_type_only: false,
2107 }),
2108 ImportSpecifier::Named(ImportNamedSpecifier {
2109 span: DUMMY_SP,
2110 local: quote_ident!("decryptActionBoundArgs").into(),
2111 imported: None,
2112 is_type_only: false,
2113 }),
2114 ],
2115 src: Box::new(Str {
2116 span: DUMMY_SP,
2117 value: atom!("private-next-rsc-action-encryption"),
2118 raw: None,
2119 }),
2120 type_only: false,
2121 with: None,
2122 phase: Default::default(),
2123 })));
2124
2125 new.rotate_right(2);
2127 }
2128
2129 if self.has_action || self.has_cache {
2130 if self.config.is_react_server_layer {
2131 self.comments.add_leading(
2133 self.start_pos,
2134 Comment {
2135 span: DUMMY_SP,
2136 kind: CommentKind::Block,
2137 text: generate_server_actions_comment(
2138 &actions,
2139 match self.mode {
2140 ServerActionsMode::Webpack => None,
2141 ServerActionsMode::Turbopack => Some(("", "")),
2142 },
2143 )
2144 .into(),
2145 },
2146 );
2147 } else {
2148 match self.mode {
2149 ServerActionsMode::Webpack => {
2150 self.comments.add_leading(
2151 self.start_pos,
2152 Comment {
2153 span: DUMMY_SP,
2154 kind: CommentKind::Block,
2155 text: generate_server_actions_comment(&actions, None).into(),
2156 },
2157 );
2158 new.push(client_layer_import.unwrap());
2159 new.rotate_right(1);
2160 new.extend(client_layer_exports.into_iter().map(|(_, (v, _))| v));
2161 }
2162 ServerActionsMode::Turbopack => {
2163 new.push(ModuleItem::Stmt(Stmt::Expr(ExprStmt {
2164 expr: Box::new(Expr::Lit(Lit::Str(
2165 atom!("use turbopack no side effects").into(),
2166 ))),
2167 span: DUMMY_SP,
2168 })));
2169 new.rotate_right(1);
2170 for (export, (stmt, ref_id)) in client_layer_exports {
2171 new.push(ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(
2172 NamedExport {
2173 specifiers: vec![ExportSpecifier::Named(
2174 ExportNamedSpecifier {
2175 span: DUMMY_SP,
2176 orig: ModuleExportName::Ident(export.clone().into()),
2177 exported: None,
2178 is_type_only: false,
2179 },
2180 )],
2181 src: Some(Box::new(
2182 program_to_data_url(
2183 &self.file_name,
2184 &self.cm,
2185 vec![
2186 ModuleItem::Stmt(Stmt::Expr(ExprStmt {
2187 expr: Box::new(Expr::Lit(Lit::Str(
2188 atom!("use turbopack no side effects")
2189 .into(),
2190 ))),
2191 span: DUMMY_SP,
2192 })),
2193 client_layer_import.clone().unwrap(),
2194 stmt,
2195 ],
2196 Comment {
2197 span: DUMMY_SP,
2198 kind: CommentKind::Block,
2199 text: generate_server_actions_comment(
2200 &std::iter::once((ref_id, export)).collect(),
2201 Some((
2202 &self.file_name,
2203 self.file_query.as_ref().map_or("", |v| v),
2204 )),
2205 )
2206 .into(),
2207 },
2208 )
2209 .into(),
2210 )),
2211 span: DUMMY_SP,
2212 type_only: false,
2213 with: None,
2214 },
2215 )));
2216 }
2217 }
2218 }
2219 }
2220 }
2221
2222 *stmts = new;
2223
2224 self.annotations = old_annotations;
2225 }
2226
2227 fn visit_mut_stmts(&mut self, stmts: &mut Vec<Stmt>) {
2228 let old_annotations = self.annotations.take();
2229
2230 let mut new = Vec::with_capacity(stmts.len());
2231 for mut stmt in stmts.take() {
2232 stmt.visit_mut_with(self);
2233
2234 new.push(stmt);
2235 new.append(&mut self.annotations);
2236 }
2237
2238 *stmts = new;
2239
2240 self.annotations = old_annotations;
2241 }
2242
2243 fn visit_mut_jsx_attr(&mut self, attr: &mut JSXAttr) {
2244 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.take();
2245
2246 if let (Some(JSXAttrValue::JSXExprContainer(container)), JSXAttrName::Ident(ident_name)) =
2247 (&attr.value, &attr.name)
2248 {
2249 match &container.expr {
2250 JSXExpr::Expr(box Expr::Arrow(_)) | JSXExpr::Expr(box Expr::Fn(_)) => {
2251 self.arrow_or_fn_expr_ident = Some(ident_name.clone().into());
2252 }
2253 _ => {}
2254 }
2255 }
2256
2257 attr.visit_mut_children_with(self);
2258 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
2259 }
2260
2261 fn visit_mut_var_declarator(&mut self, var_declarator: &mut VarDeclarator) {
2262 let old_in_exported_expr = self.in_exported_expr;
2263 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.take();
2264
2265 if let (Pat::Ident(ident), Some(box Expr::Arrow(_) | box Expr::Fn(_))) =
2266 (&var_declarator.name, &var_declarator.init)
2267 {
2268 if self.in_module_level && self.exported_local_ids.contains(&ident.to_id()) {
2269 self.in_exported_expr = true
2270 }
2271
2272 self.arrow_or_fn_expr_ident = Some(ident.id.clone());
2273 }
2274
2275 var_declarator.visit_mut_children_with(self);
2276
2277 self.in_exported_expr = old_in_exported_expr;
2278 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
2279 }
2280
2281 fn visit_mut_assign_expr(&mut self, assign_expr: &mut AssignExpr) {
2282 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.clone();
2283
2284 if let (
2285 AssignTarget::Simple(SimpleAssignTarget::Ident(ident)),
2286 box Expr::Arrow(_) | box Expr::Fn(_),
2287 ) = (&assign_expr.left, &assign_expr.right)
2288 {
2289 if !ident.id.to_id().0.starts_with("$$RSC_SERVER_") {
2291 self.arrow_or_fn_expr_ident = Some(ident.id.clone());
2292 }
2293 }
2294
2295 assign_expr.visit_mut_children_with(self);
2296 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
2297 }
2298
2299 fn visit_mut_this_expr(&mut self, n: &mut ThisExpr) {
2300 if let ThisStatus::Forbidden { directive } = &self.this_status {
2301 emit_error(ServerActionsErrorKind::ForbiddenExpression {
2302 span: n.span,
2303 expr: "this".into(),
2304 directive: directive.clone(),
2305 });
2306 }
2307 }
2308
2309 fn visit_mut_super(&mut self, n: &mut Super) {
2310 if let ThisStatus::Forbidden { directive } = &self.this_status {
2311 emit_error(ServerActionsErrorKind::ForbiddenExpression {
2312 span: n.span,
2313 expr: "super".into(),
2314 directive: directive.clone(),
2315 });
2316 }
2317 }
2318
2319 fn visit_mut_ident(&mut self, n: &mut Ident) {
2320 if n.sym == *"arguments" {
2321 if let ThisStatus::Forbidden { directive } = &self.this_status {
2322 emit_error(ServerActionsErrorKind::ForbiddenExpression {
2323 span: n.span,
2324 expr: "arguments".into(),
2325 directive: directive.clone(),
2326 });
2327 }
2328 }
2329 }
2330
2331 noop_visit_mut_type!();
2332}
2333
2334fn retain_names_from_declared_idents(
2335 child_names: &mut Vec<Name>,
2336 current_declared_idents: &[Ident],
2337) {
2338 let mut retained_names = Vec::new();
2340
2341 for name in child_names.iter() {
2342 let mut should_retain = true;
2343
2344 for another_name in child_names.iter() {
2350 if name != another_name
2351 && name.0 == another_name.0
2352 && name.1.len() >= another_name.1.len()
2353 {
2354 let mut is_prefix = true;
2355 for i in 0..another_name.1.len() {
2356 if name.1[i] != another_name.1[i] {
2357 is_prefix = false;
2358 break;
2359 }
2360 }
2361 if is_prefix {
2362 should_retain = false;
2363 break;
2364 }
2365 }
2366 }
2367
2368 if should_retain
2369 && current_declared_idents
2370 .iter()
2371 .any(|ident| ident.to_id() == name.0)
2372 && !retained_names.contains(name)
2373 {
2374 retained_names.push(name.clone());
2375 }
2376 }
2377
2378 *child_names = retained_names;
2380}
2381
2382fn wrap_cache_expr(expr: Box<Expr>, name: &str, id: &str, bound_args_len: usize) -> Box<Expr> {
2383 Box::new(Expr::Call(CallExpr {
2385 span: DUMMY_SP,
2386 callee: quote_ident!("$$cache__").as_callee(),
2387 args: vec![
2388 ExprOrSpread {
2389 spread: None,
2390 expr: Box::new(name.into()),
2391 },
2392 ExprOrSpread {
2393 spread: None,
2394 expr: Box::new(id.into()),
2395 },
2396 Number::from(bound_args_len).as_arg(),
2397 expr.as_arg(),
2398 ],
2399 ..Default::default()
2400 }))
2401}
2402
2403fn create_var_declarator(ident: &Ident, extra_items: &mut Vec<ModuleItem>) {
2404 extra_items.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl {
2406 span: DUMMY_SP,
2407 kind: VarDeclKind::Var,
2408 decls: vec![VarDeclarator {
2409 span: DUMMY_SP,
2410 name: ident.clone().into(),
2411 init: None,
2412 definite: Default::default(),
2413 }],
2414 ..Default::default()
2415 })))));
2416}
2417
2418fn assign_name_to_ident(ident: &Ident, name: &str, extra_items: &mut Vec<ModuleItem>) {
2419 extra_items.push(quote!(
2421 "Object[\"defineProperty\"]($action, \"name\", { value: $name, writable: false });"
2429 as ModuleItem,
2430 action: Ident = ident.clone(),
2431 name: Expr = name.into(),
2432 ));
2433}
2434
2435fn assign_arrow_expr(ident: &Ident, expr: Expr) -> Expr {
2436 if let Expr::Paren(_paren) = &expr {
2437 expr
2438 } else {
2439 Expr::Paren(ParenExpr {
2441 span: DUMMY_SP,
2442 expr: Box::new(Expr::Assign(AssignExpr {
2443 span: DUMMY_SP,
2444 left: ident.clone().into(),
2445 op: op!("="),
2446 right: Box::new(expr),
2447 })),
2448 })
2449 }
2450}
2451
2452fn annotate_ident_as_server_reference(ident: Ident, action_id: Atom, original_span: Span) -> Expr {
2453 Expr::Call(CallExpr {
2455 span: original_span,
2456 callee: quote_ident!("registerServerReference").as_callee(),
2457 args: vec![
2458 ExprOrSpread {
2459 spread: None,
2460 expr: Box::new(Expr::Ident(ident)),
2461 },
2462 ExprOrSpread {
2463 spread: None,
2464 expr: Box::new(action_id.clone().into()),
2465 },
2466 ExprOrSpread {
2467 spread: None,
2468 expr: Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))),
2469 },
2470 ],
2471 ..Default::default()
2472 })
2473}
2474
2475fn bind_args_to_ident(ident: Ident, bound: Vec<Option<ExprOrSpread>>, action_id: Atom) -> Expr {
2476 Expr::Call(CallExpr {
2478 span: DUMMY_SP,
2479 callee: Expr::Member(MemberExpr {
2480 span: DUMMY_SP,
2481 obj: Box::new(ident.into()),
2482 prop: MemberProp::Ident(quote_ident!("bind")),
2483 })
2484 .as_callee(),
2485 args: vec![
2486 ExprOrSpread {
2487 spread: None,
2488 expr: Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))),
2489 },
2490 ExprOrSpread {
2491 spread: None,
2492 expr: Box::new(Expr::Call(CallExpr {
2493 span: DUMMY_SP,
2494 callee: quote_ident!("encryptActionBoundArgs").as_callee(),
2495 args: std::iter::once(ExprOrSpread {
2496 spread: None,
2497 expr: Box::new(action_id.into()),
2498 })
2499 .chain(bound.into_iter().flatten())
2500 .collect(),
2501 ..Default::default()
2502 })),
2503 },
2504 ],
2505 ..Default::default()
2506 })
2507}
2508
2509fn detect_similar_strings(a: &str, b: &str) -> bool {
2524 let mut a = a.chars().collect::<Vec<char>>();
2525 let mut b = b.chars().collect::<Vec<char>>();
2526
2527 if a.len() < b.len() {
2528 (a, b) = (b, a);
2529 }
2530
2531 if a.len() == b.len() {
2532 let mut diff = 0;
2534 for i in 0..a.len() {
2535 if a[i] != b[i] {
2536 diff += 1;
2537 if diff > 2 {
2538 return false;
2539 }
2540 }
2541 }
2542
2543 diff != 0
2545 } else {
2546 if a.len() - b.len() > 1 {
2547 return false;
2548 }
2549
2550 for i in 0..b.len() {
2552 if a[i] != b[i] {
2553 return a[i + 1..] == b[i..];
2559 }
2560 }
2561
2562 true
2564 }
2565}
2566
2567fn has_body_directive(maybe_body: &Option<BlockStmt>) -> (bool, bool) {
2572 let mut is_action_fn = false;
2573 let mut is_cache_fn = false;
2574
2575 if let Some(body) = maybe_body {
2576 for stmt in body.stmts.iter() {
2577 match stmt {
2578 Stmt::Expr(ExprStmt {
2579 expr: box Expr::Lit(Lit::Str(Str { value, .. })),
2580 ..
2581 }) => {
2582 if value == "use server" {
2583 is_action_fn = true;
2584 break;
2585 } else if value == "use cache" || value.starts_with("use cache: ") {
2586 is_cache_fn = true;
2587 break;
2588 }
2589 }
2590 _ => break,
2591 }
2592 }
2593 }
2594
2595 (is_action_fn, is_cache_fn)
2596}
2597
2598fn collect_idents_in_array_pat(elems: &[Option<Pat>], idents: &mut Vec<Ident>) {
2599 for elem in elems.iter().flatten() {
2600 match elem {
2601 Pat::Ident(ident) => {
2602 idents.push(ident.id.clone());
2603 }
2604 Pat::Array(array) => {
2605 collect_idents_in_array_pat(&array.elems, idents);
2606 }
2607 Pat::Object(object) => {
2608 collect_idents_in_object_pat(&object.props, idents);
2609 }
2610 Pat::Rest(rest) => {
2611 if let Pat::Ident(ident) = &*rest.arg {
2612 idents.push(ident.id.clone());
2613 }
2614 }
2615 Pat::Assign(AssignPat { left, .. }) => {
2616 collect_idents_in_pat(left, idents);
2617 }
2618 Pat::Expr(..) | Pat::Invalid(..) => {}
2619 }
2620 }
2621}
2622
2623fn collect_idents_in_object_pat(props: &[ObjectPatProp], idents: &mut Vec<Ident>) {
2624 for prop in props {
2625 match prop {
2626 ObjectPatProp::KeyValue(KeyValuePatProp { key, value }) => {
2627 if let PropName::Ident(ident) = key {
2628 idents.push(Ident::new(
2629 ident.sym.clone(),
2630 ident.span,
2631 SyntaxContext::empty(),
2632 ));
2633 }
2634
2635 match &**value {
2636 Pat::Ident(ident) => {
2637 idents.push(ident.id.clone());
2638 }
2639 Pat::Array(array) => {
2640 collect_idents_in_array_pat(&array.elems, idents);
2641 }
2642 Pat::Object(object) => {
2643 collect_idents_in_object_pat(&object.props, idents);
2644 }
2645 _ => {}
2646 }
2647 }
2648 ObjectPatProp::Assign(AssignPatProp { key, .. }) => {
2649 idents.push(key.id.clone());
2650 }
2651 ObjectPatProp::Rest(RestPat { arg, .. }) => {
2652 if let Pat::Ident(ident) = &**arg {
2653 idents.push(ident.id.clone());
2654 }
2655 }
2656 }
2657 }
2658}
2659
2660fn collect_idents_in_var_decls(decls: &[VarDeclarator], idents: &mut Vec<Ident>) {
2661 for decl in decls {
2662 collect_idents_in_pat(&decl.name, idents);
2663 }
2664}
2665
2666fn collect_idents_in_pat(pat: &Pat, idents: &mut Vec<Ident>) {
2667 match pat {
2668 Pat::Ident(ident) => {
2669 idents.push(ident.id.clone());
2670 }
2671 Pat::Array(array) => {
2672 collect_idents_in_array_pat(&array.elems, idents);
2673 }
2674 Pat::Object(object) => {
2675 collect_idents_in_object_pat(&object.props, idents);
2676 }
2677 Pat::Assign(AssignPat { left, .. }) => {
2678 collect_idents_in_pat(left, idents);
2679 }
2680 Pat::Rest(RestPat { arg, .. }) => {
2681 if let Pat::Ident(ident) = &**arg {
2682 idents.push(ident.id.clone());
2683 }
2684 }
2685 Pat::Expr(..) | Pat::Invalid(..) => {}
2686 }
2687}
2688
2689fn collect_decl_idents_in_stmt(stmt: &Stmt, idents: &mut Vec<Ident>) {
2690 if let Stmt::Decl(decl) = stmt {
2691 match decl {
2692 Decl::Var(var) => {
2693 collect_idents_in_var_decls(&var.decls, idents);
2694 }
2695 Decl::Fn(fn_decl) => {
2696 idents.push(fn_decl.ident.clone());
2697 }
2698 _ => {}
2699 }
2700 }
2701}
2702
2703struct DirectiveVisitor<'a> {
2704 config: &'a Config,
2705 location: DirectiveLocation,
2706 directive: Option<Directive>,
2707 has_file_directive: bool,
2708 is_allowed_position: bool,
2709 use_cache_telemetry_tracker: Rc<RefCell<FxHashMap<String, usize>>>,
2710}
2711
2712impl DirectiveVisitor<'_> {
2713 fn visit_stmt(&mut self, stmt: &Stmt) -> bool {
2718 let in_fn_body = matches!(self.location, DirectiveLocation::FunctionBody);
2719 let allow_inline = self.config.is_react_server_layer || self.has_file_directive;
2720
2721 match stmt {
2722 Stmt::Expr(ExprStmt {
2723 expr: box Expr::Lit(Lit::Str(Str { value, span, .. })),
2724 ..
2725 }) => {
2726 if value == "use server" {
2727 if in_fn_body && !allow_inline {
2728 emit_error(ServerActionsErrorKind::InlineUseServerInClientComponent {
2729 span: *span,
2730 })
2731 } else if let Some(Directive::UseCache { .. }) = self.directive {
2732 emit_error(ServerActionsErrorKind::MultipleDirectives {
2733 span: *span,
2734 location: self.location.clone(),
2735 });
2736 } else if self.is_allowed_position {
2737 self.directive = Some(Directive::UseServer);
2738
2739 return true;
2740 } else {
2741 emit_error(ServerActionsErrorKind::MisplacedDirective {
2742 span: *span,
2743 directive: value.to_string(),
2744 location: self.location.clone(),
2745 });
2746 }
2747 } else if detect_similar_strings(value, "use server") {
2748 emit_error(ServerActionsErrorKind::MisspelledDirective {
2750 span: *span,
2751 directive: value.to_string(),
2752 expected_directive: "use server".to_string(),
2753 });
2754 } else if value == "use action" {
2755 emit_error(ServerActionsErrorKind::MisspelledDirective {
2756 span: *span,
2757 directive: value.to_string(),
2758 expected_directive: "use server".to_string(),
2759 });
2760 } else
2761 if let Some(rest) = value.strip_prefix("use cache") {
2763 if in_fn_body && !allow_inline {
2766 emit_error(ServerActionsErrorKind::InlineUseCacheInClientComponent {
2767 span: *span,
2768 })
2769 } else if let Some(Directive::UseServer) = self.directive {
2770 emit_error(ServerActionsErrorKind::MultipleDirectives {
2771 span: *span,
2772 location: self.location.clone(),
2773 });
2774 } else if self.is_allowed_position {
2775 if !self.config.use_cache_enabled {
2776 emit_error(ServerActionsErrorKind::UseCacheWithoutCacheComponents {
2777 span: *span,
2778 directive: value.to_string(),
2779 });
2780 }
2781
2782 if rest.is_empty() {
2783 self.directive = Some(Directive::UseCache {
2784 cache_kind: rcstr!("default"),
2785 });
2786
2787 self.increment_cache_usage_counter("default");
2788
2789 return true;
2790 }
2791
2792 if rest.starts_with(": ") {
2793 let cache_kind = RcStr::from(rest.split_at(": ".len()).1);
2794
2795 if !cache_kind.is_empty() {
2796 if !self.config.cache_kinds.contains(&cache_kind) {
2797 emit_error(ServerActionsErrorKind::UnknownCacheKind {
2798 span: *span,
2799 cache_kind: cache_kind.clone(),
2800 });
2801 }
2802
2803 self.increment_cache_usage_counter(&cache_kind);
2804 self.directive = Some(Directive::UseCache { cache_kind });
2805
2806 return true;
2807 }
2808 }
2809
2810 let expected_directive = if let Some(colon_pos) = rest.find(':') {
2813 let kind = rest[colon_pos + 1..].trim();
2814
2815 if kind.is_empty() {
2816 "use cache: <cache-kind>".to_string()
2817 } else {
2818 format!("use cache: {kind}")
2819 }
2820 } else {
2821 let kind = rest.trim();
2822
2823 if kind.is_empty() {
2824 "use cache".to_string()
2825 } else {
2826 format!("use cache: {kind}")
2827 }
2828 };
2829
2830 emit_error(ServerActionsErrorKind::MisspelledDirective {
2831 span: *span,
2832 directive: value.to_string(),
2833 expected_directive,
2834 });
2835
2836 return true;
2837 } else {
2838 emit_error(ServerActionsErrorKind::MisplacedDirective {
2839 span: *span,
2840 directive: value.to_string(),
2841 location: self.location.clone(),
2842 });
2843 }
2844 } else {
2845 if detect_similar_strings(value, "use cache") {
2847 emit_error(ServerActionsErrorKind::MisspelledDirective {
2848 span: *span,
2849 directive: value.to_string(),
2850 expected_directive: "use cache".to_string(),
2851 });
2852 }
2853 }
2854 }
2855 Stmt::Expr(ExprStmt {
2856 expr:
2857 box Expr::Paren(ParenExpr {
2858 expr: box Expr::Lit(Lit::Str(Str { value, .. })),
2859 ..
2860 }),
2861 span,
2862 ..
2863 }) => {
2864 if value == "use server" || detect_similar_strings(value, "use server") {
2866 if self.is_allowed_position {
2867 emit_error(ServerActionsErrorKind::WrappedDirective {
2868 span: *span,
2869 directive: "use server".to_string(),
2870 });
2871 } else {
2872 emit_error(ServerActionsErrorKind::MisplacedWrappedDirective {
2873 span: *span,
2874 directive: "use server".to_string(),
2875 location: self.location.clone(),
2876 });
2877 }
2878 } else if value == "use cache" || detect_similar_strings(value, "use cache") {
2879 if self.is_allowed_position {
2880 emit_error(ServerActionsErrorKind::WrappedDirective {
2881 span: *span,
2882 directive: "use cache".to_string(),
2883 });
2884 } else {
2885 emit_error(ServerActionsErrorKind::MisplacedWrappedDirective {
2886 span: *span,
2887 directive: "use cache".to_string(),
2888 location: self.location.clone(),
2889 });
2890 }
2891 }
2892 }
2893 _ => {
2894 self.is_allowed_position = false;
2896 }
2897 };
2898
2899 false
2900 }
2901
2902 fn increment_cache_usage_counter(&mut self, cache_kind: &str) {
2904 let mut tracker_map = RefCell::borrow_mut(&self.use_cache_telemetry_tracker);
2905 let entry = tracker_map.entry(cache_kind.to_string());
2906 match entry {
2907 hash_map::Entry::Occupied(mut occupied) => {
2908 *occupied.get_mut() += 1;
2909 }
2910 hash_map::Entry::Vacant(vacant) => {
2911 vacant.insert(1);
2912 }
2913 }
2914 }
2915}
2916
2917pub(crate) struct ClosureReplacer<'a> {
2918 used_ids: &'a [Name],
2919 private_ctxt: SyntaxContext,
2920}
2921
2922impl ClosureReplacer<'_> {
2923 fn index(&self, e: &Expr) -> Option<usize> {
2924 let name = Name::try_from(e).ok()?;
2925 self.used_ids.iter().position(|used_id| *used_id == name)
2926 }
2927}
2928
2929impl VisitMut for ClosureReplacer<'_> {
2930 fn visit_mut_expr(&mut self, e: &mut Expr) {
2931 e.visit_mut_children_with(self);
2932
2933 if let Some(index) = self.index(e) {
2934 *e = Expr::Ident(Ident::new(
2935 format!("$$ACTION_ARG_{index}").into(),
2937 DUMMY_SP,
2938 self.private_ctxt,
2939 ));
2940 }
2941 }
2942
2943 fn visit_mut_prop_or_spread(&mut self, n: &mut PropOrSpread) {
2944 n.visit_mut_children_with(self);
2945
2946 if let PropOrSpread::Prop(box Prop::Shorthand(i)) = n {
2947 let name = Name::from(&*i);
2948 if let Some(index) = self.used_ids.iter().position(|used_id| *used_id == name) {
2949 *n = PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
2950 key: PropName::Ident(i.clone().into()),
2951 value: Box::new(Expr::Ident(Ident::new(
2952 format!("$$ACTION_ARG_{index}").into(),
2954 DUMMY_SP,
2955 self.private_ctxt,
2956 ))),
2957 })));
2958 }
2959 }
2960 }
2961
2962 noop_visit_mut_type!();
2963}
2964
2965#[derive(Debug, Clone, PartialEq, Eq)]
2966struct NamePart {
2967 prop: Atom,
2968 is_member: bool,
2969 optional: bool,
2970}
2971
2972#[derive(Debug, Clone, PartialEq, Eq)]
2973struct Name(Id, Vec<NamePart>);
2974
2975impl From<&'_ Ident> for Name {
2976 fn from(value: &Ident) -> Self {
2977 Name(value.to_id(), vec![])
2978 }
2979}
2980
2981impl TryFrom<&'_ Expr> for Name {
2982 type Error = ();
2983
2984 fn try_from(value: &Expr) -> Result<Self, Self::Error> {
2985 match value {
2986 Expr::Ident(i) => Ok(Name(i.to_id(), vec![])),
2987 Expr::Member(e) => e.try_into(),
2988 Expr::OptChain(e) => e.try_into(),
2989 _ => Err(()),
2990 }
2991 }
2992}
2993
2994impl TryFrom<&'_ MemberExpr> for Name {
2995 type Error = ();
2996
2997 fn try_from(value: &MemberExpr) -> Result<Self, Self::Error> {
2998 match &value.prop {
2999 MemberProp::Ident(prop) => {
3000 let mut obj: Name = value.obj.as_ref().try_into()?;
3001 obj.1.push(NamePart {
3002 prop: prop.sym.clone(),
3003 is_member: true,
3004 optional: false,
3005 });
3006 Ok(obj)
3007 }
3008 _ => Err(()),
3009 }
3010 }
3011}
3012
3013impl TryFrom<&'_ OptChainExpr> for Name {
3014 type Error = ();
3015
3016 fn try_from(value: &OptChainExpr) -> Result<Self, Self::Error> {
3017 match &*value.base {
3018 OptChainBase::Member(m) => match &m.prop {
3019 MemberProp::Ident(prop) => {
3020 let mut obj: Name = m.obj.as_ref().try_into()?;
3021 obj.1.push(NamePart {
3022 prop: prop.sym.clone(),
3023 is_member: false,
3024 optional: value.optional,
3025 });
3026 Ok(obj)
3027 }
3028 _ => Err(()),
3029 },
3030 OptChainBase::Call(_) => Err(()),
3031 }
3032 }
3033}
3034
3035impl From<Name> for Box<Expr> {
3036 fn from(value: Name) -> Self {
3037 let mut expr = Box::new(Expr::Ident(value.0.into()));
3038
3039 for NamePart {
3040 prop,
3041 is_member,
3042 optional,
3043 } in value.1.into_iter()
3044 {
3045 if is_member {
3046 expr = Box::new(Expr::Member(MemberExpr {
3047 span: DUMMY_SP,
3048 obj: expr,
3049 prop: MemberProp::Ident(IdentName::new(prop, DUMMY_SP)),
3050 }));
3051 } else {
3052 expr = Box::new(Expr::OptChain(OptChainExpr {
3053 span: DUMMY_SP,
3054 base: Box::new(OptChainBase::Member(MemberExpr {
3055 span: DUMMY_SP,
3056 obj: expr,
3057 prop: MemberProp::Ident(IdentName::new(prop, DUMMY_SP)),
3058 })),
3059 optional,
3060 }));
3061 }
3062 }
3063
3064 expr
3065 }
3066}
3067
3068fn emit_error(error_kind: ServerActionsErrorKind) {
3069 let (span, msg) = match error_kind {
3070 ServerActionsErrorKind::ExportedSyncFunction {
3071 span,
3072 in_action_file,
3073 } => (
3074 span,
3075 formatdoc! {
3076 r#"
3077 Only async functions are allowed to be exported in a {directive} file.
3078 "#,
3079 directive = if in_action_file {
3080 "\"use server\""
3081 } else {
3082 "\"use cache\""
3083 }
3084 },
3085 ),
3086 ServerActionsErrorKind::ForbiddenExpression {
3087 span,
3088 expr,
3089 directive,
3090 } => (
3091 span,
3092 formatdoc! {
3093 r#"
3094 {subject} cannot use `{expr}`.
3095 "#,
3096 subject = if let Directive::UseServer = directive {
3097 "Server Actions"
3098 } else {
3099 "\"use cache\" functions"
3100 }
3101 },
3102 ),
3103 ServerActionsErrorKind::InlineUseCacheInClassInstanceMethod { span } => (
3104 span,
3105 formatdoc! {
3106 r#"
3107 It is not allowed to define inline "use cache" annotated class instance methods.
3108 To define cached functions, use functions, object method properties, or static class methods instead.
3109 "#
3110 },
3111 ),
3112 ServerActionsErrorKind::InlineUseCacheInClientComponent { span } => (
3113 span,
3114 formatdoc! {
3115 r#"
3116 It is not allowed to define inline "use cache" annotated functions in Client Components.
3117 To use "use cache" functions in a Client Component, you can either export them from a separate file with "use cache" or "use server" at the top, or pass them down through props from a Server Component.
3118 "#
3119 },
3120 ),
3121 ServerActionsErrorKind::InlineUseServerInClassInstanceMethod { span } => (
3122 span,
3123 formatdoc! {
3124 r#"
3125 It is not allowed to define inline "use server" annotated class instance methods.
3126 To define Server Actions, use functions, object method properties, or static class methods instead.
3127 "#
3128 },
3129 ),
3130 ServerActionsErrorKind::InlineUseServerInClientComponent { span } => (
3131 span,
3132 formatdoc! {
3133 r#"
3134 It is not allowed to define inline "use server" annotated Server Actions in Client Components.
3135 To use Server Actions in a Client Component, you can either export them from a separate file with "use server" at the top, or pass them down through props from a Server Component.
3136
3137 Read more: https://nextjs.org/docs/app/api-reference/directives/use-server#using-server-functions-in-a-client-component
3138 "#
3139 },
3140 ),
3141 ServerActionsErrorKind::InlineSyncFunction { span, directive } => (
3142 span,
3143 formatdoc! {
3144 r#"
3145 {subject} must be async functions.
3146 "#,
3147 subject = if let Directive::UseServer = directive {
3148 "Server Actions"
3149 } else {
3150 "\"use cache\" functions"
3151 }
3152 },
3153 ),
3154 ServerActionsErrorKind::MisplacedDirective {
3155 span,
3156 directive,
3157 location,
3158 } => (
3159 span,
3160 formatdoc! {
3161 r#"
3162 The "{directive}" directive must be at the top of the {location}.
3163 "#,
3164 location = match location {
3165 DirectiveLocation::Module => "file",
3166 DirectiveLocation::FunctionBody => "function body",
3167 }
3168 },
3169 ),
3170 ServerActionsErrorKind::MisplacedWrappedDirective {
3171 span,
3172 directive,
3173 location,
3174 } => (
3175 span,
3176 formatdoc! {
3177 r#"
3178 The "{directive}" directive must be at the top of the {location}, and cannot be wrapped in parentheses.
3179 "#,
3180 location = match location {
3181 DirectiveLocation::Module => "file",
3182 DirectiveLocation::FunctionBody => "function body",
3183 }
3184 },
3185 ),
3186 ServerActionsErrorKind::MisspelledDirective {
3187 span,
3188 directive,
3189 expected_directive,
3190 } => (
3191 span,
3192 formatdoc! {
3193 r#"
3194 Did you mean "{expected_directive}"? "{directive}" is not a supported directive name."
3195 "#
3196 },
3197 ),
3198 ServerActionsErrorKind::MultipleDirectives { span, location } => (
3199 span,
3200 formatdoc! {
3201 r#"
3202 Conflicting directives "use server" and "use cache" found in the same {location}. You cannot place both directives at the top of a {location}. Please remove one of them.
3203 "#,
3204 location = match location {
3205 DirectiveLocation::Module => "file",
3206 DirectiveLocation::FunctionBody => "function body",
3207 }
3208 },
3209 ),
3210 ServerActionsErrorKind::UnknownCacheKind { span, cache_kind } => (
3211 span,
3212 formatdoc! {
3213 r#"
3214 Unknown cache kind "{cache_kind}". Please configure a cache handler for this kind in the `cacheHandlers` object in your Next.js config.
3215 "#
3216 },
3217 ),
3218 ServerActionsErrorKind::UseCacheWithoutCacheComponents { span, directive } => (
3219 span,
3220 formatdoc! {
3221 r#"
3222 To use "{directive}", please enable the feature flag `cacheComponents` in your Next.js config.
3223
3224 Read more: https://nextjs.org/docs/canary/app/api-reference/directives/use-cache#usage
3225 "#
3226 },
3227 ),
3228 ServerActionsErrorKind::WrappedDirective { span, directive } => (
3229 span,
3230 formatdoc! {
3231 r#"
3232 The "{directive}" directive cannot be wrapped in parentheses.
3233 "#
3234 },
3235 ),
3236 };
3237
3238 HANDLER.with(|handler| handler.struct_span_err(span, &msg).emit());
3239}
3240
3241fn program_to_data_url(
3242 file_name: &str,
3243 cm: &Arc<SourceMap>,
3244 body: Vec<ModuleItem>,
3245 prepend_comment: Comment,
3246) -> String {
3247 let module_span = Span::dummy_with_cmt();
3248 let comments = SingleThreadedComments::default();
3249 comments.add_leading(module_span.lo, prepend_comment);
3250
3251 let program = &Program::Module(Module {
3252 span: module_span,
3253 body,
3254 shebang: None,
3255 });
3256
3257 let mut output = vec![];
3258 let mut mappings = vec![];
3259 let mut emitter = Emitter {
3260 cfg: codegen::Config::default().with_minify(true),
3261 cm: cm.clone(),
3262 wr: Box::new(JsWriter::new(
3263 cm.clone(),
3264 " ",
3265 &mut output,
3266 Some(&mut mappings),
3267 )),
3268 comments: Some(&comments),
3269 };
3270
3271 emitter.emit_program(program).unwrap();
3272 drop(emitter);
3273
3274 pub struct InlineSourcesContentConfig<'a> {
3275 folder_path: Option<&'a Path>,
3276 }
3277 impl SourceMapGenConfig for InlineSourcesContentConfig<'_> {
3280 fn file_name_to_source(&self, file: &FileName) -> String {
3281 let FileName::Custom(file) = file else {
3282 return file.to_string();
3284 };
3285 let Some(folder_path) = &self.folder_path else {
3286 return file.to_string();
3287 };
3288
3289 if let Some(rel_path) = diff_paths(file, folder_path) {
3290 format!("./{}", rel_path.display())
3291 } else {
3292 file.to_string()
3293 }
3294 }
3295
3296 fn inline_sources_content(&self, _f: &FileName) -> bool {
3297 true
3298 }
3299 }
3300
3301 let map = cm.build_source_map(
3302 &mappings,
3303 None,
3304 InlineSourcesContentConfig {
3305 folder_path: PathBuf::from(format!("[project]/{file_name}")).parent(),
3306 },
3307 );
3308 let map = {
3309 if map.get_token_count() > 0 {
3310 let mut buf = vec![];
3311 map.to_writer(&mut buf)
3312 .expect("failed to generate sourcemap");
3313 Some(buf)
3314 } else {
3315 None
3316 }
3317 };
3318
3319 let mut output = String::from_utf8(output).expect("codegen generated non-utf8 output");
3320 if let Some(map) = map {
3321 output.extend(
3322 format!(
3323 "\n//# sourceMappingURL=data:application/json;base64,{}",
3324 Base64Display::new(&map, &BASE64_STANDARD)
3325 )
3326 .chars(),
3327 );
3328 }
3329 format!("data:text/javascript,{}", urlencoding::encode(&output))
3330}