turbopack_ecmascript/references/esm/
binding.rs

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