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(async |element| {
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                            None,
180                        )
181                        .await?,
182                        request_str: request_str.to_string(),
183                    },
184                    AmdDefineDependencyElement::Exports => {
185                        ResolvedElement::Expr(quote!("exports" as Expr))
186                    }
187                    AmdDefineDependencyElement::Module => {
188                        ResolvedElement::Expr(quote!("module" as Expr))
189                    }
190                    AmdDefineDependencyElement::Require => {
191                        ResolvedElement::Expr(TURBOPACK_REQUIRE.into())
192                    }
193                })
194            })
195            .try_join()
196            .await?;
197
198        let factory_type = self.factory_type;
199
200        visitors.push(create_visitor!(
201            exact,
202            self.path,
203            visit_mut_call_expr,
204            |call_expr: &mut CallExpr| {
205                transform_amd_factory(call_expr, &resolved_elements, factory_type)
206            }
207        ));
208
209        Ok(CodeGeneration::visitors(visitors))
210    }
211}
212
213impl From<AmdDefineWithDependenciesCodeGen> for CodeGen {
214    fn from(val: AmdDefineWithDependenciesCodeGen) -> Self {
215        CodeGen::AmdDefineWithDependenciesCodeGen(Box::new(val))
216    }
217}
218
219enum ResolvedElement {
220    PatternMapping {
221        pattern_mapping: ReadRef<PatternMapping>,
222        request_str: String,
223    },
224    Expr(Expr),
225}
226
227/// Transforms `define([dep1, dep2], factory)` into:
228/// ```js
229/// __turbopack_export_value__(
230///   factory(
231///     __turbopack_require__(dep1),
232///     __turbopack_require__(dep2),
233///   ),
234/// );
235/// ```
236fn transform_amd_factory(
237    call_expr: &mut CallExpr,
238    resolved_elements: &[ResolvedElement],
239    factory_type: AmdDefineFactoryType,
240) {
241    let CallExpr { args, callee, .. } = call_expr;
242    let Some(factory) = take(args).pop().map(|e| e.expr) else {
243        return;
244    };
245
246    let deps = resolved_elements
247        .iter()
248        .map(|element| match element {
249            ResolvedElement::PatternMapping {
250                pattern_mapping: pm,
251                request_str: request,
252            } => {
253                let key_expr = Expr::Lit(Lit::Str(request.as_str().into()));
254                pm.create_require(key_expr)
255            }
256            ResolvedElement::Expr(expr) => expr.clone(),
257        })
258        .map(ExprOrSpread::from)
259        .collect();
260
261    match factory_type {
262        AmdDefineFactoryType::Unknown => {
263            // ((f, r = typeof f !== "function" ? f : f([...])) => r !== undefined &&
264            // __turbopack_export_value__(r))(...)
265            let f = private_ident!("f");
266            let call_f = Expr::Call(CallExpr {
267                args: deps,
268                callee: Callee::Expr(Box::new(Expr::Ident(f.clone()))),
269                span: DUMMY_SP,
270                ..Default::default()
271            });
272            *callee = Callee::Expr(quote_expr!(
273                "($f1, r = typeof $f2 !== \"function\" ? $f3 : $call_f) => r !== undefined && \
274                 $turbopack_export_value(r)",
275                 f1 = f.clone(),
276                 f2 = f.clone(),
277                 f3 = f,
278                 call_f: Expr = call_f,
279                 turbopack_export_value: Expr = TURBOPACK_EXPORT_VALUE.into()
280            ));
281            args.push(ExprOrSpread {
282                expr: factory,
283                spread: None,
284            });
285        }
286        AmdDefineFactoryType::Function => {
287            // (r => r !== undefined && __turbopack_export_value__(r))(...([...]))
288            *callee = Callee::Expr(quote_expr!(
289                "r => r !== undefined && $turbopack_export_value(r)",
290                turbopack_export_value: Expr = TURBOPACK_EXPORT_VALUE.into()
291            ));
292            args.push(ExprOrSpread {
293                expr: Box::new(Expr::Call(CallExpr {
294                    args: deps,
295                    callee: Callee::Expr(factory),
296                    ..Default::default()
297                })),
298                spread: None,
299            });
300        }
301        AmdDefineFactoryType::Value => {
302            // __turbopack_export_value__(...)
303            *callee = Callee::Expr(Box::new(TURBOPACK_EXPORT_VALUE.into()));
304            args.push(ExprOrSpread {
305                expr: factory,
306                spread: None,
307            });
308        }
309    }
310}