next_custom_transforms/transforms/
track_dynamic_imports.rs1use swc_core::{
2 common::{source_map::PURE_SP, util::take::Take, Mark, SyntaxContext},
3 ecma::{
4 ast::*,
5 utils::{prepend_stmt, private_ident, quote_ident, quote_str},
6 visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith},
7 },
8 quote,
9};
10
11pub fn track_dynamic_imports(unresolved_mark: Mark) -> impl VisitMut + Pass {
12 visit_mut_pass(ImportReplacer::new(unresolved_mark))
13}
14
15struct ImportReplacer {
16 unresolved_ctxt: SyntaxContext,
17 has_dynamic_import: bool,
18 wrapper_function_local_ident: Ident,
19}
20
21impl ImportReplacer {
22 pub fn new(unresolved_mark: Mark) -> Self {
23 ImportReplacer {
24 unresolved_ctxt: SyntaxContext::empty().apply_mark(unresolved_mark),
25 has_dynamic_import: false,
26 wrapper_function_local_ident: private_ident!("$$trackDynamicImport__"),
27 }
28 }
29}
30
31impl VisitMut for ImportReplacer {
32 noop_visit_mut_type!();
33
34 fn visit_mut_program(&mut self, program: &mut Program) {
35 program.visit_mut_children_with(self);
36 if self.has_dynamic_import {
39 let import_args = MakeNamedImportArgs {
40 original_ident: quote_ident!("trackDynamicImport").into(),
41 local_ident: self.wrapper_function_local_ident.clone(),
42 source: "private-next-rsc-track-dynamic-import",
43 unresolved_ctxt: self.unresolved_ctxt,
44 };
45 match program {
46 Program::Module(module) => {
47 prepend_stmt(&mut module.body, make_named_import_esm(import_args));
48 }
49 Program::Script(script) => {
50 prepend_stmt(&mut script.body, make_named_import_cjs(import_args));
54 }
55 }
56 }
57 }
58
59 fn visit_mut_expr(&mut self, expr: &mut Expr) {
60 expr.visit_mut_children_with(self);
61
62 if let Expr::Call(CallExpr {
66 callee: Callee::Import(_),
67 ..
68 }) = expr
69 {
70 self.has_dynamic_import = true;
71 let replacement_expr = quote!(
72 "$wrapper_fn($expr)" as Expr,
73 wrapper_fn = self.wrapper_function_local_ident.clone(),
74 expr: Expr = expr.take()
75 )
76 .with_span(PURE_SP);
77 *expr = replacement_expr
78 }
79 }
80}
81
82struct MakeNamedImportArgs<'a> {
83 original_ident: Ident,
84 local_ident: Ident,
85 source: &'a str,
86 unresolved_ctxt: SyntaxContext,
87}
88
89fn make_named_import_esm(args: MakeNamedImportArgs) -> ModuleItem {
90 let MakeNamedImportArgs {
91 original_ident,
92 local_ident,
93 source,
94 ..
95 } = args;
96 let mut item = quote!(
97 "import { $original_ident as $local_ident } from 'dummy'" as ModuleItem,
98 original_ident = original_ident,
99 local_ident = local_ident,
100 );
101 let decl = item.as_mut_module_decl().unwrap().as_mut_import().unwrap();
103 decl.src = Box::new(source.into());
104 item
105}
106
107fn make_named_import_cjs(args: MakeNamedImportArgs) -> Stmt {
108 let MakeNamedImportArgs {
109 original_ident,
110 local_ident,
111 source,
112 unresolved_ctxt,
113 } = args;
114 quote!(
115 "const { [$original_name]: $local_ident } = $require($source)" as Stmt,
116 original_name: Expr = quote_str!(original_ident.sym).into(),
117 local_ident = local_ident,
118 source: Expr = quote_str!(source).into(),
119 require = quote_ident!(unresolved_ctxt, "require")
123 )
124}