1use std::{
2 cell::RefCell,
3 collections::{BTreeMap, hash_map},
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, Wtf8Atom, atom},
20 common::{
21 BytePos, DUMMY_SP, FileName, Mark, SourceMap, Span, SyntaxContext,
22 comments::{Comment, CommentKind, Comments, SingleThreadedComments},
23 errors::HANDLER,
24 source_map::{PURE_SP, SourceMapGenConfig},
25 util::take::Take,
26 },
27 ecma::{
28 ast::*,
29 codegen::{self, Emitter, text_writer::JsWriter},
30 utils::{ExprFactory, private_ident, quote_ident},
31 visit::{VisitMut, VisitMutWith, noop_visit_mut_type, visit_mut_pass},
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 + use<C> {
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 let closure_bound_ident =
485 Ident::new(atom!("$$ACTION_CLOSURE_BOUND"), DUMMY_SP, self.private_ctxt);
486
487 if !ids_from_closure.is_empty() {
488 new_params.push(Param {
490 span: DUMMY_SP,
491 decorators: vec![],
492 pat: Pat::Ident(closure_bound_ident.clone().into()),
493 });
494 }
495
496 for p in arrow.params.iter() {
497 new_params.push(Param::from(p.clone()));
498 }
499
500 let action_name = self.gen_action_ident();
501 let action_ident = Ident::new(action_name.clone(), arrow.span, self.private_ctxt);
502 let action_id = self.generate_server_reference_id(
503 &ModuleExportName::Ident(action_ident.clone()),
504 false,
505 Some(&new_params),
506 );
507
508 self.has_action = true;
509 self.reference_ids_by_export_name.insert(
510 ModuleExportName::Ident(action_ident.clone()),
511 action_id.clone(),
512 );
513
514 if self.current_export_name.is_some()
517 && let Some(arrow_ident) = &self.arrow_or_fn_expr_ident
518 {
519 self.export_name_by_local_id
520 .swap_remove(&arrow_ident.to_id());
521 }
522
523 if let BlockStmtOrExpr::BlockStmt(block) = &mut *arrow.body {
524 block.visit_mut_with(&mut ClosureReplacer {
525 used_ids: &ids_from_closure,
526 private_ctxt: self.private_ctxt,
527 });
528 }
529
530 let mut new_body: BlockStmtOrExpr = *arrow.body.clone();
531
532 if !ids_from_closure.is_empty() {
533 let decryption_decl = VarDecl {
537 span: DUMMY_SP,
538 kind: VarDeclKind::Var,
539 declare: false,
540 decls: vec![VarDeclarator {
541 span: DUMMY_SP,
542 name: self.create_bound_action_args_array_pat(ids_from_closure.len()),
543 init: Some(Box::new(Expr::Await(AwaitExpr {
544 span: DUMMY_SP,
545 arg: Box::new(Expr::Call(CallExpr {
546 span: DUMMY_SP,
547 callee: quote_ident!("decryptActionBoundArgs").as_callee(),
548 args: vec![action_id.clone().as_arg(), closure_bound_ident.as_arg()],
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 let closure_bound_ident =
650 Ident::new(atom!("$$ACTION_CLOSURE_BOUND"), DUMMY_SP, self.private_ctxt);
651
652 if !ids_from_closure.is_empty() {
653 new_params.push(Param {
655 span: DUMMY_SP,
656 decorators: vec![],
657 pat: Pat::Ident(closure_bound_ident.clone().into()),
658 });
659 }
660
661 new_params.append(&mut function.params);
662
663 let action_name: Atom = self.gen_action_ident();
664 let mut action_ident = Ident::new(action_name.clone(), function.span, self.private_ctxt);
665 if action_ident.span.lo == self.start_pos {
666 action_ident.span = Span::dummy_with_cmt();
667 }
668
669 let action_id = self.generate_server_reference_id(
670 &ModuleExportName::Ident(action_ident.clone()),
671 false,
672 Some(&new_params),
673 );
674
675 self.has_action = true;
676 self.reference_ids_by_export_name.insert(
677 ModuleExportName::Ident(action_ident.clone()),
678 action_id.clone(),
679 );
680
681 if self.current_export_name.is_some()
684 && let Some(ref fn_name) = fn_name
685 {
686 self.export_name_by_local_id.swap_remove(&fn_name.to_id());
687 }
688
689 function.body.visit_mut_with(&mut ClosureReplacer {
690 used_ids: &ids_from_closure,
691 private_ctxt: self.private_ctxt,
692 });
693
694 let mut new_body: Option<BlockStmt> = function.body.clone();
695
696 if !ids_from_closure.is_empty() {
697 let decryption_decl = VarDecl {
701 span: DUMMY_SP,
702 kind: VarDeclKind::Var,
703 decls: vec![VarDeclarator {
704 span: DUMMY_SP,
705 name: self.create_bound_action_args_array_pat(ids_from_closure.len()),
706 init: Some(Box::new(Expr::Await(AwaitExpr {
707 span: DUMMY_SP,
708 arg: Box::new(Expr::Call(CallExpr {
709 span: DUMMY_SP,
710 callee: quote_ident!("decryptActionBoundArgs").as_callee(),
711 args: vec![action_id.clone().as_arg(), closure_bound_ident.as_arg()],
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 && let Some(arrow_ident) = &self.arrow_or_fn_expr_ident
826 {
827 self.export_name_by_local_id
828 .swap_remove(&arrow_ident.to_id());
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 && let Some(ref fn_name) = fn_name
931 {
932 self.export_name_by_local_id.swap_remove(&fn_name.to_id());
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 && let Some(fn_name) = fn_name
1252 {
1253 self.export_name_by_local_id.swap_remove(&fn_name.to_id());
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 && let Some(export_name) = self.current_export_name.clone()
1270 {
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 if !self.config.is_react_server_layer {
1292 if matches!(directive, Directive::UseCache { .. })
1293 && let Some(export_name) = self.current_export_name.clone()
1294 {
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 return;
1304 }
1305
1306 if let Directive::UseCache { cache_kind } = directive {
1307 retain_names_from_declared_idents(
1310 &mut child_names,
1311 &self.declared_idents[..declared_idents_until],
1312 );
1313
1314 let new_expr = self.maybe_hoist_and_create_proxy_for_cache_function(
1315 child_names.clone(),
1316 self.fn_decl_ident
1317 .as_ref()
1318 .or(self.arrow_or_fn_expr_ident.as_ref())
1319 .cloned(),
1320 cache_kind,
1321 f,
1322 );
1323
1324 if self.is_default_export() {
1325 self.rewrite_default_fn_expr_to_proxy_expr = Some(new_expr);
1330 } else if let Some(ident) = &self.fn_decl_ident {
1331 self.rewrite_fn_decl_to_proxy_decl = Some(VarDecl {
1333 span: DUMMY_SP,
1334 kind: VarDeclKind::Var,
1335 decls: vec![VarDeclarator {
1336 span: DUMMY_SP,
1337 name: Pat::Ident(ident.clone().into()),
1338 init: Some(new_expr),
1339 definite: false,
1340 }],
1341 ..Default::default()
1342 });
1343 } else {
1344 self.rewrite_expr_to_proxy_expr = Some(new_expr);
1345 }
1346 } else {
1347 retain_names_from_declared_idents(
1350 &mut child_names,
1351 &self.declared_idents[..declared_idents_until],
1352 );
1353
1354 let new_expr = self.maybe_hoist_and_create_proxy_for_server_action_function(
1355 child_names,
1356 f,
1357 fn_name,
1358 );
1359
1360 if self.is_default_export() {
1361 self.rewrite_default_fn_expr_to_proxy_expr = Some(new_expr);
1366 } else if let Some(ident) = &self.fn_decl_ident {
1367 self.rewrite_fn_decl_to_proxy_decl = Some(VarDecl {
1370 span: DUMMY_SP,
1371 kind: VarDeclKind::Var,
1372 decls: vec![VarDeclarator {
1373 span: DUMMY_SP,
1374 name: Pat::Ident(ident.clone().into()),
1375 init: Some(new_expr),
1376 definite: false,
1377 }],
1378 ..Default::default()
1379 });
1380 } else {
1381 self.rewrite_expr_to_proxy_expr = Some(new_expr);
1382 }
1383 }
1384 }
1385 }
1386
1387 fn visit_mut_decl(&mut self, d: &mut Decl) {
1388 self.rewrite_fn_decl_to_proxy_decl = None;
1389 d.visit_mut_children_with(self);
1390
1391 if let Some(decl) = &self.rewrite_fn_decl_to_proxy_decl {
1392 *d = (*decl).clone().into();
1393 }
1394
1395 self.rewrite_fn_decl_to_proxy_decl = None;
1396 }
1397
1398 fn visit_mut_fn_decl(&mut self, f: &mut FnDecl) {
1399 let old_this_status = replace(&mut self.this_status, ThisStatus::Allowed);
1400 let old_current_export_name = self.current_export_name.take();
1401 if self.in_module_level
1402 && let Some(export_name) = self.export_name_by_local_id.get(&f.ident.to_id())
1403 {
1404 self.current_export_name = Some(export_name.clone());
1405 }
1406 let old_fn_decl_ident = self.fn_decl_ident.replace(f.ident.clone());
1407 f.visit_mut_children_with(self);
1408 self.this_status = old_this_status;
1409 self.current_export_name = old_current_export_name;
1410 self.fn_decl_ident = old_fn_decl_ident;
1411 }
1412
1413 fn visit_mut_arrow_expr(&mut self, a: &mut ArrowExpr) {
1414 let directive = self.get_directive_for_function(
1417 if let BlockStmtOrExpr::BlockStmt(block) = &mut *a.body {
1418 Some(block)
1419 } else {
1420 None
1421 },
1422 );
1423
1424 if let Some(directive) = &directive {
1425 self.this_status = ThisStatus::Forbidden {
1426 directive: directive.clone(),
1427 };
1428 }
1429
1430 let declared_idents_until = self.declared_idents.len();
1431 let old_names = take(&mut self.names);
1432
1433 {
1434 let old_in_module = replace(&mut self.in_module_level, false);
1436 let should_track_names = directive.is_some() || self.should_track_names;
1437 let old_should_track_names = replace(&mut self.should_track_names, should_track_names);
1438 let old_current_export_name = self.current_export_name.take();
1439 {
1440 for n in &mut a.params {
1441 collect_idents_in_pat(n, &mut self.declared_idents);
1442 }
1443 }
1444 a.visit_mut_children_with(self);
1445 self.in_module_level = old_in_module;
1446 self.should_track_names = old_should_track_names;
1447 self.current_export_name = old_current_export_name;
1448 }
1449
1450 let mut child_names = take(&mut self.names);
1451
1452 if self.should_track_names {
1453 self.names = [old_names, child_names.clone()].concat();
1454 }
1455
1456 if let Some(directive) = directive {
1457 let arrow_ident = self.arrow_or_fn_expr_ident.clone();
1458
1459 if !self.validate_async_function(a.is_async, a.span, arrow_ident.as_ref(), &directive) {
1460 if self.current_export_name.is_some()
1463 && let Some(arrow_ident) = arrow_ident
1464 {
1465 self.export_name_by_local_id
1466 .swap_remove(&arrow_ident.to_id());
1467 }
1468
1469 return;
1470 }
1471
1472 if HANDLER.with(|handler| handler.has_errors()) {
1475 return;
1476 }
1477
1478 if matches!(self.file_directive, Some(Directive::UseServer))
1481 && matches!(directive, Directive::UseServer)
1482 && let Some(export_name) = self.current_export_name.clone()
1483 {
1484 let params: Vec<Param> = a.params.iter().map(|p| Param::from(p.clone())).collect();
1485
1486 self.register_server_action_export(
1487 &export_name,
1488 arrow_ident.as_ref(),
1489 Some(¶ms),
1490 a.span,
1491 &mut || Box::new(Expr::Arrow(a.take())),
1492 );
1493
1494 return;
1495 }
1496
1497 if !self.config.is_react_server_layer {
1499 if matches!(directive, Directive::UseCache { .. })
1500 && let Some(export_name) = self.current_export_name.clone()
1501 {
1502 let params: Vec<Param> =
1503 a.params.iter().map(|p| Param::from(p.clone())).collect();
1504
1505 self.register_cache_export_on_client(
1506 &export_name,
1507 arrow_ident.as_ref(),
1508 Some(¶ms),
1509 a.span,
1510 );
1511 }
1512
1513 return;
1514 }
1515
1516 retain_names_from_declared_idents(
1519 &mut child_names,
1520 &self.declared_idents[..declared_idents_until],
1521 );
1522
1523 if let Directive::UseCache { cache_kind } = directive {
1524 self.rewrite_expr_to_proxy_expr =
1525 Some(self.maybe_hoist_and_create_proxy_for_cache_arrow_expr(
1526 child_names,
1527 cache_kind,
1528 a,
1529 ));
1530 } else {
1531 self.rewrite_expr_to_proxy_expr = Some(
1532 self.maybe_hoist_and_create_proxy_for_server_action_arrow_expr(child_names, a),
1533 );
1534 }
1535 }
1536 }
1537
1538 fn visit_mut_module(&mut self, m: &mut Module) {
1539 self.start_pos = m.span.lo;
1540 m.visit_mut_children_with(self);
1541 }
1542
1543 fn visit_mut_stmt(&mut self, n: &mut Stmt) {
1544 n.visit_mut_children_with(self);
1545
1546 if self.in_module_level {
1547 return;
1548 }
1549
1550 collect_decl_idents_in_stmt(n, &mut self.declared_idents);
1553 }
1554
1555 fn visit_mut_param(&mut self, n: &mut Param) {
1556 n.visit_mut_children_with(self);
1557
1558 if self.in_module_level {
1559 return;
1560 }
1561
1562 collect_idents_in_pat(&n.pat, &mut self.declared_idents);
1563 }
1564
1565 fn visit_mut_prop_or_spread(&mut self, n: &mut PropOrSpread) {
1566 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.clone();
1567 let old_current_export_name = self.current_export_name.take();
1568
1569 match n {
1570 PropOrSpread::Prop(box Prop::KeyValue(KeyValueProp {
1571 key: PropName::Ident(ident_name),
1572 value: box Expr::Arrow(_) | box Expr::Fn(_),
1573 ..
1574 })) => {
1575 self.current_export_name = None;
1576 self.arrow_or_fn_expr_ident = Some(ident_name.clone().into());
1577 }
1578 PropOrSpread::Prop(box Prop::Method(MethodProp { key, .. })) => {
1579 let key = key.clone();
1580
1581 if let PropName::Ident(ident_name) = &key {
1582 self.arrow_or_fn_expr_ident = Some(ident_name.clone().into());
1583 }
1584
1585 let old_this_status = replace(&mut self.this_status, ThisStatus::Allowed);
1586 self.rewrite_expr_to_proxy_expr = None;
1587 self.current_export_name = None;
1588 n.visit_mut_children_with(self);
1589 self.current_export_name = old_current_export_name.clone();
1590 self.this_status = old_this_status;
1591
1592 if let Some(expr) = self.rewrite_expr_to_proxy_expr.take() {
1593 *n = PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
1594 key,
1595 value: expr,
1596 })));
1597 }
1598
1599 return;
1600 }
1601 _ => {}
1602 }
1603
1604 if !self.in_module_level
1605 && self.should_track_names
1606 && let PropOrSpread::Prop(box Prop::Shorthand(i)) = n
1607 {
1608 self.names.push(Name::from(&*i));
1609 self.should_track_names = false;
1610 n.visit_mut_children_with(self);
1611 self.should_track_names = true;
1612 return;
1613 }
1614
1615 n.visit_mut_children_with(self);
1616 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
1617 self.current_export_name = old_current_export_name;
1618 }
1619
1620 fn visit_mut_class(&mut self, n: &mut Class) {
1621 let old_this_status = replace(&mut self.this_status, ThisStatus::Allowed);
1622 n.visit_mut_children_with(self);
1623 self.this_status = old_this_status;
1624 }
1625
1626 fn visit_mut_class_member(&mut self, n: &mut ClassMember) {
1627 if let ClassMember::Method(ClassMethod {
1628 is_abstract: false,
1629 is_static: true,
1630 kind: MethodKind::Method,
1631 key,
1632 span,
1633 accessibility: None | Some(Accessibility::Public),
1634 ..
1635 }) = n
1636 {
1637 let key = key.clone();
1638 let span = *span;
1639 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.clone();
1640
1641 if let PropName::Ident(ident_name) = &key {
1642 self.arrow_or_fn_expr_ident = Some(ident_name.clone().into());
1643 }
1644
1645 let old_this_status = replace(&mut self.this_status, ThisStatus::Allowed);
1646 let old_current_export_name = self.current_export_name.take();
1647 self.rewrite_expr_to_proxy_expr = None;
1648 self.current_export_name = None;
1649 n.visit_mut_children_with(self);
1650 self.this_status = old_this_status;
1651 self.current_export_name = old_current_export_name;
1652 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
1653
1654 if let Some(expr) = self.rewrite_expr_to_proxy_expr.take() {
1655 *n = ClassMember::ClassProp(ClassProp {
1656 span,
1657 key,
1658 value: Some(expr),
1659 is_static: true,
1660 ..Default::default()
1661 });
1662 }
1663 } else {
1664 n.visit_mut_children_with(self);
1665 }
1666 }
1667
1668 fn visit_mut_class_method(&mut self, n: &mut ClassMethod) {
1669 if n.is_static {
1670 n.visit_mut_children_with(self);
1671 } else {
1672 let (is_action_fn, is_cache_fn) = has_body_directive(&n.function.body);
1673
1674 if is_action_fn {
1675 emit_error(
1676 ServerActionsErrorKind::InlineUseServerInClassInstanceMethod { span: n.span },
1677 );
1678 } else if is_cache_fn {
1679 emit_error(
1680 ServerActionsErrorKind::InlineUseCacheInClassInstanceMethod { span: n.span },
1681 );
1682 } else {
1683 n.visit_mut_children_with(self);
1684 }
1685 }
1686 }
1687
1688 fn visit_mut_call_expr(&mut self, n: &mut CallExpr) {
1689 if let Callee::Expr(box Expr::Ident(Ident { sym, .. })) = &mut n.callee
1690 && (sym == "jsxDEV" || sym == "_jsxDEV")
1691 {
1692 if n.args.len() > 4 {
1696 for arg in &mut n.args[0..4] {
1697 arg.visit_mut_with(self);
1698 }
1699 return;
1700 }
1701 }
1702
1703 let old_current_export_name = self.current_export_name.take();
1704 n.visit_mut_children_with(self);
1705 self.current_export_name = old_current_export_name;
1706 }
1707
1708 fn visit_mut_callee(&mut self, n: &mut Callee) {
1709 let old_in_callee = replace(&mut self.in_callee, true);
1710 n.visit_mut_children_with(self);
1711 self.in_callee = old_in_callee;
1712 }
1713
1714 fn visit_mut_expr(&mut self, n: &mut Expr) {
1715 if !self.in_module_level
1716 && self.should_track_names
1717 && let Ok(mut name) = Name::try_from(&*n)
1718 {
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 self.rewrite_expr_to_proxy_expr = None;
1735 n.visit_mut_children_with(self);
1736 if let Some(expr) = self.rewrite_expr_to_proxy_expr.take() {
1737 *n = *expr;
1738 }
1739 }
1740
1741 fn visit_mut_module_items(&mut self, stmts: &mut Vec<ModuleItem>) {
1742 self.file_directive = self.get_directive_for_module(stmts);
1743
1744 let in_cache_file = matches!(self.file_directive, Some(Directive::UseCache { .. }));
1745 let in_action_file = matches!(self.file_directive, Some(Directive::UseServer));
1746
1747 let should_track_exports = in_action_file || in_cache_file;
1749
1750 if should_track_exports {
1756 for stmt in stmts.iter() {
1757 match stmt {
1758 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(export_default_expr)) => {
1759 if let Expr::Ident(ident) = &*export_default_expr.expr {
1760 self.export_name_by_local_id.insert(
1761 ident.to_id(),
1762 ModuleExportName::Ident(atom!("default").into()),
1763 );
1764 }
1765 }
1766 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(export_default_decl)) => {
1767 if let DefaultDecl::Fn(f) = &export_default_decl.decl
1769 && let Some(ident) = &f.ident
1770 {
1771 self.export_name_by_local_id.insert(
1772 ident.to_id(),
1773 ModuleExportName::Ident(atom!("default").into()),
1774 );
1775 }
1776 }
1777 ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export_decl)) => {
1778 match &export_decl.decl {
1780 Decl::Fn(f) => {
1781 self.export_name_by_local_id.insert(
1782 f.ident.to_id(),
1783 ModuleExportName::Ident(f.ident.clone()),
1784 );
1785 }
1786 Decl::Var(var) => {
1787 for decl in &var.decls {
1788 let mut idents = vec![];
1794 collect_idents_in_pat(&decl.name, &mut idents);
1795
1796 let is_destructuring = !matches!(&decl.name, Pat::Ident(_));
1797 let needs_wrapper = if is_destructuring {
1798 true
1799 } else if let Some(init) = &decl.init {
1800 may_need_cache_runtime_wrapper(init)
1801 } else {
1802 false
1803 };
1804
1805 for ident in idents {
1806 self.export_name_by_local_id.insert(
1807 ident.to_id(),
1808 ModuleExportName::Ident(ident.clone()),
1809 );
1810
1811 if needs_wrapper {
1812 self.local_ids_that_need_cache_runtime_wrapper_if_exported
1813 .insert(ident.to_id());
1814 }
1815 }
1816 }
1817 }
1818 _ => {}
1819 }
1820 }
1821 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(named_export)) => {
1822 if named_export.src.is_none() {
1823 for spec in &named_export.specifiers {
1824 match spec {
1825 ExportSpecifier::Named(ExportNamedSpecifier {
1826 orig: ModuleExportName::Ident(orig),
1827 exported: Some(exported),
1828 is_type_only: false,
1829 ..
1830 }) => {
1831 self.export_name_by_local_id
1833 .insert(orig.to_id(), exported.clone());
1834 }
1835 ExportSpecifier::Named(ExportNamedSpecifier {
1836 orig: ModuleExportName::Ident(orig),
1837 exported: None,
1838 is_type_only: false,
1839 ..
1840 }) => {
1841 self.export_name_by_local_id.insert(
1843 orig.to_id(),
1844 ModuleExportName::Ident(orig.clone()),
1845 );
1846 }
1847 _ => {}
1848 }
1849 }
1850 }
1851 }
1852 ModuleItem::Stmt(Stmt::Decl(Decl::Var(var_decl))) => {
1853 for decl in &var_decl.decls {
1855 if let Pat::Ident(ident_pat) = &decl.name
1856 && let Some(init) = &decl.init
1857 && may_need_cache_runtime_wrapper(init)
1858 {
1859 self.local_ids_that_need_cache_runtime_wrapper_if_exported
1860 .insert(ident_pat.id.to_id());
1861 }
1862 }
1863 }
1864 ModuleItem::Stmt(Stmt::Decl(Decl::Fn(_fn_decl))) => {
1865 }
1868 ModuleItem::ModuleDecl(ModuleDecl::Import(import_decl)) => {
1869 for spec in &import_decl.specifiers {
1872 match spec {
1873 ImportSpecifier::Named(named) => {
1874 self.local_ids_that_need_cache_runtime_wrapper_if_exported
1875 .insert(named.local.to_id());
1876 }
1877 ImportSpecifier::Default(default) => {
1878 self.local_ids_that_need_cache_runtime_wrapper_if_exported
1879 .insert(default.local.to_id());
1880 }
1881 ImportSpecifier::Namespace(ns) => {
1882 self.local_ids_that_need_cache_runtime_wrapper_if_exported
1883 .insert(ns.local.to_id());
1884 }
1885 }
1886 }
1887 }
1888 _ => {}
1889 }
1890 }
1891 }
1892
1893 let old_annotations = self.annotations.take();
1894 let mut new = Vec::with_capacity(stmts.len());
1895
1896 for mut stmt in stmts.take() {
1899 let mut should_remove_statement = false;
1900
1901 if should_track_exports {
1902 let mut disallowed_export_span = DUMMY_SP;
1903
1904 match &mut stmt {
1905 ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { decl, span })) => {
1906 match decl {
1907 Decl::Var(var) => {
1908 let mut has_export_needing_wrapper = false;
1909
1910 for decl in &var.decls {
1911 if let Pat::Ident(_) = &decl.name
1912 && let Some(init) = &decl.init
1913 {
1914 if let Expr::Lit(_) = &**init {
1921 disallowed_export_span = *span;
1922 }
1923 }
1924
1925 if in_cache_file {
1928 let mut idents: Vec<Ident> = Vec::new();
1929 collect_idents_in_pat(&decl.name, &mut idents);
1930
1931 for ident in idents {
1932 let needs_cache_runtime_wrapper = self
1933 .local_ids_that_need_cache_runtime_wrapper_if_exported
1934 .contains(&ident.to_id());
1935
1936 if needs_cache_runtime_wrapper {
1937 has_export_needing_wrapper = true;
1938 }
1939 }
1940 }
1941 }
1942
1943 if in_cache_file && has_export_needing_wrapper {
1946 stmt = ModuleItem::Stmt(Stmt::Decl(Decl::Var(var.clone())));
1947 }
1948 }
1949 Decl::Fn(_)
1950 | Decl::TsInterface(_)
1951 | Decl::TsTypeAlias(_)
1952 | Decl::TsEnum(_) => {}
1953 _ => {
1954 disallowed_export_span = *span;
1955 }
1956 }
1957 }
1958 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(named)) => {
1959 if !named.type_only {
1960 if let Some(src) = &named.src {
1961 if in_cache_file {
1963 let import_specs: Vec<ImportSpecifier> = named
1966 .specifiers
1967 .iter()
1968 .filter_map(|spec| {
1969 if let ExportSpecifier::Named(ExportNamedSpecifier {
1970 orig: ModuleExportName::Ident(orig),
1971 exported,
1972 is_type_only: false,
1973 ..
1974 }) = spec
1975 {
1976 let export_name =
1980 if let Some(exported) = exported {
1981 exported.clone()
1982 } else {
1983 ModuleExportName::Ident(orig.clone())
1984 };
1985
1986 self.export_name_by_local_id
1987 .insert(orig.to_id(), export_name);
1988
1989 self.local_ids_that_need_cache_runtime_wrapper_if_exported
1990 .insert(orig.to_id());
1991
1992 return Some(ImportSpecifier::Named(
1993 ImportNamedSpecifier {
1994 span: DUMMY_SP,
1995 local: orig.clone(),
1996 imported: None,
1997 is_type_only: false,
1998 },
1999 ));
2000 }
2001 None
2002 })
2003 .collect();
2004
2005 if !import_specs.is_empty() {
2006 self.extra_items.push(ModuleItem::ModuleDecl(
2008 ModuleDecl::Import(ImportDecl {
2009 span: named.span,
2010 specifiers: import_specs,
2011 src: src.clone(),
2012 type_only: false,
2013 with: named.with.clone(),
2014 phase: Default::default(),
2015 }),
2016 ));
2017 }
2018
2019 named.specifiers.retain(|spec| {
2022 matches!(
2023 spec,
2024 ExportSpecifier::Named(ExportNamedSpecifier {
2025 is_type_only: true,
2026 ..
2027 })
2028 )
2029 });
2030
2031 if named.specifiers.is_empty() {
2034 should_remove_statement = true;
2035 }
2036 } else if named.specifiers.iter().any(|s| match s {
2037 ExportSpecifier::Namespace(_) | ExportSpecifier::Default(_) => {
2038 true
2039 }
2040 ExportSpecifier::Named(s) => !s.is_type_only,
2041 }) {
2042 disallowed_export_span = named.span;
2043 }
2044 } else {
2045 if in_cache_file {
2049 named.specifiers.retain(|spec| {
2050 if let ExportSpecifier::Named(ExportNamedSpecifier {
2051 orig: ModuleExportName::Ident(ident),
2052 is_type_only: false,
2053 ..
2054 }) = spec
2055 {
2056 !self
2057 .local_ids_that_need_cache_runtime_wrapper_if_exported
2058 .contains(&ident.to_id())
2059 } else {
2060 true
2061 }
2062 });
2063
2064 if named.specifiers.is_empty() {
2065 should_remove_statement = true;
2066 }
2067 }
2068 }
2069 }
2070 }
2071 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(ExportDefaultDecl {
2072 decl,
2073 span,
2074 })) => match decl {
2075 DefaultDecl::Fn(_) | DefaultDecl::TsInterfaceDecl(_) => {}
2076 _ => {
2077 disallowed_export_span = *span;
2078 }
2079 },
2080 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(default_expr)) => {
2081 match &mut *default_expr.expr {
2082 Expr::Fn(_) | Expr::Arrow(_) => {}
2083 Expr::Ident(ident) => {
2084 if in_cache_file {
2087 let needs_cache_runtime_wrapper = self
2088 .local_ids_that_need_cache_runtime_wrapper_if_exported
2089 .contains(&ident.to_id());
2090
2091 if needs_cache_runtime_wrapper {
2092 should_remove_statement = true;
2093 }
2094 }
2095 }
2096 Expr::Call(_call) => {
2097 if in_cache_file {
2101 should_remove_statement = true;
2102 }
2103 }
2104 _ => {
2105 disallowed_export_span = default_expr.span;
2106 }
2107 }
2108 }
2109 ModuleItem::ModuleDecl(ModuleDecl::ExportAll(ExportAll {
2110 span,
2111 type_only,
2112 ..
2113 })) => {
2114 if !*type_only {
2115 disallowed_export_span = *span;
2116 }
2117 }
2118 _ => {}
2119 }
2120
2121 if disallowed_export_span != DUMMY_SP {
2123 emit_error(ServerActionsErrorKind::ExportedSyncFunction {
2124 span: disallowed_export_span,
2125 in_action_file,
2126 });
2127 return;
2128 }
2129 }
2130
2131 stmt.visit_mut_with(self);
2132
2133 let new_stmt = if should_remove_statement {
2134 None
2135 } else if let Some(expr) = self.rewrite_default_fn_expr_to_proxy_expr.take() {
2136 Some(ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(
2137 ExportDefaultExpr {
2138 span: DUMMY_SP,
2139 expr,
2140 },
2141 )))
2142 } else {
2143 Some(stmt)
2144 };
2145
2146 if self.config.is_react_server_layer || self.file_directive.is_none() {
2147 new.append(&mut self.hoisted_extra_items);
2148 if let Some(stmt) = new_stmt {
2149 new.push(stmt);
2150 }
2151 new.extend(self.annotations.drain(..).map(ModuleItem::Stmt));
2152 new.append(&mut self.extra_items);
2153 }
2154 }
2155
2156 if should_track_exports {
2159 for (id, export_name) in &self.export_name_by_local_id {
2160 if self.reference_ids_by_export_name.contains_key(export_name) {
2161 continue;
2162 }
2163
2164 if in_cache_file
2165 && !self
2166 .local_ids_that_need_cache_runtime_wrapper_if_exported
2167 .contains(id)
2168 {
2169 continue;
2170 }
2171
2172 self.server_reference_exports.push(ServerReferenceExport {
2173 ident: Ident::from(id.clone()),
2174 export_name: export_name.clone(),
2175 reference_id: self.generate_server_reference_id(
2176 export_name,
2177 in_cache_file,
2178 None,
2179 ),
2180 needs_cache_runtime_wrapper: in_cache_file,
2181 });
2182 }
2183 }
2184
2185 if in_action_file || in_cache_file && !self.config.is_react_server_layer {
2186 self.reference_ids_by_export_name.extend(
2187 self.server_reference_exports
2188 .iter()
2189 .map(|e| (e.export_name.clone(), e.reference_id.clone())),
2190 );
2191
2192 if !self.reference_ids_by_export_name.is_empty() {
2193 self.has_action |= in_action_file;
2194 self.has_cache |= in_cache_file;
2195 }
2196 };
2197
2198 let create_ref_ident = private_ident!("createServerReference");
2201 let call_server_ident = private_ident!("callServer");
2202 let find_source_map_url_ident = private_ident!("findSourceMapURL");
2203
2204 let client_layer_import = ((self.has_action || self.has_cache)
2205 && !self.config.is_react_server_layer)
2206 .then(|| {
2207 ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
2214 span: DUMMY_SP,
2215 specifiers: vec![
2216 ImportSpecifier::Named(ImportNamedSpecifier {
2217 span: DUMMY_SP,
2218 local: create_ref_ident.clone(),
2219 imported: None,
2220 is_type_only: false,
2221 }),
2222 ImportSpecifier::Named(ImportNamedSpecifier {
2223 span: DUMMY_SP,
2224 local: call_server_ident.clone(),
2225 imported: None,
2226 is_type_only: false,
2227 }),
2228 ImportSpecifier::Named(ImportNamedSpecifier {
2229 span: DUMMY_SP,
2230 local: find_source_map_url_ident.clone(),
2231 imported: None,
2232 is_type_only: false,
2233 }),
2234 ],
2235 src: Box::new(Str {
2236 span: DUMMY_SP,
2237 value: atom!("private-next-rsc-action-client-wrapper").into(),
2238 raw: None,
2239 }),
2240 type_only: false,
2241 with: None,
2242 phase: Default::default(),
2243 }))
2244 });
2245
2246 let mut client_layer_exports = FxIndexMap::default();
2247
2248 if should_track_exports {
2250 let server_reference_exports = self.server_reference_exports.take();
2251
2252 for ServerReferenceExport {
2253 ident,
2254 export_name,
2255 reference_id: ref_id,
2256 needs_cache_runtime_wrapper,
2257 ..
2258 } in &server_reference_exports
2259 {
2260 if !self.config.is_react_server_layer {
2261 if matches!(export_name, ModuleExportName::Ident(i) if i.sym == *"default") {
2262 let export_expr = ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(
2263 ExportDefaultExpr {
2264 span: DUMMY_SP,
2265 expr: Box::new(Expr::Call(CallExpr {
2266 span: if self.config.is_react_server_layer
2270 || self.config.is_development
2271 {
2272 self.comments.add_pure_comment(ident.span.lo);
2273 ident.span
2274 } else {
2275 PURE_SP
2276 },
2277 callee: Callee::Expr(Box::new(Expr::Ident(
2278 create_ref_ident.clone(),
2279 ))),
2280 args: vec![
2281 ref_id.clone().as_arg(),
2282 call_server_ident.clone().as_arg(),
2283 Expr::undefined(DUMMY_SP).as_arg(),
2284 find_source_map_url_ident.clone().as_arg(),
2285 "default".as_arg(),
2286 ],
2287 ..Default::default()
2288 })),
2289 },
2290 ));
2291 client_layer_exports.insert(
2292 atom!("default"),
2293 (
2294 vec![export_expr],
2295 ModuleExportName::Ident(atom!("default").into()),
2296 ref_id.clone(),
2297 ),
2298 );
2299 } else {
2300 let var_name = if in_cache_file {
2301 self.gen_cache_ident()
2302 } else {
2303 self.gen_action_ident()
2304 };
2305
2306 let var_ident = Ident::new(var_name.clone(), DUMMY_SP, self.private_ctxt);
2307
2308 let name_span =
2312 if self.config.is_react_server_layer || self.config.is_development {
2313 ident.span
2314 } else {
2315 DUMMY_SP
2316 };
2317
2318 let export_name_str: Wtf8Atom = match export_name {
2319 ModuleExportName::Ident(i) => i.sym.clone().into(),
2320 ModuleExportName::Str(s) => s.value.clone(),
2321 };
2322
2323 let var_decl = ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl {
2324 span: DUMMY_SP,
2325 kind: VarDeclKind::Const,
2326 decls: vec![VarDeclarator {
2327 span: DUMMY_SP,
2328 name: Pat::Ident(
2329 Ident::new(var_name.clone(), name_span, self.private_ctxt)
2330 .into(),
2331 ),
2332 init: Some(Box::new(Expr::Call(CallExpr {
2333 span: PURE_SP,
2334 callee: Callee::Expr(Box::new(Expr::Ident(
2335 create_ref_ident.clone(),
2336 ))),
2337 args: vec![
2338 ref_id.clone().as_arg(),
2339 call_server_ident.clone().as_arg(),
2340 Expr::undefined(DUMMY_SP).as_arg(),
2341 find_source_map_url_ident.clone().as_arg(),
2342 export_name_str.as_arg(),
2343 ],
2344 ..Default::default()
2345 }))),
2346 definite: false,
2347 }],
2348 ..Default::default()
2349 }))));
2350
2351 let exported_name =
2355 if self.config.is_react_server_layer || self.config.is_development {
2356 export_name.clone()
2357 } else {
2358 strip_export_name_span(export_name)
2359 };
2360
2361 let export_named =
2362 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(NamedExport {
2363 span: DUMMY_SP,
2364 specifiers: vec![ExportSpecifier::Named(ExportNamedSpecifier {
2365 span: DUMMY_SP,
2366 orig: ModuleExportName::Ident(var_ident),
2367 exported: Some(exported_name),
2368 is_type_only: false,
2369 })],
2370 src: None,
2371 type_only: false,
2372 with: None,
2373 }));
2374
2375 client_layer_exports.insert(
2376 var_name,
2377 (
2378 vec![var_decl, export_named],
2379 export_name.clone(),
2380 ref_id.clone(),
2381 ),
2382 );
2383 }
2384 } else if in_cache_file {
2385 if !*needs_cache_runtime_wrapper {
2390 continue;
2391 }
2392
2393 let wrapper_ident = Ident::new(
2395 format!("$$RSC_SERVER_CACHE_{}", export_name.atom()).into(),
2396 ident.span,
2397 self.private_ctxt,
2398 );
2399
2400 self.has_cache = true;
2401 self.reference_ids_by_export_name
2402 .insert(export_name.clone(), ref_id.clone());
2403
2404 self.extra_items
2406 .push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl {
2407 kind: VarDeclKind::Let,
2408 decls: vec![VarDeclarator {
2409 span: ident.span,
2410 name: Pat::Ident(wrapper_ident.clone().into()),
2411 init: Some(Box::new(Expr::Ident(ident.clone()))),
2412 definite: false,
2413 }],
2414 ..Default::default()
2415 })))));
2416
2417 let wrapper_stmts = {
2418 let mut stmts = vec![
2419 Stmt::Expr(ExprStmt {
2421 span: DUMMY_SP,
2422 expr: Box::new(Expr::Assign(AssignExpr {
2423 span: DUMMY_SP,
2424 op: op!("="),
2425 left: AssignTarget::Simple(SimpleAssignTarget::Ident(
2426 wrapper_ident.clone().into(),
2427 )),
2428 right: Box::new(create_cache_wrapper(
2429 "default",
2430 ref_id.clone(),
2431 0,
2432 None,
2435 Expr::Ident(ident.clone()),
2436 ident.span,
2437 None,
2438 self.unresolved_ctxt,
2439 )),
2440 })),
2441 }),
2442 Stmt::Expr(ExprStmt {
2444 span: DUMMY_SP,
2445 expr: Box::new(annotate_ident_as_server_reference(
2446 wrapper_ident.clone(),
2447 ref_id.clone(),
2448 ident.span,
2449 )),
2450 }),
2451 ];
2452
2453 if !ident.sym.starts_with("$$RSC_SERVER_") {
2455 stmts.push(assign_name_to_ident(
2457 &wrapper_ident,
2458 &ident.sym,
2459 self.unresolved_ctxt,
2460 ));
2461 }
2462
2463 stmts
2464 };
2465
2466 self.extra_items.push(ModuleItem::Stmt(Stmt::If(IfStmt {
2468 test: Box::new(Expr::Bin(BinExpr {
2469 span: DUMMY_SP,
2470 op: op!("==="),
2471 left: Box::new(Expr::Unary(UnaryExpr {
2472 span: DUMMY_SP,
2473 op: op!("typeof"),
2474 arg: Box::new(Expr::Ident(ident.clone())),
2475 })),
2476 right: Box::new(Expr::Lit(Lit::Str(Str {
2477 span: DUMMY_SP,
2478 value: atom!("function").into(),
2479 raw: None,
2480 }))),
2481 })),
2482 cons: Box::new(Stmt::Block(BlockStmt {
2483 stmts: wrapper_stmts,
2484 ..Default::default()
2485 })),
2486 ..Default::default()
2487 })));
2488
2489 if matches!(export_name, ModuleExportName::Ident(i) if i.sym == *"default") {
2491 self.extra_items.push(ModuleItem::ModuleDecl(
2492 ModuleDecl::ExportDefaultExpr(ExportDefaultExpr {
2493 span: DUMMY_SP,
2494 expr: Box::new(Expr::Ident(wrapper_ident)),
2495 }),
2496 ));
2497 } else {
2498 self.extra_items
2499 .push(ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(
2500 NamedExport {
2501 span: DUMMY_SP,
2502 specifiers: vec![ExportSpecifier::Named(
2503 ExportNamedSpecifier {
2504 span: DUMMY_SP,
2505 orig: ModuleExportName::Ident(wrapper_ident),
2506 exported: Some(export_name.clone()),
2507 is_type_only: false,
2508 },
2509 )],
2510 src: None,
2511 type_only: false,
2512 with: None,
2513 },
2514 )));
2515 }
2516 } else {
2517 self.annotations.push(Stmt::Expr(ExprStmt {
2518 span: DUMMY_SP,
2519 expr: Box::new(annotate_ident_as_server_reference(
2520 ident.clone(),
2521 ref_id.clone(),
2522 ident.span,
2523 )),
2524 }));
2525 }
2526 }
2527
2528 if (self.has_action || self.has_cache) && self.config.is_react_server_layer {
2536 new.append(&mut self.extra_items);
2537
2538 if !in_cache_file && !server_reference_exports.is_empty() {
2540 let ensure_ident = private_ident!("ensureServerEntryExports");
2541 new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
2542 span: DUMMY_SP,
2543 specifiers: vec![ImportSpecifier::Named(ImportNamedSpecifier {
2544 span: DUMMY_SP,
2545 local: ensure_ident.clone(),
2546 imported: None,
2547 is_type_only: false,
2548 })],
2549 src: Box::new(Str {
2550 span: DUMMY_SP,
2551 value: atom!("private-next-rsc-action-validate").into(),
2552 raw: None,
2553 }),
2554 type_only: false,
2555 with: None,
2556 phase: Default::default(),
2557 })));
2558 new.push(ModuleItem::Stmt(Stmt::Expr(ExprStmt {
2559 span: DUMMY_SP,
2560 expr: Box::new(Expr::Call(CallExpr {
2561 span: DUMMY_SP,
2562 callee: Callee::Expr(Box::new(Expr::Ident(ensure_ident))),
2563 args: vec![ExprOrSpread {
2564 spread: None,
2565 expr: Box::new(Expr::Array(ArrayLit {
2566 span: DUMMY_SP,
2567 elems: server_reference_exports
2568 .iter()
2569 .map(|ServerReferenceExport { ident, .. }| {
2570 Some(ExprOrSpread {
2571 spread: None,
2572 expr: Box::new(Expr::Ident(ident.clone())),
2573 })
2574 })
2575 .collect(),
2576 })),
2577 }],
2578 ..Default::default()
2579 })),
2580 })));
2581 }
2582
2583 new.extend(self.annotations.drain(..).map(ModuleItem::Stmt));
2585 }
2586 }
2587
2588 if self.has_cache && self.config.is_react_server_layer {
2591 new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
2592 span: DUMMY_SP,
2593 specifiers: vec![ImportSpecifier::Named(ImportNamedSpecifier {
2594 span: DUMMY_SP,
2595 local: quote_ident!("$$cache__").into(),
2596 imported: Some(quote_ident!("cache").into()),
2597 is_type_only: false,
2598 })],
2599 src: Box::new(Str {
2600 span: DUMMY_SP,
2601 value: atom!("private-next-rsc-cache-wrapper").into(),
2602 raw: None,
2603 }),
2604 type_only: false,
2605 with: None,
2606 phase: Default::default(),
2607 })));
2608
2609 new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
2610 span: DUMMY_SP,
2611 specifiers: vec![ImportSpecifier::Named(ImportNamedSpecifier {
2612 span: DUMMY_SP,
2613 local: quote_ident!("$$reactCache__").into(),
2614 imported: Some(quote_ident!("cache").into()),
2615 is_type_only: false,
2616 })],
2617 src: Box::new(Str {
2618 span: DUMMY_SP,
2619 value: atom!("react").into(),
2620 raw: None,
2621 }),
2622 type_only: false,
2623 with: None,
2624 phase: Default::default(),
2625 })));
2626
2627 new.rotate_right(2);
2629 }
2630
2631 if (self.has_action || self.has_cache) && self.config.is_react_server_layer {
2632 new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
2635 span: DUMMY_SP,
2636 specifiers: vec![ImportSpecifier::Named(ImportNamedSpecifier {
2637 span: DUMMY_SP,
2638 local: quote_ident!("registerServerReference").into(),
2639 imported: None,
2640 is_type_only: false,
2641 })],
2642 src: Box::new(Str {
2643 span: DUMMY_SP,
2644 value: atom!("private-next-rsc-server-reference").into(),
2645 raw: None,
2646 }),
2647 type_only: false,
2648 with: None,
2649 phase: Default::default(),
2650 })));
2651
2652 let mut import_count = 1;
2653
2654 if self.has_server_reference_with_bound_args {
2656 new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
2659 span: DUMMY_SP,
2660 specifiers: vec![
2661 ImportSpecifier::Named(ImportNamedSpecifier {
2662 span: DUMMY_SP,
2663 local: quote_ident!("encryptActionBoundArgs").into(),
2664 imported: None,
2665 is_type_only: false,
2666 }),
2667 ImportSpecifier::Named(ImportNamedSpecifier {
2668 span: DUMMY_SP,
2669 local: quote_ident!("decryptActionBoundArgs").into(),
2670 imported: None,
2671 is_type_only: false,
2672 }),
2673 ],
2674 src: Box::new(Str {
2675 span: DUMMY_SP,
2676 value: atom!("private-next-rsc-action-encryption").into(),
2677 raw: None,
2678 }),
2679 type_only: false,
2680 with: None,
2681 phase: Default::default(),
2682 })));
2683 import_count += 1;
2684 }
2685
2686 new.rotate_right(import_count);
2688 }
2689
2690 if self.has_action || self.has_cache {
2691 let export_infos_ordered_by_reference_id = self
2693 .reference_ids_by_export_name
2694 .iter()
2695 .map(|(export_name, reference_id)| {
2696 let name_atom = export_name.atom().into_owned();
2697 (reference_id, ServerReferenceExportInfo { name: name_atom })
2698 })
2699 .collect::<BTreeMap<_, _>>();
2700
2701 if self.config.is_react_server_layer {
2702 self.comments.add_leading(
2704 self.start_pos,
2705 Comment {
2706 span: DUMMY_SP,
2707 kind: CommentKind::Block,
2708 text: generate_server_references_comment(
2709 &export_infos_ordered_by_reference_id,
2710 match self.mode {
2711 ServerActionsMode::Webpack => None,
2712 ServerActionsMode::Turbopack => Some((
2713 &self.file_name,
2714 self.file_query.as_ref().map_or("", |v| v),
2715 )),
2716 },
2717 )
2718 .into(),
2719 },
2720 );
2721 } else {
2722 match self.mode {
2723 ServerActionsMode::Webpack => {
2724 self.comments.add_leading(
2725 self.start_pos,
2726 Comment {
2727 span: DUMMY_SP,
2728 kind: CommentKind::Block,
2729 text: generate_server_references_comment(
2730 &export_infos_ordered_by_reference_id,
2731 None,
2732 )
2733 .into(),
2734 },
2735 );
2736 new.push(client_layer_import.unwrap());
2737 new.rotate_right(1);
2738 new.extend(
2739 client_layer_exports
2740 .into_iter()
2741 .flat_map(|(_, (items, _, _))| items),
2742 );
2743 }
2744 ServerActionsMode::Turbopack => {
2745 new.push(ModuleItem::Stmt(Stmt::Expr(ExprStmt {
2746 expr: Box::new(Expr::Lit(Lit::Str(
2747 atom!("use turbopack no side effects").into(),
2748 ))),
2749 span: DUMMY_SP,
2750 })));
2751 new.rotate_right(1);
2752 for (_, (items, export_name, ref_id)) in client_layer_exports {
2753 let mut module_items = vec![
2754 ModuleItem::Stmt(Stmt::Expr(ExprStmt {
2755 expr: Box::new(Expr::Lit(Lit::Str(
2756 atom!("use turbopack no side effects").into(),
2757 ))),
2758 span: DUMMY_SP,
2759 })),
2760 client_layer_import.clone().unwrap(),
2761 ];
2762 module_items.extend(items);
2763
2764 let stripped_export_name = strip_export_name_span(&export_name);
2767
2768 let name_atom = export_name.atom().into_owned();
2769 let export_info = ServerReferenceExportInfo { name: name_atom };
2770
2771 new.push(ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(
2772 NamedExport {
2773 specifiers: vec![ExportSpecifier::Named(
2774 ExportNamedSpecifier {
2775 span: DUMMY_SP,
2776 orig: stripped_export_name,
2777 exported: None,
2778 is_type_only: false,
2779 },
2780 )],
2781 src: Some(Box::new(
2782 program_to_data_url(
2783 &self.file_name,
2784 &self.cm,
2785 module_items,
2786 Comment {
2787 span: DUMMY_SP,
2788 kind: CommentKind::Block,
2789 text: generate_server_references_comment(
2790 &std::iter::once((&ref_id, export_info))
2791 .collect(),
2792 Some((
2793 &self.file_name,
2794 self.file_query.as_ref().map_or("", |v| v),
2795 )),
2796 )
2797 .into(),
2798 },
2799 )
2800 .into(),
2801 )),
2802 span: DUMMY_SP,
2803 type_only: false,
2804 with: None,
2805 },
2806 )));
2807 }
2808 }
2809 }
2810 }
2811 }
2812
2813 *stmts = new;
2814
2815 self.annotations = old_annotations;
2816 }
2817
2818 fn visit_mut_stmts(&mut self, stmts: &mut Vec<Stmt>) {
2819 let old_annotations = self.annotations.take();
2820
2821 let mut new = Vec::with_capacity(stmts.len());
2822 for mut stmt in stmts.take() {
2823 stmt.visit_mut_with(self);
2824
2825 new.push(stmt);
2826 new.append(&mut self.annotations);
2827 }
2828
2829 *stmts = new;
2830
2831 self.annotations = old_annotations;
2832 }
2833
2834 fn visit_mut_jsx_attr(&mut self, attr: &mut JSXAttr) {
2835 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.take();
2836
2837 if let (Some(JSXAttrValue::JSXExprContainer(container)), JSXAttrName::Ident(ident_name)) =
2838 (&attr.value, &attr.name)
2839 {
2840 match &container.expr {
2841 JSXExpr::Expr(box Expr::Arrow(_)) | JSXExpr::Expr(box Expr::Fn(_)) => {
2842 self.arrow_or_fn_expr_ident = Some(ident_name.clone().into());
2843 }
2844 _ => {}
2845 }
2846 }
2847
2848 attr.visit_mut_children_with(self);
2849 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
2850 }
2851
2852 fn visit_mut_var_declarator(&mut self, var_declarator: &mut VarDeclarator) {
2853 let old_current_export_name = self.current_export_name.take();
2854 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.take();
2855
2856 if let (Pat::Ident(ident), Some(box Expr::Arrow(_) | box Expr::Fn(_))) =
2857 (&var_declarator.name, &var_declarator.init)
2858 {
2859 if self.in_module_level
2860 && let Some(export_name) = self.export_name_by_local_id.get(&ident.to_id())
2861 {
2862 self.current_export_name = Some(export_name.clone());
2863 }
2864
2865 self.arrow_or_fn_expr_ident = Some(ident.id.clone());
2866 }
2867
2868 var_declarator.visit_mut_children_with(self);
2869
2870 self.current_export_name = old_current_export_name;
2871 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
2872 }
2873
2874 fn visit_mut_assign_expr(&mut self, assign_expr: &mut AssignExpr) {
2875 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.clone();
2876
2877 if let (
2878 AssignTarget::Simple(SimpleAssignTarget::Ident(ident)),
2879 box Expr::Arrow(_) | box Expr::Fn(_),
2880 ) = (&assign_expr.left, &assign_expr.right)
2881 {
2882 self.arrow_or_fn_expr_ident = Some(ident.id.clone());
2883 }
2884
2885 assign_expr.visit_mut_children_with(self);
2886 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
2887 }
2888
2889 fn visit_mut_this_expr(&mut self, n: &mut ThisExpr) {
2890 if let ThisStatus::Forbidden { directive } = &self.this_status {
2891 emit_error(ServerActionsErrorKind::ForbiddenExpression {
2892 span: n.span,
2893 expr: "this".into(),
2894 directive: directive.clone(),
2895 });
2896 }
2897 }
2898
2899 fn visit_mut_super(&mut self, n: &mut Super) {
2900 if let ThisStatus::Forbidden { directive } = &self.this_status {
2901 emit_error(ServerActionsErrorKind::ForbiddenExpression {
2902 span: n.span,
2903 expr: "super".into(),
2904 directive: directive.clone(),
2905 });
2906 }
2907 }
2908
2909 fn visit_mut_ident(&mut self, n: &mut Ident) {
2910 if n.sym == *"arguments"
2911 && let ThisStatus::Forbidden { directive } = &self.this_status
2912 {
2913 emit_error(ServerActionsErrorKind::ForbiddenExpression {
2914 span: n.span,
2915 expr: "arguments".into(),
2916 directive: directive.clone(),
2917 });
2918 }
2919 }
2920
2921 noop_visit_mut_type!();
2922}
2923
2924fn retain_names_from_declared_idents(
2925 child_names: &mut Vec<Name>,
2926 current_declared_idents: &[Ident],
2927) {
2928 let mut retained_names = Vec::new();
2930
2931 for name in child_names.iter() {
2932 let mut should_retain = true;
2933
2934 for another_name in child_names.iter() {
2940 if name != another_name
2941 && name.0 == another_name.0
2942 && name.1.len() >= another_name.1.len()
2943 {
2944 let mut is_prefix = true;
2945 for i in 0..another_name.1.len() {
2946 if name.1[i] != another_name.1[i] {
2947 is_prefix = false;
2948 break;
2949 }
2950 }
2951 if is_prefix {
2952 should_retain = false;
2953 break;
2954 }
2955 }
2956 }
2957
2958 if should_retain
2959 && current_declared_idents
2960 .iter()
2961 .any(|ident| ident.to_id() == name.0)
2962 && !retained_names.contains(name)
2963 {
2964 retained_names.push(name.clone());
2965 }
2966 }
2967
2968 *child_names = retained_names;
2970}
2971
2972fn may_need_cache_runtime_wrapper(expr: &Expr) -> bool {
2975 match expr {
2976 Expr::Arrow(_) | Expr::Fn(_) => false,
2978 Expr::Object(_) | Expr::Array(_) | Expr::Lit(_) => false,
2980 _ => true,
2982 }
2983}
2984
2985#[allow(clippy::too_many_arguments)]
2988fn create_cache_wrapper(
2989 cache_kind: &str,
2990 reference_id: Atom,
2991 bound_args_length: usize,
2992 fn_ident: Option<Ident>,
2993 target_expr: Expr,
2994 original_span: Span,
2995 params: Option<&[Param]>,
2996 unresolved_ctxt: SyntaxContext,
2997) -> Expr {
2998 let cache_call = CallExpr {
2999 span: original_span,
3000 callee: quote_ident!("$$cache__").as_callee(),
3001 args: vec![
3002 Box::new(Expr::from(cache_kind)).as_arg(),
3003 Box::new(Expr::from(reference_id.as_str())).as_arg(),
3004 Box::new(Expr::Lit(Lit::Num(Number {
3005 span: DUMMY_SP,
3006 value: bound_args_length as f64,
3007 raw: None,
3008 })))
3009 .as_arg(),
3010 Box::new(target_expr).as_arg(),
3011 match params {
3012 Some(params) if !params.iter().any(|p| matches!(p.pat, Pat::Rest(_))) => {
3014 if params.is_empty() {
3015 Box::new(Expr::Array(ArrayLit {
3018 span: DUMMY_SP,
3019 elems: vec![],
3020 }))
3021 .as_arg()
3022 } else {
3023 Box::new(quote!(
3025 "$array.prototype.slice.call(arguments, 0, $end)" as Expr,
3026 array = quote_ident!(unresolved_ctxt, "Array"),
3027 end: Expr = params.len().into(),
3028 ))
3029 .as_arg()
3030 }
3031 }
3032 _ => {
3034 Box::new(quote!(
3036 "$array.prototype.slice.call(arguments)" as Expr,
3037 array = quote_ident!(unresolved_ctxt, "Array"),
3038 ))
3039 .as_arg()
3040 }
3041 },
3042 ],
3043 ..Default::default()
3044 };
3045
3046 let wrapper_fn_expr = Box::new(Expr::Fn(FnExpr {
3048 ident: fn_ident,
3049 function: Box::new(Function {
3050 body: Some(BlockStmt {
3051 stmts: vec![Stmt::Return(ReturnStmt {
3052 span: DUMMY_SP,
3053 arg: Some(Box::new(Expr::Call(cache_call))),
3054 })],
3055 ..Default::default()
3056 }),
3057 span: original_span,
3058 ..Default::default()
3059 }),
3060 }));
3061
3062 Expr::Call(CallExpr {
3063 callee: quote_ident!("$$reactCache__").as_callee(),
3064 args: vec![wrapper_fn_expr.as_arg()],
3065 ..Default::default()
3066 })
3067}
3068
3069#[allow(clippy::too_many_arguments)]
3070fn create_and_hoist_cache_function(
3071 cache_kind: &str,
3072 reference_id: Atom,
3073 bound_args_length: usize,
3074 cache_name: Atom,
3075 fn_ident: Option<Ident>,
3076 params: Vec<Param>,
3077 body: Option<BlockStmt>,
3078 original_span: Span,
3079 hoisted_extra_items: &mut Vec<ModuleItem>,
3080 unresolved_ctxt: SyntaxContext,
3081) -> Ident {
3082 let cache_ident = private_ident!(Span::dummy_with_cmt(), cache_name.clone());
3083 let inner_fn_name: Atom = format!("{}_INNER", cache_name).into();
3084 let inner_fn_ident = private_ident!(Span::dummy_with_cmt(), inner_fn_name);
3085
3086 let wrapper_fn = Box::new(create_cache_wrapper(
3087 cache_kind,
3088 reference_id.clone(),
3089 bound_args_length,
3090 fn_ident.clone(),
3091 Expr::Ident(inner_fn_ident.clone()),
3092 original_span,
3093 Some(¶ms),
3094 unresolved_ctxt,
3095 ));
3096
3097 let inner_fn_expr = FnExpr {
3098 ident: fn_ident.clone(),
3099 function: Box::new(Function {
3100 params,
3101 body,
3102 span: original_span,
3103 is_async: true,
3104 ..Default::default()
3105 }),
3106 };
3107
3108 hoisted_extra_items.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl {
3109 span: original_span,
3110 kind: VarDeclKind::Const,
3111 decls: vec![VarDeclarator {
3112 span: original_span,
3113 name: Pat::Ident(BindingIdent {
3114 id: inner_fn_ident.clone(),
3115 type_ann: None,
3116 }),
3117 init: Some(Box::new(Expr::Fn(inner_fn_expr))),
3118 definite: false,
3119 }],
3120 ..Default::default()
3121 })))));
3122
3123 if fn_ident.is_none() {
3126 hoisted_extra_items.push(ModuleItem::Stmt(assign_name_to_ident(
3127 &inner_fn_ident,
3128 "",
3129 unresolved_ctxt,
3130 )));
3131 }
3132
3133 hoisted_extra_items.push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
3134 span: DUMMY_SP,
3135 decl: VarDecl {
3136 kind: VarDeclKind::Var,
3137 decls: vec![VarDeclarator {
3138 span: original_span,
3139 name: Pat::Ident(cache_ident.clone().into()),
3140 init: Some(wrapper_fn),
3141 definite: false,
3142 }],
3143 ..Default::default()
3144 }
3145 .into(),
3146 })));
3147
3148 hoisted_extra_items.push(ModuleItem::Stmt(Stmt::Expr(ExprStmt {
3149 span: DUMMY_SP,
3150 expr: Box::new(annotate_ident_as_server_reference(
3151 cache_ident.clone(),
3152 reference_id,
3153 original_span,
3154 )),
3155 })));
3156
3157 cache_ident
3158}
3159
3160fn assign_name_to_ident(ident: &Ident, name: &str, unresolved_ctxt: SyntaxContext) -> Stmt {
3161 quote!(
3163 "$object[\"defineProperty\"]($action, \"name\", { value: $name });"
3171 as Stmt,
3172 object = quote_ident!(unresolved_ctxt, "Object"),
3173 action: Ident = ident.clone(),
3174 name: Expr = name.into(),
3175 )
3176}
3177
3178fn annotate_ident_as_server_reference(ident: Ident, action_id: Atom, original_span: Span) -> Expr {
3179 Expr::Call(CallExpr {
3181 span: original_span,
3182 callee: quote_ident!("registerServerReference").as_callee(),
3183 args: vec![
3184 ExprOrSpread {
3185 spread: None,
3186 expr: Box::new(Expr::Ident(ident)),
3187 },
3188 ExprOrSpread {
3189 spread: None,
3190 expr: Box::new(action_id.clone().into()),
3191 },
3192 ExprOrSpread {
3193 spread: None,
3194 expr: Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))),
3195 },
3196 ],
3197 ..Default::default()
3198 })
3199}
3200
3201fn bind_args_to_ident(ident: Ident, bound: Vec<Option<ExprOrSpread>>, action_id: Atom) -> Expr {
3202 Expr::Call(CallExpr {
3204 span: DUMMY_SP,
3205 callee: Expr::Member(MemberExpr {
3206 span: DUMMY_SP,
3207 obj: Box::new(ident.into()),
3208 prop: MemberProp::Ident(quote_ident!("bind")),
3209 })
3210 .as_callee(),
3211 args: vec![
3212 ExprOrSpread {
3213 spread: None,
3214 expr: Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))),
3215 },
3216 ExprOrSpread {
3217 spread: None,
3218 expr: Box::new(Expr::Call(CallExpr {
3219 span: DUMMY_SP,
3220 callee: quote_ident!("encryptActionBoundArgs").as_callee(),
3221 args: std::iter::once(ExprOrSpread {
3222 spread: None,
3223 expr: Box::new(action_id.into()),
3224 })
3225 .chain(bound.into_iter().flatten())
3226 .collect(),
3227 ..Default::default()
3228 })),
3229 },
3230 ],
3231 ..Default::default()
3232 })
3233}
3234
3235fn detect_similar_strings(a: &str, b: &str) -> bool {
3250 let mut a = a.chars().collect::<Vec<char>>();
3251 let mut b = b.chars().collect::<Vec<char>>();
3252
3253 if a.len() < b.len() {
3254 (a, b) = (b, a);
3255 }
3256
3257 if a.len() == b.len() {
3258 let mut diff = 0;
3260 for i in 0..a.len() {
3261 if a[i] != b[i] {
3262 diff += 1;
3263 if diff > 2 {
3264 return false;
3265 }
3266 }
3267 }
3268
3269 diff != 0
3271 } else {
3272 if a.len() - b.len() > 1 {
3273 return false;
3274 }
3275
3276 for i in 0..b.len() {
3278 if a[i] != b[i] {
3279 return a[i + 1..] == b[i..];
3285 }
3286 }
3287
3288 true
3290 }
3291}
3292
3293fn has_body_directive(maybe_body: &Option<BlockStmt>) -> (bool, bool) {
3298 let mut is_action_fn = false;
3299 let mut is_cache_fn = false;
3300
3301 if let Some(body) = maybe_body {
3302 for stmt in body.stmts.iter() {
3303 match stmt {
3304 Stmt::Expr(ExprStmt {
3305 expr: box Expr::Lit(Lit::Str(Str { value, .. })),
3306 ..
3307 }) => {
3308 if value == "use server" {
3309 is_action_fn = true;
3310 break;
3311 } else if value == "use cache" || value.starts_with("use cache: ") {
3312 is_cache_fn = true;
3313 break;
3314 }
3315 }
3316 _ => break,
3317 }
3318 }
3319 }
3320
3321 (is_action_fn, is_cache_fn)
3322}
3323
3324fn collect_idents_in_array_pat(elems: &[Option<Pat>], idents: &mut Vec<Ident>) {
3325 for elem in elems.iter().flatten() {
3326 match elem {
3327 Pat::Ident(ident) => {
3328 idents.push(ident.id.clone());
3329 }
3330 Pat::Array(array) => {
3331 collect_idents_in_array_pat(&array.elems, idents);
3332 }
3333 Pat::Object(object) => {
3334 collect_idents_in_object_pat(&object.props, idents);
3335 }
3336 Pat::Rest(rest) => {
3337 if let Pat::Ident(ident) = &*rest.arg {
3338 idents.push(ident.id.clone());
3339 }
3340 }
3341 Pat::Assign(AssignPat { left, .. }) => {
3342 collect_idents_in_pat(left, idents);
3343 }
3344 Pat::Expr(..) | Pat::Invalid(..) => {}
3345 }
3346 }
3347}
3348
3349fn collect_idents_in_object_pat(props: &[ObjectPatProp], idents: &mut Vec<Ident>) {
3350 for prop in props {
3351 match prop {
3352 ObjectPatProp::KeyValue(KeyValuePatProp { value, .. }) => {
3353 match &**value {
3356 Pat::Ident(ident) => {
3357 idents.push(ident.id.clone());
3358 }
3359 Pat::Array(array) => {
3360 collect_idents_in_array_pat(&array.elems, idents);
3361 }
3362 Pat::Object(object) => {
3363 collect_idents_in_object_pat(&object.props, idents);
3364 }
3365 _ => {}
3366 }
3367 }
3368 ObjectPatProp::Assign(AssignPatProp { key, .. }) => {
3369 idents.push(key.id.clone());
3371 }
3372 ObjectPatProp::Rest(RestPat { arg, .. }) => {
3373 if let Pat::Ident(ident) = &**arg {
3374 idents.push(ident.id.clone());
3375 }
3376 }
3377 }
3378 }
3379}
3380
3381fn collect_idents_in_var_decls(decls: &[VarDeclarator], idents: &mut Vec<Ident>) {
3382 for decl in decls {
3383 collect_idents_in_pat(&decl.name, idents);
3384 }
3385}
3386
3387fn collect_idents_in_pat(pat: &Pat, idents: &mut Vec<Ident>) {
3388 match pat {
3389 Pat::Ident(ident) => {
3390 idents.push(ident.id.clone());
3391 }
3392 Pat::Array(array) => {
3393 collect_idents_in_array_pat(&array.elems, idents);
3394 }
3395 Pat::Object(object) => {
3396 collect_idents_in_object_pat(&object.props, idents);
3397 }
3398 Pat::Assign(AssignPat { left, .. }) => {
3399 collect_idents_in_pat(left, idents);
3400 }
3401 Pat::Rest(RestPat { arg, .. }) => {
3402 if let Pat::Ident(ident) = &**arg {
3403 idents.push(ident.id.clone());
3404 }
3405 }
3406 Pat::Expr(..) | Pat::Invalid(..) => {}
3407 }
3408}
3409
3410fn collect_decl_idents_in_stmt(stmt: &Stmt, idents: &mut Vec<Ident>) {
3411 if let Stmt::Decl(decl) = stmt {
3412 match decl {
3413 Decl::Var(var) => {
3414 collect_idents_in_var_decls(&var.decls, idents);
3415 }
3416 Decl::Fn(fn_decl) => {
3417 idents.push(fn_decl.ident.clone());
3418 }
3419 _ => {}
3420 }
3421 }
3422}
3423
3424struct DirectiveVisitor<'a> {
3425 config: &'a Config,
3426 location: DirectiveLocation,
3427 directive: Option<Directive>,
3428 has_file_directive: bool,
3429 is_allowed_position: bool,
3430 use_cache_telemetry_tracker: Rc<RefCell<FxHashMap<String, usize>>>,
3431}
3432
3433impl DirectiveVisitor<'_> {
3434 fn visit_stmt(&mut self, stmt: &Stmt) -> bool {
3439 let in_fn_body = matches!(self.location, DirectiveLocation::FunctionBody);
3440 let allow_inline = self.config.is_react_server_layer || self.has_file_directive;
3441
3442 match stmt {
3443 Stmt::Expr(ExprStmt {
3444 expr: box Expr::Lit(Lit::Str(Str { value, span, .. })),
3445 ..
3446 }) => {
3447 if value == "use server" {
3448 if in_fn_body && !allow_inline {
3449 emit_error(ServerActionsErrorKind::InlineUseServerInClientComponent {
3450 span: *span,
3451 })
3452 } else if let Some(Directive::UseCache { .. }) = self.directive {
3453 emit_error(ServerActionsErrorKind::MultipleDirectives {
3454 span: *span,
3455 location: self.location.clone(),
3456 });
3457 } else if self.is_allowed_position {
3458 self.directive = Some(Directive::UseServer);
3459
3460 return true;
3461 } else {
3462 emit_error(ServerActionsErrorKind::MisplacedDirective {
3463 span: *span,
3464 directive: value.to_string_lossy().into_owned(),
3465 location: self.location.clone(),
3466 });
3467 }
3468 } else if detect_similar_strings(&value.to_string_lossy(), "use server") {
3469 emit_error(ServerActionsErrorKind::MisspelledDirective {
3471 span: *span,
3472 directive: value.to_string_lossy().into_owned(),
3473 expected_directive: "use server".to_string(),
3474 });
3475 } else if value == "use action" {
3476 emit_error(ServerActionsErrorKind::MisspelledDirective {
3477 span: *span,
3478 directive: value.to_string_lossy().into_owned(),
3479 expected_directive: "use server".to_string(),
3480 });
3481 } else
3482 if let Some(rest) = value.as_str().and_then(|s| s.strip_prefix("use cache"))
3484 {
3485 if in_fn_body && !allow_inline {
3488 emit_error(ServerActionsErrorKind::InlineUseCacheInClientComponent {
3489 span: *span,
3490 })
3491 } else if let Some(Directive::UseServer) = self.directive {
3492 emit_error(ServerActionsErrorKind::MultipleDirectives {
3493 span: *span,
3494 location: self.location.clone(),
3495 });
3496 } else if self.is_allowed_position {
3497 if !self.config.use_cache_enabled {
3498 emit_error(ServerActionsErrorKind::UseCacheWithoutCacheComponents {
3499 span: *span,
3500 directive: value.to_string_lossy().into_owned(),
3501 });
3502 }
3503
3504 if rest.is_empty() {
3505 self.directive = Some(Directive::UseCache {
3506 cache_kind: rcstr!("default"),
3507 });
3508
3509 self.increment_cache_usage_counter("default");
3510
3511 return true;
3512 }
3513
3514 if rest.starts_with(": ") {
3515 let cache_kind = RcStr::from(rest.split_at(": ".len()).1.to_string());
3516
3517 if !cache_kind.is_empty() {
3518 if !self.config.cache_kinds.contains(&cache_kind) {
3519 emit_error(ServerActionsErrorKind::UnknownCacheKind {
3520 span: *span,
3521 cache_kind: cache_kind.clone(),
3522 });
3523 }
3524
3525 self.increment_cache_usage_counter(&cache_kind);
3526 self.directive = Some(Directive::UseCache { cache_kind });
3527
3528 return true;
3529 }
3530 }
3531
3532 let expected_directive = if let Some(colon_pos) = rest.find(':') {
3535 let kind = rest[colon_pos + 1..].trim();
3536
3537 if kind.is_empty() {
3538 "use cache: <cache-kind>".to_string()
3539 } else {
3540 format!("use cache: {kind}")
3541 }
3542 } else {
3543 let kind = rest.trim();
3544
3545 if kind.is_empty() {
3546 "use cache".to_string()
3547 } else {
3548 format!("use cache: {kind}")
3549 }
3550 };
3551
3552 emit_error(ServerActionsErrorKind::MisspelledDirective {
3553 span: *span,
3554 directive: value.to_string_lossy().into_owned(),
3555 expected_directive,
3556 });
3557
3558 return true;
3559 } else {
3560 emit_error(ServerActionsErrorKind::MisplacedDirective {
3561 span: *span,
3562 directive: value.to_string_lossy().into_owned(),
3563 location: self.location.clone(),
3564 });
3565 }
3566 } else {
3567 if detect_similar_strings(&value.to_string_lossy(), "use cache") {
3569 emit_error(ServerActionsErrorKind::MisspelledDirective {
3570 span: *span,
3571 directive: value.to_string_lossy().into_owned(),
3572 expected_directive: "use cache".to_string(),
3573 });
3574 }
3575 }
3576 }
3577 Stmt::Expr(ExprStmt {
3578 expr:
3579 box Expr::Paren(ParenExpr {
3580 expr: box Expr::Lit(Lit::Str(Str { value, .. })),
3581 ..
3582 }),
3583 span,
3584 ..
3585 }) => {
3586 if value == "use server"
3588 || detect_similar_strings(&value.to_string_lossy(), "use server")
3589 {
3590 if self.is_allowed_position {
3591 emit_error(ServerActionsErrorKind::WrappedDirective {
3592 span: *span,
3593 directive: "use server".to_string(),
3594 });
3595 } else {
3596 emit_error(ServerActionsErrorKind::MisplacedWrappedDirective {
3597 span: *span,
3598 directive: "use server".to_string(),
3599 location: self.location.clone(),
3600 });
3601 }
3602 } else if value == "use cache"
3603 || detect_similar_strings(&value.to_string_lossy(), "use cache")
3604 {
3605 if self.is_allowed_position {
3606 emit_error(ServerActionsErrorKind::WrappedDirective {
3607 span: *span,
3608 directive: "use cache".to_string(),
3609 });
3610 } else {
3611 emit_error(ServerActionsErrorKind::MisplacedWrappedDirective {
3612 span: *span,
3613 directive: "use cache".to_string(),
3614 location: self.location.clone(),
3615 });
3616 }
3617 }
3618 }
3619 _ => {
3620 self.is_allowed_position = false;
3622 }
3623 };
3624
3625 false
3626 }
3627
3628 fn increment_cache_usage_counter(&mut self, cache_kind: &str) {
3630 let mut tracker_map = RefCell::borrow_mut(&self.use_cache_telemetry_tracker);
3631 let entry = tracker_map.entry(cache_kind.to_string());
3632 match entry {
3633 hash_map::Entry::Occupied(mut occupied) => {
3634 *occupied.get_mut() += 1;
3635 }
3636 hash_map::Entry::Vacant(vacant) => {
3637 vacant.insert(1);
3638 }
3639 }
3640 }
3641}
3642
3643pub(crate) struct ClosureReplacer<'a> {
3644 used_ids: &'a [Name],
3645 private_ctxt: SyntaxContext,
3646}
3647
3648impl ClosureReplacer<'_> {
3649 fn index(&self, e: &Expr) -> Option<usize> {
3650 let name = Name::try_from(e).ok()?;
3651 self.used_ids.iter().position(|used_id| *used_id == name)
3652 }
3653}
3654
3655impl VisitMut for ClosureReplacer<'_> {
3656 fn visit_mut_expr(&mut self, e: &mut Expr) {
3657 e.visit_mut_children_with(self);
3658
3659 if let Some(index) = self.index(e) {
3660 *e = Expr::Ident(Ident::new(
3661 format!("$$ACTION_ARG_{index}").into(),
3663 DUMMY_SP,
3664 self.private_ctxt,
3665 ));
3666 }
3667 }
3668
3669 fn visit_mut_prop_or_spread(&mut self, n: &mut PropOrSpread) {
3670 n.visit_mut_children_with(self);
3671
3672 if let PropOrSpread::Prop(box Prop::Shorthand(i)) = n {
3673 let name = Name::from(&*i);
3674 if let Some(index) = self.used_ids.iter().position(|used_id| *used_id == name) {
3675 *n = PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
3676 key: PropName::Ident(i.clone().into()),
3677 value: Box::new(Expr::Ident(Ident::new(
3678 format!("$$ACTION_ARG_{index}").into(),
3680 DUMMY_SP,
3681 self.private_ctxt,
3682 ))),
3683 })));
3684 }
3685 }
3686 }
3687
3688 noop_visit_mut_type!();
3689}
3690
3691#[derive(Debug, Clone, PartialEq, Eq)]
3692struct NamePart {
3693 prop: Atom,
3694 is_member: bool,
3695 optional: bool,
3696}
3697
3698#[derive(Debug, Clone, PartialEq, Eq)]
3699struct Name(Id, Vec<NamePart>);
3700
3701impl From<&'_ Ident> for Name {
3702 fn from(value: &Ident) -> Self {
3703 Name(value.to_id(), vec![])
3704 }
3705}
3706
3707impl TryFrom<&'_ Expr> for Name {
3708 type Error = ();
3709
3710 fn try_from(value: &Expr) -> Result<Self, Self::Error> {
3711 match value {
3712 Expr::Ident(i) => Ok(Name(i.to_id(), vec![])),
3713 Expr::Member(e) => e.try_into(),
3714 Expr::OptChain(e) => e.try_into(),
3715 _ => Err(()),
3716 }
3717 }
3718}
3719
3720impl TryFrom<&'_ MemberExpr> for Name {
3721 type Error = ();
3722
3723 fn try_from(value: &MemberExpr) -> Result<Self, Self::Error> {
3724 match &value.prop {
3725 MemberProp::Ident(prop) => {
3726 let mut obj: Name = value.obj.as_ref().try_into()?;
3727 obj.1.push(NamePart {
3728 prop: prop.sym.clone(),
3729 is_member: true,
3730 optional: false,
3731 });
3732 Ok(obj)
3733 }
3734 _ => Err(()),
3735 }
3736 }
3737}
3738
3739impl TryFrom<&'_ OptChainExpr> for Name {
3740 type Error = ();
3741
3742 fn try_from(value: &OptChainExpr) -> Result<Self, Self::Error> {
3743 match &*value.base {
3744 OptChainBase::Member(m) => match &m.prop {
3745 MemberProp::Ident(prop) => {
3746 let mut obj: Name = m.obj.as_ref().try_into()?;
3747 obj.1.push(NamePart {
3748 prop: prop.sym.clone(),
3749 is_member: false,
3750 optional: value.optional,
3751 });
3752 Ok(obj)
3753 }
3754 _ => Err(()),
3755 },
3756 OptChainBase::Call(_) => Err(()),
3757 }
3758 }
3759}
3760
3761impl From<Name> for Box<Expr> {
3762 fn from(value: Name) -> Self {
3763 let mut expr = Box::new(Expr::Ident(value.0.into()));
3764
3765 for NamePart {
3766 prop,
3767 is_member,
3768 optional,
3769 } in value.1.into_iter()
3770 {
3771 #[allow(clippy::replace_box)]
3772 if is_member {
3773 expr = Box::new(Expr::Member(MemberExpr {
3774 span: DUMMY_SP,
3775 obj: expr,
3776 prop: MemberProp::Ident(IdentName::new(prop, DUMMY_SP)),
3777 }));
3778 } else {
3779 expr = Box::new(Expr::OptChain(OptChainExpr {
3780 span: DUMMY_SP,
3781 base: Box::new(OptChainBase::Member(MemberExpr {
3782 span: DUMMY_SP,
3783 obj: expr,
3784 prop: MemberProp::Ident(IdentName::new(prop, DUMMY_SP)),
3785 })),
3786 optional,
3787 }));
3788 }
3789 }
3790
3791 expr
3792 }
3793}
3794
3795fn emit_error(error_kind: ServerActionsErrorKind) {
3796 let (span, msg) = match error_kind {
3797 ServerActionsErrorKind::ExportedSyncFunction {
3798 span,
3799 in_action_file,
3800 } => (
3801 span,
3802 formatdoc! {
3803 r#"
3804 Only async functions are allowed to be exported in a {directive} file.
3805 "#,
3806 directive = if in_action_file {
3807 "\"use server\""
3808 } else {
3809 "\"use cache\""
3810 }
3811 },
3812 ),
3813 ServerActionsErrorKind::ForbiddenExpression {
3814 span,
3815 expr,
3816 directive,
3817 } => (
3818 span,
3819 formatdoc! {
3820 r#"
3821 {subject} cannot use `{expr}`.
3822 "#,
3823 subject = if let Directive::UseServer = directive {
3824 "Server Actions"
3825 } else {
3826 "\"use cache\" functions"
3827 }
3828 },
3829 ),
3830 ServerActionsErrorKind::InlineUseCacheInClassInstanceMethod { span } => (
3831 span,
3832 formatdoc! {
3833 r#"
3834 It is not allowed to define inline "use cache" annotated class instance methods.
3835 To define cached functions, use functions, object method properties, or static class methods instead.
3836 "#
3837 },
3838 ),
3839 ServerActionsErrorKind::InlineUseCacheInClientComponent { span } => (
3840 span,
3841 formatdoc! {
3842 r#"
3843 It is not allowed to define inline "use cache" annotated functions in Client Components.
3844 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.
3845 "#
3846 },
3847 ),
3848 ServerActionsErrorKind::InlineUseServerInClassInstanceMethod { span } => (
3849 span,
3850 formatdoc! {
3851 r#"
3852 It is not allowed to define inline "use server" annotated class instance methods.
3853 To define Server Actions, use functions, object method properties, or static class methods instead.
3854 "#
3855 },
3856 ),
3857 ServerActionsErrorKind::InlineUseServerInClientComponent { span } => (
3858 span,
3859 formatdoc! {
3860 r#"
3861 It is not allowed to define inline "use server" annotated Server Actions in Client Components.
3862 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.
3863
3864 Read more: https://nextjs.org/docs/app/api-reference/directives/use-server#using-server-functions-in-a-client-component
3865 "#
3866 },
3867 ),
3868 ServerActionsErrorKind::InlineSyncFunction { span, directive } => (
3869 span,
3870 formatdoc! {
3871 r#"
3872 {subject} must be async functions.
3873 "#,
3874 subject = if let Directive::UseServer = directive {
3875 "Server Actions"
3876 } else {
3877 "\"use cache\" functions"
3878 }
3879 },
3880 ),
3881 ServerActionsErrorKind::MisplacedDirective {
3882 span,
3883 directive,
3884 location,
3885 } => (
3886 span,
3887 formatdoc! {
3888 r#"
3889 The "{directive}" directive must be at the top of the {location}.
3890 "#,
3891 location = match location {
3892 DirectiveLocation::Module => "file",
3893 DirectiveLocation::FunctionBody => "function body",
3894 }
3895 },
3896 ),
3897 ServerActionsErrorKind::MisplacedWrappedDirective {
3898 span,
3899 directive,
3900 location,
3901 } => (
3902 span,
3903 formatdoc! {
3904 r#"
3905 The "{directive}" directive must be at the top of the {location}, and cannot be wrapped in parentheses.
3906 "#,
3907 location = match location {
3908 DirectiveLocation::Module => "file",
3909 DirectiveLocation::FunctionBody => "function body",
3910 }
3911 },
3912 ),
3913 ServerActionsErrorKind::MisspelledDirective {
3914 span,
3915 directive,
3916 expected_directive,
3917 } => (
3918 span,
3919 formatdoc! {
3920 r#"
3921 Did you mean "{expected_directive}"? "{directive}" is not a supported directive name."
3922 "#
3923 },
3924 ),
3925 ServerActionsErrorKind::MultipleDirectives { span, location } => (
3926 span,
3927 formatdoc! {
3928 r#"
3929 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.
3930 "#,
3931 location = match location {
3932 DirectiveLocation::Module => "file",
3933 DirectiveLocation::FunctionBody => "function body",
3934 }
3935 },
3936 ),
3937 ServerActionsErrorKind::UnknownCacheKind { span, cache_kind } => (
3938 span,
3939 formatdoc! {
3940 r#"
3941 Unknown cache kind "{cache_kind}". Please configure a cache handler for this kind in the `cacheHandlers` object in your Next.js config.
3942 "#
3943 },
3944 ),
3945 ServerActionsErrorKind::UseCacheWithoutCacheComponents { span, directive } => (
3946 span,
3947 formatdoc! {
3948 r#"
3949 To use "{directive}", please enable the feature flag `cacheComponents` in your Next.js config.
3950
3951 Read more: https://nextjs.org/docs/canary/app/api-reference/directives/use-cache#usage
3952 "#
3953 },
3954 ),
3955 ServerActionsErrorKind::WrappedDirective { span, directive } => (
3956 span,
3957 formatdoc! {
3958 r#"
3959 The "{directive}" directive cannot be wrapped in parentheses.
3960 "#
3961 },
3962 ),
3963 };
3964
3965 HANDLER.with(|handler| handler.struct_span_err(span, &msg).emit());
3966}
3967
3968fn strip_export_name_span(export_name: &ModuleExportName) -> ModuleExportName {
3971 match export_name {
3972 ModuleExportName::Ident(i) => {
3973 ModuleExportName::Ident(Ident::new(i.sym.clone(), DUMMY_SP, i.ctxt))
3974 }
3975 ModuleExportName::Str(s) => ModuleExportName::Str(Str {
3976 span: DUMMY_SP,
3977 value: s.value.clone(),
3978 raw: None,
3979 }),
3980 }
3981}
3982
3983fn program_to_data_url(
3984 file_name: &str,
3985 cm: &Arc<SourceMap>,
3986 body: Vec<ModuleItem>,
3987 prepend_comment: Comment,
3988) -> String {
3989 let module_span = Span::dummy_with_cmt();
3990 let comments = SingleThreadedComments::default();
3991 comments.add_leading(module_span.lo, prepend_comment);
3992
3993 let program = &Program::Module(Module {
3994 span: module_span,
3995 body,
3996 shebang: None,
3997 });
3998
3999 let mut output = vec![];
4000 let mut mappings = vec![];
4001 let mut emitter = Emitter {
4002 cfg: codegen::Config::default().with_minify(true),
4003 cm: cm.clone(),
4004 wr: Box::new(JsWriter::new(
4005 cm.clone(),
4006 " ",
4007 &mut output,
4008 Some(&mut mappings),
4009 )),
4010 comments: Some(&comments),
4011 };
4012
4013 emitter.emit_program(program).unwrap();
4014 drop(emitter);
4015
4016 pub struct InlineSourcesContentConfig<'a> {
4017 folder_path: Option<&'a Path>,
4018 }
4019 impl SourceMapGenConfig for InlineSourcesContentConfig<'_> {
4022 fn file_name_to_source(&self, file: &FileName) -> String {
4023 let FileName::Custom(file) = file else {
4024 return file.to_string();
4026 };
4027 let Some(folder_path) = &self.folder_path else {
4028 return file.to_string();
4029 };
4030
4031 if let Some(rel_path) = diff_paths(file, folder_path) {
4032 format!("./{}", rel_path.display())
4033 } else {
4034 file.to_string()
4035 }
4036 }
4037
4038 fn inline_sources_content(&self, _f: &FileName) -> bool {
4039 true
4040 }
4041 }
4042
4043 let map = cm.build_source_map(
4044 &mappings,
4045 None,
4046 InlineSourcesContentConfig {
4047 folder_path: PathBuf::from(format!("[project]/{file_name}")).parent(),
4048 },
4049 );
4050 let map = {
4051 if map.get_token_count() > 0 {
4052 let mut buf = vec![];
4053 map.to_writer(&mut buf)
4054 .expect("failed to generate sourcemap");
4055 Some(buf)
4056 } else {
4057 None
4058 }
4059 };
4060
4061 let mut output = String::from_utf8(output).expect("codegen generated non-utf8 output");
4062 if let Some(map) = map {
4063 output.extend(
4064 format!(
4065 "\n//# sourceMappingURL=data:application/json;base64,{}",
4066 Base64Display::new(&map, &BASE64_STANDARD)
4067 )
4068 .chars(),
4069 );
4070 }
4071 format!("data:text/javascript,{}", urlencoding::encode(&output))
4072}