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