turbopack_ecmascript/references/
cjs.rs

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