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