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
228fn 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 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 *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 *callee = Callee::Expr(Box::new(TURBOPACK_EXPORT_VALUE.into()));
305 args.push(ExprOrSpread {
306 expr: factory,
307 spread: None,
308 });
309 }
310 }
311}