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