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