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