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;
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 UseCacheWithoutExperimentalFlag {
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 gen_ref_ident(&mut self) -> Atom {
356 let id: Atom = format!("$$RSC_SERVER_REF_{0}", self.reference_index).into();
357 self.reference_index += 1;
358 id
359 }
360
361 fn create_bound_action_args_array_pat(&mut self, arg_len: usize) -> Pat {
362 Pat::Array(ArrayPat {
363 span: DUMMY_SP,
364 elems: (0..arg_len)
365 .map(|i| {
366 Some(Pat::Ident(
367 Ident::new(
368 format!("$$ACTION_ARG_{i}").into(),
369 DUMMY_SP,
370 self.private_ctxt,
371 )
372 .into(),
373 ))
374 })
375 .collect(),
376 optional: false,
377 type_ann: None,
378 })
379 }
380
381 fn get_directive_for_function(
384 &mut self,
385 maybe_body: Option<&mut BlockStmt>,
386 ) -> Option<Directive> {
387 let mut directive: Option<Directive> = None;
388
389 if let Some(body) = maybe_body {
392 let directive_visitor = &mut DirectiveVisitor {
393 config: &self.config,
394 directive: None,
395 has_file_directive: self.file_directive.is_some(),
396 is_allowed_position: true,
397 location: DirectiveLocation::FunctionBody,
398 use_cache_telemetry_tracker: self.use_cache_telemetry_tracker.clone(),
399 };
400
401 body.stmts.retain(|stmt| {
402 let has_directive = directive_visitor.visit_stmt(stmt);
403
404 !has_directive
405 });
406
407 directive = directive_visitor.directive.clone();
408 }
409
410 if self.in_exported_expr && directive.is_none() && self.file_directive.is_some() {
412 return self.file_directive.clone();
413 }
414
415 directive
416 }
417
418 fn get_directive_for_module(&mut self, stmts: &mut Vec<ModuleItem>) -> Option<Directive> {
419 let directive_visitor = &mut DirectiveVisitor {
420 config: &self.config,
421 directive: None,
422 has_file_directive: false,
423 is_allowed_position: true,
424 location: DirectiveLocation::Module,
425 use_cache_telemetry_tracker: self.use_cache_telemetry_tracker.clone(),
426 };
427
428 stmts.retain(|item| {
429 if let ModuleItem::Stmt(stmt) = item {
430 let has_directive = directive_visitor.visit_stmt(stmt);
431
432 !has_directive
433 } else {
434 directive_visitor.is_allowed_position = false;
435 true
436 }
437 });
438
439 directive_visitor.directive.clone()
440 }
441
442 fn maybe_hoist_and_create_proxy_for_server_action_arrow_expr(
443 &mut self,
444 ids_from_closure: Vec<Name>,
445 arrow: &mut ArrowExpr,
446 ) -> Box<Expr> {
447 let mut new_params: Vec<Param> = vec![];
448
449 if !ids_from_closure.is_empty() {
450 new_params.push(Param {
452 span: DUMMY_SP,
453 decorators: vec![],
454 pat: Pat::Ident(IdentName::new("$$ACTION_CLOSURE_BOUND".into(), DUMMY_SP).into()),
455 });
456 }
457
458 for p in arrow.params.iter() {
459 new_params.push(Param::from(p.clone()));
460 }
461
462 let action_name = self.gen_action_ident();
463 let action_ident = Ident::new(action_name.clone(), arrow.span, self.private_ctxt);
464 let action_id = self.generate_server_reference_id(&action_name, false, Some(&new_params));
465
466 self.has_action = true;
467 self.export_actions
468 .push((action_name.clone(), action_id.clone()));
469
470 let register_action_expr = bind_args_to_ref_expr(
471 annotate_ident_as_server_reference(action_ident.clone(), action_id.clone(), arrow.span),
472 ids_from_closure
473 .iter()
474 .cloned()
475 .map(|id| Some(id.as_arg()))
476 .collect(),
477 action_id.clone(),
478 );
479
480 if let BlockStmtOrExpr::BlockStmt(block) = &mut *arrow.body {
481 block.visit_mut_with(&mut ClosureReplacer {
482 used_ids: &ids_from_closure,
483 private_ctxt: self.private_ctxt,
484 });
485 }
486
487 let mut new_body: BlockStmtOrExpr = *arrow.body.clone();
488
489 if !ids_from_closure.is_empty() {
490 let decryption_decl = VarDecl {
494 span: DUMMY_SP,
495 kind: VarDeclKind::Var,
496 declare: false,
497 decls: vec![VarDeclarator {
498 span: DUMMY_SP,
499 name: self.create_bound_action_args_array_pat(ids_from_closure.len()),
500 init: Some(Box::new(Expr::Await(AwaitExpr {
501 span: DUMMY_SP,
502 arg: Box::new(Expr::Call(CallExpr {
503 span: DUMMY_SP,
504 callee: quote_ident!("decryptActionBoundArgs").as_callee(),
505 args: vec![
506 action_id.as_arg(),
507 quote_ident!("$$ACTION_CLOSURE_BOUND").as_arg(),
508 ],
509 ..Default::default()
510 })),
511 }))),
512 definite: Default::default(),
513 }],
514 ..Default::default()
515 };
516
517 match &mut new_body {
518 BlockStmtOrExpr::BlockStmt(body) => {
519 body.stmts.insert(0, decryption_decl.into());
520 }
521 BlockStmtOrExpr::Expr(body_expr) => {
522 new_body = BlockStmtOrExpr::BlockStmt(BlockStmt {
523 span: DUMMY_SP,
524 stmts: vec![
525 decryption_decl.into(),
526 Stmt::Return(ReturnStmt {
527 span: DUMMY_SP,
528 arg: Some(body_expr.take()),
529 }),
530 ],
531 ..Default::default()
532 });
533 }
534 }
535 }
536
537 self.hoisted_extra_items
540 .push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
541 span: DUMMY_SP,
542 decl: VarDecl {
543 kind: VarDeclKind::Const,
544 span: DUMMY_SP,
545 decls: vec![VarDeclarator {
546 span: DUMMY_SP,
547 name: Pat::Ident(action_ident.clone().into()),
548 definite: false,
549 init: Some(Box::new(Expr::Fn(FnExpr {
550 ident: self.arrow_or_fn_expr_ident.clone(),
551 function: Box::new(Function {
552 params: new_params,
553 body: match new_body {
554 BlockStmtOrExpr::BlockStmt(body) => Some(body),
555 BlockStmtOrExpr::Expr(expr) => Some(BlockStmt {
556 span: DUMMY_SP,
557 stmts: vec![Stmt::Return(ReturnStmt {
558 span: DUMMY_SP,
559 arg: Some(expr),
560 })],
561 ..Default::default()
562 }),
563 },
564 decorators: vec![],
565 span: DUMMY_SP,
566 is_generator: false,
567 is_async: true,
568 ..Default::default()
569 }),
570 }))),
571 }],
572 declare: Default::default(),
573 ctxt: self.private_ctxt,
574 }
575 .into(),
576 })));
577
578 Box::new(register_action_expr.clone())
579 }
580
581 fn maybe_hoist_and_create_proxy_for_server_action_function(
582 &mut self,
583 ids_from_closure: Vec<Name>,
584 function: &mut Function,
585 fn_name: Option<Ident>,
586 ) -> Box<Expr> {
587 let mut new_params: Vec<Param> = vec![];
588
589 if !ids_from_closure.is_empty() {
590 new_params.push(Param {
592 span: DUMMY_SP,
593 decorators: vec![],
594 pat: Pat::Ident(IdentName::new("$$ACTION_CLOSURE_BOUND".into(), DUMMY_SP).into()),
595 });
596 }
597
598 new_params.append(&mut function.params);
599
600 let action_name: Atom = self.gen_action_ident();
601 let mut action_ident = Ident::new(action_name.clone(), function.span, self.private_ctxt);
602 if action_ident.span.lo == self.start_pos {
603 action_ident.span = Span::dummy_with_cmt();
604 }
605
606 let action_id = self.generate_server_reference_id(&action_name, false, Some(&new_params));
607
608 self.has_action = true;
609 self.export_actions
610 .push((action_name.clone(), action_id.clone()));
611
612 let register_action_expr = bind_args_to_ref_expr(
613 annotate_ident_as_server_reference(
614 action_ident.clone(),
615 action_id.clone(),
616 function.span,
617 ),
618 ids_from_closure
619 .iter()
620 .cloned()
621 .map(|id| Some(id.as_arg()))
622 .collect(),
623 action_id.clone(),
624 );
625
626 function.body.visit_mut_with(&mut ClosureReplacer {
627 used_ids: &ids_from_closure,
628 private_ctxt: self.private_ctxt,
629 });
630
631 let mut new_body: Option<BlockStmt> = function.body.clone();
632
633 if !ids_from_closure.is_empty() {
634 let decryption_decl = VarDecl {
638 span: DUMMY_SP,
639 kind: VarDeclKind::Var,
640 decls: vec![VarDeclarator {
641 span: DUMMY_SP,
642 name: self.create_bound_action_args_array_pat(ids_from_closure.len()),
643 init: Some(Box::new(Expr::Await(AwaitExpr {
644 span: DUMMY_SP,
645 arg: Box::new(Expr::Call(CallExpr {
646 span: DUMMY_SP,
647 callee: quote_ident!("decryptActionBoundArgs").as_callee(),
648 args: vec![
649 action_id.as_arg(),
650 quote_ident!("$$ACTION_CLOSURE_BOUND").as_arg(),
651 ],
652 ..Default::default()
653 })),
654 }))),
655 definite: Default::default(),
656 }],
657 ..Default::default()
658 };
659
660 if let Some(body) = &mut new_body {
661 body.stmts.insert(0, decryption_decl.into());
662 } else {
663 new_body = Some(BlockStmt {
664 span: DUMMY_SP,
665 stmts: vec![decryption_decl.into()],
666 ..Default::default()
667 });
668 }
669 }
670
671 self.hoisted_extra_items
674 .push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
675 span: DUMMY_SP,
676 decl: VarDecl {
677 kind: VarDeclKind::Const,
678 span: DUMMY_SP,
679 decls: vec![VarDeclarator {
680 span: DUMMY_SP, name: Pat::Ident(action_ident.clone().into()),
682 definite: false,
683 init: Some(Box::new(Expr::Fn(FnExpr {
684 ident: fn_name,
685 function: Box::new(Function {
686 params: new_params,
687 body: new_body,
688 ..function.take()
689 }),
690 }))),
691 }],
692 declare: Default::default(),
693 ctxt: self.private_ctxt,
694 }
695 .into(),
696 })));
697
698 Box::new(register_action_expr)
699 }
700
701 fn maybe_hoist_and_create_proxy_for_cache_arrow_expr(
702 &mut self,
703 ids_from_closure: Vec<Name>,
704 cache_kind: RcStr,
705 arrow: &mut ArrowExpr,
706 ) -> Box<Expr> {
707 let mut new_params: Vec<Param> = vec![];
708
709 if !ids_from_closure.is_empty() {
713 new_params.push(Param {
714 span: DUMMY_SP,
715 decorators: vec![],
716 pat: self.create_bound_action_args_array_pat(ids_from_closure.len()),
717 });
718 }
719
720 for p in arrow.params.iter() {
721 new_params.push(Param::from(p.clone()));
722 }
723
724 let cache_name: Atom = self.gen_cache_ident();
725 let cache_ident = private_ident!(Span::dummy_with_cmt(), cache_name.clone());
726 let export_name: Atom = cache_name;
727
728 let reference_id = self.generate_server_reference_id(&export_name, true, Some(&new_params));
729
730 self.has_cache = true;
731 self.export_actions
732 .push((export_name.clone(), reference_id.clone()));
733
734 if let BlockStmtOrExpr::BlockStmt(block) = &mut *arrow.body {
735 block.visit_mut_with(&mut ClosureReplacer {
736 used_ids: &ids_from_closure,
737 private_ctxt: self.private_ctxt,
738 });
739 }
740
741 self.hoisted_extra_items
744 .push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
745 span: DUMMY_SP,
746 decl: VarDecl {
747 span: DUMMY_SP,
748 kind: VarDeclKind::Var,
749 decls: vec![VarDeclarator {
750 span: arrow.span,
751 name: Pat::Ident(cache_ident.clone().into()),
752 init: Some(wrap_cache_expr(
753 Box::new(Expr::Fn(FnExpr {
754 ident: None,
755 function: Box::new(Function {
756 params: new_params,
757 body: match *arrow.body.take() {
758 BlockStmtOrExpr::BlockStmt(body) => Some(body),
759 BlockStmtOrExpr::Expr(expr) => Some(BlockStmt {
760 span: DUMMY_SP,
761 stmts: vec![Stmt::Return(ReturnStmt {
762 span: DUMMY_SP,
763 arg: Some(expr),
764 })],
765 ..Default::default()
766 }),
767 },
768 decorators: vec![],
769 span: DUMMY_SP,
770 is_generator: false,
771 is_async: true,
772 ..Default::default()
773 }),
774 })),
775 &cache_kind,
776 &reference_id,
777 ids_from_closure.len(),
778 )),
779 definite: false,
780 }],
781 ..Default::default()
782 }
783 .into(),
784 })));
785
786 if let Some(Ident { sym, .. }) = &self.arrow_or_fn_expr_ident {
787 assign_name_to_ident(&cache_ident, sym.as_str(), &mut self.hoisted_extra_items);
788 }
789
790 let bound_args: Vec<_> = ids_from_closure
791 .iter()
792 .cloned()
793 .map(|id| Some(id.as_arg()))
794 .collect();
795
796 let register_action_expr = annotate_ident_as_server_reference(
797 cache_ident.clone(),
798 reference_id.clone(),
799 arrow.span,
800 );
801
802 if !bound_args.is_empty() {
806 let ref_ident = private_ident!(self.gen_ref_ident());
807
808 let ref_decl = VarDecl {
809 span: DUMMY_SP,
810 kind: VarDeclKind::Var,
811 decls: vec![VarDeclarator {
812 span: DUMMY_SP,
813 name: Pat::Ident(ref_ident.clone().into()),
814 init: Some(Box::new(register_action_expr.clone())),
815 definite: false,
816 }],
817 ..Default::default()
818 };
819
820 self.extra_items
822 .push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(ref_decl)))));
823
824 Box::new(bind_args_to_ref_expr(
825 Expr::Ident(ref_ident.clone()),
826 bound_args,
827 reference_id.clone(),
828 ))
829 } else {
830 Box::new(register_action_expr)
831 }
832 }
833
834 fn maybe_hoist_and_create_proxy_for_cache_function(
835 &mut self,
836 ids_from_closure: Vec<Name>,
837 fn_name: Option<Ident>,
838 cache_kind: RcStr,
839 function: &mut Function,
840 ) -> Box<Expr> {
841 let mut new_params: Vec<Param> = vec![];
842
843 if !ids_from_closure.is_empty() {
847 new_params.push(Param {
848 span: DUMMY_SP,
849 decorators: vec![],
850 pat: self.create_bound_action_args_array_pat(ids_from_closure.len()),
851 });
852 }
853
854 for p in function.params.iter() {
855 new_params.push(p.clone());
856 }
857
858 let cache_name: Atom = self.gen_cache_ident();
859 let cache_ident = private_ident!(Span::dummy_with_cmt(), cache_name.clone());
860
861 let reference_id = self.generate_server_reference_id(&cache_name, true, Some(&new_params));
862
863 self.has_cache = true;
864 self.export_actions
865 .push((cache_name.clone(), reference_id.clone()));
866
867 let register_action_expr = annotate_ident_as_server_reference(
868 cache_ident.clone(),
869 reference_id.clone(),
870 function.span,
871 );
872
873 function.body.visit_mut_with(&mut ClosureReplacer {
874 used_ids: &ids_from_closure,
875 private_ctxt: self.private_ctxt,
876 });
877
878 self.hoisted_extra_items
880 .push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
881 span: DUMMY_SP,
882 decl: VarDecl {
883 span: DUMMY_SP,
884 kind: VarDeclKind::Var,
885 decls: vec![VarDeclarator {
886 span: function.span,
887 name: Pat::Ident(cache_ident.clone().into()),
888 init: Some(wrap_cache_expr(
889 Box::new(Expr::Fn(FnExpr {
890 ident: fn_name.clone(),
891 function: Box::new(Function {
892 params: new_params,
893 ..function.take()
894 }),
895 })),
896 &cache_kind,
897 &reference_id,
898 ids_from_closure.len(),
899 )),
900 definite: false,
901 }],
902 ..Default::default()
903 }
904 .into(),
905 })));
906
907 if let Some(Ident { sym, .. }) = fn_name {
908 assign_name_to_ident(&cache_ident, sym.as_str(), &mut self.hoisted_extra_items);
909 } else if self.in_default_export_decl {
910 assign_name_to_ident(&cache_ident, "default", &mut self.hoisted_extra_items);
911 }
912
913 let bound_args: Vec<_> = ids_from_closure
914 .iter()
915 .cloned()
916 .map(|id| Some(id.as_arg()))
917 .collect();
918
919 if !bound_args.is_empty() {
923 let ref_ident = private_ident!(self.gen_ref_ident());
924
925 let ref_decl = VarDecl {
926 span: DUMMY_SP,
927 kind: VarDeclKind::Var,
928 decls: vec![VarDeclarator {
929 span: DUMMY_SP,
930 name: Pat::Ident(ref_ident.clone().into()),
931 init: Some(Box::new(register_action_expr.clone())),
932 definite: false,
933 }],
934 ..Default::default()
935 };
936
937 self.extra_items
939 .push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(ref_decl)))));
940
941 Box::new(bind_args_to_ref_expr(
942 Expr::Ident(ref_ident.clone()),
943 bound_args,
944 reference_id.clone(),
945 ))
946 } else {
947 Box::new(register_action_expr)
948 }
949 }
950}
951
952impl<C: Comments> VisitMut for ServerActions<C> {
953 fn visit_mut_export_decl(&mut self, decl: &mut ExportDecl) {
954 let old_in_exported_expr = replace(&mut self.in_exported_expr, true);
955 decl.decl.visit_mut_with(self);
956 self.in_exported_expr = old_in_exported_expr;
957 }
958
959 fn visit_mut_export_default_decl(&mut self, decl: &mut ExportDefaultDecl) {
960 let old_in_exported_expr = replace(&mut self.in_exported_expr, true);
961 let old_in_default_export_decl = replace(&mut self.in_default_export_decl, true);
962 self.rewrite_default_fn_expr_to_proxy_expr = None;
963 decl.decl.visit_mut_with(self);
964 self.in_exported_expr = old_in_exported_expr;
965 self.in_default_export_decl = old_in_default_export_decl;
966 }
967
968 fn visit_mut_export_default_expr(&mut self, expr: &mut ExportDefaultExpr) {
969 let old_in_exported_expr = replace(&mut self.in_exported_expr, true);
970 let old_in_default_export_decl = replace(&mut self.in_default_export_decl, true);
971 expr.expr.visit_mut_with(self);
972 self.in_exported_expr = old_in_exported_expr;
973 self.in_default_export_decl = old_in_default_export_decl;
974 }
975
976 fn visit_mut_fn_expr(&mut self, f: &mut FnExpr) {
977 let old_this_status = replace(&mut self.this_status, ThisStatus::Allowed);
978 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.clone();
979 if let Some(ident) = &f.ident {
980 self.arrow_or_fn_expr_ident = Some(ident.clone());
981 }
982 f.visit_mut_children_with(self);
983 self.this_status = old_this_status;
984 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
985 }
986
987 fn visit_mut_function(&mut self, f: &mut Function) {
988 let directive = self.get_directive_for_function(f.body.as_mut());
989 let declared_idents_until = self.declared_idents.len();
990 let old_names = take(&mut self.names);
991
992 if let Some(directive) = &directive {
993 self.this_status = ThisStatus::Forbidden {
994 directive: directive.clone(),
995 };
996 }
997
998 {
1000 let old_in_module = replace(&mut self.in_module_level, false);
1001 let should_track_names = directive.is_some() || self.should_track_names;
1002 let old_should_track_names = replace(&mut self.should_track_names, should_track_names);
1003 let old_in_exported_expr = replace(&mut self.in_exported_expr, false);
1004 let old_in_default_export_decl = replace(&mut self.in_default_export_decl, false);
1005 let old_fn_decl_ident = self.fn_decl_ident.take();
1006 f.visit_mut_children_with(self);
1007 self.in_module_level = old_in_module;
1008 self.should_track_names = old_should_track_names;
1009 self.in_exported_expr = old_in_exported_expr;
1010 self.in_default_export_decl = old_in_default_export_decl;
1011 self.fn_decl_ident = old_fn_decl_ident;
1012 }
1013
1014 if let Some(directive) = directive {
1015 if !f.is_async {
1016 emit_error(ServerActionsErrorKind::InlineSyncFunction {
1017 span: f.span,
1018 directive,
1019 });
1020
1021 return;
1022 }
1023
1024 let has_errors = HANDLER.with(|handler| handler.has_errors());
1025
1026 if has_errors || !self.config.is_react_server_layer {
1028 return;
1029 }
1030
1031 let mut child_names = take(&mut self.names);
1032
1033 if self.should_track_names {
1034 self.names = [old_names, child_names.clone()].concat();
1035 }
1036
1037 if let Directive::UseCache { cache_kind } = directive {
1038 retain_names_from_declared_idents(
1041 &mut child_names,
1042 &self.declared_idents[..declared_idents_until],
1043 );
1044
1045 let new_expr = self.maybe_hoist_and_create_proxy_for_cache_function(
1046 child_names.clone(),
1047 self.fn_decl_ident
1048 .clone()
1049 .or(self.arrow_or_fn_expr_ident.clone()),
1050 cache_kind,
1051 f,
1052 );
1053
1054 if self.in_default_export_decl {
1055 self.rewrite_default_fn_expr_to_proxy_expr = Some(new_expr);
1060 } else if let Some(ident) = &self.fn_decl_ident {
1061 self.rewrite_fn_decl_to_proxy_decl = Some(VarDecl {
1063 span: DUMMY_SP,
1064 kind: VarDeclKind::Var,
1065 decls: vec![VarDeclarator {
1066 span: DUMMY_SP,
1067 name: Pat::Ident(ident.clone().into()),
1068 init: Some(new_expr),
1069 definite: false,
1070 }],
1071 ..Default::default()
1072 });
1073 } else {
1074 self.rewrite_expr_to_proxy_expr = Some(new_expr);
1075 }
1076 } else if !(matches!(self.file_directive, Some(Directive::UseServer))
1077 && self.in_exported_expr)
1078 {
1079 retain_names_from_declared_idents(
1082 &mut child_names,
1083 &self.declared_idents[..declared_idents_until],
1084 );
1085
1086 let new_expr = self.maybe_hoist_and_create_proxy_for_server_action_function(
1087 child_names,
1088 f,
1089 self.fn_decl_ident
1090 .clone()
1091 .or(self.arrow_or_fn_expr_ident.clone()),
1092 );
1093
1094 if self.in_default_export_decl {
1095 self.rewrite_default_fn_expr_to_proxy_expr = Some(new_expr);
1100 } else if let Some(ident) = &self.fn_decl_ident {
1101 self.rewrite_fn_decl_to_proxy_decl = Some(VarDecl {
1104 span: DUMMY_SP,
1105 kind: VarDeclKind::Var,
1106 decls: vec![VarDeclarator {
1107 span: DUMMY_SP,
1108 name: Pat::Ident(ident.clone().into()),
1109 init: Some(new_expr),
1110 definite: false,
1111 }],
1112 ..Default::default()
1113 });
1114 } else {
1115 self.rewrite_expr_to_proxy_expr = Some(new_expr);
1116 }
1117 }
1118 }
1119 }
1120
1121 fn visit_mut_decl(&mut self, d: &mut Decl) {
1122 self.rewrite_fn_decl_to_proxy_decl = None;
1123 d.visit_mut_children_with(self);
1124
1125 if let Some(decl) = &self.rewrite_fn_decl_to_proxy_decl {
1126 *d = (*decl).clone().into();
1127 }
1128
1129 self.rewrite_fn_decl_to_proxy_decl = None;
1130 }
1131
1132 fn visit_mut_fn_decl(&mut self, f: &mut FnDecl) {
1133 let old_this_status = replace(&mut self.this_status, ThisStatus::Allowed);
1134 let old_in_exported_expr = self.in_exported_expr;
1135 if self.in_module_level && self.exported_local_ids.contains(&f.ident.to_id()) {
1136 self.in_exported_expr = true
1137 }
1138 let old_fn_decl_ident = self.fn_decl_ident.replace(f.ident.clone());
1139 f.visit_mut_children_with(self);
1140 self.this_status = old_this_status;
1141 self.in_exported_expr = old_in_exported_expr;
1142 self.fn_decl_ident = old_fn_decl_ident;
1143 }
1144
1145 fn visit_mut_arrow_expr(&mut self, a: &mut ArrowExpr) {
1146 let directive = self.get_directive_for_function(
1149 if let BlockStmtOrExpr::BlockStmt(block) = &mut *a.body {
1150 Some(block)
1151 } else {
1152 None
1153 },
1154 );
1155
1156 if let Some(directive) = &directive {
1157 self.this_status = ThisStatus::Forbidden {
1158 directive: directive.clone(),
1159 };
1160 }
1161
1162 let declared_idents_until = self.declared_idents.len();
1163 let old_names = take(&mut self.names);
1164
1165 {
1166 let old_in_module = replace(&mut self.in_module_level, false);
1168 let should_track_names = directive.is_some() || self.should_track_names;
1169 let old_should_track_names = replace(&mut self.should_track_names, should_track_names);
1170 let old_in_exported_expr = replace(&mut self.in_exported_expr, false);
1171 let old_in_default_export_decl = replace(&mut self.in_default_export_decl, false);
1172 {
1173 for n in &mut a.params {
1174 collect_idents_in_pat(n, &mut self.declared_idents);
1175 }
1176 }
1177 a.visit_mut_children_with(self);
1178 self.in_module_level = old_in_module;
1179 self.should_track_names = old_should_track_names;
1180 self.in_exported_expr = old_in_exported_expr;
1181 self.in_default_export_decl = old_in_default_export_decl;
1182 }
1183
1184 if let Some(directive) = directive {
1185 if !a.is_async {
1186 emit_error(ServerActionsErrorKind::InlineSyncFunction {
1187 span: a.span,
1188 directive,
1189 });
1190
1191 return;
1192 }
1193
1194 let has_errors = HANDLER.with(|handler| handler.has_errors());
1195
1196 if has_errors || !self.config.is_react_server_layer {
1199 return;
1200 }
1201
1202 let mut child_names = take(&mut self.names);
1203
1204 if self.should_track_names {
1205 self.names = [old_names, child_names.clone()].concat();
1206 }
1207
1208 retain_names_from_declared_idents(
1211 &mut child_names,
1212 &self.declared_idents[..declared_idents_until],
1213 );
1214
1215 if let Directive::UseCache { cache_kind } = directive {
1216 self.rewrite_expr_to_proxy_expr =
1217 Some(self.maybe_hoist_and_create_proxy_for_cache_arrow_expr(
1218 child_names,
1219 cache_kind,
1220 a,
1221 ));
1222 } else if !matches!(self.file_directive, Some(Directive::UseServer)) {
1223 self.rewrite_expr_to_proxy_expr = Some(
1224 self.maybe_hoist_and_create_proxy_for_server_action_arrow_expr(child_names, a),
1225 );
1226 }
1227 }
1228 }
1229
1230 fn visit_mut_module(&mut self, m: &mut Module) {
1231 self.start_pos = m.span.lo;
1232 m.visit_mut_children_with(self);
1233 }
1234
1235 fn visit_mut_stmt(&mut self, n: &mut Stmt) {
1236 n.visit_mut_children_with(self);
1237
1238 if self.in_module_level {
1239 return;
1240 }
1241
1242 collect_decl_idents_in_stmt(n, &mut self.declared_idents);
1245 }
1246
1247 fn visit_mut_param(&mut self, n: &mut Param) {
1248 n.visit_mut_children_with(self);
1249
1250 if self.in_module_level {
1251 return;
1252 }
1253
1254 collect_idents_in_pat(&n.pat, &mut self.declared_idents);
1255 }
1256
1257 fn visit_mut_prop_or_spread(&mut self, n: &mut PropOrSpread) {
1258 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.clone();
1259 let old_in_exported_expr = self.in_exported_expr;
1260
1261 match n {
1262 PropOrSpread::Prop(box Prop::KeyValue(KeyValueProp {
1263 key: PropName::Ident(ident_name),
1264 value: box Expr::Arrow(_) | box Expr::Fn(_),
1265 ..
1266 })) => {
1267 self.in_exported_expr = false;
1268 self.arrow_or_fn_expr_ident = Some(ident_name.clone().into());
1269 }
1270 PropOrSpread::Prop(box Prop::Method(MethodProp { key, .. })) => {
1271 let key = key.clone();
1272
1273 if let PropName::Ident(ident_name) = &key {
1274 self.arrow_or_fn_expr_ident = Some(ident_name.clone().into());
1275 }
1276
1277 let old_this_status = replace(&mut self.this_status, ThisStatus::Allowed);
1278 self.rewrite_expr_to_proxy_expr = None;
1279 self.in_exported_expr = false;
1280 n.visit_mut_children_with(self);
1281 self.in_exported_expr = old_in_exported_expr;
1282 self.this_status = old_this_status;
1283
1284 if let Some(expr) = self.rewrite_expr_to_proxy_expr.take() {
1285 *n = PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
1286 key,
1287 value: expr,
1288 })));
1289 }
1290
1291 return;
1292 }
1293 _ => {}
1294 }
1295
1296 if !self.in_module_level && self.should_track_names {
1297 if let PropOrSpread::Prop(box Prop::Shorthand(i)) = n {
1298 self.names.push(Name::from(&*i));
1299 self.should_track_names = false;
1300 n.visit_mut_children_with(self);
1301 self.should_track_names = true;
1302 return;
1303 }
1304 }
1305
1306 n.visit_mut_children_with(self);
1307 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
1308 self.in_exported_expr = old_in_exported_expr;
1309 }
1310
1311 fn visit_mut_class(&mut self, n: &mut Class) {
1312 let old_this_status = replace(&mut self.this_status, ThisStatus::Allowed);
1313 n.visit_mut_children_with(self);
1314 self.this_status = old_this_status;
1315 }
1316
1317 fn visit_mut_class_member(&mut self, n: &mut ClassMember) {
1318 if let ClassMember::Method(ClassMethod {
1319 is_abstract: false,
1320 is_static: true,
1321 kind: MethodKind::Method,
1322 key,
1323 span,
1324 accessibility: None | Some(Accessibility::Public),
1325 ..
1326 }) = n
1327 {
1328 let key = key.clone();
1329 let span = *span;
1330 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.clone();
1331
1332 if let PropName::Ident(ident_name) = &key {
1333 self.arrow_or_fn_expr_ident = Some(ident_name.clone().into());
1334 }
1335
1336 let old_this_status = replace(&mut self.this_status, ThisStatus::Allowed);
1337 self.rewrite_expr_to_proxy_expr = None;
1338 self.in_exported_expr = false;
1339 n.visit_mut_children_with(self);
1340 self.this_status = old_this_status;
1341 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
1342
1343 if let Some(expr) = self.rewrite_expr_to_proxy_expr.take() {
1344 *n = ClassMember::ClassProp(ClassProp {
1345 span,
1346 key,
1347 value: Some(expr),
1348 is_static: true,
1349 ..Default::default()
1350 });
1351 }
1352 } else {
1353 n.visit_mut_children_with(self);
1354 }
1355 }
1356
1357 fn visit_mut_class_method(&mut self, n: &mut ClassMethod) {
1358 if n.is_static {
1359 n.visit_mut_children_with(self);
1360 } else {
1361 let (is_action_fn, is_cache_fn) = has_body_directive(&n.function.body);
1362
1363 if is_action_fn {
1364 emit_error(
1365 ServerActionsErrorKind::InlineUseServerInClassInstanceMethod { span: n.span },
1366 );
1367 } else if is_cache_fn {
1368 emit_error(
1369 ServerActionsErrorKind::InlineUseCacheInClassInstanceMethod { span: n.span },
1370 );
1371 } else {
1372 n.visit_mut_children_with(self);
1373 }
1374 }
1375 }
1376
1377 fn visit_mut_call_expr(&mut self, n: &mut CallExpr) {
1378 if let Callee::Expr(box Expr::Ident(Ident { sym, .. })) = &mut n.callee {
1379 if sym == "jsxDEV" || sym == "_jsxDEV" {
1380 if n.args.len() > 4 {
1384 for arg in &mut n.args[0..4] {
1385 arg.visit_mut_with(self);
1386 }
1387 return;
1388 }
1389 }
1390 }
1391
1392 n.visit_mut_children_with(self);
1393 }
1394
1395 fn visit_mut_callee(&mut self, n: &mut Callee) {
1396 let old_in_callee = replace(&mut self.in_callee, true);
1397 n.visit_mut_children_with(self);
1398 self.in_callee = old_in_callee;
1399 }
1400
1401 fn visit_mut_expr(&mut self, n: &mut Expr) {
1402 if !self.in_module_level && self.should_track_names {
1403 if let Ok(mut name) = Name::try_from(&*n) {
1404 if self.in_callee {
1405 if !name.1.is_empty() {
1408 name.1.pop();
1409 }
1410 }
1411
1412 self.names.push(name);
1413 self.should_track_names = false;
1414 n.visit_mut_children_with(self);
1415 self.should_track_names = true;
1416 return;
1417 }
1418 }
1419
1420 self.rewrite_expr_to_proxy_expr = None;
1421 n.visit_mut_children_with(self);
1422 if let Some(expr) = self.rewrite_expr_to_proxy_expr.take() {
1423 *n = *expr;
1424 }
1425 }
1426
1427 fn visit_mut_module_items(&mut self, stmts: &mut Vec<ModuleItem>) {
1428 self.file_directive = self.get_directive_for_module(stmts);
1429
1430 let in_cache_file = matches!(self.file_directive, Some(Directive::UseCache { .. }));
1431 let in_action_file = matches!(self.file_directive, Some(Directive::UseServer));
1432
1433 if in_cache_file {
1434 for stmt in stmts.iter() {
1445 match stmt {
1446 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(export_default_expr)) => {
1447 if let Expr::Ident(ident) = &*export_default_expr.expr {
1448 self.exported_local_ids.insert(ident.to_id());
1449 }
1450 }
1451 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(named_export)) => {
1452 if named_export.src.is_none() {
1453 for spec in &named_export.specifiers {
1454 if let ExportSpecifier::Named(ExportNamedSpecifier {
1455 orig: ModuleExportName::Ident(ident),
1456 ..
1457 }) = spec
1458 {
1459 self.exported_local_ids.insert(ident.to_id());
1460 }
1461 }
1462 }
1463 }
1464 _ => {}
1465 }
1466 }
1467 }
1468
1469 let should_track_exports = self.file_directive.is_some();
1471
1472 let old_annotations = self.annotations.take();
1473 let mut new = Vec::with_capacity(stmts.len());
1474
1475 for mut stmt in stmts.take() {
1476 if should_track_exports {
1479 let mut disallowed_export_span = DUMMY_SP;
1480
1481 match &mut stmt {
1483 ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { decl, span })) => {
1484 match decl {
1485 Decl::Fn(f) => {
1486 let (is_action_fn, is_cache_fn) =
1489 has_body_directive(&f.function.body);
1490
1491 let ref_id = if is_action_fn {
1492 false
1493 } else if is_cache_fn {
1494 true
1495 } else {
1496 in_cache_file
1497 };
1498
1499 if !(is_cache_fn && self.config.is_react_server_layer) {
1505 self.exported_idents.push((
1506 f.ident.clone(),
1507 f.ident.sym.clone(),
1508 self.generate_server_reference_id(
1509 f.ident.sym.as_ref(),
1510 ref_id,
1511 Some(&f.function.params),
1512 ),
1513 ));
1514 }
1515 }
1516 Decl::Var(var) => {
1517 let mut idents: Vec<Ident> = Vec::new();
1519 collect_idents_in_var_decls(&var.decls, &mut idents);
1520
1521 for ident in &idents {
1522 self.exported_idents.push((
1523 ident.clone(),
1524 ident.sym.clone(),
1525 self.generate_server_reference_id(
1526 ident.sym.as_ref(),
1527 in_cache_file,
1528 None,
1529 ),
1530 ));
1531 }
1532
1533 for decl in &mut var.decls {
1534 if let Some(init) = &decl.init {
1535 if let Expr::Lit(_) = &**init {
1536 disallowed_export_span = *span;
1538 }
1539 }
1540 }
1541 }
1542 Decl::TsInterface(_) => {}
1543 Decl::TsTypeAlias(_) => {}
1544 Decl::TsEnum(_) => {}
1545 _ => {
1546 disallowed_export_span = *span;
1547 }
1548 }
1549 }
1550 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(named)) => {
1551 if named.src.is_some() {
1552 disallowed_export_span = named.span;
1553 } else {
1554 for spec in &mut named.specifiers {
1555 if let ExportSpecifier::Named(ExportNamedSpecifier {
1556 orig: ModuleExportName::Ident(ident),
1557 exported,
1558 ..
1559 }) = spec
1560 {
1561 if let Some(export_name) = exported {
1562 if let ModuleExportName::Ident(Ident { sym, .. }) =
1563 export_name
1564 {
1565 self.exported_idents.push((
1567 ident.clone(),
1568 sym.clone(),
1569 self.generate_server_reference_id(
1570 sym.as_ref(),
1571 in_cache_file,
1572 None,
1573 ),
1574 ));
1575 } else if let ModuleExportName::Str(str) = export_name {
1576 self.exported_idents.push((
1578 ident.clone(),
1579 str.value.clone(),
1580 self.generate_server_reference_id(
1581 str.value.as_ref(),
1582 in_cache_file,
1583 None,
1584 ),
1585 ));
1586 }
1587 } else {
1588 self.exported_idents.push((
1590 ident.clone(),
1591 ident.sym.clone(),
1592 self.generate_server_reference_id(
1593 ident.sym.as_ref(),
1594 in_cache_file,
1595 None,
1596 ),
1597 ));
1598 }
1599 } else {
1600 disallowed_export_span = named.span;
1601 }
1602 }
1603 }
1604 }
1605 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(ExportDefaultDecl {
1606 decl,
1607 span,
1608 ..
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 "default".into(),
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 "default".into(),
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 "default".into(),
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 "default".into(),
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 "default".into(),
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 { span, .. })) => {
1778 disallowed_export_span = *span;
1779 }
1780 _ => {}
1781 }
1782
1783 if disallowed_export_span != DUMMY_SP {
1784 emit_error(ServerActionsErrorKind::ExportedSyncFunction {
1785 span: disallowed_export_span,
1786 in_action_file,
1787 });
1788
1789 return;
1790 }
1791 }
1792
1793 stmt.visit_mut_with(self);
1794
1795 let mut new_stmt = stmt;
1796
1797 if let Some(expr) = &self.rewrite_default_fn_expr_to_proxy_expr {
1798 new_stmt =
1800 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(ExportDefaultExpr {
1801 span: DUMMY_SP,
1802 expr: expr.clone(),
1803 }));
1804 self.rewrite_default_fn_expr_to_proxy_expr = None;
1805 }
1806
1807 if self.config.is_react_server_layer || self.file_directive.is_none() {
1808 new.append(&mut self.hoisted_extra_items);
1809 new.push(new_stmt);
1810 new.extend(self.annotations.drain(..).map(ModuleItem::Stmt));
1811 new.append(&mut self.extra_items);
1812 }
1813 }
1814
1815 let mut actions = self.export_actions.take();
1816
1817 if in_action_file || in_cache_file && !self.config.is_react_server_layer {
1818 actions.extend(
1819 self.exported_idents
1820 .iter()
1821 .map(|e| (e.1.clone(), e.2.clone())),
1822 );
1823
1824 if !actions.is_empty() {
1825 self.has_action |= in_action_file;
1826 self.has_cache |= in_cache_file;
1827 }
1828 };
1829
1830 let actions = actions
1832 .into_iter()
1833 .map(|a| (a.1, a.0))
1834 .collect::<ActionsMap>();
1835
1836 let create_ref_ident = private_ident!("createServerReference");
1839 let call_server_ident = private_ident!("callServer");
1840 let find_source_map_url_ident = private_ident!("findSourceMapURL");
1841
1842 let client_layer_import = ((self.has_action || self.has_cache)
1843 && !self.config.is_react_server_layer)
1844 .then(|| {
1845 ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
1852 span: DUMMY_SP,
1853 specifiers: vec![
1854 ImportSpecifier::Named(ImportNamedSpecifier {
1855 span: DUMMY_SP,
1856 local: create_ref_ident.clone(),
1857 imported: None,
1858 is_type_only: false,
1859 }),
1860 ImportSpecifier::Named(ImportNamedSpecifier {
1861 span: DUMMY_SP,
1862 local: call_server_ident.clone(),
1863 imported: None,
1864 is_type_only: false,
1865 }),
1866 ImportSpecifier::Named(ImportNamedSpecifier {
1867 span: DUMMY_SP,
1868 local: find_source_map_url_ident.clone(),
1869 imported: None,
1870 is_type_only: false,
1871 }),
1872 ],
1873 src: Box::new(Str {
1874 span: DUMMY_SP,
1875 value: "private-next-rsc-action-client-wrapper".into(),
1876 raw: None,
1877 }),
1878 type_only: false,
1879 with: None,
1880 phase: Default::default(),
1881 }))
1882 });
1883
1884 let mut client_layer_exports = FxIndexMap::default();
1885
1886 if should_track_exports {
1888 for (ident, export_name, ref_id) in self.exported_idents.iter() {
1889 if !self.config.is_react_server_layer {
1890 if export_name == "default" {
1891 let export_expr = ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(
1892 ExportDefaultExpr {
1893 span: DUMMY_SP,
1894 expr: Box::new(Expr::Call(CallExpr {
1895 span: if self.config.is_react_server_layer
1900 || self.config.is_development
1901 {
1902 self.comments.add_pure_comment(ident.span.lo);
1903 ident.span
1904 } else {
1905 PURE_SP
1906 },
1907 callee: Callee::Expr(Box::new(Expr::Ident(
1908 create_ref_ident.clone(),
1909 ))),
1910 args: vec![
1911 ref_id.clone().as_arg(),
1912 call_server_ident.clone().as_arg(),
1913 Expr::undefined(DUMMY_SP).as_arg(),
1914 find_source_map_url_ident.clone().as_arg(),
1915 "default".as_arg(),
1916 ],
1917 ..Default::default()
1918 })),
1919 },
1920 ));
1921 client_layer_exports
1922 .insert(atom!("default"), (export_expr, ref_id.clone()));
1923 } else {
1924 let export_expr =
1925 ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
1926 span: DUMMY_SP,
1927 decl: Decl::Var(Box::new(VarDecl {
1928 span: DUMMY_SP,
1929 kind: VarDeclKind::Var,
1930 decls: vec![VarDeclarator {
1931 span: DUMMY_SP,
1932 name: Pat::Ident(
1933 IdentName::new(
1934 export_name.clone(),
1935 if self.config.is_react_server_layer
1941 || self.config.is_development
1942 {
1943 ident.span
1944 } else {
1945 DUMMY_SP
1946 },
1947 )
1948 .into(),
1949 ),
1950 init: Some(Box::new(Expr::Call(CallExpr {
1951 span: PURE_SP,
1952 callee: Callee::Expr(Box::new(Expr::Ident(
1953 create_ref_ident.clone(),
1954 ))),
1955 args: vec![
1956 ref_id.clone().as_arg(),
1957 call_server_ident.clone().as_arg(),
1958 Expr::undefined(DUMMY_SP).as_arg(),
1959 find_source_map_url_ident.clone().as_arg(),
1960 export_name.clone().as_arg(),
1961 ],
1962 ..Default::default()
1963 }))),
1964 definite: false,
1965 }],
1966 ..Default::default()
1967 })),
1968 }));
1969 client_layer_exports
1970 .insert(export_name.clone(), (export_expr, ref_id.clone()));
1971 }
1972 } else if !in_cache_file {
1973 self.annotations.push(Stmt::Expr(ExprStmt {
1974 span: DUMMY_SP,
1975 expr: Box::new(annotate_ident_as_server_reference(
1976 ident.clone(),
1977 ref_id.clone(),
1978 ident.span,
1979 )),
1980 }));
1981 }
1982 }
1983
1984 if (self.has_action || self.has_cache) && self.config.is_react_server_layer {
1992 new.append(&mut self.extra_items);
1993
1994 if !in_cache_file && !self.exported_idents.is_empty() {
1996 let ensure_ident = private_ident!("ensureServerEntryExports");
1997 new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
1998 span: DUMMY_SP,
1999 specifiers: vec![ImportSpecifier::Named(ImportNamedSpecifier {
2000 span: DUMMY_SP,
2001 local: ensure_ident.clone(),
2002 imported: None,
2003 is_type_only: false,
2004 })],
2005 src: Box::new(Str {
2006 span: DUMMY_SP,
2007 value: "private-next-rsc-action-validate".into(),
2008 raw: None,
2009 }),
2010 type_only: false,
2011 with: None,
2012 phase: Default::default(),
2013 })));
2014 new.push(ModuleItem::Stmt(Stmt::Expr(ExprStmt {
2015 span: DUMMY_SP,
2016 expr: Box::new(Expr::Call(CallExpr {
2017 span: DUMMY_SP,
2018 callee: Callee::Expr(Box::new(Expr::Ident(ensure_ident))),
2019 args: vec![ExprOrSpread {
2020 spread: None,
2021 expr: Box::new(Expr::Array(ArrayLit {
2022 span: DUMMY_SP,
2023 elems: self
2024 .exported_idents
2025 .iter()
2026 .map(|(ident, _, _)| {
2027 Some(ExprOrSpread {
2028 spread: None,
2029 expr: Box::new(Expr::Ident(ident.clone())),
2030 })
2031 })
2032 .collect(),
2033 })),
2034 }],
2035 ..Default::default()
2036 })),
2037 })));
2038 }
2039
2040 new.extend(self.annotations.drain(..).map(ModuleItem::Stmt));
2042 }
2043 }
2044
2045 if self.has_cache && self.config.is_react_server_layer {
2047 new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
2048 span: DUMMY_SP,
2049 specifiers: vec![ImportSpecifier::Named(ImportNamedSpecifier {
2050 span: DUMMY_SP,
2051 local: quote_ident!("$$cache__").into(),
2052 imported: Some(quote_ident!("cache").into()),
2053 is_type_only: false,
2054 })],
2055 src: Box::new(Str {
2056 span: DUMMY_SP,
2057 value: "private-next-rsc-cache-wrapper".into(),
2058 raw: None,
2059 }),
2060 type_only: false,
2061 with: None,
2062 phase: Default::default(),
2063 })));
2064
2065 new.rotate_right(1);
2067 }
2068
2069 if (self.has_action || self.has_cache) && self.config.is_react_server_layer {
2070 new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
2073 span: DUMMY_SP,
2074 specifiers: vec![ImportSpecifier::Named(ImportNamedSpecifier {
2075 span: DUMMY_SP,
2076 local: quote_ident!("registerServerReference").into(),
2077 imported: None,
2078 is_type_only: false,
2079 })],
2080 src: Box::new(Str {
2081 span: DUMMY_SP,
2082 value: "private-next-rsc-server-reference".into(),
2083 raw: None,
2084 }),
2085 type_only: false,
2086 with: None,
2087 phase: Default::default(),
2088 })));
2089
2090 new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
2094 span: DUMMY_SP,
2095 specifiers: vec![
2096 ImportSpecifier::Named(ImportNamedSpecifier {
2097 span: DUMMY_SP,
2098 local: quote_ident!("encryptActionBoundArgs").into(),
2099 imported: None,
2100 is_type_only: false,
2101 }),
2102 ImportSpecifier::Named(ImportNamedSpecifier {
2103 span: DUMMY_SP,
2104 local: quote_ident!("decryptActionBoundArgs").into(),
2105 imported: None,
2106 is_type_only: false,
2107 }),
2108 ],
2109 src: Box::new(Str {
2110 span: DUMMY_SP,
2111 value: "private-next-rsc-action-encryption".into(),
2112 raw: None,
2113 }),
2114 type_only: false,
2115 with: None,
2116 phase: Default::default(),
2117 })));
2118
2119 new.rotate_right(2);
2121 }
2122
2123 if self.has_action || self.has_cache {
2124 if self.config.is_react_server_layer {
2125 self.comments.add_leading(
2127 self.start_pos,
2128 Comment {
2129 span: DUMMY_SP,
2130 kind: CommentKind::Block,
2131 text: generate_server_actions_comment(
2132 &actions,
2133 match self.mode {
2134 ServerActionsMode::Webpack => None,
2135 ServerActionsMode::Turbopack => Some(("", "")),
2136 },
2137 )
2138 .into(),
2139 },
2140 );
2141 } else {
2142 match self.mode {
2143 ServerActionsMode::Webpack => {
2144 self.comments.add_leading(
2145 self.start_pos,
2146 Comment {
2147 span: DUMMY_SP,
2148 kind: CommentKind::Block,
2149 text: generate_server_actions_comment(&actions, None).into(),
2150 },
2151 );
2152 new.push(client_layer_import.unwrap());
2153 new.rotate_right(1);
2154 new.extend(client_layer_exports.into_iter().map(|(_, (v, _))| v));
2155 }
2156 ServerActionsMode::Turbopack => {
2157 new.push(ModuleItem::Stmt(Stmt::Expr(ExprStmt {
2158 expr: Box::new(Expr::Lit(Lit::Str(
2159 "use turbopack no side effects".into(),
2160 ))),
2161 span: DUMMY_SP,
2162 })));
2163 new.rotate_right(1);
2164 for (export, (stmt, ref_id)) in client_layer_exports {
2165 new.push(ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(
2166 NamedExport {
2167 specifiers: vec![ExportSpecifier::Named(
2168 ExportNamedSpecifier {
2169 span: DUMMY_SP,
2170 orig: ModuleExportName::Ident(export.clone().into()),
2171 exported: None,
2172 is_type_only: false,
2173 },
2174 )],
2175 src: Some(Box::new(
2176 program_to_data_url(
2177 &self.file_name,
2178 &self.cm,
2179 vec![
2180 ModuleItem::Stmt(Stmt::Expr(ExprStmt {
2181 expr: Box::new(Expr::Lit(Lit::Str(
2182 "use turbopack no side effects".into(),
2183 ))),
2184 span: DUMMY_SP,
2185 })),
2186 client_layer_import.clone().unwrap(),
2187 stmt,
2188 ],
2189 Comment {
2190 span: DUMMY_SP,
2191 kind: CommentKind::Block,
2192 text: generate_server_actions_comment(
2193 &std::iter::once((ref_id, export)).collect(),
2194 Some((
2195 &self.file_name,
2196 self.file_query.as_ref().map_or("", |v| v),
2197 )),
2198 )
2199 .into(),
2200 },
2201 )
2202 .into(),
2203 )),
2204 span: DUMMY_SP,
2205 type_only: false,
2206 with: None,
2207 },
2208 )));
2209 }
2210 }
2211 }
2212 }
2213 }
2214
2215 *stmts = new;
2216
2217 self.annotations = old_annotations;
2218 }
2219
2220 fn visit_mut_stmts(&mut self, stmts: &mut Vec<Stmt>) {
2221 let old_annotations = self.annotations.take();
2222
2223 let mut new = Vec::with_capacity(stmts.len());
2224 for mut stmt in stmts.take() {
2225 stmt.visit_mut_with(self);
2226
2227 new.push(stmt);
2228 new.append(&mut self.annotations);
2229 }
2230
2231 *stmts = new;
2232
2233 self.annotations = old_annotations;
2234 }
2235
2236 fn visit_mut_jsx_attr(&mut self, attr: &mut JSXAttr) {
2237 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.take();
2238
2239 if let (Some(JSXAttrValue::JSXExprContainer(container)), JSXAttrName::Ident(ident_name)) =
2240 (&attr.value, &attr.name)
2241 {
2242 match &container.expr {
2243 JSXExpr::Expr(box Expr::Arrow(_)) | JSXExpr::Expr(box Expr::Fn(_)) => {
2244 self.arrow_or_fn_expr_ident = Some(ident_name.clone().into());
2245 }
2246 _ => {}
2247 }
2248 }
2249
2250 attr.visit_mut_children_with(self);
2251 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
2252 }
2253
2254 fn visit_mut_var_declarator(&mut self, var_declarator: &mut VarDeclarator) {
2255 let old_in_exported_expr = self.in_exported_expr;
2256 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.take();
2257
2258 if let (Pat::Ident(ident), Some(box Expr::Arrow(_) | box Expr::Fn(_))) =
2259 (&var_declarator.name, &var_declarator.init)
2260 {
2261 if self.in_module_level && self.exported_local_ids.contains(&ident.to_id()) {
2262 self.in_exported_expr = true
2263 }
2264
2265 self.arrow_or_fn_expr_ident = Some(ident.id.clone());
2266 }
2267
2268 var_declarator.visit_mut_children_with(self);
2269
2270 self.in_exported_expr = old_in_exported_expr;
2271 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
2272 }
2273
2274 fn visit_mut_assign_expr(&mut self, assign_expr: &mut AssignExpr) {
2275 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.clone();
2276
2277 if let (
2278 AssignTarget::Simple(SimpleAssignTarget::Ident(ident)),
2279 box Expr::Arrow(_) | box Expr::Fn(_),
2280 ) = (&assign_expr.left, &assign_expr.right)
2281 {
2282 if !ident.id.to_id().0.starts_with("$$RSC_SERVER_") {
2284 self.arrow_or_fn_expr_ident = Some(ident.id.clone());
2285 }
2286 }
2287
2288 assign_expr.visit_mut_children_with(self);
2289 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
2290 }
2291
2292 fn visit_mut_this_expr(&mut self, n: &mut ThisExpr) {
2293 if let ThisStatus::Forbidden { directive } = &self.this_status {
2294 emit_error(ServerActionsErrorKind::ForbiddenExpression {
2295 span: n.span,
2296 expr: "this".into(),
2297 directive: directive.clone(),
2298 });
2299 }
2300 }
2301
2302 fn visit_mut_super(&mut self, n: &mut Super) {
2303 if let ThisStatus::Forbidden { directive } = &self.this_status {
2304 emit_error(ServerActionsErrorKind::ForbiddenExpression {
2305 span: n.span,
2306 expr: "super".into(),
2307 directive: directive.clone(),
2308 });
2309 }
2310 }
2311
2312 fn visit_mut_ident(&mut self, n: &mut Ident) {
2313 if n.sym == *"arguments" {
2314 if let ThisStatus::Forbidden { directive } = &self.this_status {
2315 emit_error(ServerActionsErrorKind::ForbiddenExpression {
2316 span: n.span,
2317 expr: "arguments".into(),
2318 directive: directive.clone(),
2319 });
2320 }
2321 }
2322 }
2323
2324 noop_visit_mut_type!();
2325}
2326
2327fn retain_names_from_declared_idents(
2328 child_names: &mut Vec<Name>,
2329 current_declared_idents: &[Ident],
2330) {
2331 let mut retained_names = Vec::new();
2333
2334 for name in child_names.iter() {
2335 let mut should_retain = true;
2336
2337 for another_name in child_names.iter() {
2343 if name != another_name
2344 && name.0 == another_name.0
2345 && name.1.len() >= another_name.1.len()
2346 {
2347 let mut is_prefix = true;
2348 for i in 0..another_name.1.len() {
2349 if name.1[i] != another_name.1[i] {
2350 is_prefix = false;
2351 break;
2352 }
2353 }
2354 if is_prefix {
2355 should_retain = false;
2356 break;
2357 }
2358 }
2359 }
2360
2361 if should_retain
2362 && current_declared_idents
2363 .iter()
2364 .any(|ident| ident.to_id() == name.0)
2365 && !retained_names.contains(name)
2366 {
2367 retained_names.push(name.clone());
2368 }
2369 }
2370
2371 *child_names = retained_names;
2373}
2374
2375fn wrap_cache_expr(expr: Box<Expr>, name: &str, id: &str, bound_args_len: usize) -> Box<Expr> {
2376 Box::new(Expr::Call(CallExpr {
2378 span: DUMMY_SP,
2379 callee: quote_ident!("$$cache__").as_callee(),
2380 args: vec![
2381 ExprOrSpread {
2382 spread: None,
2383 expr: Box::new(name.into()),
2384 },
2385 ExprOrSpread {
2386 spread: None,
2387 expr: Box::new(id.into()),
2388 },
2389 Number::from(bound_args_len).as_arg(),
2390 expr.as_arg(),
2391 ],
2392 ..Default::default()
2393 }))
2394}
2395
2396fn create_var_declarator(ident: &Ident, extra_items: &mut Vec<ModuleItem>) {
2397 extra_items.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl {
2399 span: DUMMY_SP,
2400 kind: VarDeclKind::Var,
2401 decls: vec![VarDeclarator {
2402 span: DUMMY_SP,
2403 name: ident.clone().into(),
2404 init: None,
2405 definite: Default::default(),
2406 }],
2407 ..Default::default()
2408 })))));
2409}
2410
2411fn assign_name_to_ident(ident: &Ident, name: &str, extra_items: &mut Vec<ModuleItem>) {
2412 extra_items.push(quote!(
2414 "Object[\"defineProperty\"]($action, \"name\", { value: $name, writable: false });"
2422 as ModuleItem,
2423 action: Ident = ident.clone(),
2424 name: Expr = name.into(),
2425 ));
2426}
2427
2428fn assign_arrow_expr(ident: &Ident, expr: Expr) -> Expr {
2429 if let Expr::Paren(_paren) = &expr {
2430 expr
2431 } else {
2432 Expr::Paren(ParenExpr {
2434 span: DUMMY_SP,
2435 expr: Box::new(Expr::Assign(AssignExpr {
2436 span: DUMMY_SP,
2437 left: ident.clone().into(),
2438 op: op!("="),
2439 right: Box::new(expr),
2440 })),
2441 })
2442 }
2443}
2444
2445fn annotate_ident_as_server_reference(ident: Ident, action_id: Atom, original_span: Span) -> Expr {
2446 Expr::Call(CallExpr {
2448 span: original_span,
2449 callee: quote_ident!("registerServerReference").as_callee(),
2450 args: vec![
2451 ExprOrSpread {
2452 spread: None,
2453 expr: Box::new(Expr::Ident(ident)),
2454 },
2455 ExprOrSpread {
2456 spread: None,
2457 expr: Box::new(action_id.clone().into()),
2458 },
2459 ExprOrSpread {
2460 spread: None,
2461 expr: Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))),
2462 },
2463 ],
2464 ..Default::default()
2465 })
2466}
2467
2468fn bind_args_to_ref_expr(expr: Expr, bound: Vec<Option<ExprOrSpread>>, action_id: Atom) -> Expr {
2469 if bound.is_empty() {
2470 expr
2471 } else {
2472 Expr::Call(CallExpr {
2474 span: DUMMY_SP,
2475 callee: Expr::Member(MemberExpr {
2476 span: DUMMY_SP,
2477 obj: Box::new(expr),
2478 prop: MemberProp::Ident(quote_ident!("bind")),
2479 })
2480 .as_callee(),
2481 args: vec![
2482 ExprOrSpread {
2483 spread: None,
2484 expr: Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))),
2485 },
2486 ExprOrSpread {
2487 spread: None,
2488 expr: Box::new(Expr::Call(CallExpr {
2489 span: DUMMY_SP,
2490 callee: quote_ident!("encryptActionBoundArgs").as_callee(),
2491 args: std::iter::once(ExprOrSpread {
2492 spread: None,
2493 expr: Box::new(action_id.into()),
2494 })
2495 .chain(bound.into_iter().flatten())
2496 .collect(),
2497 ..Default::default()
2498 })),
2499 },
2500 ],
2501 ..Default::default()
2502 })
2503 }
2504}
2505
2506fn detect_similar_strings(a: &str, b: &str) -> bool {
2521 let mut a = a.chars().collect::<Vec<char>>();
2522 let mut b = b.chars().collect::<Vec<char>>();
2523
2524 if a.len() < b.len() {
2525 (a, b) = (b, a);
2526 }
2527
2528 if a.len() == b.len() {
2529 let mut diff = 0;
2531 for i in 0..a.len() {
2532 if a[i] != b[i] {
2533 diff += 1;
2534 if diff > 2 {
2535 return false;
2536 }
2537 }
2538 }
2539
2540 diff != 0
2542 } else {
2543 if a.len() - b.len() > 1 {
2544 return false;
2545 }
2546
2547 for i in 0..b.len() {
2549 if a[i] != b[i] {
2550 return a[i + 1..] == b[i..];
2556 }
2557 }
2558
2559 true
2561 }
2562}
2563
2564fn has_body_directive(maybe_body: &Option<BlockStmt>) -> (bool, bool) {
2569 let mut is_action_fn = false;
2570 let mut is_cache_fn = false;
2571
2572 if let Some(body) = maybe_body {
2573 for stmt in body.stmts.iter() {
2574 match stmt {
2575 Stmt::Expr(ExprStmt {
2576 expr: box Expr::Lit(Lit::Str(Str { value, .. })),
2577 ..
2578 }) => {
2579 if value == "use server" {
2580 is_action_fn = true;
2581 break;
2582 } else if value == "use cache" || value.starts_with("use cache: ") {
2583 is_cache_fn = true;
2584 break;
2585 }
2586 }
2587 _ => break,
2588 }
2589 }
2590 }
2591
2592 (is_action_fn, is_cache_fn)
2593}
2594
2595fn collect_idents_in_array_pat(elems: &[Option<Pat>], idents: &mut Vec<Ident>) {
2596 for elem in elems.iter().flatten() {
2597 match elem {
2598 Pat::Ident(ident) => {
2599 idents.push(ident.id.clone());
2600 }
2601 Pat::Array(array) => {
2602 collect_idents_in_array_pat(&array.elems, idents);
2603 }
2604 Pat::Object(object) => {
2605 collect_idents_in_object_pat(&object.props, idents);
2606 }
2607 Pat::Rest(rest) => {
2608 if let Pat::Ident(ident) = &*rest.arg {
2609 idents.push(ident.id.clone());
2610 }
2611 }
2612 Pat::Assign(AssignPat { left, .. }) => {
2613 collect_idents_in_pat(left, idents);
2614 }
2615 Pat::Expr(..) | Pat::Invalid(..) => {}
2616 }
2617 }
2618}
2619
2620fn collect_idents_in_object_pat(props: &[ObjectPatProp], idents: &mut Vec<Ident>) {
2621 for prop in props {
2622 match prop {
2623 ObjectPatProp::KeyValue(KeyValuePatProp { key, value }) => {
2624 if let PropName::Ident(ident) = key {
2625 idents.push(Ident::new(
2626 ident.sym.clone(),
2627 ident.span,
2628 SyntaxContext::empty(),
2629 ));
2630 }
2631
2632 match &**value {
2633 Pat::Ident(ident) => {
2634 idents.push(ident.id.clone());
2635 }
2636 Pat::Array(array) => {
2637 collect_idents_in_array_pat(&array.elems, idents);
2638 }
2639 Pat::Object(object) => {
2640 collect_idents_in_object_pat(&object.props, idents);
2641 }
2642 _ => {}
2643 }
2644 }
2645 ObjectPatProp::Assign(AssignPatProp { key, .. }) => {
2646 idents.push(key.id.clone());
2647 }
2648 ObjectPatProp::Rest(RestPat { arg, .. }) => {
2649 if let Pat::Ident(ident) = &**arg {
2650 idents.push(ident.id.clone());
2651 }
2652 }
2653 }
2654 }
2655}
2656
2657fn collect_idents_in_var_decls(decls: &[VarDeclarator], idents: &mut Vec<Ident>) {
2658 for decl in decls {
2659 collect_idents_in_pat(&decl.name, idents);
2660 }
2661}
2662
2663fn collect_idents_in_pat(pat: &Pat, idents: &mut Vec<Ident>) {
2664 match pat {
2665 Pat::Ident(ident) => {
2666 idents.push(ident.id.clone());
2667 }
2668 Pat::Array(array) => {
2669 collect_idents_in_array_pat(&array.elems, idents);
2670 }
2671 Pat::Object(object) => {
2672 collect_idents_in_object_pat(&object.props, idents);
2673 }
2674 Pat::Assign(AssignPat { left, .. }) => {
2675 collect_idents_in_pat(left, idents);
2676 }
2677 Pat::Rest(RestPat { arg, .. }) => {
2678 if let Pat::Ident(ident) = &**arg {
2679 idents.push(ident.id.clone());
2680 }
2681 }
2682 Pat::Expr(..) | Pat::Invalid(..) => {}
2683 }
2684}
2685
2686fn collect_decl_idents_in_stmt(stmt: &Stmt, idents: &mut Vec<Ident>) {
2687 if let Stmt::Decl(decl) = stmt {
2688 match decl {
2689 Decl::Var(var) => {
2690 collect_idents_in_var_decls(&var.decls, idents);
2691 }
2692 Decl::Fn(fn_decl) => {
2693 idents.push(fn_decl.ident.clone());
2694 }
2695 _ => {}
2696 }
2697 }
2698}
2699
2700struct DirectiveVisitor<'a> {
2701 config: &'a Config,
2702 location: DirectiveLocation,
2703 directive: Option<Directive>,
2704 has_file_directive: bool,
2705 is_allowed_position: bool,
2706 use_cache_telemetry_tracker: Rc<RefCell<FxHashMap<String, usize>>>,
2707}
2708
2709impl DirectiveVisitor<'_> {
2710 fn visit_stmt(&mut self, stmt: &Stmt) -> bool {
2715 let in_fn_body = matches!(self.location, DirectiveLocation::FunctionBody);
2716 let allow_inline = self.config.is_react_server_layer || self.has_file_directive;
2717
2718 match stmt {
2719 Stmt::Expr(ExprStmt {
2720 expr: box Expr::Lit(Lit::Str(Str { value, span, .. })),
2721 ..
2722 }) => {
2723 if value == "use server" {
2724 if in_fn_body && !allow_inline {
2725 emit_error(ServerActionsErrorKind::InlineUseServerInClientComponent {
2726 span: *span,
2727 })
2728 } else if let Some(Directive::UseCache { .. }) = self.directive {
2729 emit_error(ServerActionsErrorKind::MultipleDirectives {
2730 span: *span,
2731 location: self.location.clone(),
2732 });
2733 } else if self.is_allowed_position {
2734 self.directive = Some(Directive::UseServer);
2735
2736 return true;
2737 } else {
2738 emit_error(ServerActionsErrorKind::MisplacedDirective {
2739 span: *span,
2740 directive: value.to_string(),
2741 location: self.location.clone(),
2742 });
2743 }
2744 } else if detect_similar_strings(value, "use server") {
2745 emit_error(ServerActionsErrorKind::MisspelledDirective {
2747 span: *span,
2748 directive: value.to_string(),
2749 expected_directive: "use server".to_string(),
2750 });
2751 } else if value == "use action" {
2752 emit_error(ServerActionsErrorKind::MisspelledDirective {
2753 span: *span,
2754 directive: value.to_string(),
2755 expected_directive: "use server".to_string(),
2756 });
2757 } else
2758 if value == "use cache" || value.starts_with("use cache: ") {
2760 if in_fn_body && !allow_inline {
2763 emit_error(ServerActionsErrorKind::InlineUseCacheInClientComponent {
2764 span: *span,
2765 })
2766 } else if let Some(Directive::UseServer) = self.directive {
2767 emit_error(ServerActionsErrorKind::MultipleDirectives {
2768 span: *span,
2769 location: self.location.clone(),
2770 });
2771 } else if self.is_allowed_position {
2772 if !self.config.use_cache_enabled {
2773 emit_error(ServerActionsErrorKind::UseCacheWithoutExperimentalFlag {
2774 span: *span,
2775 directive: value.to_string(),
2776 });
2777 }
2778
2779 if value == "use cache" {
2780 self.directive = Some(Directive::UseCache {
2781 cache_kind: RcStr::from("default"),
2782 });
2783 self.increment_cache_usage_counter("default");
2784 } else {
2785 let cache_kind = RcStr::from(value.split_at("use cache: ".len()).1);
2787
2788 if !self.config.cache_kinds.contains(&cache_kind) {
2789 emit_error(ServerActionsErrorKind::UnknownCacheKind {
2790 span: *span,
2791 cache_kind: cache_kind.clone(),
2792 });
2793 }
2794
2795 self.increment_cache_usage_counter(&cache_kind);
2796 self.directive = Some(Directive::UseCache { cache_kind });
2797 }
2798
2799 return true;
2800 } else {
2801 emit_error(ServerActionsErrorKind::MisplacedDirective {
2802 span: *span,
2803 directive: value.to_string(),
2804 location: self.location.clone(),
2805 });
2806 }
2807 } else {
2808 if detect_similar_strings(value, "use cache") {
2810 emit_error(ServerActionsErrorKind::MisspelledDirective {
2811 span: *span,
2812 directive: value.to_string(),
2813 expected_directive: "use cache".to_string(),
2814 });
2815 }
2816 }
2817 }
2818 Stmt::Expr(ExprStmt {
2819 expr:
2820 box Expr::Paren(ParenExpr {
2821 expr: box Expr::Lit(Lit::Str(Str { value, .. })),
2822 ..
2823 }),
2824 span,
2825 ..
2826 }) => {
2827 if value == "use server" || detect_similar_strings(value, "use server") {
2829 if self.is_allowed_position {
2830 emit_error(ServerActionsErrorKind::WrappedDirective {
2831 span: *span,
2832 directive: "use server".to_string(),
2833 });
2834 } else {
2835 emit_error(ServerActionsErrorKind::MisplacedWrappedDirective {
2836 span: *span,
2837 directive: "use server".to_string(),
2838 location: self.location.clone(),
2839 });
2840 }
2841 } else if value == "use cache" || detect_similar_strings(value, "use cache") {
2842 if self.is_allowed_position {
2843 emit_error(ServerActionsErrorKind::WrappedDirective {
2844 span: *span,
2845 directive: "use cache".to_string(),
2846 });
2847 } else {
2848 emit_error(ServerActionsErrorKind::MisplacedWrappedDirective {
2849 span: *span,
2850 directive: "use cache".to_string(),
2851 location: self.location.clone(),
2852 });
2853 }
2854 }
2855 }
2856 _ => {
2857 self.is_allowed_position = false;
2859 }
2860 };
2861
2862 false
2863 }
2864
2865 fn increment_cache_usage_counter(&mut self, cache_kind: &str) {
2867 let mut tracker_map = RefCell::borrow_mut(&self.use_cache_telemetry_tracker);
2868 let entry = tracker_map.entry(cache_kind.to_string());
2869 match entry {
2870 hash_map::Entry::Occupied(mut occupied) => {
2871 *occupied.get_mut() += 1;
2872 }
2873 hash_map::Entry::Vacant(vacant) => {
2874 vacant.insert(1);
2875 }
2876 }
2877 }
2878}
2879
2880pub(crate) struct ClosureReplacer<'a> {
2881 used_ids: &'a [Name],
2882 private_ctxt: SyntaxContext,
2883}
2884
2885impl ClosureReplacer<'_> {
2886 fn index(&self, e: &Expr) -> Option<usize> {
2887 let name = Name::try_from(e).ok()?;
2888 self.used_ids.iter().position(|used_id| *used_id == name)
2889 }
2890}
2891
2892impl VisitMut for ClosureReplacer<'_> {
2893 fn visit_mut_expr(&mut self, e: &mut Expr) {
2894 e.visit_mut_children_with(self);
2895
2896 if let Some(index) = self.index(e) {
2897 *e = Expr::Ident(Ident::new(
2898 format!("$$ACTION_ARG_{index}").into(),
2900 DUMMY_SP,
2901 self.private_ctxt,
2902 ));
2903 }
2904 }
2905
2906 fn visit_mut_prop_or_spread(&mut self, n: &mut PropOrSpread) {
2907 n.visit_mut_children_with(self);
2908
2909 if let PropOrSpread::Prop(box Prop::Shorthand(i)) = n {
2910 let name = Name::from(&*i);
2911 if let Some(index) = self.used_ids.iter().position(|used_id| *used_id == name) {
2912 *n = PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
2913 key: PropName::Ident(i.clone().into()),
2914 value: Box::new(Expr::Ident(Ident::new(
2915 format!("$$ACTION_ARG_{index}").into(),
2917 DUMMY_SP,
2918 self.private_ctxt,
2919 ))),
2920 })));
2921 }
2922 }
2923 }
2924
2925 noop_visit_mut_type!();
2926}
2927
2928#[derive(Debug, Clone, PartialEq, Eq)]
2929struct NamePart {
2930 prop: Atom,
2931 is_member: bool,
2932 optional: bool,
2933}
2934
2935#[derive(Debug, Clone, PartialEq, Eq)]
2936struct Name(Id, Vec<NamePart>);
2937
2938impl From<&'_ Ident> for Name {
2939 fn from(value: &Ident) -> Self {
2940 Name(value.to_id(), vec![])
2941 }
2942}
2943
2944impl TryFrom<&'_ Expr> for Name {
2945 type Error = ();
2946
2947 fn try_from(value: &Expr) -> Result<Self, Self::Error> {
2948 match value {
2949 Expr::Ident(i) => Ok(Name(i.to_id(), vec![])),
2950 Expr::Member(e) => e.try_into(),
2951 Expr::OptChain(e) => e.try_into(),
2952 _ => Err(()),
2953 }
2954 }
2955}
2956
2957impl TryFrom<&'_ MemberExpr> for Name {
2958 type Error = ();
2959
2960 fn try_from(value: &MemberExpr) -> Result<Self, Self::Error> {
2961 match &value.prop {
2962 MemberProp::Ident(prop) => {
2963 let mut obj: Name = value.obj.as_ref().try_into()?;
2964 obj.1.push(NamePart {
2965 prop: prop.sym.clone(),
2966 is_member: true,
2967 optional: false,
2968 });
2969 Ok(obj)
2970 }
2971 _ => Err(()),
2972 }
2973 }
2974}
2975
2976impl TryFrom<&'_ OptChainExpr> for Name {
2977 type Error = ();
2978
2979 fn try_from(value: &OptChainExpr) -> Result<Self, Self::Error> {
2980 match &*value.base {
2981 OptChainBase::Member(m) => match &m.prop {
2982 MemberProp::Ident(prop) => {
2983 let mut obj: Name = m.obj.as_ref().try_into()?;
2984 obj.1.push(NamePart {
2985 prop: prop.sym.clone(),
2986 is_member: false,
2987 optional: value.optional,
2988 });
2989 Ok(obj)
2990 }
2991 _ => Err(()),
2992 },
2993 OptChainBase::Call(_) => Err(()),
2994 }
2995 }
2996}
2997
2998impl From<Name> for Box<Expr> {
2999 fn from(value: Name) -> Self {
3000 let mut expr = Box::new(Expr::Ident(value.0.into()));
3001
3002 for NamePart {
3003 prop,
3004 is_member,
3005 optional,
3006 } in value.1.into_iter()
3007 {
3008 if is_member {
3009 expr = Box::new(Expr::Member(MemberExpr {
3010 span: DUMMY_SP,
3011 obj: expr,
3012 prop: MemberProp::Ident(IdentName::new(prop, DUMMY_SP)),
3013 }));
3014 } else {
3015 expr = Box::new(Expr::OptChain(OptChainExpr {
3016 span: DUMMY_SP,
3017 base: Box::new(OptChainBase::Member(MemberExpr {
3018 span: DUMMY_SP,
3019 obj: expr,
3020 prop: MemberProp::Ident(IdentName::new(prop, DUMMY_SP)),
3021 })),
3022 optional,
3023 }));
3024 }
3025 }
3026
3027 expr
3028 }
3029}
3030
3031fn emit_error(error_kind: ServerActionsErrorKind) {
3032 let (span, msg) = match error_kind {
3033 ServerActionsErrorKind::ExportedSyncFunction {
3034 span,
3035 in_action_file,
3036 } => (
3037 span,
3038 formatdoc! {
3039 r#"
3040 Only async functions are allowed to be exported in a {directive} file.
3041 "#,
3042 directive = if in_action_file {
3043 "\"use server\""
3044 } else {
3045 "\"use cache\""
3046 }
3047 },
3048 ),
3049 ServerActionsErrorKind::ForbiddenExpression {
3050 span,
3051 expr,
3052 directive,
3053 } => (
3054 span,
3055 formatdoc! {
3056 r#"
3057 {subject} cannot use `{expr}`.
3058 "#,
3059 subject = if let Directive::UseServer = directive {
3060 "Server Actions"
3061 } else {
3062 "\"use cache\" functions"
3063 }
3064 },
3065 ),
3066 ServerActionsErrorKind::InlineUseCacheInClassInstanceMethod { span } => (
3067 span,
3068 formatdoc! {
3069 r#"
3070 It is not allowed to define inline "use cache" annotated class instance methods.
3071 To define cached functions, use functions, object method properties, or static class methods instead.
3072 "#
3073 },
3074 ),
3075 ServerActionsErrorKind::InlineUseCacheInClientComponent { span } => (
3076 span,
3077 formatdoc! {
3078 r#"
3079 It is not allowed to define inline "use cache" annotated functions in Client Components.
3080 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.
3081 "#
3082 },
3083 ),
3084 ServerActionsErrorKind::InlineUseServerInClassInstanceMethod { span } => (
3085 span,
3086 formatdoc! {
3087 r#"
3088 It is not allowed to define inline "use server" annotated class instance methods.
3089 To define Server Actions, use functions, object method properties, or static class methods instead.
3090 "#
3091 },
3092 ),
3093 ServerActionsErrorKind::InlineUseServerInClientComponent { span } => (
3094 span,
3095 formatdoc! {
3096 r#"
3097 It is not allowed to define inline "use server" annotated Server Actions in Client Components.
3098 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.
3099
3100 Read more: https://nextjs.org/docs/app/api-reference/functions/server-actions#with-client-components
3101 "#
3102 },
3103 ),
3104 ServerActionsErrorKind::InlineSyncFunction { span, directive } => (
3105 span,
3106 formatdoc! {
3107 r#"
3108 {subject} must be async functions.
3109 "#,
3110 subject = if let Directive::UseServer = directive {
3111 "Server Actions"
3112 } else {
3113 "\"use cache\" functions"
3114 }
3115 },
3116 ),
3117 ServerActionsErrorKind::MisplacedDirective {
3118 span,
3119 directive,
3120 location,
3121 } => (
3122 span,
3123 formatdoc! {
3124 r#"
3125 The "{directive}" directive must be at the top of the {location}.
3126 "#,
3127 location = match location {
3128 DirectiveLocation::Module => "file",
3129 DirectiveLocation::FunctionBody => "function body",
3130 }
3131 },
3132 ),
3133 ServerActionsErrorKind::MisplacedWrappedDirective {
3134 span,
3135 directive,
3136 location,
3137 } => (
3138 span,
3139 formatdoc! {
3140 r#"
3141 The "{directive}" directive must be at the top of the {location}, and cannot be wrapped in parentheses.
3142 "#,
3143 location = match location {
3144 DirectiveLocation::Module => "file",
3145 DirectiveLocation::FunctionBody => "function body",
3146 }
3147 },
3148 ),
3149 ServerActionsErrorKind::MisspelledDirective {
3150 span,
3151 directive,
3152 expected_directive,
3153 } => (
3154 span,
3155 formatdoc! {
3156 r#"
3157 Did you mean "{expected_directive}"? "{directive}" is not a supported directive name."
3158 "#
3159 },
3160 ),
3161 ServerActionsErrorKind::MultipleDirectives { span, location } => (
3162 span,
3163 formatdoc! {
3164 r#"
3165 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.
3166 "#,
3167 location = match location {
3168 DirectiveLocation::Module => "file",
3169 DirectiveLocation::FunctionBody => "function body",
3170 }
3171 },
3172 ),
3173 ServerActionsErrorKind::UnknownCacheKind { span, cache_kind } => (
3174 span,
3175 formatdoc! {
3176 r#"
3177 Unknown cache kind "{cache_kind}". Please configure a cache handler for this kind in the "experimental.cacheHandlers" object in your Next.js config.
3178 "#
3179 },
3180 ),
3181 ServerActionsErrorKind::UseCacheWithoutExperimentalFlag { span, directive } => (
3182 span,
3183 formatdoc! {
3184 r#"
3185 To use "{directive}", please enable the experimental feature flag "useCache" in your Next.js config.
3186
3187 Read more: https://nextjs.org/docs/canary/app/api-reference/directives/use-cache#usage
3188 "#
3189 },
3190 ),
3191 ServerActionsErrorKind::WrappedDirective { span, directive } => (
3192 span,
3193 formatdoc! {
3194 r#"
3195 The "{directive}" directive cannot be wrapped in parentheses.
3196 "#
3197 },
3198 ),
3199 };
3200
3201 HANDLER.with(|handler| handler.struct_span_err(span, &msg).emit());
3202}
3203
3204fn program_to_data_url(
3205 file_name: &str,
3206 cm: &Arc<SourceMap>,
3207 body: Vec<ModuleItem>,
3208 prepend_comment: Comment,
3209) -> String {
3210 let module_span = Span::dummy_with_cmt();
3211 let comments = SingleThreadedComments::default();
3212 comments.add_leading(module_span.lo, prepend_comment);
3213
3214 let program = &Program::Module(Module {
3215 span: module_span,
3216 body,
3217 shebang: None,
3218 });
3219
3220 let mut output = vec![];
3221 let mut mappings = vec![];
3222 let mut emitter = Emitter {
3223 cfg: codegen::Config::default().with_minify(true),
3224 cm: cm.clone(),
3225 wr: Box::new(JsWriter::new(
3226 cm.clone(),
3227 " ",
3228 &mut output,
3229 Some(&mut mappings),
3230 )),
3231 comments: Some(&comments),
3232 };
3233
3234 emitter.emit_program(program).unwrap();
3235 drop(emitter);
3236
3237 pub struct InlineSourcesContentConfig<'a> {
3238 folder_path: Option<&'a Path>,
3239 }
3240 impl SourceMapGenConfig for InlineSourcesContentConfig<'_> {
3243 fn file_name_to_source(&self, file: &FileName) -> String {
3244 let FileName::Custom(file) = file else {
3245 return file.to_string();
3247 };
3248 let Some(folder_path) = &self.folder_path else {
3249 return file.to_string();
3250 };
3251
3252 if let Some(rel_path) = diff_paths(file, folder_path) {
3253 format!("./{}", rel_path.display())
3254 } else {
3255 file.to_string()
3256 }
3257 }
3258
3259 fn inline_sources_content(&self, _f: &FileName) -> bool {
3260 true
3261 }
3262 }
3263
3264 let map = cm.build_source_map(
3265 &mappings,
3266 None,
3267 InlineSourcesContentConfig {
3268 folder_path: PathBuf::from(format!("[project]/{file_name}")).parent(),
3269 },
3270 );
3271 let map = {
3272 if map.get_token_count() > 0 {
3273 let mut buf = vec![];
3274 map.to_writer(&mut buf)
3275 .expect("failed to generate sourcemap");
3276 Some(buf)
3277 } else {
3278 None
3279 }
3280 };
3281
3282 let mut output = String::from_utf8(output).expect("codegen generated non-utf8 output");
3283 if let Some(map) = map {
3284 output.extend(
3285 format!(
3286 "\n//# sourceMappingURL=data:application/json;base64,{}",
3287 Base64Display::new(&map, &BASE64_STANDARD)
3288 )
3289 .chars(),
3290 );
3291 }
3292 format!("data:text/javascript,{}", urlencoding::encode(&output))
3293}