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, module_graph::ModuleGraph};
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        _module_graph: Vc<ModuleGraph>,
61        chunking_context: Vc<Box<dyn ChunkingContext>>,
62        scope_hoisting_context: ScopeHoistingContext<'_>,
63    ) -> Result<CodeGeneration> {
64        let mut visitors = vec![];
65
66        let export = self.export.clone();
67        let imported_module = self.reference.get_referenced_asset().await?;
68
69        enum ImportedIdent {
70            Module(ReferencedAssetIdent),
71            None,
72            Unresolvable,
73        }
74
75        let imported_ident = match &*imported_module {
76            ReferencedAsset::None => ImportedIdent::None,
77            imported_module => imported_module
78                .get_ident(chunking_context, export, scope_hoisting_context)
79                .await?
80                .map_or(ImportedIdent::Unresolvable, ImportedIdent::Module),
81        };
82
83        let mut ast_path = self.ast_path.0.clone();
84        loop {
85            match ast_path.last() {
86                // Shorthand properties get special treatment because we need to rewrite them to
87                // normal key-value pairs.
88                Some(swc_core::ecma::visit::AstParentKind::Prop(PropField::Shorthand)) => {
89                    ast_path.pop();
90                    visitors.push(create_visitor!(
91                        exact,
92                        ast_path,
93                        visit_mut_prop,
94                        |prop: &mut Prop| {
95                            if let Prop::Shorthand(ident) = prop {
96                                // TODO: Merge with the above condition when https://rust-lang.github.io/rfcs/2497-if-let-chains.html lands.
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                Some(swc_core::ecma::visit::AstParentKind::BindingIdent(
154                    swc_core::ecma::visit::fields::BindingIdentField::Id,
155                )) => {
156                    ast_path.pop();
157
158                    // We need to handle LHS because of code like
159                    // (function (RouteKind1){})(RouteKind || RouteKind = {})
160                    if let Some(swc_core::ecma::visit::AstParentKind::SimpleAssignTarget(
161                        swc_core::ecma::visit::fields::SimpleAssignTargetField::Ident,
162                    )) = ast_path.last()
163                    {
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                }
194                Some(_) => {
195                    ast_path.pop();
196                }
197                None => break,
198            }
199        }
200
201        Ok(CodeGeneration::visitors(visitors))
202    }
203}
204
205impl From<EsmBinding> for CodeGen {
206    fn from(val: EsmBinding) -> Self {
207        CodeGen::EsmBinding(val)
208    }
209}