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