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