Skip to main content

turbopack_ecmascript/references/
cjs.rs

1use anyhow::Result;
2use bincode::{Decode, Encode};
3use swc_core::{
4    common::util::take::Take,
5    ecma::ast::{CallExpr, Expr, ExprOrSpread, Lit},
6    quote,
7};
8use turbo_tasks::{
9    NonLocalValue, ResolvedVc, ValueToString, Vc, debug::ValueDebugFormat, trace::TraceRawVcs,
10};
11use turbopack_core::{
12    chunk::{ChunkingContext, ChunkingType},
13    issue::IssueSource,
14    reference::ModuleReference,
15    reference_type::CommonJsReferenceSubType,
16    resolve::{ModuleResolveResult, ResolveErrorMode, origin::ResolveOrigin, parse::Request},
17};
18use turbopack_resolve::ecmascript::cjs_resolve;
19
20use crate::{
21    code_gen::{CodeGen, CodeGeneration, IntoCodeGenReference},
22    create_visitor,
23    references::{
24        AstPath,
25        pattern_mapping::{PatternMapping, ResolveType},
26        util::SpecifiedChunkingType,
27    },
28    runtime_functions::TURBOPACK_CACHE,
29};
30
31#[turbo_tasks::value]
32#[derive(Hash, Debug, ValueToString)]
33#[value_to_string("generic commonjs {request}")]
34pub struct CjsAssetReference {
35    pub origin: ResolvedVc<Box<dyn ResolveOrigin>>,
36    pub request: ResolvedVc<Request>,
37    pub issue_source: IssueSource,
38    pub error_mode: ResolveErrorMode,
39}
40
41#[turbo_tasks::value_impl]
42impl CjsAssetReference {
43    #[turbo_tasks::function]
44    pub fn new(
45        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
46        request: ResolvedVc<Request>,
47        issue_source: IssueSource,
48        error_mode: ResolveErrorMode,
49    ) -> Result<Vc<Self>> {
50        Ok(Self::cell(CjsAssetReference {
51            origin,
52            request,
53            issue_source,
54            error_mode,
55        }))
56    }
57}
58
59#[turbo_tasks::value_impl]
60impl ModuleReference for CjsAssetReference {
61    #[turbo_tasks::function]
62    fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
63        cjs_resolve(
64            *self.origin,
65            *self.request,
66            CommonJsReferenceSubType::Undefined,
67            Some(self.issue_source),
68            self.error_mode,
69        )
70    }
71
72    fn chunking_type(&self) -> Option<ChunkingType> {
73        Some(ChunkingType::Parallel {
74            inherit_async: false,
75            hoisted: false,
76        })
77    }
78}
79
80#[turbo_tasks::value]
81#[derive(Hash, Debug, ValueToString)]
82#[value_to_string("require {request}")]
83pub struct CjsRequireAssetReference {
84    origin: ResolvedVc<Box<dyn ResolveOrigin>>,
85    request: ResolvedVc<Request>,
86    issue_source: IssueSource,
87    error_mode: ResolveErrorMode,
88    chunking_type_attribute: Option<SpecifiedChunkingType>,
89}
90
91impl CjsRequireAssetReference {
92    pub fn new(
93        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
94        request: ResolvedVc<Request>,
95        issue_source: IssueSource,
96        error_mode: ResolveErrorMode,
97        chunking_type_attribute: Option<SpecifiedChunkingType>,
98    ) -> Self {
99        CjsRequireAssetReference {
100            origin,
101            request,
102            issue_source,
103            error_mode,
104            chunking_type_attribute,
105        }
106    }
107}
108
109#[turbo_tasks::value_impl]
110impl ModuleReference for CjsRequireAssetReference {
111    #[turbo_tasks::function]
112    fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
113        cjs_resolve(
114            *self.origin,
115            *self.request,
116            CommonJsReferenceSubType::Undefined,
117            Some(self.issue_source),
118            self.error_mode,
119        )
120    }
121
122    fn chunking_type(&self) -> Option<ChunkingType> {
123        self.chunking_type_attribute.map_or_else(
124            || {
125                Some(ChunkingType::Parallel {
126                    inherit_async: false,
127                    hoisted: false,
128                })
129            },
130            |c| c.as_chunking_type(false, false),
131        )
132    }
133}
134
135impl IntoCodeGenReference for CjsRequireAssetReference {
136    fn into_code_gen_reference(
137        self,
138        path: AstPath,
139    ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
140        let reference = self.resolved_cell();
141        (
142            ResolvedVc::upcast(reference),
143            CodeGen::CjsRequireAssetReferenceCodeGen(CjsRequireAssetReferenceCodeGen {
144                reference,
145                path,
146            }),
147        )
148    }
149}
150
151#[derive(
152    PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode,
153)]
154pub struct CjsRequireAssetReferenceCodeGen {
155    reference: ResolvedVc<CjsRequireAssetReference>,
156    path: AstPath,
157}
158
159impl CjsRequireAssetReferenceCodeGen {
160    pub async fn code_generation(
161        &self,
162        chunking_context: Vc<Box<dyn ChunkingContext>>,
163    ) -> Result<CodeGeneration> {
164        let reference = self.reference.await?;
165
166        let pm = PatternMapping::resolve_request(
167            *reference.request,
168            *reference.origin,
169            chunking_context,
170            self.reference.resolve_reference(),
171            ResolveType::ChunkItem,
172        )
173        .await?;
174        let mut visitors = Vec::new();
175
176        visitors.push(create_visitor!(
177            self.path,
178            visit_mut_expr,
179            |expr: &mut Expr| {
180                let old_expr = expr.take();
181                let message = if let Expr::Call(CallExpr { args, .. }) = old_expr {
182                    match args.into_iter().next() {
183                        Some(ExprOrSpread {
184                            spread: None,
185                            expr: key_expr,
186                        }) => {
187                            *expr = pm.create_require(*key_expr);
188                            return;
189                        }
190                        Some(ExprOrSpread {
191                            spread: Some(_),
192                            expr: _,
193                        }) => "spread operator is not analyze-able in require() expressions.",
194                        _ => "require() expressions require at least 1 argument",
195                    }
196                } else {
197                    "visitor must be executed on a CallExpr"
198                };
199                *expr = quote!(
200                    "(() => { throw new Error($message); })()" as Expr,
201                    message: Expr = Expr::Lit(Lit::Str(message.into()))
202                );
203            }
204        ));
205
206        Ok(CodeGeneration::visitors(visitors))
207    }
208}
209
210#[turbo_tasks::value]
211#[derive(Hash, Debug, ValueToString)]
212#[value_to_string("require.resolve {request}")]
213pub struct CjsRequireResolveAssetReference {
214    origin: ResolvedVc<Box<dyn ResolveOrigin>>,
215    request: ResolvedVc<Request>,
216    issue_source: IssueSource,
217    error_mode: ResolveErrorMode,
218    chunking_type_attribute: Option<SpecifiedChunkingType>,
219}
220
221impl CjsRequireResolveAssetReference {
222    pub fn new(
223        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
224        request: ResolvedVc<Request>,
225        issue_source: IssueSource,
226        error_mode: ResolveErrorMode,
227        chunking_type_attribute: Option<SpecifiedChunkingType>,
228    ) -> Self {
229        CjsRequireResolveAssetReference {
230            origin,
231            request,
232            issue_source,
233            error_mode,
234            chunking_type_attribute,
235        }
236    }
237}
238
239#[turbo_tasks::value_impl]
240impl ModuleReference for CjsRequireResolveAssetReference {
241    #[turbo_tasks::function]
242    fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
243        cjs_resolve(
244            *self.origin,
245            *self.request,
246            CommonJsReferenceSubType::Undefined,
247            Some(self.issue_source),
248            self.error_mode,
249        )
250    }
251
252    fn chunking_type(&self) -> Option<ChunkingType> {
253        self.chunking_type_attribute.map_or_else(
254            || {
255                Some(ChunkingType::Parallel {
256                    inherit_async: false,
257                    hoisted: false,
258                })
259            },
260            |c| c.as_chunking_type(false, false),
261        )
262    }
263}
264
265impl IntoCodeGenReference for CjsRequireResolveAssetReference {
266    fn into_code_gen_reference(
267        self,
268        path: AstPath,
269    ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
270        let reference = self.resolved_cell();
271        (
272            ResolvedVc::upcast(reference),
273            CodeGen::CjsRequireResolveAssetReferenceCodeGen(
274                CjsRequireResolveAssetReferenceCodeGen { reference, path },
275            ),
276        )
277    }
278}
279
280#[derive(
281    PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode,
282)]
283pub struct CjsRequireResolveAssetReferenceCodeGen {
284    reference: ResolvedVc<CjsRequireResolveAssetReference>,
285    path: AstPath,
286}
287
288impl CjsRequireResolveAssetReferenceCodeGen {
289    pub async fn code_generation(
290        &self,
291        chunking_context: Vc<Box<dyn ChunkingContext>>,
292    ) -> Result<CodeGeneration> {
293        let reference = self.reference.await?;
294
295        let pm = PatternMapping::resolve_request(
296            *reference.request,
297            *reference.origin,
298            chunking_context,
299            self.reference.resolve_reference(),
300            ResolveType::ChunkItem,
301        )
302        .await?;
303        let mut visitors = Vec::new();
304
305        // Inline the result of the `require.resolve` call as a literal.
306        visitors.push(create_visitor!(
307            self.path,
308            visit_mut_expr,
309            |expr: &mut Expr| {
310                if let Expr::Call(call_expr) = expr {
311                    let args = std::mem::take(&mut call_expr.args);
312                    *expr = match args.into_iter().next() {
313                        Some(ExprOrSpread { expr, spread: None }) => pm.create_id(*expr),
314                        other => {
315                            let message = match other {
316                                // These are SWC bugs: https://github.com/swc-project/swc/issues/5394
317                                Some(ExprOrSpread {
318                                    spread: Some(_),
319                                    expr: _,
320                                }) => {
321                                    "spread operator is not analyze-able in require() expressions."
322                                }
323                                _ => "require() expressions require at least 1 argument",
324                            };
325                            quote!(
326                                "(() => { throw new Error($message); })()" as Expr,
327                                message: Expr = Expr::Lit(Lit::Str(message.into()))
328                            )
329                        }
330                    };
331                }
332                // CjsRequireResolveAssetReference will only be used for Expr::Call.
333                // Due to eventual consistency the path might match something else,
334                // but we can ignore that as it will be recomputed anyway.
335            }
336        ));
337
338        Ok(CodeGeneration::visitors(visitors))
339    }
340}
341
342#[derive(
343    PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Debug, Hash, Encode, Decode,
344)]
345pub struct CjsRequireCacheAccess {
346    pub path: AstPath,
347}
348impl CjsRequireCacheAccess {
349    pub fn new(path: AstPath) -> Self {
350        CjsRequireCacheAccess { path }
351    }
352
353    pub async fn code_generation(
354        &self,
355        _chunking_context: Vc<Box<dyn ChunkingContext>>,
356    ) -> Result<CodeGeneration> {
357        let mut visitors = Vec::new();
358
359        visitors.push(create_visitor!(
360            self.path,
361            visit_mut_expr,
362            |expr: &mut Expr| {
363                if let Expr::Member(_) = expr {
364                    *expr = TURBOPACK_CACHE.into();
365                } else {
366                    unreachable!("`CjsRequireCacheAccess` is only created from `MemberExpr`");
367                }
368            }
369        ));
370
371        Ok(CodeGeneration::visitors(visitors))
372    }
373}
374
375impl From<CjsRequireCacheAccess> for CodeGen {
376    fn from(val: CjsRequireCacheAccess) -> Self {
377        CodeGen::CjsRequireCacheAccess(val)
378    }
379}