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        let mut visitors = vec![];
66
67        let export = self.export.clone();
68        let imported_module = self.reference.get_referenced_asset().await?;
69
70        enum ImportedIdent {
71            Module(ReferencedAssetIdent),
72            None,
73            Unresolvable,
74        }
75
76        let imported_ident = match &*imported_module {
77            ReferencedAsset::None => ImportedIdent::None,
78            imported_module => imported_module
79                .get_ident(chunking_context, export, scope_hoisting_context)
80                .await?
81                .map_or(ImportedIdent::Unresolvable, ImportedIdent::Module),
82        };
83
84        let mut ast_path = self.ast_path.0.clone();
85        loop {
86            match ast_path.last() {
87                // Shorthand properties get special treatment because we need to rewrite them to
88                // normal key-value pairs.
89                Some(swc_core::ecma::visit::AstParentKind::Prop(PropField::Shorthand)) => {
90                    ast_path.pop();
91                    visitors.push(create_visitor!(
92                        exact,
93                        ast_path,
94                        visit_mut_prop,
95                        |prop: &mut Prop| {
96                            if let Prop::Shorthand(ident) = prop {
97                                match &imported_ident {
98                                    ImportedIdent::Module(imported_ident) => {
99                                        *prop = Prop::KeyValue(KeyValueProp {
100                                            key: PropName::Ident(ident.clone().into()),
101                                            value: Box::new(
102                                                imported_ident.as_expr(ident.span, false),
103                                            ),
104                                        });
105                                    }
106                                    ImportedIdent::None => {
107                                        *prop = Prop::KeyValue(KeyValueProp {
108                                            key: PropName::Ident(ident.clone().into()),
109                                            value: Expr::undefined(ident.span),
110                                        });
111                                    }
112                                    ImportedIdent::Unresolvable => {
113                                        // Do nothing, the reference will insert a throw
114                                    }
115                                }
116                            }
117                        }
118                    ));
119                    break;
120                }
121                // Any other expression can be replaced with the import accessor.
122                Some(swc_core::ecma::visit::AstParentKind::Expr(_)) => {
123                    ast_path.pop();
124                    let in_call = !self.keep_this
125                        && matches!(
126                            ast_path.last(),
127                            Some(swc_core::ecma::visit::AstParentKind::Callee(
128                                CalleeField::Expr
129                            ))
130                        );
131
132                    visitors.push(create_visitor!(
133                        exact,
134                        ast_path,
135                        visit_mut_expr,
136                        |expr: &mut Expr| {
137                            use swc_core::common::Spanned;
138                            match &imported_ident {
139                                ImportedIdent::Module(imported_ident) => {
140                                    *expr = imported_ident.as_expr(expr.span(), in_call);
141                                }
142                                ImportedIdent::None => {
143                                    *expr = *Expr::undefined(expr.span());
144                                }
145                                ImportedIdent::Unresolvable => {
146                                    // Do nothing, the reference will insert a throw
147                                }
148                            }
149                        }
150                    ));
151                    break;
152                }
153                // We need to handle LHS because of code like
154                // (function (RouteKind1){})(RouteKind || RouteKind = {})
155                Some(swc_core::ecma::visit::AstParentKind::SimpleAssignTarget(_)) => {
156                    ast_path.pop();
157
158                    visitors.push(create_visitor!(
159                        exact,
160                        ast_path,
161                        visit_mut_simple_assign_target,
162                        |l: &mut SimpleAssignTarget| {
163                            use swc_core::common::Spanned;
164                            match &imported_ident {
165                                ImportedIdent::Module(imported_ident) => {
166                                    *l = imported_ident
167                                        .as_expr_individual(l.span())
168                                        .map_either(
169                                            |i| SimpleAssignTarget::Ident(i.into()),
170                                            SimpleAssignTarget::Member,
171                                        )
172                                        .into_inner();
173                                }
174                                ImportedIdent::None => {
175                                    // Do nothing, cannot assign to `undefined`
176                                }
177                                ImportedIdent::Unresolvable => {
178                                    // Do nothing, the reference will insert a throw
179                                }
180                            }
181                        }
182                    ));
183                    break;
184                }
185                Some(_) => {
186                    ast_path.pop();
187                }
188                None => break,
189            }
190        }
191
192        Ok(CodeGeneration::visitors(visitors))
193    }
194}
195
196impl From<EsmBinding> for CodeGen {
197    fn from(val: EsmBinding) -> Self {
198        CodeGen::EsmBinding(val)
199    }
200}