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