turbopack_ecmascript/references/esm/
binding.rs1use anyhow::Result;
2use serde::{Deserialize, Serialize};
3use swc_core::{
4 common::Span,
5 ecma::{
6 ast::{
7 ComputedPropName, Expr, Ident, KeyValueProp, Lit, MemberExpr, MemberProp, Number, Prop,
8 PropName, SeqExpr, SimpleAssignTarget, Str,
9 },
10 visit::fields::{CalleeField, PropField},
11 },
12};
13use turbo_rcstr::RcStr;
14use turbo_tasks::{NonLocalValue, ResolvedVc, Vc, trace::TraceRawVcs};
15use turbopack_core::{chunk::ChunkingContext, module_graph::ModuleGraph};
16
17use super::EsmAssetReference;
18use crate::{
19 code_gen::{CodeGen, CodeGeneration},
20 create_visitor,
21 references::{AstPath, esm::base::ReferencedAsset},
22};
23
24#[derive(Hash, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, TraceRawVcs, NonLocalValue)]
25pub struct EsmBinding {
26 pub reference: ResolvedVc<EsmAssetReference>,
27 pub export: Option<RcStr>,
28 pub ast_path: AstPath,
29}
30
31impl EsmBinding {
32 pub fn new(
33 reference: ResolvedVc<EsmAssetReference>,
34 export: Option<RcStr>,
35 ast_path: AstPath,
36 ) -> Self {
37 EsmBinding {
38 reference,
39 export,
40 ast_path,
41 }
42 }
43
44 pub async fn code_generation(
45 &self,
46 _module_graph: Vc<ModuleGraph>,
47 chunking_context: Vc<Box<dyn ChunkingContext>>,
48 ) -> Result<CodeGeneration> {
49 let mut visitors = vec![];
50
51 let export = self.export.clone();
52 let imported_module = self.reference.get_referenced_asset();
53
54 enum ImportedIdent {
55 Module(String),
56 None,
57 Unresolvable,
58 }
59
60 let imported_ident = match &*imported_module.await? {
61 ReferencedAsset::None => ImportedIdent::None,
62 imported_module => imported_module
63 .get_ident(chunking_context)
64 .await?
65 .map_or(ImportedIdent::Unresolvable, ImportedIdent::Module),
66 };
67
68 let mut ast_path = self.ast_path.0.clone();
69 loop {
70 match ast_path.last() {
71 Some(swc_core::ecma::visit::AstParentKind::Prop(PropField::Shorthand)) => {
74 ast_path.pop();
75 visitors.push(
76 create_visitor!(exact ast_path, visit_mut_prop(prop: &mut Prop) {
77 if let Prop::Shorthand(ident) = prop {
78 match &imported_ident {
80 ImportedIdent::Module(imported_ident) => {
81 *prop = Prop::KeyValue(KeyValueProp {
82 key: PropName::Ident(ident.clone().into()),
83 value: Box::new(make_expr(
84 imported_ident,
85 export.as_deref(),
86 ident.span,
87 false,
88 )),
89 });
90 }
91 ImportedIdent::None => {
92 *prop = Prop::KeyValue(KeyValueProp {
93 key: PropName::Ident(ident.clone().into()),
94 value: Expr::undefined(ident.span),
95 });
96 }
97 ImportedIdent::Unresolvable => {
98 }
100 }
101 }
102 }),
103 );
104 break;
105 }
106 Some(swc_core::ecma::visit::AstParentKind::Expr(_)) => {
108 ast_path.pop();
109 let in_call = matches!(
110 ast_path.last(),
111 Some(swc_core::ecma::visit::AstParentKind::Callee(
112 CalleeField::Expr
113 ))
114 );
115
116 visitors.push(
117 create_visitor!(exact ast_path, visit_mut_expr(expr: &mut Expr) {
118 use swc_core::common::Spanned;
119 match &imported_ident {
120 ImportedIdent::Module(imported_ident) => {
121 *expr = make_expr(imported_ident, export.as_deref(), expr.span(), in_call);
122 }
123 ImportedIdent::None => {
124 *expr = *Expr::undefined(expr.span());
125 }
126 ImportedIdent::Unresolvable => {
127 }
129 }
130 }),
131 );
132 break;
133 }
134 Some(swc_core::ecma::visit::AstParentKind::BindingIdent(
135 swc_core::ecma::visit::fields::BindingIdentField::Id,
136 )) => {
137 ast_path.pop();
138
139 if let Some(swc_core::ecma::visit::AstParentKind::SimpleAssignTarget(
142 swc_core::ecma::visit::fields::SimpleAssignTargetField::Ident,
143 )) = ast_path.last()
144 {
145 ast_path.pop();
146
147 visitors.push(
148 create_visitor!(exact ast_path, visit_mut_simple_assign_target(l: &mut SimpleAssignTarget) {
149 use swc_core::common::Spanned;
150 match &imported_ident {
151 ImportedIdent::Module(imported_ident) => {
152 *l = match make_expr(imported_ident, export.as_deref(), l.span(), false) {
153 Expr::Ident(ident) => SimpleAssignTarget::Ident(ident.into()),
154 Expr::Member(member) => SimpleAssignTarget::Member(member),
155 _ => unreachable!(),
156 };
157 }
158 ImportedIdent::None => {
159 }
161 ImportedIdent::Unresolvable => {
162 }
164 }
165 }));
166 break;
167 }
168 }
169 Some(_) => {
170 ast_path.pop();
171 }
172 None => break,
173 }
174 }
175
176 Ok(CodeGeneration::visitors(visitors))
177 }
178}
179
180impl From<EsmBinding> for CodeGen {
181 fn from(val: EsmBinding) -> Self {
182 CodeGen::EsmBinding(val)
183 }
184}
185
186fn make_expr(imported_module: &str, export: Option<&str>, span: Span, in_call: bool) -> Expr {
187 if let Some(export) = export {
188 let mut expr = Expr::Member(MemberExpr {
189 span,
190 obj: Box::new(Expr::Ident(Ident::new(
191 imported_module.into(),
192 span,
193 Default::default(),
194 ))),
195 prop: MemberProp::Computed(ComputedPropName {
196 span,
197 expr: Box::new(Expr::Lit(Lit::Str(Str {
198 span,
199 value: export.into(),
200 raw: None,
201 }))),
202 }),
203 });
204 if in_call {
205 expr = Expr::Seq(SeqExpr {
206 exprs: vec![
207 Box::new(Expr::Lit(Lit::Num(Number {
208 span,
209 value: 0.0,
210 raw: None,
211 }))),
212 Box::new(expr),
213 ],
214 span,
215 });
216 }
217 expr
218 } else {
219 Expr::Ident(Ident::new(imported_module.into(), span, Default::default()))
220 }
221}