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, Wtf8Atom},
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)]
74struct ServerReferenceExport {
75 ident: Ident,
76 export_name: ModuleExportName,
77 reference_id: Atom,
78 needs_cache_runtime_wrapper: bool,
79}
80
81#[derive(Clone, Debug, serde::Serialize)]
83struct ServerReferenceExportInfo {
84 name: Atom,
85}
86
87#[derive(Clone, Debug)]
88enum ServerActionsErrorKind {
89 ExportedSyncFunction {
90 span: Span,
91 in_action_file: bool,
92 },
93 ForbiddenExpression {
94 span: Span,
95 expr: String,
96 directive: Directive,
97 },
98 InlineSyncFunction {
99 span: Span,
100 directive: Directive,
101 },
102 InlineUseCacheInClassInstanceMethod {
103 span: Span,
104 },
105 InlineUseCacheInClientComponent {
106 span: Span,
107 },
108 InlineUseServerInClassInstanceMethod {
109 span: Span,
110 },
111 InlineUseServerInClientComponent {
112 span: Span,
113 },
114 MisplacedDirective {
115 span: Span,
116 directive: String,
117 location: DirectiveLocation,
118 },
119 MisplacedWrappedDirective {
120 span: Span,
121 directive: String,
122 location: DirectiveLocation,
123 },
124 MisspelledDirective {
125 span: Span,
126 directive: String,
127 expected_directive: String,
128 },
129 MultipleDirectives {
130 span: Span,
131 location: DirectiveLocation,
132 },
133 UnknownCacheKind {
134 span: Span,
135 cache_kind: RcStr,
136 },
137 UseCacheWithoutCacheComponents {
138 span: Span,
139 directive: String,
140 },
141 WrappedDirective {
142 span: Span,
143 directive: String,
144 },
145}
146
147#[allow(clippy::too_many_arguments)]
148#[tracing::instrument(level = tracing::Level::TRACE, skip_all)]
149pub fn server_actions<C: Comments>(
150 file_name: &FileName,
151 file_query: Option<RcStr>,
152 config: Config,
153 comments: C,
154 unresolved_mark: Mark,
155 cm: Arc<SourceMap>,
156 use_cache_telemetry_tracker: Rc<RefCell<FxHashMap<String, usize>>>,
157 mode: ServerActionsMode,
158) -> impl Pass {
159 visit_mut_pass(ServerActions {
160 config,
161 mode,
162 comments,
163 cm,
164 file_name: file_name.to_string(),
165 file_query,
166 start_pos: BytePos(0),
167 file_directive: None,
168 current_export_name: None,
169 fn_decl_ident: None,
170 in_callee: false,
171 has_action: false,
172 has_cache: false,
173 this_status: ThisStatus::Allowed,
174
175 reference_index: 0,
176 in_module_level: true,
177 should_track_names: false,
178 has_server_reference_with_bound_args: false,
179
180 names: Default::default(),
181 declared_idents: Default::default(),
182
183 rewrite_fn_decl_to_proxy_decl: None,
185 rewrite_default_fn_expr_to_proxy_expr: None,
186 rewrite_expr_to_proxy_expr: None,
187
188 annotations: Default::default(),
189 extra_items: Default::default(),
190 hoisted_extra_items: Default::default(),
191 reference_ids_by_export_name: Default::default(),
192 server_reference_exports: Default::default(),
193
194 private_ctxt: SyntaxContext::empty().apply_mark(Mark::new()),
195 unresolved_ctxt: SyntaxContext::empty().apply_mark(unresolved_mark),
196
197 arrow_or_fn_expr_ident: None,
198 export_name_by_local_id: Default::default(),
199 local_ids_that_need_cache_runtime_wrapper_if_exported: FxHashSet::default(),
200
201 use_cache_telemetry_tracker,
202 })
203}
204
205fn generate_server_references_comment(
208 export_infos_ordered_by_reference_id: &BTreeMap<&Atom, ServerReferenceExportInfo>,
209 entry_path_query: Option<(&str, &str)>,
210) -> String {
211 format!(
212 " __next_internal_action_entry_do_not_use__ {} ",
213 if let Some(entry_path_query) = entry_path_query {
214 serde_json::to_string(&(
215 &export_infos_ordered_by_reference_id,
216 entry_path_query.0,
217 entry_path_query.1,
218 ))
219 } else {
220 serde_json::to_string(&export_infos_ordered_by_reference_id)
221 }
222 .unwrap()
223 )
224}
225
226struct ServerActions<C: Comments> {
227 #[allow(unused)]
228 config: Config,
229 file_name: String,
230 file_query: Option<RcStr>,
231 comments: C,
232 cm: Arc<SourceMap>,
233 mode: ServerActionsMode,
234
235 start_pos: BytePos,
236 file_directive: Option<Directive>,
237 current_export_name: Option<ModuleExportName>,
238 fn_decl_ident: Option<Ident>,
239 in_callee: bool,
240 has_action: bool,
241 has_cache: bool,
242 this_status: ThisStatus,
243
244 reference_index: u32,
245 in_module_level: bool,
246 should_track_names: bool,
247 has_server_reference_with_bound_args: bool,
248
249 names: Vec<Name>,
250 declared_idents: Vec<Ident>,
251
252 rewrite_fn_decl_to_proxy_decl: Option<VarDecl>,
254 rewrite_default_fn_expr_to_proxy_expr: Option<Box<Expr>>,
255 rewrite_expr_to_proxy_expr: Option<Box<Expr>>,
256
257 annotations: Vec<Stmt>,
258 extra_items: Vec<ModuleItem>,
259 hoisted_extra_items: Vec<ModuleItem>,
260
261 reference_ids_by_export_name: FxIndexMap<ModuleExportName, Atom>,
263
264 server_reference_exports: Vec<ServerReferenceExport>,
266
267 private_ctxt: SyntaxContext,
268 unresolved_ctxt: SyntaxContext,
269
270 arrow_or_fn_expr_ident: Option<Ident>,
271 export_name_by_local_id: FxIndexMap<Id, ModuleExportName>,
272
273 local_ids_that_need_cache_runtime_wrapper_if_exported: FxHashSet<Id>,
279
280 use_cache_telemetry_tracker: Rc<RefCell<FxHashMap<String, usize>>>,
281}
282
283impl<C: Comments> ServerActions<C> {
284 fn generate_server_reference_id(
285 &self,
286 export_name: &ModuleExportName,
287 is_cache: bool,
288 params: Option<&Vec<Param>>,
289 ) -> Atom {
290 let mut hasher = Sha1::new();
295 hasher.update(self.config.hash_salt.as_bytes());
296 hasher.update(self.file_name.as_bytes());
297 hasher.update(b":");
298
299 let export_name_bytes = match export_name {
300 ModuleExportName::Ident(ident) => &ident.sym.as_bytes(),
301 ModuleExportName::Str(s) => &s.value.as_bytes(),
302 };
303
304 hasher.update(export_name_bytes);
305
306 let mut result = hasher.finalize().to_vec();
307
308 let type_bit = if is_cache { 1u8 } else { 0u8 };
336 let mut arg_mask = 0u8;
337 let mut rest_args = 0u8;
338
339 if let Some(params) = params {
340 for (i, param) in params.iter().enumerate() {
345 if let Pat::Rest(_) = param.pat {
346 arg_mask = 0b111111;
349 rest_args = 0b1;
350 break;
351 }
352 if i < 6 {
353 arg_mask |= 0b1 << (5 - i);
354 } else {
355 rest_args = 0b1;
358 break;
359 }
360 }
361 } else {
362 arg_mask = 0b111111;
365 rest_args = 0b1;
366 }
367
368 result.push((type_bit << 7) | (arg_mask << 1) | rest_args);
369 result.rotate_right(1);
370
371 Atom::from(hex_encode(result))
372 }
373
374 fn is_default_export(&self) -> bool {
375 matches!(
376 self.current_export_name,
377 Some(ModuleExportName::Ident(ref i)) if i.sym == *"default"
378 )
379 }
380
381 fn gen_action_ident(&mut self) -> Atom {
382 let id: Atom = format!("$$RSC_SERVER_ACTION_{0}", self.reference_index).into();
383 self.reference_index += 1;
384 id
385 }
386
387 fn gen_cache_ident(&mut self) -> Atom {
388 let id: Atom = format!("$$RSC_SERVER_CACHE_{0}", self.reference_index).into();
389 self.reference_index += 1;
390 id
391 }
392
393 fn create_bound_action_args_array_pat(&mut self, arg_len: usize) -> Pat {
394 Pat::Array(ArrayPat {
395 span: DUMMY_SP,
396 elems: (0..arg_len)
397 .map(|i| {
398 Some(Pat::Ident(
399 Ident::new(
400 format!("$$ACTION_ARG_{i}").into(),
401 DUMMY_SP,
402 self.private_ctxt,
403 )
404 .into(),
405 ))
406 })
407 .collect(),
408 optional: false,
409 type_ann: None,
410 })
411 }
412
413 fn get_directive_for_function(
416 &mut self,
417 maybe_body: Option<&mut BlockStmt>,
418 ) -> Option<Directive> {
419 let mut directive: Option<Directive> = None;
420
421 if let Some(body) = maybe_body {
424 let directive_visitor = &mut DirectiveVisitor {
425 config: &self.config,
426 directive: None,
427 has_file_directive: self.file_directive.is_some(),
428 is_allowed_position: true,
429 location: DirectiveLocation::FunctionBody,
430 use_cache_telemetry_tracker: self.use_cache_telemetry_tracker.clone(),
431 };
432
433 body.stmts.retain(|stmt| {
434 let has_directive = directive_visitor.visit_stmt(stmt);
435
436 !has_directive
437 });
438
439 directive = directive_visitor.directive.clone();
440 }
441
442 if self.current_export_name.is_some()
444 && directive.is_none()
445 && self.file_directive.is_some()
446 {
447 return self.file_directive.clone();
448 }
449
450 directive
451 }
452
453 fn get_directive_for_module(&mut self, stmts: &mut Vec<ModuleItem>) -> Option<Directive> {
454 let directive_visitor = &mut DirectiveVisitor {
455 config: &self.config,
456 directive: None,
457 has_file_directive: false,
458 is_allowed_position: true,
459 location: DirectiveLocation::Module,
460 use_cache_telemetry_tracker: self.use_cache_telemetry_tracker.clone(),
461 };
462
463 stmts.retain(|item| {
464 if let ModuleItem::Stmt(stmt) = item {
465 let has_directive = directive_visitor.visit_stmt(stmt);
466
467 !has_directive
468 } else {
469 directive_visitor.is_allowed_position = false;
470 true
471 }
472 });
473
474 directive_visitor.directive.clone()
475 }
476
477 fn maybe_hoist_and_create_proxy_for_server_action_arrow_expr(
478 &mut self,
479 ids_from_closure: Vec<Name>,
480 arrow: &mut ArrowExpr,
481 ) -> Box<Expr> {
482 let mut new_params: Vec<Param> = vec![];
483
484 if !ids_from_closure.is_empty() {
485 new_params.push(Param {
487 span: DUMMY_SP,
488 decorators: vec![],
489 pat: Pat::Ident(IdentName::new(atom!("$$ACTION_CLOSURE_BOUND"), DUMMY_SP).into()),
490 });
491 }
492
493 for p in arrow.params.iter() {
494 new_params.push(Param::from(p.clone()));
495 }
496
497 let action_name = self.gen_action_ident();
498 let action_ident = Ident::new(action_name.clone(), arrow.span, self.private_ctxt);
499 let action_id = self.generate_server_reference_id(
500 &ModuleExportName::Ident(action_ident.clone()),
501 false,
502 Some(&new_params),
503 );
504
505 self.has_action = true;
506 self.reference_ids_by_export_name.insert(
507 ModuleExportName::Ident(action_ident.clone()),
508 action_id.clone(),
509 );
510
511 if self.current_export_name.is_some() {
514 if let Some(arrow_ident) = &self.arrow_or_fn_expr_ident {
515 self.export_name_by_local_id
516 .swap_remove(&arrow_ident.to_id());
517 }
518 }
519
520 if let BlockStmtOrExpr::BlockStmt(block) = &mut *arrow.body {
521 block.visit_mut_with(&mut ClosureReplacer {
522 used_ids: &ids_from_closure,
523 private_ctxt: self.private_ctxt,
524 });
525 }
526
527 let mut new_body: BlockStmtOrExpr = *arrow.body.clone();
528
529 if !ids_from_closure.is_empty() {
530 let decryption_decl = VarDecl {
534 span: DUMMY_SP,
535 kind: VarDeclKind::Var,
536 declare: false,
537 decls: vec![VarDeclarator {
538 span: DUMMY_SP,
539 name: self.create_bound_action_args_array_pat(ids_from_closure.len()),
540 init: Some(Box::new(Expr::Await(AwaitExpr {
541 span: DUMMY_SP,
542 arg: Box::new(Expr::Call(CallExpr {
543 span: DUMMY_SP,
544 callee: quote_ident!("decryptActionBoundArgs").as_callee(),
545 args: vec![
546 action_id.clone().as_arg(),
547 quote_ident!("$$ACTION_CLOSURE_BOUND").as_arg(),
548 ],
549 ..Default::default()
550 })),
551 }))),
552 definite: Default::default(),
553 }],
554 ..Default::default()
555 };
556
557 match &mut new_body {
558 BlockStmtOrExpr::BlockStmt(body) => {
559 body.stmts.insert(0, decryption_decl.into());
560 }
561 BlockStmtOrExpr::Expr(body_expr) => {
562 new_body = BlockStmtOrExpr::BlockStmt(BlockStmt {
563 span: DUMMY_SP,
564 stmts: vec![
565 decryption_decl.into(),
566 Stmt::Return(ReturnStmt {
567 span: DUMMY_SP,
568 arg: Some(body_expr.take()),
569 }),
570 ],
571 ..Default::default()
572 });
573 }
574 }
575 }
576
577 self.hoisted_extra_items
580 .push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
581 span: DUMMY_SP,
582 decl: VarDecl {
583 kind: VarDeclKind::Const,
584 span: DUMMY_SP,
585 decls: vec![VarDeclarator {
586 span: DUMMY_SP,
587 name: Pat::Ident(action_ident.clone().into()),
588 definite: false,
589 init: Some(Box::new(Expr::Fn(FnExpr {
590 ident: self.arrow_or_fn_expr_ident.clone(),
591 function: Box::new(Function {
592 params: new_params,
593 body: match new_body {
594 BlockStmtOrExpr::BlockStmt(body) => Some(body),
595 BlockStmtOrExpr::Expr(expr) => Some(BlockStmt {
596 span: DUMMY_SP,
597 stmts: vec![Stmt::Return(ReturnStmt {
598 span: DUMMY_SP,
599 arg: Some(expr),
600 })],
601 ..Default::default()
602 }),
603 },
604 is_async: true,
605 ..Default::default()
606 }),
607 }))),
608 }],
609 declare: Default::default(),
610 ctxt: self.private_ctxt,
611 }
612 .into(),
613 })));
614
615 self.hoisted_extra_items
616 .push(ModuleItem::Stmt(Stmt::Expr(ExprStmt {
617 span: DUMMY_SP,
618 expr: Box::new(annotate_ident_as_server_reference(
619 action_ident.clone(),
620 action_id.clone(),
621 arrow.span,
622 )),
623 })));
624
625 if ids_from_closure.is_empty() {
626 Box::new(action_ident.clone().into())
627 } else {
628 self.has_server_reference_with_bound_args = true;
629 Box::new(bind_args_to_ident(
630 action_ident.clone(),
631 ids_from_closure
632 .iter()
633 .cloned()
634 .map(|id| Some(id.as_arg()))
635 .collect(),
636 action_id.clone(),
637 ))
638 }
639 }
640
641 fn maybe_hoist_and_create_proxy_for_server_action_function(
642 &mut self,
643 ids_from_closure: Vec<Name>,
644 function: &mut Function,
645 fn_name: Option<Ident>,
646 ) -> Box<Expr> {
647 let mut new_params: Vec<Param> = vec![];
648
649 if !ids_from_closure.is_empty() {
650 new_params.push(Param {
652 span: DUMMY_SP,
653 decorators: vec![],
654 pat: Pat::Ident(IdentName::new(atom!("$$ACTION_CLOSURE_BOUND"), DUMMY_SP).into()),
655 });
656 }
657
658 new_params.append(&mut function.params);
659
660 let action_name: Atom = self.gen_action_ident();
661 let mut action_ident = Ident::new(action_name.clone(), function.span, self.private_ctxt);
662 if action_ident.span.lo == self.start_pos {
663 action_ident.span = Span::dummy_with_cmt();
664 }
665
666 let action_id = self.generate_server_reference_id(
667 &ModuleExportName::Ident(action_ident.clone()),
668 false,
669 Some(&new_params),
670 );
671
672 self.has_action = true;
673 self.reference_ids_by_export_name.insert(
674 ModuleExportName::Ident(action_ident.clone()),
675 action_id.clone(),
676 );
677
678 if self.current_export_name.is_some() {
681 if let Some(ref fn_name) = fn_name {
682 self.export_name_by_local_id.swap_remove(&fn_name.to_id());
683 }
684 }
685
686 function.body.visit_mut_with(&mut ClosureReplacer {
687 used_ids: &ids_from_closure,
688 private_ctxt: self.private_ctxt,
689 });
690
691 let mut new_body: Option<BlockStmt> = function.body.clone();
692
693 if !ids_from_closure.is_empty() {
694 let decryption_decl = VarDecl {
698 span: DUMMY_SP,
699 kind: VarDeclKind::Var,
700 decls: vec![VarDeclarator {
701 span: DUMMY_SP,
702 name: self.create_bound_action_args_array_pat(ids_from_closure.len()),
703 init: Some(Box::new(Expr::Await(AwaitExpr {
704 span: DUMMY_SP,
705 arg: Box::new(Expr::Call(CallExpr {
706 span: DUMMY_SP,
707 callee: quote_ident!("decryptActionBoundArgs").as_callee(),
708 args: vec![
709 action_id.clone().as_arg(),
710 quote_ident!("$$ACTION_CLOSURE_BOUND").as_arg(),
711 ],
712 ..Default::default()
713 })),
714 }))),
715 definite: Default::default(),
716 }],
717 ..Default::default()
718 };
719
720 if let Some(body) = &mut new_body {
721 body.stmts.insert(0, decryption_decl.into());
722 } else {
723 new_body = Some(BlockStmt {
724 span: DUMMY_SP,
725 stmts: vec![decryption_decl.into()],
726 ..Default::default()
727 });
728 }
729 }
730
731 self.hoisted_extra_items
734 .push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
735 span: DUMMY_SP,
736 decl: VarDecl {
737 kind: VarDeclKind::Const,
738 span: DUMMY_SP,
739 decls: vec![VarDeclarator {
740 span: DUMMY_SP, name: Pat::Ident(action_ident.clone().into()),
742 definite: false,
743 init: Some(Box::new(Expr::Fn(FnExpr {
744 ident: fn_name,
745 function: Box::new(Function {
746 params: new_params,
747 body: new_body,
748 ..function.take()
749 }),
750 }))),
751 }],
752 declare: Default::default(),
753 ctxt: self.private_ctxt,
754 }
755 .into(),
756 })));
757
758 self.hoisted_extra_items
759 .push(ModuleItem::Stmt(Stmt::Expr(ExprStmt {
760 span: DUMMY_SP,
761 expr: Box::new(annotate_ident_as_server_reference(
762 action_ident.clone(),
763 action_id.clone(),
764 function.span,
765 )),
766 })));
767
768 if ids_from_closure.is_empty() {
769 Box::new(action_ident.clone().into())
770 } else {
771 self.has_server_reference_with_bound_args = true;
772 Box::new(bind_args_to_ident(
773 action_ident.clone(),
774 ids_from_closure
775 .iter()
776 .cloned()
777 .map(|id| Some(id.as_arg()))
778 .collect(),
779 action_id.clone(),
780 ))
781 }
782 }
783
784 fn maybe_hoist_and_create_proxy_for_cache_arrow_expr(
785 &mut self,
786 ids_from_closure: Vec<Name>,
787 cache_kind: RcStr,
788 arrow: &mut ArrowExpr,
789 ) -> Box<Expr> {
790 let mut new_params: Vec<Param> = vec![];
791
792 if !ids_from_closure.is_empty() {
796 new_params.push(Param {
797 span: DUMMY_SP,
798 decorators: vec![],
799 pat: self.create_bound_action_args_array_pat(ids_from_closure.len()),
800 });
801 }
802
803 for p in arrow.params.iter() {
804 new_params.push(Param::from(p.clone()));
805 }
806
807 let cache_name: Atom = self.gen_cache_ident();
808 let export_name: Atom = cache_name.clone();
809
810 let reference_id = self.generate_server_reference_id(
811 &ModuleExportName::Ident(export_name.clone().into()),
812 true,
813 Some(&new_params),
814 );
815
816 self.has_cache = true;
817 self.reference_ids_by_export_name.insert(
818 ModuleExportName::Ident(export_name.clone().into()),
819 reference_id.clone(),
820 );
821
822 if self.current_export_name.is_some() {
825 if let Some(arrow_ident) = &self.arrow_or_fn_expr_ident {
826 self.export_name_by_local_id
827 .swap_remove(&arrow_ident.to_id());
828 }
829 }
830
831 if let BlockStmtOrExpr::BlockStmt(block) = &mut *arrow.body {
832 block.visit_mut_with(&mut ClosureReplacer {
833 used_ids: &ids_from_closure,
834 private_ctxt: self.private_ctxt,
835 });
836 }
837
838 let inner_fn_body = match *arrow.body.take() {
839 BlockStmtOrExpr::BlockStmt(body) => Some(body),
840 BlockStmtOrExpr::Expr(expr) => Some(BlockStmt {
841 stmts: vec![Stmt::Return(ReturnStmt {
842 span: DUMMY_SP,
843 arg: Some(expr),
844 })],
845 ..Default::default()
846 }),
847 };
848
849 let cache_ident = create_and_hoist_cache_function(
850 cache_kind.as_str(),
851 reference_id.clone(),
852 ids_from_closure.len(),
853 cache_name,
854 self.arrow_or_fn_expr_ident.clone(),
855 new_params.clone(),
856 inner_fn_body,
857 arrow.span,
858 &mut self.hoisted_extra_items,
859 self.unresolved_ctxt,
860 );
861
862 if let Some(Ident { sym, .. }) = &self.arrow_or_fn_expr_ident {
863 self.hoisted_extra_items
864 .push(ModuleItem::Stmt(assign_name_to_ident(
865 &cache_ident,
866 sym.as_str(),
867 self.unresolved_ctxt,
868 )));
869 }
870
871 let bound_args: Vec<_> = ids_from_closure
872 .iter()
873 .cloned()
874 .map(|id| Some(id.as_arg()))
875 .collect();
876
877 if bound_args.is_empty() {
878 Box::new(cache_ident.clone().into())
879 } else {
880 self.has_server_reference_with_bound_args = true;
881 Box::new(bind_args_to_ident(
882 cache_ident.clone(),
883 bound_args,
884 reference_id.clone(),
885 ))
886 }
887 }
888
889 fn maybe_hoist_and_create_proxy_for_cache_function(
890 &mut self,
891 ids_from_closure: Vec<Name>,
892 fn_name: Option<Ident>,
893 cache_kind: RcStr,
894 function: &mut Function,
895 ) -> Box<Expr> {
896 let mut new_params: Vec<Param> = vec![];
897
898 if !ids_from_closure.is_empty() {
902 new_params.push(Param {
903 span: DUMMY_SP,
904 decorators: vec![],
905 pat: self.create_bound_action_args_array_pat(ids_from_closure.len()),
906 });
907 }
908
909 for p in function.params.iter() {
910 new_params.push(p.clone());
911 }
912
913 let cache_name: Atom = self.gen_cache_ident();
914
915 let reference_id = self.generate_server_reference_id(
916 &ModuleExportName::Ident(cache_name.clone().into()),
917 true,
918 Some(&new_params),
919 );
920
921 self.has_cache = true;
922 self.reference_ids_by_export_name.insert(
923 ModuleExportName::Ident(cache_name.clone().into()),
924 reference_id.clone(),
925 );
926
927 if self.current_export_name.is_some() {
930 if let Some(ref fn_name) = fn_name {
931 self.export_name_by_local_id.swap_remove(&fn_name.to_id());
932 }
933 }
934
935 function.body.visit_mut_with(&mut ClosureReplacer {
936 used_ids: &ids_from_closure,
937 private_ctxt: self.private_ctxt,
938 });
939
940 let function_body = function.body.take();
941 let function_span = function.span;
942
943 let cache_ident = create_and_hoist_cache_function(
944 cache_kind.as_str(),
945 reference_id.clone(),
946 ids_from_closure.len(),
947 cache_name,
948 fn_name.clone(),
949 new_params.clone(),
950 function_body,
951 function_span,
952 &mut self.hoisted_extra_items,
953 self.unresolved_ctxt,
954 );
955
956 if let Some(Ident { ref sym, .. }) = fn_name {
957 self.hoisted_extra_items
958 .push(ModuleItem::Stmt(assign_name_to_ident(
959 &cache_ident,
960 sym.as_str(),
961 self.unresolved_ctxt,
962 )));
963 } else if self.is_default_export() {
964 self.hoisted_extra_items
965 .push(ModuleItem::Stmt(assign_name_to_ident(
966 &cache_ident,
967 "default",
968 self.unresolved_ctxt,
969 )));
970 }
971
972 let bound_args: Vec<_> = ids_from_closure
973 .iter()
974 .cloned()
975 .map(|id| Some(id.as_arg()))
976 .collect();
977
978 if bound_args.is_empty() {
979 Box::new(cache_ident.clone().into())
980 } else {
981 self.has_server_reference_with_bound_args = true;
982 Box::new(bind_args_to_ident(
983 cache_ident.clone(),
984 bound_args,
985 reference_id.clone(),
986 ))
987 }
988 }
989
990 fn validate_async_function(
993 &self,
994 is_async: bool,
995 span: Span,
996 fn_name: Option<&Ident>,
997 directive: &Directive,
998 ) -> bool {
999 if is_async {
1000 true
1001 } else {
1002 emit_error(ServerActionsErrorKind::InlineSyncFunction {
1003 span: fn_name.as_ref().map_or(span, |ident| ident.span),
1004 directive: directive.clone(),
1005 });
1006 false
1007 }
1008 }
1009
1010 fn register_server_action_export(
1012 &mut self,
1013 export_name: &ModuleExportName,
1014 fn_name: Option<&Ident>,
1015 params: Option<&Vec<Param>>,
1016 span: Span,
1017 take_fn_or_arrow_expr: &mut dyn FnMut() -> Box<Expr>,
1018 ) {
1019 if let Some(fn_name) = fn_name {
1020 let reference_id = self.generate_server_reference_id(export_name, false, params);
1021
1022 self.has_action = true;
1023 self.reference_ids_by_export_name
1024 .insert(export_name.clone(), reference_id.clone());
1025
1026 self.server_reference_exports.push(ServerReferenceExport {
1027 ident: fn_name.clone(),
1028 export_name: export_name.clone(),
1029 reference_id: reference_id.clone(),
1030 needs_cache_runtime_wrapper: false,
1031 });
1032 } else if self.is_default_export() {
1033 let action_ident = Ident::new(self.gen_action_ident(), span, self.private_ctxt);
1034 let reference_id = self.generate_server_reference_id(export_name, false, params);
1035
1036 self.has_action = true;
1037 self.reference_ids_by_export_name
1038 .insert(export_name.clone(), reference_id.clone());
1039
1040 self.server_reference_exports.push(ServerReferenceExport {
1041 ident: action_ident.clone(),
1042 export_name: export_name.clone(),
1043 reference_id: reference_id.clone(),
1044 needs_cache_runtime_wrapper: false,
1045 });
1046
1047 if self.config.is_react_server_layer {
1049 self.hoisted_extra_items
1050 .push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl {
1051 kind: VarDeclKind::Const,
1052 decls: vec![VarDeclarator {
1053 span: DUMMY_SP,
1054 name: Pat::Ident(action_ident.clone().into()),
1055 init: Some(take_fn_or_arrow_expr()),
1056 definite: false,
1057 }],
1058 ..Default::default()
1059 })))));
1060
1061 self.hoisted_extra_items
1062 .push(ModuleItem::Stmt(assign_name_to_ident(
1063 &action_ident,
1064 "default",
1065 self.unresolved_ctxt,
1066 )));
1067
1068 self.rewrite_default_fn_expr_to_proxy_expr =
1069 Some(Box::new(Expr::Ident(action_ident)));
1070 }
1071 }
1072 }
1073
1074 fn register_cache_export_on_client(
1076 &mut self,
1077 export_name: &ModuleExportName,
1078 fn_name: Option<&Ident>,
1079 params: Option<&Vec<Param>>,
1080 span: Span,
1081 ) {
1082 if let Some(fn_name) = fn_name {
1083 let reference_id = self.generate_server_reference_id(export_name, true, params);
1084
1085 self.has_cache = true;
1086 self.reference_ids_by_export_name
1087 .insert(export_name.clone(), reference_id.clone());
1088
1089 self.server_reference_exports.push(ServerReferenceExport {
1090 ident: fn_name.clone(),
1091 export_name: export_name.clone(),
1092 reference_id: reference_id.clone(),
1093 needs_cache_runtime_wrapper: false,
1094 });
1095 } else if self.is_default_export() {
1096 let cache_ident = Ident::new(self.gen_cache_ident(), span, self.private_ctxt);
1097 let reference_id = self.generate_server_reference_id(export_name, true, params);
1098
1099 self.has_cache = true;
1100 self.reference_ids_by_export_name
1101 .insert(export_name.clone(), reference_id.clone());
1102
1103 self.server_reference_exports.push(ServerReferenceExport {
1104 ident: cache_ident.clone(),
1105 export_name: export_name.clone(),
1106 reference_id: reference_id.clone(),
1107 needs_cache_runtime_wrapper: false,
1108 });
1109 }
1110 }
1111}
1112
1113impl<C: Comments> VisitMut for ServerActions<C> {
1114 fn visit_mut_export_decl(&mut self, decl: &mut ExportDecl) {
1115 decl.decl.visit_mut_with(self);
1119 }
1120
1121 fn visit_mut_export_default_decl(&mut self, decl: &mut ExportDefaultDecl) {
1122 let old_current_export_name = self.current_export_name.take();
1123 self.current_export_name = Some(ModuleExportName::Ident(atom!("default").into()));
1124 self.rewrite_default_fn_expr_to_proxy_expr = None;
1125 decl.decl.visit_mut_with(self);
1126 self.current_export_name = old_current_export_name;
1127 }
1128
1129 fn visit_mut_export_default_expr(&mut self, expr: &mut ExportDefaultExpr) {
1130 let old_current_export_name = self.current_export_name.take();
1131 self.current_export_name = Some(ModuleExportName::Ident(atom!("default").into()));
1132 expr.expr.visit_mut_with(self);
1133 self.current_export_name = old_current_export_name;
1134
1135 if matches!(&*expr.expr, Expr::Call(_)) {
1138 if matches!(self.file_directive, Some(Directive::UseServer)) {
1139 let export_name = ModuleExportName::Ident(atom!("default").into());
1140 let action_ident =
1141 Ident::new(self.gen_action_ident(), expr.span, self.private_ctxt);
1142 let action_id = self.generate_server_reference_id(&export_name, false, None);
1143
1144 self.has_action = true;
1145 self.reference_ids_by_export_name
1146 .insert(export_name.clone(), action_id.clone());
1147
1148 self.server_reference_exports.push(ServerReferenceExport {
1149 ident: action_ident.clone(),
1150 export_name: export_name.clone(),
1151 reference_id: action_id.clone(),
1152 needs_cache_runtime_wrapper: false,
1153 });
1154
1155 self.hoisted_extra_items
1156 .push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl {
1157 kind: VarDeclKind::Const,
1158 decls: vec![VarDeclarator {
1159 span: DUMMY_SP,
1160 name: Pat::Ident(action_ident.clone().into()),
1161 init: Some(expr.expr.take()),
1162 definite: false,
1163 }],
1164 ..Default::default()
1165 })))));
1166
1167 self.rewrite_default_fn_expr_to_proxy_expr =
1168 Some(Box::new(Expr::Ident(action_ident)));
1169 } else if matches!(self.file_directive, Some(Directive::UseCache { .. })) {
1170 let cache_ident = Ident::new(self.gen_cache_ident(), expr.span, self.private_ctxt);
1171
1172 self.export_name_by_local_id.insert(
1173 cache_ident.to_id(),
1174 ModuleExportName::Ident(atom!("default").into()),
1175 );
1176
1177 self.local_ids_that_need_cache_runtime_wrapper_if_exported
1178 .insert(cache_ident.to_id());
1179
1180 self.hoisted_extra_items
1181 .push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl {
1182 kind: VarDeclKind::Const,
1183 decls: vec![VarDeclarator {
1184 span: DUMMY_SP,
1185 name: Pat::Ident(cache_ident.into()),
1186 init: Some(expr.expr.take()),
1187 definite: false,
1188 }],
1189 ..Default::default()
1190 })))));
1191
1192 }
1195 }
1196 }
1197
1198 fn visit_mut_fn_expr(&mut self, f: &mut FnExpr) {
1199 let old_this_status = replace(&mut self.this_status, ThisStatus::Allowed);
1200 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.clone();
1201 if let Some(ident) = &f.ident {
1202 self.arrow_or_fn_expr_ident = Some(ident.clone());
1203 }
1204 f.visit_mut_children_with(self);
1205 self.this_status = old_this_status;
1206 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
1207 }
1208
1209 fn visit_mut_function(&mut self, f: &mut Function) {
1210 let directive = self.get_directive_for_function(f.body.as_mut());
1211 let declared_idents_until = self.declared_idents.len();
1212 let old_names = take(&mut self.names);
1213
1214 if let Some(directive) = &directive {
1215 self.this_status = ThisStatus::Forbidden {
1216 directive: directive.clone(),
1217 };
1218 }
1219
1220 {
1222 let old_in_module = replace(&mut self.in_module_level, false);
1223 let should_track_names = directive.is_some() || self.should_track_names;
1224 let old_should_track_names = replace(&mut self.should_track_names, should_track_names);
1225 let old_current_export_name = self.current_export_name.take();
1226 let old_fn_decl_ident = self.fn_decl_ident.take();
1227 f.visit_mut_children_with(self);
1228 self.in_module_level = old_in_module;
1229 self.should_track_names = old_should_track_names;
1230 self.current_export_name = old_current_export_name;
1231 self.fn_decl_ident = old_fn_decl_ident;
1232 }
1233
1234 let mut child_names = take(&mut self.names);
1235
1236 if self.should_track_names {
1237 self.names = [old_names, child_names.clone()].concat();
1238 }
1239
1240 if let Some(directive) = directive {
1241 let fn_name = self
1242 .fn_decl_ident
1243 .as_ref()
1244 .or(self.arrow_or_fn_expr_ident.as_ref())
1245 .cloned();
1246
1247 if !self.validate_async_function(f.is_async, f.span, fn_name.as_ref(), &directive) {
1248 if self.current_export_name.is_some() {
1251 if let Some(fn_name) = fn_name {
1252 self.export_name_by_local_id.swap_remove(&fn_name.to_id());
1253 }
1254 }
1255
1256 return;
1257 }
1258
1259 if HANDLER.with(|handler| handler.has_errors()) {
1262 return;
1263 }
1264
1265 if matches!(self.file_directive, Some(Directive::UseServer))
1268 && matches!(directive, Directive::UseServer)
1269 {
1270 if let Some(export_name) = self.current_export_name.clone() {
1271 let params = f.params.clone();
1272 let span = f.span;
1273
1274 self.register_server_action_export(
1275 &export_name,
1276 fn_name.as_ref(),
1277 Some(¶ms),
1278 span,
1279 &mut || {
1280 Box::new(Expr::Fn(FnExpr {
1281 ident: fn_name.clone(),
1282 function: Box::new(f.take()),
1283 }))
1284 },
1285 );
1286
1287 return;
1288 }
1289 }
1290
1291 if !self.config.is_react_server_layer {
1293 if matches!(directive, Directive::UseCache { .. }) {
1294 if let Some(export_name) = self.current_export_name.clone() {
1295 self.register_cache_export_on_client(
1296 &export_name,
1297 fn_name.as_ref(),
1298 Some(&f.params),
1299 f.span,
1300 );
1301 }
1302 }
1303
1304 return;
1305 }
1306
1307 if let Directive::UseCache { cache_kind } = directive {
1308 retain_names_from_declared_idents(
1311 &mut child_names,
1312 &self.declared_idents[..declared_idents_until],
1313 );
1314
1315 let new_expr = self.maybe_hoist_and_create_proxy_for_cache_function(
1316 child_names.clone(),
1317 self.fn_decl_ident
1318 .as_ref()
1319 .or(self.arrow_or_fn_expr_ident.as_ref())
1320 .cloned(),
1321 cache_kind,
1322 f,
1323 );
1324
1325 if self.is_default_export() {
1326 self.rewrite_default_fn_expr_to_proxy_expr = Some(new_expr);
1331 } else if let Some(ident) = &self.fn_decl_ident {
1332 self.rewrite_fn_decl_to_proxy_decl = Some(VarDecl {
1334 span: DUMMY_SP,
1335 kind: VarDeclKind::Var,
1336 decls: vec![VarDeclarator {
1337 span: DUMMY_SP,
1338 name: Pat::Ident(ident.clone().into()),
1339 init: Some(new_expr),
1340 definite: false,
1341 }],
1342 ..Default::default()
1343 });
1344 } else {
1345 self.rewrite_expr_to_proxy_expr = Some(new_expr);
1346 }
1347 } else {
1348 retain_names_from_declared_idents(
1351 &mut child_names,
1352 &self.declared_idents[..declared_idents_until],
1353 );
1354
1355 let new_expr = self.maybe_hoist_and_create_proxy_for_server_action_function(
1356 child_names,
1357 f,
1358 fn_name,
1359 );
1360
1361 if self.is_default_export() {
1362 self.rewrite_default_fn_expr_to_proxy_expr = Some(new_expr);
1367 } else if let Some(ident) = &self.fn_decl_ident {
1368 self.rewrite_fn_decl_to_proxy_decl = Some(VarDecl {
1371 span: DUMMY_SP,
1372 kind: VarDeclKind::Var,
1373 decls: vec![VarDeclarator {
1374 span: DUMMY_SP,
1375 name: Pat::Ident(ident.clone().into()),
1376 init: Some(new_expr),
1377 definite: false,
1378 }],
1379 ..Default::default()
1380 });
1381 } else {
1382 self.rewrite_expr_to_proxy_expr = Some(new_expr);
1383 }
1384 }
1385 }
1386 }
1387
1388 fn visit_mut_decl(&mut self, d: &mut Decl) {
1389 self.rewrite_fn_decl_to_proxy_decl = None;
1390 d.visit_mut_children_with(self);
1391
1392 if let Some(decl) = &self.rewrite_fn_decl_to_proxy_decl {
1393 *d = (*decl).clone().into();
1394 }
1395
1396 self.rewrite_fn_decl_to_proxy_decl = None;
1397 }
1398
1399 fn visit_mut_fn_decl(&mut self, f: &mut FnDecl) {
1400 let old_this_status = replace(&mut self.this_status, ThisStatus::Allowed);
1401 let old_current_export_name = self.current_export_name.take();
1402 if self.in_module_level {
1403 if let Some(export_name) = self.export_name_by_local_id.get(&f.ident.to_id()) {
1404 self.current_export_name = Some(export_name.clone());
1405 }
1406 }
1407 let old_fn_decl_ident = self.fn_decl_ident.replace(f.ident.clone());
1408 f.visit_mut_children_with(self);
1409 self.this_status = old_this_status;
1410 self.current_export_name = old_current_export_name;
1411 self.fn_decl_ident = old_fn_decl_ident;
1412 }
1413
1414 fn visit_mut_arrow_expr(&mut self, a: &mut ArrowExpr) {
1415 let directive = self.get_directive_for_function(
1418 if let BlockStmtOrExpr::BlockStmt(block) = &mut *a.body {
1419 Some(block)
1420 } else {
1421 None
1422 },
1423 );
1424
1425 if let Some(directive) = &directive {
1426 self.this_status = ThisStatus::Forbidden {
1427 directive: directive.clone(),
1428 };
1429 }
1430
1431 let declared_idents_until = self.declared_idents.len();
1432 let old_names = take(&mut self.names);
1433
1434 {
1435 let old_in_module = replace(&mut self.in_module_level, false);
1437 let should_track_names = directive.is_some() || self.should_track_names;
1438 let old_should_track_names = replace(&mut self.should_track_names, should_track_names);
1439 let old_current_export_name = self.current_export_name.take();
1440 {
1441 for n in &mut a.params {
1442 collect_idents_in_pat(n, &mut self.declared_idents);
1443 }
1444 }
1445 a.visit_mut_children_with(self);
1446 self.in_module_level = old_in_module;
1447 self.should_track_names = old_should_track_names;
1448 self.current_export_name = old_current_export_name;
1449 }
1450
1451 let mut child_names = take(&mut self.names);
1452
1453 if self.should_track_names {
1454 self.names = [old_names, child_names.clone()].concat();
1455 }
1456
1457 if let Some(directive) = directive {
1458 let arrow_ident = self.arrow_or_fn_expr_ident.clone();
1459
1460 if !self.validate_async_function(a.is_async, a.span, arrow_ident.as_ref(), &directive) {
1461 if self.current_export_name.is_some() {
1464 if let Some(arrow_ident) = arrow_ident {
1465 self.export_name_by_local_id
1466 .swap_remove(&arrow_ident.to_id());
1467 }
1468 }
1469
1470 return;
1471 }
1472
1473 if HANDLER.with(|handler| handler.has_errors()) {
1476 return;
1477 }
1478
1479 if matches!(self.file_directive, Some(Directive::UseServer))
1482 && matches!(directive, Directive::UseServer)
1483 {
1484 if let Some(export_name) = self.current_export_name.clone() {
1485 let params: Vec<Param> =
1486 a.params.iter().map(|p| Param::from(p.clone())).collect();
1487
1488 self.register_server_action_export(
1489 &export_name,
1490 arrow_ident.as_ref(),
1491 Some(¶ms),
1492 a.span,
1493 &mut || Box::new(Expr::Arrow(a.take())),
1494 );
1495
1496 return;
1497 }
1498 }
1499
1500 if !self.config.is_react_server_layer {
1502 if matches!(directive, Directive::UseCache { .. }) {
1503 if let Some(export_name) = self.current_export_name.clone() {
1504 let params: Vec<Param> =
1505 a.params.iter().map(|p| Param::from(p.clone())).collect();
1506
1507 self.register_cache_export_on_client(
1508 &export_name,
1509 arrow_ident.as_ref(),
1510 Some(¶ms),
1511 a.span,
1512 );
1513 }
1514 }
1515
1516 return;
1517 }
1518
1519 retain_names_from_declared_idents(
1522 &mut child_names,
1523 &self.declared_idents[..declared_idents_until],
1524 );
1525
1526 if let Directive::UseCache { cache_kind } = directive {
1527 self.rewrite_expr_to_proxy_expr =
1528 Some(self.maybe_hoist_and_create_proxy_for_cache_arrow_expr(
1529 child_names,
1530 cache_kind,
1531 a,
1532 ));
1533 } else {
1534 self.rewrite_expr_to_proxy_expr = Some(
1535 self.maybe_hoist_and_create_proxy_for_server_action_arrow_expr(child_names, a),
1536 );
1537 }
1538 }
1539 }
1540
1541 fn visit_mut_module(&mut self, m: &mut Module) {
1542 self.start_pos = m.span.lo;
1543 m.visit_mut_children_with(self);
1544 }
1545
1546 fn visit_mut_stmt(&mut self, n: &mut Stmt) {
1547 n.visit_mut_children_with(self);
1548
1549 if self.in_module_level {
1550 return;
1551 }
1552
1553 collect_decl_idents_in_stmt(n, &mut self.declared_idents);
1556 }
1557
1558 fn visit_mut_param(&mut self, n: &mut Param) {
1559 n.visit_mut_children_with(self);
1560
1561 if self.in_module_level {
1562 return;
1563 }
1564
1565 collect_idents_in_pat(&n.pat, &mut self.declared_idents);
1566 }
1567
1568 fn visit_mut_prop_or_spread(&mut self, n: &mut PropOrSpread) {
1569 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.clone();
1570 let old_current_export_name = self.current_export_name.take();
1571
1572 match n {
1573 PropOrSpread::Prop(box Prop::KeyValue(KeyValueProp {
1574 key: PropName::Ident(ident_name),
1575 value: box Expr::Arrow(_) | box Expr::Fn(_),
1576 ..
1577 })) => {
1578 self.current_export_name = None;
1579 self.arrow_or_fn_expr_ident = Some(ident_name.clone().into());
1580 }
1581 PropOrSpread::Prop(box Prop::Method(MethodProp { key, .. })) => {
1582 let key = key.clone();
1583
1584 if let PropName::Ident(ident_name) = &key {
1585 self.arrow_or_fn_expr_ident = Some(ident_name.clone().into());
1586 }
1587
1588 let old_this_status = replace(&mut self.this_status, ThisStatus::Allowed);
1589 self.rewrite_expr_to_proxy_expr = None;
1590 self.current_export_name = None;
1591 n.visit_mut_children_with(self);
1592 self.current_export_name = old_current_export_name.clone();
1593 self.this_status = old_this_status;
1594
1595 if let Some(expr) = self.rewrite_expr_to_proxy_expr.take() {
1596 *n = PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
1597 key,
1598 value: expr,
1599 })));
1600 }
1601
1602 return;
1603 }
1604 _ => {}
1605 }
1606
1607 if !self.in_module_level && self.should_track_names {
1608 if let PropOrSpread::Prop(box Prop::Shorthand(i)) = n {
1609 self.names.push(Name::from(&*i));
1610 self.should_track_names = false;
1611 n.visit_mut_children_with(self);
1612 self.should_track_names = true;
1613 return;
1614 }
1615 }
1616
1617 n.visit_mut_children_with(self);
1618 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
1619 self.current_export_name = old_current_export_name;
1620 }
1621
1622 fn visit_mut_class(&mut self, n: &mut Class) {
1623 let old_this_status = replace(&mut self.this_status, ThisStatus::Allowed);
1624 n.visit_mut_children_with(self);
1625 self.this_status = old_this_status;
1626 }
1627
1628 fn visit_mut_class_member(&mut self, n: &mut ClassMember) {
1629 if let ClassMember::Method(ClassMethod {
1630 is_abstract: false,
1631 is_static: true,
1632 kind: MethodKind::Method,
1633 key,
1634 span,
1635 accessibility: None | Some(Accessibility::Public),
1636 ..
1637 }) = n
1638 {
1639 let key = key.clone();
1640 let span = *span;
1641 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.clone();
1642
1643 if let PropName::Ident(ident_name) = &key {
1644 self.arrow_or_fn_expr_ident = Some(ident_name.clone().into());
1645 }
1646
1647 let old_this_status = replace(&mut self.this_status, ThisStatus::Allowed);
1648 let old_current_export_name = self.current_export_name.take();
1649 self.rewrite_expr_to_proxy_expr = None;
1650 self.current_export_name = None;
1651 n.visit_mut_children_with(self);
1652 self.this_status = old_this_status;
1653 self.current_export_name = old_current_export_name;
1654 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
1655
1656 if let Some(expr) = self.rewrite_expr_to_proxy_expr.take() {
1657 *n = ClassMember::ClassProp(ClassProp {
1658 span,
1659 key,
1660 value: Some(expr),
1661 is_static: true,
1662 ..Default::default()
1663 });
1664 }
1665 } else {
1666 n.visit_mut_children_with(self);
1667 }
1668 }
1669
1670 fn visit_mut_class_method(&mut self, n: &mut ClassMethod) {
1671 if n.is_static {
1672 n.visit_mut_children_with(self);
1673 } else {
1674 let (is_action_fn, is_cache_fn) = has_body_directive(&n.function.body);
1675
1676 if is_action_fn {
1677 emit_error(
1678 ServerActionsErrorKind::InlineUseServerInClassInstanceMethod { span: n.span },
1679 );
1680 } else if is_cache_fn {
1681 emit_error(
1682 ServerActionsErrorKind::InlineUseCacheInClassInstanceMethod { span: n.span },
1683 );
1684 } else {
1685 n.visit_mut_children_with(self);
1686 }
1687 }
1688 }
1689
1690 fn visit_mut_call_expr(&mut self, n: &mut CallExpr) {
1691 if let Callee::Expr(box Expr::Ident(Ident { sym, .. })) = &mut n.callee {
1692 if sym == "jsxDEV" || sym == "_jsxDEV" {
1693 if n.args.len() > 4 {
1697 for arg in &mut n.args[0..4] {
1698 arg.visit_mut_with(self);
1699 }
1700 return;
1701 }
1702 }
1703 }
1704
1705 let old_current_export_name = self.current_export_name.take();
1706 n.visit_mut_children_with(self);
1707 self.current_export_name = old_current_export_name;
1708 }
1709
1710 fn visit_mut_callee(&mut self, n: &mut Callee) {
1711 let old_in_callee = replace(&mut self.in_callee, true);
1712 n.visit_mut_children_with(self);
1713 self.in_callee = old_in_callee;
1714 }
1715
1716 fn visit_mut_expr(&mut self, n: &mut Expr) {
1717 if !self.in_module_level && self.should_track_names {
1718 if let Ok(mut name) = Name::try_from(&*n) {
1719 if self.in_callee {
1720 if !name.1.is_empty() {
1723 name.1.pop();
1724 }
1725 }
1726
1727 self.names.push(name);
1728 self.should_track_names = false;
1729 n.visit_mut_children_with(self);
1730 self.should_track_names = true;
1731 return;
1732 }
1733 }
1734
1735 self.rewrite_expr_to_proxy_expr = None;
1736 n.visit_mut_children_with(self);
1737 if let Some(expr) = self.rewrite_expr_to_proxy_expr.take() {
1738 *n = *expr;
1739 }
1740 }
1741
1742 fn visit_mut_module_items(&mut self, stmts: &mut Vec<ModuleItem>) {
1743 self.file_directive = self.get_directive_for_module(stmts);
1744
1745 let in_cache_file = matches!(self.file_directive, Some(Directive::UseCache { .. }));
1746 let in_action_file = matches!(self.file_directive, Some(Directive::UseServer));
1747
1748 let should_track_exports = in_action_file || in_cache_file;
1750
1751 if should_track_exports {
1757 for stmt in stmts.iter() {
1758 match stmt {
1759 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(export_default_expr)) => {
1760 if let Expr::Ident(ident) = &*export_default_expr.expr {
1761 self.export_name_by_local_id.insert(
1762 ident.to_id(),
1763 ModuleExportName::Ident(atom!("default").into()),
1764 );
1765 }
1766 }
1767 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(export_default_decl)) => {
1768 if let DefaultDecl::Fn(f) = &export_default_decl.decl {
1770 if let Some(ident) = &f.ident {
1771 self.export_name_by_local_id.insert(
1772 ident.to_id(),
1773 ModuleExportName::Ident(atom!("default").into()),
1774 );
1775 }
1776 }
1777 }
1778 ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export_decl)) => {
1779 match &export_decl.decl {
1781 Decl::Fn(f) => {
1782 self.export_name_by_local_id.insert(
1783 f.ident.to_id(),
1784 ModuleExportName::Ident(f.ident.clone()),
1785 );
1786 }
1787 Decl::Var(var) => {
1788 for decl in &var.decls {
1789 let mut idents = vec![];
1795 collect_idents_in_pat(&decl.name, &mut idents);
1796
1797 let is_destructuring = !matches!(&decl.name, Pat::Ident(_));
1798 let needs_wrapper = if is_destructuring {
1799 true
1800 } else if let Some(init) = &decl.init {
1801 may_need_cache_runtime_wrapper(init)
1802 } else {
1803 false
1804 };
1805
1806 for ident in idents {
1807 self.export_name_by_local_id.insert(
1808 ident.to_id(),
1809 ModuleExportName::Ident(ident.clone()),
1810 );
1811
1812 if needs_wrapper {
1813 self.local_ids_that_need_cache_runtime_wrapper_if_exported
1814 .insert(ident.to_id());
1815 }
1816 }
1817 }
1818 }
1819 _ => {}
1820 }
1821 }
1822 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(named_export)) => {
1823 if named_export.src.is_none() {
1824 for spec in &named_export.specifiers {
1825 match spec {
1826 ExportSpecifier::Named(ExportNamedSpecifier {
1827 orig: ModuleExportName::Ident(orig),
1828 exported: Some(exported),
1829 is_type_only: false,
1830 ..
1831 }) => {
1832 self.export_name_by_local_id
1834 .insert(orig.to_id(), exported.clone());
1835 }
1836 ExportSpecifier::Named(ExportNamedSpecifier {
1837 orig: ModuleExportName::Ident(orig),
1838 exported: None,
1839 is_type_only: false,
1840 ..
1841 }) => {
1842 self.export_name_by_local_id.insert(
1844 orig.to_id(),
1845 ModuleExportName::Ident(orig.clone()),
1846 );
1847 }
1848 _ => {}
1849 }
1850 }
1851 }
1852 }
1853 ModuleItem::Stmt(Stmt::Decl(Decl::Var(var_decl))) => {
1854 for decl in &var_decl.decls {
1856 if let Pat::Ident(ident_pat) = &decl.name {
1857 if let Some(init) = &decl.init {
1858 if may_need_cache_runtime_wrapper(init) {
1859 self.local_ids_that_need_cache_runtime_wrapper_if_exported
1860 .insert(ident_pat.id.to_id());
1861 }
1862 }
1863 }
1864 }
1865 }
1866 ModuleItem::Stmt(Stmt::Decl(Decl::Fn(_fn_decl))) => {
1867 }
1870 ModuleItem::ModuleDecl(ModuleDecl::Import(import_decl)) => {
1871 for spec in &import_decl.specifiers {
1874 match spec {
1875 ImportSpecifier::Named(named) => {
1876 self.local_ids_that_need_cache_runtime_wrapper_if_exported
1877 .insert(named.local.to_id());
1878 }
1879 ImportSpecifier::Default(default) => {
1880 self.local_ids_that_need_cache_runtime_wrapper_if_exported
1881 .insert(default.local.to_id());
1882 }
1883 ImportSpecifier::Namespace(ns) => {
1884 self.local_ids_that_need_cache_runtime_wrapper_if_exported
1885 .insert(ns.local.to_id());
1886 }
1887 }
1888 }
1889 }
1890 _ => {}
1891 }
1892 }
1893 }
1894
1895 let old_annotations = self.annotations.take();
1896 let mut new = Vec::with_capacity(stmts.len());
1897
1898 for mut stmt in stmts.take() {
1901 let mut should_remove_statement = false;
1902
1903 if should_track_exports {
1904 let mut disallowed_export_span = DUMMY_SP;
1905
1906 match &mut stmt {
1907 ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { decl, span })) => {
1908 match decl {
1909 Decl::Var(var) => {
1910 let mut has_export_needing_wrapper = false;
1911
1912 for decl in &var.decls {
1913 if let Pat::Ident(_) = &decl.name {
1914 if let Some(init) = &decl.init {
1915 if let Expr::Lit(_) = &**init {
1922 disallowed_export_span = *span;
1923 }
1924 }
1925 }
1926
1927 if in_cache_file {
1930 let mut idents: Vec<Ident> = Vec::new();
1931 collect_idents_in_pat(&decl.name, &mut idents);
1932
1933 for ident in idents {
1934 let needs_cache_runtime_wrapper = self
1935 .local_ids_that_need_cache_runtime_wrapper_if_exported
1936 .contains(&ident.to_id());
1937
1938 if needs_cache_runtime_wrapper {
1939 has_export_needing_wrapper = true;
1940 }
1941 }
1942 }
1943 }
1944
1945 if in_cache_file && has_export_needing_wrapper {
1948 stmt = ModuleItem::Stmt(Stmt::Decl(Decl::Var(var.clone())));
1949 }
1950 }
1951 Decl::Fn(_)
1952 | Decl::TsInterface(_)
1953 | Decl::TsTypeAlias(_)
1954 | Decl::TsEnum(_) => {}
1955 _ => {
1956 disallowed_export_span = *span;
1957 }
1958 }
1959 }
1960 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(named)) => {
1961 if !named.type_only {
1962 if let Some(src) = &named.src {
1963 if in_cache_file {
1965 let import_specs: Vec<ImportSpecifier> = named
1968 .specifiers
1969 .iter()
1970 .filter_map(|spec| {
1971 if let ExportSpecifier::Named(ExportNamedSpecifier {
1972 orig: ModuleExportName::Ident(orig),
1973 exported,
1974 is_type_only: false,
1975 ..
1976 }) = spec
1977 {
1978 let export_name =
1982 if let Some(exported) = exported {
1983 exported.clone()
1984 } else {
1985 ModuleExportName::Ident(orig.clone())
1986 };
1987
1988 self.export_name_by_local_id
1989 .insert(orig.to_id(), export_name);
1990
1991 self.local_ids_that_need_cache_runtime_wrapper_if_exported
1992 .insert(orig.to_id());
1993
1994 return Some(ImportSpecifier::Named(
1995 ImportNamedSpecifier {
1996 span: DUMMY_SP,
1997 local: orig.clone(),
1998 imported: None,
1999 is_type_only: false,
2000 },
2001 ));
2002 }
2003 None
2004 })
2005 .collect();
2006
2007 if !import_specs.is_empty() {
2008 self.extra_items.push(ModuleItem::ModuleDecl(
2010 ModuleDecl::Import(ImportDecl {
2011 span: named.span,
2012 specifiers: import_specs,
2013 src: src.clone(),
2014 type_only: false,
2015 with: named.with.clone(),
2016 phase: Default::default(),
2017 }),
2018 ));
2019 }
2020
2021 named.specifiers.retain(|spec| {
2024 matches!(
2025 spec,
2026 ExportSpecifier::Named(ExportNamedSpecifier {
2027 is_type_only: true,
2028 ..
2029 })
2030 )
2031 });
2032
2033 if named.specifiers.is_empty() {
2036 should_remove_statement = true;
2037 }
2038 } else if named.specifiers.iter().any(|s| match s {
2039 ExportSpecifier::Namespace(_) | ExportSpecifier::Default(_) => {
2040 true
2041 }
2042 ExportSpecifier::Named(s) => !s.is_type_only,
2043 }) {
2044 disallowed_export_span = named.span;
2045 }
2046 } else {
2047 if in_cache_file {
2051 named.specifiers.retain(|spec| {
2052 if let ExportSpecifier::Named(ExportNamedSpecifier {
2053 orig: ModuleExportName::Ident(ident),
2054 is_type_only: false,
2055 ..
2056 }) = spec
2057 {
2058 !self
2059 .local_ids_that_need_cache_runtime_wrapper_if_exported
2060 .contains(&ident.to_id())
2061 } else {
2062 true
2063 }
2064 });
2065
2066 if named.specifiers.is_empty() {
2067 should_remove_statement = true;
2068 }
2069 }
2070 }
2071 }
2072 }
2073 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(ExportDefaultDecl {
2074 decl,
2075 span,
2076 })) => match decl {
2077 DefaultDecl::Fn(_) | DefaultDecl::TsInterfaceDecl(_) => {}
2078 _ => {
2079 disallowed_export_span = *span;
2080 }
2081 },
2082 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(default_expr)) => {
2083 match &mut *default_expr.expr {
2084 Expr::Fn(_) | Expr::Arrow(_) => {}
2085 Expr::Ident(ident) => {
2086 if in_cache_file {
2089 let needs_cache_runtime_wrapper = self
2090 .local_ids_that_need_cache_runtime_wrapper_if_exported
2091 .contains(&ident.to_id());
2092
2093 if needs_cache_runtime_wrapper {
2094 should_remove_statement = true;
2095 }
2096 }
2097 }
2098 Expr::Call(_call) => {
2099 if in_cache_file {
2103 should_remove_statement = true;
2104 }
2105 }
2106 _ => {
2107 disallowed_export_span = default_expr.span;
2108 }
2109 }
2110 }
2111 ModuleItem::ModuleDecl(ModuleDecl::ExportAll(ExportAll {
2112 span,
2113 type_only,
2114 ..
2115 })) => {
2116 if !*type_only {
2117 disallowed_export_span = *span;
2118 }
2119 }
2120 _ => {}
2121 }
2122
2123 if disallowed_export_span != DUMMY_SP {
2125 emit_error(ServerActionsErrorKind::ExportedSyncFunction {
2126 span: disallowed_export_span,
2127 in_action_file,
2128 });
2129 return;
2130 }
2131 }
2132
2133 stmt.visit_mut_with(self);
2134
2135 let new_stmt = if should_remove_statement {
2136 None
2137 } else if let Some(expr) = self.rewrite_default_fn_expr_to_proxy_expr.take() {
2138 Some(ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(
2139 ExportDefaultExpr {
2140 span: DUMMY_SP,
2141 expr,
2142 },
2143 )))
2144 } else {
2145 Some(stmt)
2146 };
2147
2148 if self.config.is_react_server_layer || self.file_directive.is_none() {
2149 new.append(&mut self.hoisted_extra_items);
2150 if let Some(stmt) = new_stmt {
2151 new.push(stmt);
2152 }
2153 new.extend(self.annotations.drain(..).map(ModuleItem::Stmt));
2154 new.append(&mut self.extra_items);
2155 }
2156 }
2157
2158 if should_track_exports {
2161 for (id, export_name) in &self.export_name_by_local_id {
2162 if self.reference_ids_by_export_name.contains_key(export_name) {
2163 continue;
2164 }
2165
2166 if in_cache_file
2167 && !self
2168 .local_ids_that_need_cache_runtime_wrapper_if_exported
2169 .contains(id)
2170 {
2171 continue;
2172 }
2173
2174 self.server_reference_exports.push(ServerReferenceExport {
2175 ident: Ident::from(id.clone()),
2176 export_name: export_name.clone(),
2177 reference_id: self.generate_server_reference_id(
2178 export_name,
2179 in_cache_file,
2180 None,
2181 ),
2182 needs_cache_runtime_wrapper: in_cache_file,
2183 });
2184 }
2185 }
2186
2187 if in_action_file || in_cache_file && !self.config.is_react_server_layer {
2188 self.reference_ids_by_export_name.extend(
2189 self.server_reference_exports
2190 .iter()
2191 .map(|e| (e.export_name.clone(), e.reference_id.clone())),
2192 );
2193
2194 if !self.reference_ids_by_export_name.is_empty() {
2195 self.has_action |= in_action_file;
2196 self.has_cache |= in_cache_file;
2197 }
2198 };
2199
2200 let create_ref_ident = private_ident!("createServerReference");
2203 let call_server_ident = private_ident!("callServer");
2204 let find_source_map_url_ident = private_ident!("findSourceMapURL");
2205
2206 let client_layer_import = ((self.has_action || self.has_cache)
2207 && !self.config.is_react_server_layer)
2208 .then(|| {
2209 ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
2216 span: DUMMY_SP,
2217 specifiers: vec![
2218 ImportSpecifier::Named(ImportNamedSpecifier {
2219 span: DUMMY_SP,
2220 local: create_ref_ident.clone(),
2221 imported: None,
2222 is_type_only: false,
2223 }),
2224 ImportSpecifier::Named(ImportNamedSpecifier {
2225 span: DUMMY_SP,
2226 local: call_server_ident.clone(),
2227 imported: None,
2228 is_type_only: false,
2229 }),
2230 ImportSpecifier::Named(ImportNamedSpecifier {
2231 span: DUMMY_SP,
2232 local: find_source_map_url_ident.clone(),
2233 imported: None,
2234 is_type_only: false,
2235 }),
2236 ],
2237 src: Box::new(Str {
2238 span: DUMMY_SP,
2239 value: atom!("private-next-rsc-action-client-wrapper").into(),
2240 raw: None,
2241 }),
2242 type_only: false,
2243 with: None,
2244 phase: Default::default(),
2245 }))
2246 });
2247
2248 let mut client_layer_exports = FxIndexMap::default();
2249
2250 if should_track_exports {
2252 let server_reference_exports = self.server_reference_exports.take();
2253
2254 for ServerReferenceExport {
2255 ident,
2256 export_name,
2257 reference_id: ref_id,
2258 needs_cache_runtime_wrapper,
2259 ..
2260 } in &server_reference_exports
2261 {
2262 if !self.config.is_react_server_layer {
2263 if matches!(export_name, ModuleExportName::Ident(i) if i.sym == *"default") {
2264 let export_expr = ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(
2265 ExportDefaultExpr {
2266 span: DUMMY_SP,
2267 expr: Box::new(Expr::Call(CallExpr {
2268 span: if self.config.is_react_server_layer
2272 || self.config.is_development
2273 {
2274 self.comments.add_pure_comment(ident.span.lo);
2275 ident.span
2276 } else {
2277 PURE_SP
2278 },
2279 callee: Callee::Expr(Box::new(Expr::Ident(
2280 create_ref_ident.clone(),
2281 ))),
2282 args: vec![
2283 ref_id.clone().as_arg(),
2284 call_server_ident.clone().as_arg(),
2285 Expr::undefined(DUMMY_SP).as_arg(),
2286 find_source_map_url_ident.clone().as_arg(),
2287 "default".as_arg(),
2288 ],
2289 ..Default::default()
2290 })),
2291 },
2292 ));
2293 client_layer_exports.insert(
2294 atom!("default"),
2295 (
2296 vec![export_expr],
2297 ModuleExportName::Ident(atom!("default").into()),
2298 ref_id.clone(),
2299 ),
2300 );
2301 } else {
2302 let var_name = if in_cache_file {
2303 self.gen_cache_ident()
2304 } else {
2305 self.gen_action_ident()
2306 };
2307
2308 let var_ident = Ident::new(var_name.clone(), DUMMY_SP, self.private_ctxt);
2309
2310 let name_span =
2314 if self.config.is_react_server_layer || self.config.is_development {
2315 ident.span
2316 } else {
2317 DUMMY_SP
2318 };
2319
2320 let export_name_str: Wtf8Atom = match export_name {
2321 ModuleExportName::Ident(i) => i.sym.clone().into(),
2322 ModuleExportName::Str(s) => s.value.clone(),
2323 };
2324
2325 let var_decl = ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl {
2326 span: DUMMY_SP,
2327 kind: VarDeclKind::Const,
2328 decls: vec![VarDeclarator {
2329 span: DUMMY_SP,
2330 name: Pat::Ident(
2331 IdentName::new(var_name.clone(), name_span).into(),
2332 ),
2333 init: Some(Box::new(Expr::Call(CallExpr {
2334 span: PURE_SP,
2335 callee: Callee::Expr(Box::new(Expr::Ident(
2336 create_ref_ident.clone(),
2337 ))),
2338 args: vec![
2339 ref_id.clone().as_arg(),
2340 call_server_ident.clone().as_arg(),
2341 Expr::undefined(DUMMY_SP).as_arg(),
2342 find_source_map_url_ident.clone().as_arg(),
2343 export_name_str.as_arg(),
2344 ],
2345 ..Default::default()
2346 }))),
2347 definite: false,
2348 }],
2349 ..Default::default()
2350 }))));
2351
2352 let exported_name =
2356 if self.config.is_react_server_layer || self.config.is_development {
2357 export_name.clone()
2358 } else {
2359 strip_export_name_span(export_name)
2360 };
2361
2362 let export_named =
2363 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(NamedExport {
2364 span: DUMMY_SP,
2365 specifiers: vec![ExportSpecifier::Named(ExportNamedSpecifier {
2366 span: DUMMY_SP,
2367 orig: ModuleExportName::Ident(var_ident),
2368 exported: Some(exported_name),
2369 is_type_only: false,
2370 })],
2371 src: None,
2372 type_only: false,
2373 with: None,
2374 }));
2375
2376 client_layer_exports.insert(
2377 var_name,
2378 (
2379 vec![var_decl, export_named],
2380 export_name.clone(),
2381 ref_id.clone(),
2382 ),
2383 );
2384 }
2385 } else if in_cache_file {
2386 if !*needs_cache_runtime_wrapper {
2391 continue;
2392 }
2393
2394 let wrapper_ident = Ident::new(
2396 format!("$$RSC_SERVER_CACHE_{}", export_name.atom()).into(),
2397 ident.span,
2398 self.private_ctxt,
2399 );
2400
2401 self.has_cache = true;
2402 self.reference_ids_by_export_name
2403 .insert(export_name.clone(), ref_id.clone());
2404
2405 self.extra_items
2407 .push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl {
2408 kind: VarDeclKind::Let,
2409 decls: vec![VarDeclarator {
2410 span: ident.span,
2411 name: Pat::Ident(wrapper_ident.clone().into()),
2412 init: Some(Box::new(Expr::Ident(ident.clone()))),
2413 definite: false,
2414 }],
2415 ..Default::default()
2416 })))));
2417
2418 let wrapper_stmts = {
2419 let mut stmts = vec![
2420 Stmt::Expr(ExprStmt {
2422 span: DUMMY_SP,
2423 expr: Box::new(Expr::Assign(AssignExpr {
2424 span: DUMMY_SP,
2425 op: op!("="),
2426 left: AssignTarget::Simple(SimpleAssignTarget::Ident(
2427 wrapper_ident.clone().into(),
2428 )),
2429 right: Box::new(create_cache_wrapper(
2430 "default",
2431 ref_id.clone(),
2432 0,
2433 None,
2436 Expr::Ident(ident.clone()),
2437 ident.span,
2438 None,
2439 self.unresolved_ctxt,
2440 )),
2441 })),
2442 }),
2443 Stmt::Expr(ExprStmt {
2445 span: DUMMY_SP,
2446 expr: Box::new(annotate_ident_as_server_reference(
2447 wrapper_ident.clone(),
2448 ref_id.clone(),
2449 ident.span,
2450 )),
2451 }),
2452 ];
2453
2454 if !ident.sym.starts_with("$$RSC_SERVER_") {
2456 stmts.push(assign_name_to_ident(
2458 &wrapper_ident,
2459 &ident.sym,
2460 self.unresolved_ctxt,
2461 ));
2462 }
2463
2464 stmts
2465 };
2466
2467 self.extra_items.push(ModuleItem::Stmt(Stmt::If(IfStmt {
2469 test: Box::new(Expr::Bin(BinExpr {
2470 span: DUMMY_SP,
2471 op: op!("==="),
2472 left: Box::new(Expr::Unary(UnaryExpr {
2473 span: DUMMY_SP,
2474 op: op!("typeof"),
2475 arg: Box::new(Expr::Ident(ident.clone())),
2476 })),
2477 right: Box::new(Expr::Lit(Lit::Str(Str {
2478 span: DUMMY_SP,
2479 value: atom!("function").into(),
2480 raw: None,
2481 }))),
2482 })),
2483 cons: Box::new(Stmt::Block(BlockStmt {
2484 stmts: wrapper_stmts,
2485 ..Default::default()
2486 })),
2487 ..Default::default()
2488 })));
2489
2490 if matches!(export_name, ModuleExportName::Ident(i) if i.sym == *"default") {
2492 self.extra_items.push(ModuleItem::ModuleDecl(
2493 ModuleDecl::ExportDefaultExpr(ExportDefaultExpr {
2494 span: DUMMY_SP,
2495 expr: Box::new(Expr::Ident(wrapper_ident)),
2496 }),
2497 ));
2498 } else {
2499 self.extra_items
2500 .push(ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(
2501 NamedExport {
2502 span: DUMMY_SP,
2503 specifiers: vec![ExportSpecifier::Named(
2504 ExportNamedSpecifier {
2505 span: DUMMY_SP,
2506 orig: ModuleExportName::Ident(wrapper_ident),
2507 exported: Some(export_name.clone()),
2508 is_type_only: false,
2509 },
2510 )],
2511 src: None,
2512 type_only: false,
2513 with: None,
2514 },
2515 )));
2516 }
2517 } else {
2518 self.annotations.push(Stmt::Expr(ExprStmt {
2519 span: DUMMY_SP,
2520 expr: Box::new(annotate_ident_as_server_reference(
2521 ident.clone(),
2522 ref_id.clone(),
2523 ident.span,
2524 )),
2525 }));
2526 }
2527 }
2528
2529 if (self.has_action || self.has_cache) && self.config.is_react_server_layer {
2537 new.append(&mut self.extra_items);
2538
2539 if !in_cache_file && !server_reference_exports.is_empty() {
2541 let ensure_ident = private_ident!("ensureServerEntryExports");
2542 new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
2543 span: DUMMY_SP,
2544 specifiers: vec![ImportSpecifier::Named(ImportNamedSpecifier {
2545 span: DUMMY_SP,
2546 local: ensure_ident.clone(),
2547 imported: None,
2548 is_type_only: false,
2549 })],
2550 src: Box::new(Str {
2551 span: DUMMY_SP,
2552 value: atom!("private-next-rsc-action-validate").into(),
2553 raw: None,
2554 }),
2555 type_only: false,
2556 with: None,
2557 phase: Default::default(),
2558 })));
2559 new.push(ModuleItem::Stmt(Stmt::Expr(ExprStmt {
2560 span: DUMMY_SP,
2561 expr: Box::new(Expr::Call(CallExpr {
2562 span: DUMMY_SP,
2563 callee: Callee::Expr(Box::new(Expr::Ident(ensure_ident))),
2564 args: vec![ExprOrSpread {
2565 spread: None,
2566 expr: Box::new(Expr::Array(ArrayLit {
2567 span: DUMMY_SP,
2568 elems: server_reference_exports
2569 .iter()
2570 .map(|ServerReferenceExport { ident, .. }| {
2571 Some(ExprOrSpread {
2572 spread: None,
2573 expr: Box::new(Expr::Ident(ident.clone())),
2574 })
2575 })
2576 .collect(),
2577 })),
2578 }],
2579 ..Default::default()
2580 })),
2581 })));
2582 }
2583
2584 new.extend(self.annotations.drain(..).map(ModuleItem::Stmt));
2586 }
2587 }
2588
2589 if self.has_cache && self.config.is_react_server_layer {
2592 new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
2593 span: DUMMY_SP,
2594 specifiers: vec![ImportSpecifier::Named(ImportNamedSpecifier {
2595 span: DUMMY_SP,
2596 local: quote_ident!("$$cache__").into(),
2597 imported: Some(quote_ident!("cache").into()),
2598 is_type_only: false,
2599 })],
2600 src: Box::new(Str {
2601 span: DUMMY_SP,
2602 value: atom!("private-next-rsc-cache-wrapper").into(),
2603 raw: None,
2604 }),
2605 type_only: false,
2606 with: None,
2607 phase: Default::default(),
2608 })));
2609
2610 new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
2611 span: DUMMY_SP,
2612 specifiers: vec![ImportSpecifier::Named(ImportNamedSpecifier {
2613 span: DUMMY_SP,
2614 local: quote_ident!("$$reactCache__").into(),
2615 imported: Some(quote_ident!("cache").into()),
2616 is_type_only: false,
2617 })],
2618 src: Box::new(Str {
2619 span: DUMMY_SP,
2620 value: atom!("react").into(),
2621 raw: None,
2622 }),
2623 type_only: false,
2624 with: None,
2625 phase: Default::default(),
2626 })));
2627
2628 new.rotate_right(2);
2630 }
2631
2632 if (self.has_action || self.has_cache) && self.config.is_react_server_layer {
2633 new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
2636 span: DUMMY_SP,
2637 specifiers: vec![ImportSpecifier::Named(ImportNamedSpecifier {
2638 span: DUMMY_SP,
2639 local: quote_ident!("registerServerReference").into(),
2640 imported: None,
2641 is_type_only: false,
2642 })],
2643 src: Box::new(Str {
2644 span: DUMMY_SP,
2645 value: atom!("private-next-rsc-server-reference").into(),
2646 raw: None,
2647 }),
2648 type_only: false,
2649 with: None,
2650 phase: Default::default(),
2651 })));
2652
2653 let mut import_count = 1;
2654
2655 if self.has_server_reference_with_bound_args {
2657 new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
2660 span: DUMMY_SP,
2661 specifiers: vec![
2662 ImportSpecifier::Named(ImportNamedSpecifier {
2663 span: DUMMY_SP,
2664 local: quote_ident!("encryptActionBoundArgs").into(),
2665 imported: None,
2666 is_type_only: false,
2667 }),
2668 ImportSpecifier::Named(ImportNamedSpecifier {
2669 span: DUMMY_SP,
2670 local: quote_ident!("decryptActionBoundArgs").into(),
2671 imported: None,
2672 is_type_only: false,
2673 }),
2674 ],
2675 src: Box::new(Str {
2676 span: DUMMY_SP,
2677 value: atom!("private-next-rsc-action-encryption").into(),
2678 raw: None,
2679 }),
2680 type_only: false,
2681 with: None,
2682 phase: Default::default(),
2683 })));
2684 import_count += 1;
2685 }
2686
2687 new.rotate_right(import_count);
2689 }
2690
2691 if self.has_action || self.has_cache {
2692 let export_infos_ordered_by_reference_id = self
2694 .reference_ids_by_export_name
2695 .iter()
2696 .map(|(export_name, reference_id)| {
2697 let name_atom = export_name.atom().into_owned();
2698 (reference_id, ServerReferenceExportInfo { name: name_atom })
2699 })
2700 .collect::<BTreeMap<_, _>>();
2701
2702 if self.config.is_react_server_layer {
2703 self.comments.add_leading(
2705 self.start_pos,
2706 Comment {
2707 span: DUMMY_SP,
2708 kind: CommentKind::Block,
2709 text: generate_server_references_comment(
2710 &export_infos_ordered_by_reference_id,
2711 match self.mode {
2712 ServerActionsMode::Webpack => None,
2713 ServerActionsMode::Turbopack => Some((
2714 &self.file_name,
2715 self.file_query.as_ref().map_or("", |v| v),
2716 )),
2717 },
2718 )
2719 .into(),
2720 },
2721 );
2722 } else {
2723 match self.mode {
2724 ServerActionsMode::Webpack => {
2725 self.comments.add_leading(
2726 self.start_pos,
2727 Comment {
2728 span: DUMMY_SP,
2729 kind: CommentKind::Block,
2730 text: generate_server_references_comment(
2731 &export_infos_ordered_by_reference_id,
2732 None,
2733 )
2734 .into(),
2735 },
2736 );
2737 new.push(client_layer_import.unwrap());
2738 new.rotate_right(1);
2739 new.extend(
2740 client_layer_exports
2741 .into_iter()
2742 .flat_map(|(_, (items, _, _))| items),
2743 );
2744 }
2745 ServerActionsMode::Turbopack => {
2746 new.push(ModuleItem::Stmt(Stmt::Expr(ExprStmt {
2747 expr: Box::new(Expr::Lit(Lit::Str(
2748 atom!("use turbopack no side effects").into(),
2749 ))),
2750 span: DUMMY_SP,
2751 })));
2752 new.rotate_right(1);
2753 for (_, (items, export_name, ref_id)) in client_layer_exports {
2754 let mut module_items = vec![
2755 ModuleItem::Stmt(Stmt::Expr(ExprStmt {
2756 expr: Box::new(Expr::Lit(Lit::Str(
2757 atom!("use turbopack no side effects").into(),
2758 ))),
2759 span: DUMMY_SP,
2760 })),
2761 client_layer_import.clone().unwrap(),
2762 ];
2763 module_items.extend(items);
2764
2765 let stripped_export_name = strip_export_name_span(&export_name);
2768
2769 let name_atom = export_name.atom().into_owned();
2770 let export_info = ServerReferenceExportInfo { name: name_atom };
2771
2772 new.push(ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(
2773 NamedExport {
2774 specifiers: vec![ExportSpecifier::Named(
2775 ExportNamedSpecifier {
2776 span: DUMMY_SP,
2777 orig: stripped_export_name,
2778 exported: None,
2779 is_type_only: false,
2780 },
2781 )],
2782 src: Some(Box::new(
2783 program_to_data_url(
2784 &self.file_name,
2785 &self.cm,
2786 module_items,
2787 Comment {
2788 span: DUMMY_SP,
2789 kind: CommentKind::Block,
2790 text: generate_server_references_comment(
2791 &std::iter::once((&ref_id, export_info))
2792 .collect(),
2793 Some((
2794 &self.file_name,
2795 self.file_query.as_ref().map_or("", |v| v),
2796 )),
2797 )
2798 .into(),
2799 },
2800 )
2801 .into(),
2802 )),
2803 span: DUMMY_SP,
2804 type_only: false,
2805 with: None,
2806 },
2807 )));
2808 }
2809 }
2810 }
2811 }
2812 }
2813
2814 *stmts = new;
2815
2816 self.annotations = old_annotations;
2817 }
2818
2819 fn visit_mut_stmts(&mut self, stmts: &mut Vec<Stmt>) {
2820 let old_annotations = self.annotations.take();
2821
2822 let mut new = Vec::with_capacity(stmts.len());
2823 for mut stmt in stmts.take() {
2824 stmt.visit_mut_with(self);
2825
2826 new.push(stmt);
2827 new.append(&mut self.annotations);
2828 }
2829
2830 *stmts = new;
2831
2832 self.annotations = old_annotations;
2833 }
2834
2835 fn visit_mut_jsx_attr(&mut self, attr: &mut JSXAttr) {
2836 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.take();
2837
2838 if let (Some(JSXAttrValue::JSXExprContainer(container)), JSXAttrName::Ident(ident_name)) =
2839 (&attr.value, &attr.name)
2840 {
2841 match &container.expr {
2842 JSXExpr::Expr(box Expr::Arrow(_)) | JSXExpr::Expr(box Expr::Fn(_)) => {
2843 self.arrow_or_fn_expr_ident = Some(ident_name.clone().into());
2844 }
2845 _ => {}
2846 }
2847 }
2848
2849 attr.visit_mut_children_with(self);
2850 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
2851 }
2852
2853 fn visit_mut_var_declarator(&mut self, var_declarator: &mut VarDeclarator) {
2854 let old_current_export_name = self.current_export_name.take();
2855 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.take();
2856
2857 if let (Pat::Ident(ident), Some(box Expr::Arrow(_) | box Expr::Fn(_))) =
2858 (&var_declarator.name, &var_declarator.init)
2859 {
2860 if self.in_module_level {
2861 if let Some(export_name) = self.export_name_by_local_id.get(&ident.to_id()) {
2862 self.current_export_name = Some(export_name.clone());
2863 }
2864 }
2865
2866 self.arrow_or_fn_expr_ident = Some(ident.id.clone());
2867 }
2868
2869 var_declarator.visit_mut_children_with(self);
2870
2871 self.current_export_name = old_current_export_name;
2872 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
2873 }
2874
2875 fn visit_mut_assign_expr(&mut self, assign_expr: &mut AssignExpr) {
2876 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.clone();
2877
2878 if let (
2879 AssignTarget::Simple(SimpleAssignTarget::Ident(ident)),
2880 box Expr::Arrow(_) | box Expr::Fn(_),
2881 ) = (&assign_expr.left, &assign_expr.right)
2882 {
2883 self.arrow_or_fn_expr_ident = Some(ident.id.clone());
2884 }
2885
2886 assign_expr.visit_mut_children_with(self);
2887 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
2888 }
2889
2890 fn visit_mut_this_expr(&mut self, n: &mut ThisExpr) {
2891 if let ThisStatus::Forbidden { directive } = &self.this_status {
2892 emit_error(ServerActionsErrorKind::ForbiddenExpression {
2893 span: n.span,
2894 expr: "this".into(),
2895 directive: directive.clone(),
2896 });
2897 }
2898 }
2899
2900 fn visit_mut_super(&mut self, n: &mut Super) {
2901 if let ThisStatus::Forbidden { directive } = &self.this_status {
2902 emit_error(ServerActionsErrorKind::ForbiddenExpression {
2903 span: n.span,
2904 expr: "super".into(),
2905 directive: directive.clone(),
2906 });
2907 }
2908 }
2909
2910 fn visit_mut_ident(&mut self, n: &mut Ident) {
2911 if n.sym == *"arguments" {
2912 if let ThisStatus::Forbidden { directive } = &self.this_status {
2913 emit_error(ServerActionsErrorKind::ForbiddenExpression {
2914 span: n.span,
2915 expr: "arguments".into(),
2916 directive: directive.clone(),
2917 });
2918 }
2919 }
2920 }
2921
2922 noop_visit_mut_type!();
2923}
2924
2925fn retain_names_from_declared_idents(
2926 child_names: &mut Vec<Name>,
2927 current_declared_idents: &[Ident],
2928) {
2929 let mut retained_names = Vec::new();
2931
2932 for name in child_names.iter() {
2933 let mut should_retain = true;
2934
2935 for another_name in child_names.iter() {
2941 if name != another_name
2942 && name.0 == another_name.0
2943 && name.1.len() >= another_name.1.len()
2944 {
2945 let mut is_prefix = true;
2946 for i in 0..another_name.1.len() {
2947 if name.1[i] != another_name.1[i] {
2948 is_prefix = false;
2949 break;
2950 }
2951 }
2952 if is_prefix {
2953 should_retain = false;
2954 break;
2955 }
2956 }
2957 }
2958
2959 if should_retain
2960 && current_declared_idents
2961 .iter()
2962 .any(|ident| ident.to_id() == name.0)
2963 && !retained_names.contains(name)
2964 {
2965 retained_names.push(name.clone());
2966 }
2967 }
2968
2969 *child_names = retained_names;
2971}
2972
2973fn may_need_cache_runtime_wrapper(expr: &Expr) -> bool {
2976 match expr {
2977 Expr::Arrow(_) | Expr::Fn(_) => false,
2979 Expr::Object(_) | Expr::Array(_) | Expr::Lit(_) => false,
2981 _ => true,
2983 }
2984}
2985
2986#[allow(clippy::too_many_arguments)]
2989fn create_cache_wrapper(
2990 cache_kind: &str,
2991 reference_id: Atom,
2992 bound_args_length: usize,
2993 fn_ident: Option<Ident>,
2994 target_expr: Expr,
2995 original_span: Span,
2996 params: Option<&[Param]>,
2997 unresolved_ctxt: SyntaxContext,
2998) -> Expr {
2999 let cache_call = CallExpr {
3000 span: original_span,
3001 callee: quote_ident!("$$cache__").as_callee(),
3002 args: vec![
3003 Box::new(Expr::from(cache_kind)).as_arg(),
3004 Box::new(Expr::from(reference_id.as_str())).as_arg(),
3005 Box::new(Expr::Lit(Lit::Num(Number {
3006 span: DUMMY_SP,
3007 value: bound_args_length as f64,
3008 raw: None,
3009 })))
3010 .as_arg(),
3011 Box::new(target_expr).as_arg(),
3012 match params {
3013 Some(params) if !params.iter().any(|p| matches!(p.pat, Pat::Rest(_))) => {
3015 if params.is_empty() {
3016 Box::new(Expr::Array(ArrayLit {
3019 span: DUMMY_SP,
3020 elems: vec![],
3021 }))
3022 .as_arg()
3023 } else {
3024 Box::new(quote!(
3026 "$array.prototype.slice.call(arguments, 0, $end)" as Expr,
3027 array = quote_ident!(unresolved_ctxt, "Array"),
3028 end: Expr = params.len().into(),
3029 ))
3030 .as_arg()
3031 }
3032 }
3033 _ => {
3035 Box::new(quote!(
3037 "$array.prototype.slice.call(arguments)" as Expr,
3038 array = quote_ident!(unresolved_ctxt, "Array"),
3039 ))
3040 .as_arg()
3041 }
3042 },
3043 ],
3044 ..Default::default()
3045 };
3046
3047 let wrapper_fn_expr = Box::new(Expr::Fn(FnExpr {
3049 ident: fn_ident,
3050 function: Box::new(Function {
3051 body: Some(BlockStmt {
3052 stmts: vec![Stmt::Return(ReturnStmt {
3053 span: DUMMY_SP,
3054 arg: Some(Box::new(Expr::Call(cache_call))),
3055 })],
3056 ..Default::default()
3057 }),
3058 span: original_span,
3059 ..Default::default()
3060 }),
3061 }));
3062
3063 Expr::Call(CallExpr {
3064 callee: quote_ident!("$$reactCache__").as_callee(),
3065 args: vec![wrapper_fn_expr.as_arg()],
3066 ..Default::default()
3067 })
3068}
3069
3070#[allow(clippy::too_many_arguments)]
3071fn create_and_hoist_cache_function(
3072 cache_kind: &str,
3073 reference_id: Atom,
3074 bound_args_length: usize,
3075 cache_name: Atom,
3076 fn_ident: Option<Ident>,
3077 params: Vec<Param>,
3078 body: Option<BlockStmt>,
3079 original_span: Span,
3080 hoisted_extra_items: &mut Vec<ModuleItem>,
3081 unresolved_ctxt: SyntaxContext,
3082) -> Ident {
3083 let cache_ident = private_ident!(Span::dummy_with_cmt(), cache_name.clone());
3084 let inner_fn_name: Atom = format!("{}_INNER", cache_name).into();
3085 let inner_fn_ident = private_ident!(Span::dummy_with_cmt(), inner_fn_name);
3086
3087 let wrapper_fn = Box::new(create_cache_wrapper(
3088 cache_kind,
3089 reference_id.clone(),
3090 bound_args_length,
3091 fn_ident.clone(),
3092 Expr::Ident(inner_fn_ident.clone()),
3093 original_span,
3094 Some(¶ms),
3095 unresolved_ctxt,
3096 ));
3097
3098 let inner_fn_expr = FnExpr {
3099 ident: fn_ident.clone(),
3100 function: Box::new(Function {
3101 params,
3102 body,
3103 span: original_span,
3104 is_async: true,
3105 ..Default::default()
3106 }),
3107 };
3108
3109 hoisted_extra_items.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl {
3110 span: original_span,
3111 kind: VarDeclKind::Const,
3112 decls: vec![VarDeclarator {
3113 span: original_span,
3114 name: Pat::Ident(BindingIdent {
3115 id: inner_fn_ident.clone(),
3116 type_ann: None,
3117 }),
3118 init: Some(Box::new(Expr::Fn(inner_fn_expr))),
3119 definite: false,
3120 }],
3121 ..Default::default()
3122 })))));
3123
3124 if fn_ident.is_none() {
3127 hoisted_extra_items.push(ModuleItem::Stmt(assign_name_to_ident(
3128 &inner_fn_ident,
3129 "",
3130 unresolved_ctxt,
3131 )));
3132 }
3133
3134 hoisted_extra_items.push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
3135 span: DUMMY_SP,
3136 decl: VarDecl {
3137 kind: VarDeclKind::Var,
3138 decls: vec![VarDeclarator {
3139 span: original_span,
3140 name: Pat::Ident(cache_ident.clone().into()),
3141 init: Some(wrapper_fn),
3142 definite: false,
3143 }],
3144 ..Default::default()
3145 }
3146 .into(),
3147 })));
3148
3149 hoisted_extra_items.push(ModuleItem::Stmt(Stmt::Expr(ExprStmt {
3150 span: DUMMY_SP,
3151 expr: Box::new(annotate_ident_as_server_reference(
3152 cache_ident.clone(),
3153 reference_id,
3154 original_span,
3155 )),
3156 })));
3157
3158 cache_ident
3159}
3160
3161fn assign_name_to_ident(ident: &Ident, name: &str, unresolved_ctxt: SyntaxContext) -> Stmt {
3162 quote!(
3164 "$object[\"defineProperty\"]($action, \"name\", { value: $name });"
3172 as Stmt,
3173 object = quote_ident!(unresolved_ctxt, "Object"),
3174 action: Ident = ident.clone(),
3175 name: Expr = name.into(),
3176 )
3177}
3178
3179fn annotate_ident_as_server_reference(ident: Ident, action_id: Atom, original_span: Span) -> Expr {
3180 Expr::Call(CallExpr {
3182 span: original_span,
3183 callee: quote_ident!("registerServerReference").as_callee(),
3184 args: vec![
3185 ExprOrSpread {
3186 spread: None,
3187 expr: Box::new(Expr::Ident(ident)),
3188 },
3189 ExprOrSpread {
3190 spread: None,
3191 expr: Box::new(action_id.clone().into()),
3192 },
3193 ExprOrSpread {
3194 spread: None,
3195 expr: Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))),
3196 },
3197 ],
3198 ..Default::default()
3199 })
3200}
3201
3202fn bind_args_to_ident(ident: Ident, bound: Vec<Option<ExprOrSpread>>, action_id: Atom) -> Expr {
3203 Expr::Call(CallExpr {
3205 span: DUMMY_SP,
3206 callee: Expr::Member(MemberExpr {
3207 span: DUMMY_SP,
3208 obj: Box::new(ident.into()),
3209 prop: MemberProp::Ident(quote_ident!("bind")),
3210 })
3211 .as_callee(),
3212 args: vec![
3213 ExprOrSpread {
3214 spread: None,
3215 expr: Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))),
3216 },
3217 ExprOrSpread {
3218 spread: None,
3219 expr: Box::new(Expr::Call(CallExpr {
3220 span: DUMMY_SP,
3221 callee: quote_ident!("encryptActionBoundArgs").as_callee(),
3222 args: std::iter::once(ExprOrSpread {
3223 spread: None,
3224 expr: Box::new(action_id.into()),
3225 })
3226 .chain(bound.into_iter().flatten())
3227 .collect(),
3228 ..Default::default()
3229 })),
3230 },
3231 ],
3232 ..Default::default()
3233 })
3234}
3235
3236fn detect_similar_strings(a: &str, b: &str) -> bool {
3251 let mut a = a.chars().collect::<Vec<char>>();
3252 let mut b = b.chars().collect::<Vec<char>>();
3253
3254 if a.len() < b.len() {
3255 (a, b) = (b, a);
3256 }
3257
3258 if a.len() == b.len() {
3259 let mut diff = 0;
3261 for i in 0..a.len() {
3262 if a[i] != b[i] {
3263 diff += 1;
3264 if diff > 2 {
3265 return false;
3266 }
3267 }
3268 }
3269
3270 diff != 0
3272 } else {
3273 if a.len() - b.len() > 1 {
3274 return false;
3275 }
3276
3277 for i in 0..b.len() {
3279 if a[i] != b[i] {
3280 return a[i + 1..] == b[i..];
3286 }
3287 }
3288
3289 true
3291 }
3292}
3293
3294fn has_body_directive(maybe_body: &Option<BlockStmt>) -> (bool, bool) {
3299 let mut is_action_fn = false;
3300 let mut is_cache_fn = false;
3301
3302 if let Some(body) = maybe_body {
3303 for stmt in body.stmts.iter() {
3304 match stmt {
3305 Stmt::Expr(ExprStmt {
3306 expr: box Expr::Lit(Lit::Str(Str { value, .. })),
3307 ..
3308 }) => {
3309 if value == "use server" {
3310 is_action_fn = true;
3311 break;
3312 } else if value == "use cache" || value.starts_with("use cache: ") {
3313 is_cache_fn = true;
3314 break;
3315 }
3316 }
3317 _ => break,
3318 }
3319 }
3320 }
3321
3322 (is_action_fn, is_cache_fn)
3323}
3324
3325fn collect_idents_in_array_pat(elems: &[Option<Pat>], idents: &mut Vec<Ident>) {
3326 for elem in elems.iter().flatten() {
3327 match elem {
3328 Pat::Ident(ident) => {
3329 idents.push(ident.id.clone());
3330 }
3331 Pat::Array(array) => {
3332 collect_idents_in_array_pat(&array.elems, idents);
3333 }
3334 Pat::Object(object) => {
3335 collect_idents_in_object_pat(&object.props, idents);
3336 }
3337 Pat::Rest(rest) => {
3338 if let Pat::Ident(ident) = &*rest.arg {
3339 idents.push(ident.id.clone());
3340 }
3341 }
3342 Pat::Assign(AssignPat { left, .. }) => {
3343 collect_idents_in_pat(left, idents);
3344 }
3345 Pat::Expr(..) | Pat::Invalid(..) => {}
3346 }
3347 }
3348}
3349
3350fn collect_idents_in_object_pat(props: &[ObjectPatProp], idents: &mut Vec<Ident>) {
3351 for prop in props {
3352 match prop {
3353 ObjectPatProp::KeyValue(KeyValuePatProp { value, .. }) => {
3354 match &**value {
3357 Pat::Ident(ident) => {
3358 idents.push(ident.id.clone());
3359 }
3360 Pat::Array(array) => {
3361 collect_idents_in_array_pat(&array.elems, idents);
3362 }
3363 Pat::Object(object) => {
3364 collect_idents_in_object_pat(&object.props, idents);
3365 }
3366 _ => {}
3367 }
3368 }
3369 ObjectPatProp::Assign(AssignPatProp { key, .. }) => {
3370 idents.push(key.id.clone());
3372 }
3373 ObjectPatProp::Rest(RestPat { arg, .. }) => {
3374 if let Pat::Ident(ident) = &**arg {
3375 idents.push(ident.id.clone());
3376 }
3377 }
3378 }
3379 }
3380}
3381
3382fn collect_idents_in_var_decls(decls: &[VarDeclarator], idents: &mut Vec<Ident>) {
3383 for decl in decls {
3384 collect_idents_in_pat(&decl.name, idents);
3385 }
3386}
3387
3388fn collect_idents_in_pat(pat: &Pat, idents: &mut Vec<Ident>) {
3389 match pat {
3390 Pat::Ident(ident) => {
3391 idents.push(ident.id.clone());
3392 }
3393 Pat::Array(array) => {
3394 collect_idents_in_array_pat(&array.elems, idents);
3395 }
3396 Pat::Object(object) => {
3397 collect_idents_in_object_pat(&object.props, idents);
3398 }
3399 Pat::Assign(AssignPat { left, .. }) => {
3400 collect_idents_in_pat(left, idents);
3401 }
3402 Pat::Rest(RestPat { arg, .. }) => {
3403 if let Pat::Ident(ident) = &**arg {
3404 idents.push(ident.id.clone());
3405 }
3406 }
3407 Pat::Expr(..) | Pat::Invalid(..) => {}
3408 }
3409}
3410
3411fn collect_decl_idents_in_stmt(stmt: &Stmt, idents: &mut Vec<Ident>) {
3412 if let Stmt::Decl(decl) = stmt {
3413 match decl {
3414 Decl::Var(var) => {
3415 collect_idents_in_var_decls(&var.decls, idents);
3416 }
3417 Decl::Fn(fn_decl) => {
3418 idents.push(fn_decl.ident.clone());
3419 }
3420 _ => {}
3421 }
3422 }
3423}
3424
3425struct DirectiveVisitor<'a> {
3426 config: &'a Config,
3427 location: DirectiveLocation,
3428 directive: Option<Directive>,
3429 has_file_directive: bool,
3430 is_allowed_position: bool,
3431 use_cache_telemetry_tracker: Rc<RefCell<FxHashMap<String, usize>>>,
3432}
3433
3434impl DirectiveVisitor<'_> {
3435 fn visit_stmt(&mut self, stmt: &Stmt) -> bool {
3440 let in_fn_body = matches!(self.location, DirectiveLocation::FunctionBody);
3441 let allow_inline = self.config.is_react_server_layer || self.has_file_directive;
3442
3443 match stmt {
3444 Stmt::Expr(ExprStmt {
3445 expr: box Expr::Lit(Lit::Str(Str { value, span, .. })),
3446 ..
3447 }) => {
3448 if value == "use server" {
3449 if in_fn_body && !allow_inline {
3450 emit_error(ServerActionsErrorKind::InlineUseServerInClientComponent {
3451 span: *span,
3452 })
3453 } else if let Some(Directive::UseCache { .. }) = self.directive {
3454 emit_error(ServerActionsErrorKind::MultipleDirectives {
3455 span: *span,
3456 location: self.location.clone(),
3457 });
3458 } else if self.is_allowed_position {
3459 self.directive = Some(Directive::UseServer);
3460
3461 return true;
3462 } else {
3463 emit_error(ServerActionsErrorKind::MisplacedDirective {
3464 span: *span,
3465 directive: value.to_string_lossy().into_owned(),
3466 location: self.location.clone(),
3467 });
3468 }
3469 } else if detect_similar_strings(&value.to_string_lossy(), "use server") {
3470 emit_error(ServerActionsErrorKind::MisspelledDirective {
3472 span: *span,
3473 directive: value.to_string_lossy().into_owned(),
3474 expected_directive: "use server".to_string(),
3475 });
3476 } else if value == "use action" {
3477 emit_error(ServerActionsErrorKind::MisspelledDirective {
3478 span: *span,
3479 directive: value.to_string_lossy().into_owned(),
3480 expected_directive: "use server".to_string(),
3481 });
3482 } else
3483 if let Some(rest) = value.as_str().and_then(|s| s.strip_prefix("use cache"))
3485 {
3486 if in_fn_body && !allow_inline {
3489 emit_error(ServerActionsErrorKind::InlineUseCacheInClientComponent {
3490 span: *span,
3491 })
3492 } else if let Some(Directive::UseServer) = self.directive {
3493 emit_error(ServerActionsErrorKind::MultipleDirectives {
3494 span: *span,
3495 location: self.location.clone(),
3496 });
3497 } else if self.is_allowed_position {
3498 if !self.config.use_cache_enabled {
3499 emit_error(ServerActionsErrorKind::UseCacheWithoutCacheComponents {
3500 span: *span,
3501 directive: value.to_string_lossy().into_owned(),
3502 });
3503 }
3504
3505 if rest.is_empty() {
3506 self.directive = Some(Directive::UseCache {
3507 cache_kind: rcstr!("default"),
3508 });
3509
3510 self.increment_cache_usage_counter("default");
3511
3512 return true;
3513 }
3514
3515 if rest.starts_with(": ") {
3516 let cache_kind = RcStr::from(rest.split_at(": ".len()).1.to_string());
3517
3518 if !cache_kind.is_empty() {
3519 if !self.config.cache_kinds.contains(&cache_kind) {
3520 emit_error(ServerActionsErrorKind::UnknownCacheKind {
3521 span: *span,
3522 cache_kind: cache_kind.clone(),
3523 });
3524 }
3525
3526 self.increment_cache_usage_counter(&cache_kind);
3527 self.directive = Some(Directive::UseCache { cache_kind });
3528
3529 return true;
3530 }
3531 }
3532
3533 let expected_directive = if let Some(colon_pos) = rest.find(':') {
3536 let kind = rest[colon_pos + 1..].trim();
3537
3538 if kind.is_empty() {
3539 "use cache: <cache-kind>".to_string()
3540 } else {
3541 format!("use cache: {kind}")
3542 }
3543 } else {
3544 let kind = rest.trim();
3545
3546 if kind.is_empty() {
3547 "use cache".to_string()
3548 } else {
3549 format!("use cache: {kind}")
3550 }
3551 };
3552
3553 emit_error(ServerActionsErrorKind::MisspelledDirective {
3554 span: *span,
3555 directive: value.to_string_lossy().into_owned(),
3556 expected_directive,
3557 });
3558
3559 return true;
3560 } else {
3561 emit_error(ServerActionsErrorKind::MisplacedDirective {
3562 span: *span,
3563 directive: value.to_string_lossy().into_owned(),
3564 location: self.location.clone(),
3565 });
3566 }
3567 } else {
3568 if detect_similar_strings(&value.to_string_lossy(), "use cache") {
3570 emit_error(ServerActionsErrorKind::MisspelledDirective {
3571 span: *span,
3572 directive: value.to_string_lossy().into_owned(),
3573 expected_directive: "use cache".to_string(),
3574 });
3575 }
3576 }
3577 }
3578 Stmt::Expr(ExprStmt {
3579 expr:
3580 box Expr::Paren(ParenExpr {
3581 expr: box Expr::Lit(Lit::Str(Str { value, .. })),
3582 ..
3583 }),
3584 span,
3585 ..
3586 }) => {
3587 if value == "use server"
3589 || detect_similar_strings(&value.to_string_lossy(), "use server")
3590 {
3591 if self.is_allowed_position {
3592 emit_error(ServerActionsErrorKind::WrappedDirective {
3593 span: *span,
3594 directive: "use server".to_string(),
3595 });
3596 } else {
3597 emit_error(ServerActionsErrorKind::MisplacedWrappedDirective {
3598 span: *span,
3599 directive: "use server".to_string(),
3600 location: self.location.clone(),
3601 });
3602 }
3603 } else if value == "use cache"
3604 || detect_similar_strings(&value.to_string_lossy(), "use cache")
3605 {
3606 if self.is_allowed_position {
3607 emit_error(ServerActionsErrorKind::WrappedDirective {
3608 span: *span,
3609 directive: "use cache".to_string(),
3610 });
3611 } else {
3612 emit_error(ServerActionsErrorKind::MisplacedWrappedDirective {
3613 span: *span,
3614 directive: "use cache".to_string(),
3615 location: self.location.clone(),
3616 });
3617 }
3618 }
3619 }
3620 _ => {
3621 self.is_allowed_position = false;
3623 }
3624 };
3625
3626 false
3627 }
3628
3629 fn increment_cache_usage_counter(&mut self, cache_kind: &str) {
3631 let mut tracker_map = RefCell::borrow_mut(&self.use_cache_telemetry_tracker);
3632 let entry = tracker_map.entry(cache_kind.to_string());
3633 match entry {
3634 hash_map::Entry::Occupied(mut occupied) => {
3635 *occupied.get_mut() += 1;
3636 }
3637 hash_map::Entry::Vacant(vacant) => {
3638 vacant.insert(1);
3639 }
3640 }
3641 }
3642}
3643
3644pub(crate) struct ClosureReplacer<'a> {
3645 used_ids: &'a [Name],
3646 private_ctxt: SyntaxContext,
3647}
3648
3649impl ClosureReplacer<'_> {
3650 fn index(&self, e: &Expr) -> Option<usize> {
3651 let name = Name::try_from(e).ok()?;
3652 self.used_ids.iter().position(|used_id| *used_id == name)
3653 }
3654}
3655
3656impl VisitMut for ClosureReplacer<'_> {
3657 fn visit_mut_expr(&mut self, e: &mut Expr) {
3658 e.visit_mut_children_with(self);
3659
3660 if let Some(index) = self.index(e) {
3661 *e = Expr::Ident(Ident::new(
3662 format!("$$ACTION_ARG_{index}").into(),
3664 DUMMY_SP,
3665 self.private_ctxt,
3666 ));
3667 }
3668 }
3669
3670 fn visit_mut_prop_or_spread(&mut self, n: &mut PropOrSpread) {
3671 n.visit_mut_children_with(self);
3672
3673 if let PropOrSpread::Prop(box Prop::Shorthand(i)) = n {
3674 let name = Name::from(&*i);
3675 if let Some(index) = self.used_ids.iter().position(|used_id| *used_id == name) {
3676 *n = PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
3677 key: PropName::Ident(i.clone().into()),
3678 value: Box::new(Expr::Ident(Ident::new(
3679 format!("$$ACTION_ARG_{index}").into(),
3681 DUMMY_SP,
3682 self.private_ctxt,
3683 ))),
3684 })));
3685 }
3686 }
3687 }
3688
3689 noop_visit_mut_type!();
3690}
3691
3692#[derive(Debug, Clone, PartialEq, Eq)]
3693struct NamePart {
3694 prop: Atom,
3695 is_member: bool,
3696 optional: bool,
3697}
3698
3699#[derive(Debug, Clone, PartialEq, Eq)]
3700struct Name(Id, Vec<NamePart>);
3701
3702impl From<&'_ Ident> for Name {
3703 fn from(value: &Ident) -> Self {
3704 Name(value.to_id(), vec![])
3705 }
3706}
3707
3708impl TryFrom<&'_ Expr> for Name {
3709 type Error = ();
3710
3711 fn try_from(value: &Expr) -> Result<Self, Self::Error> {
3712 match value {
3713 Expr::Ident(i) => Ok(Name(i.to_id(), vec![])),
3714 Expr::Member(e) => e.try_into(),
3715 Expr::OptChain(e) => e.try_into(),
3716 _ => Err(()),
3717 }
3718 }
3719}
3720
3721impl TryFrom<&'_ MemberExpr> for Name {
3722 type Error = ();
3723
3724 fn try_from(value: &MemberExpr) -> Result<Self, Self::Error> {
3725 match &value.prop {
3726 MemberProp::Ident(prop) => {
3727 let mut obj: Name = value.obj.as_ref().try_into()?;
3728 obj.1.push(NamePart {
3729 prop: prop.sym.clone(),
3730 is_member: true,
3731 optional: false,
3732 });
3733 Ok(obj)
3734 }
3735 _ => Err(()),
3736 }
3737 }
3738}
3739
3740impl TryFrom<&'_ OptChainExpr> for Name {
3741 type Error = ();
3742
3743 fn try_from(value: &OptChainExpr) -> Result<Self, Self::Error> {
3744 match &*value.base {
3745 OptChainBase::Member(m) => match &m.prop {
3746 MemberProp::Ident(prop) => {
3747 let mut obj: Name = m.obj.as_ref().try_into()?;
3748 obj.1.push(NamePart {
3749 prop: prop.sym.clone(),
3750 is_member: false,
3751 optional: value.optional,
3752 });
3753 Ok(obj)
3754 }
3755 _ => Err(()),
3756 },
3757 OptChainBase::Call(_) => Err(()),
3758 }
3759 }
3760}
3761
3762impl From<Name> for Box<Expr> {
3763 fn from(value: Name) -> Self {
3764 let mut expr = Box::new(Expr::Ident(value.0.into()));
3765
3766 for NamePart {
3767 prop,
3768 is_member,
3769 optional,
3770 } in value.1.into_iter()
3771 {
3772 #[allow(clippy::replace_box)]
3773 if is_member {
3774 expr = Box::new(Expr::Member(MemberExpr {
3775 span: DUMMY_SP,
3776 obj: expr,
3777 prop: MemberProp::Ident(IdentName::new(prop, DUMMY_SP)),
3778 }));
3779 } else {
3780 expr = Box::new(Expr::OptChain(OptChainExpr {
3781 span: DUMMY_SP,
3782 base: Box::new(OptChainBase::Member(MemberExpr {
3783 span: DUMMY_SP,
3784 obj: expr,
3785 prop: MemberProp::Ident(IdentName::new(prop, DUMMY_SP)),
3786 })),
3787 optional,
3788 }));
3789 }
3790 }
3791
3792 expr
3793 }
3794}
3795
3796fn emit_error(error_kind: ServerActionsErrorKind) {
3797 let (span, msg) = match error_kind {
3798 ServerActionsErrorKind::ExportedSyncFunction {
3799 span,
3800 in_action_file,
3801 } => (
3802 span,
3803 formatdoc! {
3804 r#"
3805 Only async functions are allowed to be exported in a {directive} file.
3806 "#,
3807 directive = if in_action_file {
3808 "\"use server\""
3809 } else {
3810 "\"use cache\""
3811 }
3812 },
3813 ),
3814 ServerActionsErrorKind::ForbiddenExpression {
3815 span,
3816 expr,
3817 directive,
3818 } => (
3819 span,
3820 formatdoc! {
3821 r#"
3822 {subject} cannot use `{expr}`.
3823 "#,
3824 subject = if let Directive::UseServer = directive {
3825 "Server Actions"
3826 } else {
3827 "\"use cache\" functions"
3828 }
3829 },
3830 ),
3831 ServerActionsErrorKind::InlineUseCacheInClassInstanceMethod { span } => (
3832 span,
3833 formatdoc! {
3834 r#"
3835 It is not allowed to define inline "use cache" annotated class instance methods.
3836 To define cached functions, use functions, object method properties, or static class methods instead.
3837 "#
3838 },
3839 ),
3840 ServerActionsErrorKind::InlineUseCacheInClientComponent { span } => (
3841 span,
3842 formatdoc! {
3843 r#"
3844 It is not allowed to define inline "use cache" annotated functions in Client Components.
3845 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.
3846 "#
3847 },
3848 ),
3849 ServerActionsErrorKind::InlineUseServerInClassInstanceMethod { span } => (
3850 span,
3851 formatdoc! {
3852 r#"
3853 It is not allowed to define inline "use server" annotated class instance methods.
3854 To define Server Actions, use functions, object method properties, or static class methods instead.
3855 "#
3856 },
3857 ),
3858 ServerActionsErrorKind::InlineUseServerInClientComponent { span } => (
3859 span,
3860 formatdoc! {
3861 r#"
3862 It is not allowed to define inline "use server" annotated Server Actions in Client Components.
3863 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.
3864
3865 Read more: https://nextjs.org/docs/app/api-reference/directives/use-server#using-server-functions-in-a-client-component
3866 "#
3867 },
3868 ),
3869 ServerActionsErrorKind::InlineSyncFunction { span, directive } => (
3870 span,
3871 formatdoc! {
3872 r#"
3873 {subject} must be async functions.
3874 "#,
3875 subject = if let Directive::UseServer = directive {
3876 "Server Actions"
3877 } else {
3878 "\"use cache\" functions"
3879 }
3880 },
3881 ),
3882 ServerActionsErrorKind::MisplacedDirective {
3883 span,
3884 directive,
3885 location,
3886 } => (
3887 span,
3888 formatdoc! {
3889 r#"
3890 The "{directive}" directive must be at the top of the {location}.
3891 "#,
3892 location = match location {
3893 DirectiveLocation::Module => "file",
3894 DirectiveLocation::FunctionBody => "function body",
3895 }
3896 },
3897 ),
3898 ServerActionsErrorKind::MisplacedWrappedDirective {
3899 span,
3900 directive,
3901 location,
3902 } => (
3903 span,
3904 formatdoc! {
3905 r#"
3906 The "{directive}" directive must be at the top of the {location}, and cannot be wrapped in parentheses.
3907 "#,
3908 location = match location {
3909 DirectiveLocation::Module => "file",
3910 DirectiveLocation::FunctionBody => "function body",
3911 }
3912 },
3913 ),
3914 ServerActionsErrorKind::MisspelledDirective {
3915 span,
3916 directive,
3917 expected_directive,
3918 } => (
3919 span,
3920 formatdoc! {
3921 r#"
3922 Did you mean "{expected_directive}"? "{directive}" is not a supported directive name."
3923 "#
3924 },
3925 ),
3926 ServerActionsErrorKind::MultipleDirectives { span, location } => (
3927 span,
3928 formatdoc! {
3929 r#"
3930 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.
3931 "#,
3932 location = match location {
3933 DirectiveLocation::Module => "file",
3934 DirectiveLocation::FunctionBody => "function body",
3935 }
3936 },
3937 ),
3938 ServerActionsErrorKind::UnknownCacheKind { span, cache_kind } => (
3939 span,
3940 formatdoc! {
3941 r#"
3942 Unknown cache kind "{cache_kind}". Please configure a cache handler for this kind in the `cacheHandlers` object in your Next.js config.
3943 "#
3944 },
3945 ),
3946 ServerActionsErrorKind::UseCacheWithoutCacheComponents { span, directive } => (
3947 span,
3948 formatdoc! {
3949 r#"
3950 To use "{directive}", please enable the feature flag `cacheComponents` in your Next.js config.
3951
3952 Read more: https://nextjs.org/docs/canary/app/api-reference/directives/use-cache#usage
3953 "#
3954 },
3955 ),
3956 ServerActionsErrorKind::WrappedDirective { span, directive } => (
3957 span,
3958 formatdoc! {
3959 r#"
3960 The "{directive}" directive cannot be wrapped in parentheses.
3961 "#
3962 },
3963 ),
3964 };
3965
3966 HANDLER.with(|handler| handler.struct_span_err(span, &msg).emit());
3967}
3968
3969fn strip_export_name_span(export_name: &ModuleExportName) -> ModuleExportName {
3972 match export_name {
3973 ModuleExportName::Ident(i) => {
3974 ModuleExportName::Ident(Ident::new(i.sym.clone(), DUMMY_SP, i.ctxt))
3975 }
3976 ModuleExportName::Str(s) => ModuleExportName::Str(Str {
3977 span: DUMMY_SP,
3978 value: s.value.clone(),
3979 raw: None,
3980 }),
3981 }
3982}
3983
3984fn program_to_data_url(
3985 file_name: &str,
3986 cm: &Arc<SourceMap>,
3987 body: Vec<ModuleItem>,
3988 prepend_comment: Comment,
3989) -> String {
3990 let module_span = Span::dummy_with_cmt();
3991 let comments = SingleThreadedComments::default();
3992 comments.add_leading(module_span.lo, prepend_comment);
3993
3994 let program = &Program::Module(Module {
3995 span: module_span,
3996 body,
3997 shebang: None,
3998 });
3999
4000 let mut output = vec![];
4001 let mut mappings = vec![];
4002 let mut emitter = Emitter {
4003 cfg: codegen::Config::default().with_minify(true),
4004 cm: cm.clone(),
4005 wr: Box::new(JsWriter::new(
4006 cm.clone(),
4007 " ",
4008 &mut output,
4009 Some(&mut mappings),
4010 )),
4011 comments: Some(&comments),
4012 };
4013
4014 emitter.emit_program(program).unwrap();
4015 drop(emitter);
4016
4017 pub struct InlineSourcesContentConfig<'a> {
4018 folder_path: Option<&'a Path>,
4019 }
4020 impl SourceMapGenConfig for InlineSourcesContentConfig<'_> {
4023 fn file_name_to_source(&self, file: &FileName) -> String {
4024 let FileName::Custom(file) = file else {
4025 return file.to_string();
4027 };
4028 let Some(folder_path) = &self.folder_path else {
4029 return file.to_string();
4030 };
4031
4032 if let Some(rel_path) = diff_paths(file, folder_path) {
4033 format!("./{}", rel_path.display())
4034 } else {
4035 file.to_string()
4036 }
4037 }
4038
4039 fn inline_sources_content(&self, _f: &FileName) -> bool {
4040 true
4041 }
4042 }
4043
4044 let map = cm.build_source_map(
4045 &mappings,
4046 None,
4047 InlineSourcesContentConfig {
4048 folder_path: PathBuf::from(format!("[project]/{file_name}")).parent(),
4049 },
4050 );
4051 let map = {
4052 if map.get_token_count() > 0 {
4053 let mut buf = vec![];
4054 map.to_writer(&mut buf)
4055 .expect("failed to generate sourcemap");
4056 Some(buf)
4057 } else {
4058 None
4059 }
4060 };
4061
4062 let mut output = String::from_utf8(output).expect("codegen generated non-utf8 output");
4063 if let Some(map) = map {
4064 output.extend(
4065 format!(
4066 "\n//# sourceMappingURL=data:application/json;base64,{}",
4067 Base64Display::new(&map, &BASE64_STANDARD)
4068 )
4069 .chars(),
4070 );
4071 }
4072 format!("data:text/javascript,{}", urlencoding::encode(&output))
4073}