turbopack_ecmascript/references/
amd.rs

1use std::mem::take;
2
3use anyhow::Result;
4use serde::{Deserialize, Serialize};
5use swc_core::{
6    common::DUMMY_SP,
7    ecma::{
8        ast::{CallExpr, Callee, Expr, ExprOrSpread, Lit},
9        utils::private_ident,
10    },
11    quote, quote_expr,
12};
13use turbo_rcstr::RcStr;
14use turbo_tasks::{
15    NonLocalValue, ReadRef, ResolvedVc, TryJoinIterExt, ValueToString, Vc, debug::ValueDebugFormat,
16    trace::TraceRawVcs,
17};
18use turbopack_core::{
19    chunk::{ChunkableModuleReference, ChunkingContext},
20    issue::IssueSource,
21    reference::ModuleReference,
22    reference_type::CommonJsReferenceSubType,
23    resolve::{ModuleResolveResult, origin::ResolveOrigin, parse::Request},
24};
25use turbopack_resolve::ecmascript::cjs_resolve;
26
27use crate::{
28    code_gen::{CodeGen, CodeGeneration},
29    create_visitor,
30    references::{
31        AstPath,
32        pattern_mapping::{PatternMapping, ResolveType},
33    },
34    runtime_functions::{TURBOPACK_EXPORT_VALUE, TURBOPACK_REQUIRE},
35};
36
37#[turbo_tasks::value]
38#[derive(Hash, Debug)]
39pub struct AmdDefineAssetReference {
40    origin: ResolvedVc<Box<dyn ResolveOrigin>>,
41    request: ResolvedVc<Request>,
42    issue_source: IssueSource,
43    in_try: bool,
44}
45
46#[turbo_tasks::value_impl]
47impl AmdDefineAssetReference {
48    #[turbo_tasks::function]
49    pub fn new(
50        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
51        request: ResolvedVc<Request>,
52        issue_source: IssueSource,
53        in_try: bool,
54    ) -> Vc<Self> {
55        Self::cell(AmdDefineAssetReference {
56            origin,
57            request,
58            issue_source,
59            in_try,
60        })
61    }
62}
63
64#[turbo_tasks::value_impl]
65impl ModuleReference for AmdDefineAssetReference {
66    #[turbo_tasks::function]
67    fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
68        cjs_resolve(
69            *self.origin,
70            *self.request,
71            CommonJsReferenceSubType::Undefined,
72            Some(self.issue_source),
73            self.in_try,
74        )
75    }
76}
77
78#[turbo_tasks::value_impl]
79impl ValueToString for AmdDefineAssetReference {
80    #[turbo_tasks::function]
81    async fn to_string(&self) -> Result<Vc<RcStr>> {
82        Ok(Vc::cell(
83            format!("AMD define dependency {}", self.request.to_string().await?,).into(),
84        ))
85    }
86}
87
88#[turbo_tasks::value_impl]
89impl ChunkableModuleReference for AmdDefineAssetReference {}
90
91#[derive(
92    ValueDebugFormat,
93    Debug,
94    PartialEq,
95    Eq,
96    Serialize,
97    Deserialize,
98    TraceRawVcs,
99    Clone,
100    NonLocalValue,
101)]
102pub enum AmdDefineDependencyElement {
103    Request {
104        request: ResolvedVc<Request>,
105        request_str: String,
106    },
107    Exports,
108    Module,
109    Require,
110}
111
112#[derive(
113    ValueDebugFormat,
114    Debug,
115    PartialEq,
116    Eq,
117    Serialize,
118    Deserialize,
119    TraceRawVcs,
120    Copy,
121    Clone,
122    NonLocalValue,
123)]
124pub enum AmdDefineFactoryType {
125    Unknown,
126    Function,
127    Value,
128}
129
130#[derive(PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue)]
131pub struct AmdDefineWithDependenciesCodeGen {
132    dependencies_requests: Vec<AmdDefineDependencyElement>,
133    origin: ResolvedVc<Box<dyn ResolveOrigin>>,
134    path: AstPath,
135    factory_type: AmdDefineFactoryType,
136    issue_source: IssueSource,
137    in_try: bool,
138}
139
140impl AmdDefineWithDependenciesCodeGen {
141    pub fn new(
142        dependencies_requests: Vec<AmdDefineDependencyElement>,
143        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
144        path: AstPath,
145        factory_type: AmdDefineFactoryType,
146        issue_source: IssueSource,
147        in_try: bool,
148    ) -> Self {
149        AmdDefineWithDependenciesCodeGen {
150            dependencies_requests,
151            origin,
152            path,
153            factory_type,
154            issue_source,
155            in_try,
156        }
157    }
158
159    pub async fn code_generation(
160        &self,
161        chunking_context: Vc<Box<dyn ChunkingContext>>,
162    ) -> Result<CodeGeneration> {
163        let mut visitors = Vec::new();
164
165        let resolved_elements = self
166            .dependencies_requests
167            .iter()
168            .map(|element| async move {
169                Ok(match element {
170                    AmdDefineDependencyElement::Request {
171                        request,
172                        request_str,
173                    } => ResolvedElement::PatternMapping {
174                        pattern_mapping: PatternMapping::resolve_request(
175                            **request,
176                            *self.origin,
177                            Vc::upcast(chunking_context),
178                            cjs_resolve(
179                                *self.origin,
180                                **request,
181                                CommonJsReferenceSubType::Undefined,
182                                Some(self.issue_source),
183                                self.in_try,
184                            ),
185                            ResolveType::ChunkItem,
186                        )
187                        .await?,
188                        request_str: request_str.to_string(),
189                    },
190                    AmdDefineDependencyElement::Exports => {
191                        ResolvedElement::Expr(quote!("exports" as Expr))
192                    }
193                    AmdDefineDependencyElement::Module => {
194                        ResolvedElement::Expr(quote!("module" as Expr))
195                    }
196                    AmdDefineDependencyElement::Require => {
197                        ResolvedElement::Expr(TURBOPACK_REQUIRE.into())
198                    }
199                })
200            })
201            .try_join()
202            .await?;
203
204        let factory_type = self.factory_type;
205
206        visitors.push(create_visitor!(
207            exact,
208            self.path,
209            visit_mut_call_expr,
210            |call_expr: &mut CallExpr| {
211                transform_amd_factory(call_expr, &resolved_elements, factory_type)
212            }
213        ));
214
215        Ok(CodeGeneration::visitors(visitors))
216    }
217}
218
219impl From<AmdDefineWithDependenciesCodeGen> for CodeGen {
220    fn from(val: AmdDefineWithDependenciesCodeGen) -> Self {
221        CodeGen::AmdDefineWithDependenciesCodeGen(Box::new(val))
222    }
223}
224
225enum ResolvedElement {
226    PatternMapping {
227        pattern_mapping: ReadRef<PatternMapping>,
228        request_str: String,
229    },
230    Expr(Expr),
231}
232
233/// Transforms `define([dep1, dep2], factory)` into:
234/// ```js
235/// __turbopack_export_value__(
236///   factory(
237///     __turbopack_require__(dep1),
238///     __turbopack_require__(dep2),
239///   ),
240/// );
241/// ```
242fn transform_amd_factory(
243    call_expr: &mut CallExpr,
244    resolved_elements: &[ResolvedElement],
245    factory_type: AmdDefineFactoryType,
246) {
247    let CallExpr { args, callee, .. } = call_expr;
248    let Some(factory) = take(args).pop().map(|e| e.expr) else {
249        return;
250    };
251
252    let deps = resolved_elements
253        .iter()
254        .map(|element| match element {
255            ResolvedElement::PatternMapping {
256                pattern_mapping: pm,
257                request_str: request,
258            } => {
259                let key_expr = Expr::Lit(Lit::Str(request.as_str().into()));
260                pm.create_require(key_expr)
261            }
262            ResolvedElement::Expr(expr) => expr.clone(),
263        })
264        .map(ExprOrSpread::from)
265        .collect();
266
267    match factory_type {
268        AmdDefineFactoryType::Unknown => {
269            // ((f, r = typeof f !== "function" ? f : f([...])) => r !== undefined &&
270            // __turbopack_export_value__(r))(...)
271            let f = private_ident!("f");
272            let call_f = Expr::Call(CallExpr {
273                args: deps,
274                callee: Callee::Expr(Box::new(Expr::Ident(f.clone()))),
275                span: DUMMY_SP,
276                ..Default::default()
277            });
278            *callee = Callee::Expr(quote_expr!(
279                "($f1, r = typeof $f2 !== \"function\" ? $f3 : $call_f) => r !== undefined && \
280                 $turbopack_export_value(r)",
281                 f1 = f.clone(),
282                 f2 = f.clone(),
283                 f3 = f,
284                 call_f: Expr = call_f,
285                 turbopack_export_value: Expr = TURBOPACK_EXPORT_VALUE.into()
286            ));
287            args.push(ExprOrSpread {
288                expr: factory,
289                spread: None,
290            });
291        }
292        AmdDefineFactoryType::Function => {
293            // (r => r !== undefined && __turbopack_export_value__(r))(...([...]))
294            *callee = Callee::Expr(quote_expr!(
295                "r => r !== undefined && $turbopack_export_value(r)",
296                turbopack_export_value: Expr = TURBOPACK_EXPORT_VALUE.into()
297            ));
298            args.push(ExprOrSpread {
299                expr: Box::new(Expr::Call(CallExpr {
300                    args: deps,
301                    callee: Callee::Expr(factory),
302                    ..Default::default()
303                })),
304                spread: None,
305            });
306        }
307        AmdDefineFactoryType::Value => {
308            // __turbopack_export_value__(...)
309            *callee = Callee::Expr(Box::new(TURBOPACK_EXPORT_VALUE.into()));
310            args.push(ExprOrSpread {
311                expr: factory,
312                spread: None,
313            });
314        }
315    }
316}