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