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