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