Skip to main content

turbopack_ecmascript/references/
amd.rs

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