Skip to main content

turbopack_ecmascript/references/esm/
binding.rs

1use anyhow::Result;
2use bincode::{Decode, Encode};
3use swc_core::ecma::{
4    ast::{Expr, KeyValueProp, Prop, PropName, SimpleAssignTarget},
5    visit::fields::{CalleeField, PropField},
6};
7use turbo_rcstr::RcStr;
8use turbo_tasks::{NonLocalValue, ResolvedVc, Vc, trace::TraceRawVcs};
9use turbopack_core::chunk::ChunkingContext;
10
11use crate::{
12    ScopeHoistingContext,
13    code_gen::{CodeGen, CodeGeneration},
14    create_visitor,
15    references::{
16        AstPath,
17        esm::{
18            EsmAssetReference,
19            base::{ReferencedAsset, ReferencedAssetIdent},
20        },
21    },
22};
23
24#[derive(Hash, Clone, Debug, PartialEq, Eq, TraceRawVcs, NonLocalValue, Encode, Decode)]
25pub struct EsmBinding {
26    reference: ResolvedVc<EsmAssetReference>,
27    export: Option<RcStr>,
28    ast_path: AstPath,
29    keep_this: bool,
30}
31
32impl EsmBinding {
33    pub fn new(
34        reference: ResolvedVc<EsmAssetReference>,
35        export: Option<RcStr>,
36        ast_path: AstPath,
37    ) -> Self {
38        EsmBinding {
39            reference,
40            export,
41            ast_path,
42            keep_this: false,
43        }
44    }
45
46    /// Where possible, bind the namespace to `this` when the named import is called.
47    pub fn new_keep_this(
48        reference: ResolvedVc<EsmAssetReference>,
49        export: Option<RcStr>,
50        ast_path: AstPath,
51    ) -> Self {
52        EsmBinding {
53            reference,
54            export,
55            ast_path,
56            keep_this: true,
57        }
58    }
59
60    pub async fn code_generation(
61        &self,
62        chunking_context: Vc<Box<dyn ChunkingContext>>,
63        scope_hoisting_context: ScopeHoistingContext<'_>,
64    ) -> Result<CodeGeneration> {
65        if chunking_context
66            .unused_references()
67            .contains_key(&ResolvedVc::upcast(self.reference))
68            .await?
69        {
70            return Ok(CodeGeneration::empty());
71        }
72
73        let mut visitors = vec![];
74
75        let export = self.export.clone();
76        let imported_module = self.reference.get_referenced_asset().await?;
77
78        enum ImportedIdent {
79            Module(ReferencedAssetIdent),
80            None,
81            Unresolvable,
82        }
83
84        let imported_ident = match &imported_module {
85            ReferencedAsset::None => ImportedIdent::None,
86            imported_module => imported_module
87                .get_ident(chunking_context, export, scope_hoisting_context)
88                .await?
89                .map_or(ImportedIdent::Unresolvable, ImportedIdent::Module),
90        };
91
92        let mut ast_path = self.ast_path.0.clone();
93        loop {
94            match ast_path.last() {
95                // Shorthand properties get special treatment because we need to rewrite them to
96                // normal key-value pairs.
97                Some(swc_core::ecma::visit::AstParentKind::Prop(PropField::Shorthand)) => {
98                    ast_path.pop();
99                    visitors.push(create_visitor!(
100                        exact,
101                        ast_path,
102                        visit_mut_prop,
103                        |prop: &mut Prop| {
104                            if let Prop::Shorthand(ident) = prop {
105                                match &imported_ident {
106                                    ImportedIdent::Module(imported_ident) => {
107                                        *prop = Prop::KeyValue(KeyValueProp {
108                                            key: PropName::Ident(ident.clone().into()),
109                                            value: Box::new(
110                                                imported_ident.as_expr(ident.span, false),
111                                            ),
112                                        });
113                                    }
114                                    ImportedIdent::None => {
115                                        *prop = Prop::KeyValue(KeyValueProp {
116                                            key: PropName::Ident(ident.clone().into()),
117                                            value: Expr::undefined(ident.span),
118                                        });
119                                    }
120                                    ImportedIdent::Unresolvable => {
121                                        // Do nothing, the reference will insert a throw
122                                    }
123                                }
124                            }
125                        }
126                    ));
127                    break;
128                }
129                // Any other expression can be replaced with the import accessor.
130                Some(swc_core::ecma::visit::AstParentKind::Expr(_)) => {
131                    ast_path.pop();
132                    let in_call = !self.keep_this
133                        && matches!(
134                            ast_path.last(),
135                            Some(swc_core::ecma::visit::AstParentKind::Callee(
136                                CalleeField::Expr
137                            ))
138                        );
139
140                    visitors.push(create_visitor!(
141                        exact,
142                        ast_path,
143                        visit_mut_expr,
144                        |expr: &mut Expr| {
145                            use swc_core::common::Spanned;
146                            match &imported_ident {
147                                ImportedIdent::Module(imported_ident) => {
148                                    *expr = imported_ident.as_expr(expr.span(), in_call);
149                                }
150                                ImportedIdent::None => {
151                                    *expr = *Expr::undefined(expr.span());
152                                }
153                                ImportedIdent::Unresolvable => {
154                                    // Do nothing, the reference will insert a throw
155                                }
156                            }
157                        }
158                    ));
159                    break;
160                }
161                // We need to handle LHS because of code like
162                // (function (RouteKind1){})(RouteKind || RouteKind = {})
163                Some(swc_core::ecma::visit::AstParentKind::SimpleAssignTarget(_)) => {
164                    ast_path.pop();
165
166                    visitors.push(create_visitor!(
167                        exact,
168                        ast_path,
169                        visit_mut_simple_assign_target,
170                        |l: &mut SimpleAssignTarget| {
171                            use swc_core::common::Spanned;
172                            match &imported_ident {
173                                ImportedIdent::Module(imported_ident) => {
174                                    *l = imported_ident
175                                        .as_expr_individual(l.span())
176                                        .map_either(
177                                            |i| SimpleAssignTarget::Ident(i.into()),
178                                            SimpleAssignTarget::Member,
179                                        )
180                                        .into_inner();
181                                }
182                                ImportedIdent::None => {
183                                    // Do nothing, cannot assign to `undefined`
184                                }
185                                ImportedIdent::Unresolvable => {
186                                    // Do nothing, the reference will insert a throw
187                                }
188                            }
189                        }
190                    ));
191                    break;
192                }
193                Some(_) => {
194                    ast_path.pop();
195                }
196                None => break,
197            }
198        }
199
200        Ok(CodeGeneration::visitors(visitors))
201    }
202}
203
204impl From<EsmBinding> for CodeGen {
205    fn from(val: EsmBinding) -> Self {
206        CodeGen::EsmBinding(val)
207    }
208}