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_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(
154    PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode,
155)]
156pub struct CjsRequireAssetReferenceCodeGen {
157    reference: ResolvedVc<CjsRequireAssetReference>,
158    path: AstPath,
159}
160
161impl CjsRequireAssetReferenceCodeGen {
162    pub async fn code_generation(
163        &self,
164        chunking_context: Vc<Box<dyn ChunkingContext>>,
165    ) -> Result<CodeGeneration> {
166        let reference = self.reference.await?;
167
168        let pm = PatternMapping::resolve_request(
169            *reference.request,
170            *reference.origin,
171            chunking_context,
172            self.reference.resolve_reference(),
173            ResolveType::ChunkItem,
174        )
175        .await?;
176        let mut visitors = Vec::new();
177
178        visitors.push(create_visitor!(
179            self.path,
180            visit_mut_expr,
181            |expr: &mut Expr| {
182                let old_expr = expr.take();
183                let message = if let Expr::Call(CallExpr { args, .. }) = old_expr {
184                    match args.into_iter().next() {
185                        Some(ExprOrSpread {
186                            spread: None,
187                            expr: key_expr,
188                        }) => {
189                            *expr = pm.create_require(*key_expr);
190                            return;
191                        }
192                        Some(ExprOrSpread {
193                            spread: Some(_),
194                            expr: _,
195                        }) => "spread operator is not analyze-able in require() expressions.",
196                        _ => "require() expressions require at least 1 argument",
197                    }
198                } else {
199                    "visitor must be executed on a CallExpr"
200                };
201                *expr = quote!(
202                    "(() => { throw new Error($message); })()" as Expr,
203                    message: Expr = Expr::Lit(Lit::Str(message.into()))
204                );
205            }
206        ));
207
208        Ok(CodeGeneration::visitors(visitors))
209    }
210}
211
212#[turbo_tasks::value]
213#[derive(Hash, Debug)]
214pub struct CjsRequireResolveAssetReference {
215    pub origin: ResolvedVc<Box<dyn ResolveOrigin>>,
216    pub request: ResolvedVc<Request>,
217    pub issue_source: IssueSource,
218    pub in_try: bool,
219}
220
221impl CjsRequireResolveAssetReference {
222    pub fn new(
223        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
224        request: ResolvedVc<Request>,
225        issue_source: IssueSource,
226        in_try: bool,
227    ) -> Self {
228        CjsRequireResolveAssetReference {
229            origin,
230            request,
231            issue_source,
232            in_try,
233        }
234    }
235}
236
237#[turbo_tasks::value_impl]
238impl ModuleReference for CjsRequireResolveAssetReference {
239    #[turbo_tasks::function]
240    fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
241        cjs_resolve(
242            *self.origin,
243            *self.request,
244            CommonJsReferenceSubType::Undefined,
245            Some(self.issue_source),
246            self.in_try,
247        )
248    }
249}
250
251#[turbo_tasks::value_impl]
252impl ValueToString for CjsRequireResolveAssetReference {
253    #[turbo_tasks::function]
254    async fn to_string(&self) -> Result<Vc<RcStr>> {
255        Ok(Vc::cell(
256            format!("require.resolve {}", self.request.to_string().await?,).into(),
257        ))
258    }
259}
260
261#[turbo_tasks::value_impl]
262impl ChunkableModuleReference for CjsRequireResolveAssetReference {}
263
264impl IntoCodeGenReference for CjsRequireResolveAssetReference {
265    fn into_code_gen_reference(
266        self,
267        path: AstPath,
268    ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
269        let reference = self.resolved_cell();
270        (
271            ResolvedVc::upcast(reference),
272            CodeGen::CjsRequireResolveAssetReferenceCodeGen(
273                CjsRequireResolveAssetReferenceCodeGen { reference, path },
274            ),
275        )
276    }
277}
278
279#[derive(
280    PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode,
281)]
282pub struct CjsRequireResolveAssetReferenceCodeGen {
283    reference: ResolvedVc<CjsRequireResolveAssetReference>,
284    path: AstPath,
285}
286
287impl CjsRequireResolveAssetReferenceCodeGen {
288    pub async fn code_generation(
289        &self,
290        chunking_context: Vc<Box<dyn ChunkingContext>>,
291    ) -> Result<CodeGeneration> {
292        let reference = self.reference.await?;
293
294        let pm = PatternMapping::resolve_request(
295            *reference.request,
296            *reference.origin,
297            chunking_context,
298            self.reference.resolve_reference(),
299            ResolveType::ChunkItem,
300        )
301        .await?;
302        let mut visitors = Vec::new();
303
304        // Inline the result of the `require.resolve` call as a literal.
305        visitors.push(create_visitor!(
306            self.path,
307            visit_mut_expr,
308            |expr: &mut Expr| {
309                if let Expr::Call(call_expr) = expr {
310                    let args = std::mem::take(&mut call_expr.args);
311                    *expr = match args.into_iter().next() {
312                        Some(ExprOrSpread { expr, spread: None }) => pm.create_id(*expr),
313                        other => {
314                            let message = match other {
315                                // These are SWC bugs: https://github.com/swc-project/swc/issues/5394
316                                Some(ExprOrSpread {
317                                    spread: Some(_),
318                                    expr: _,
319                                }) => {
320                                    "spread operator is not analyze-able in require() expressions."
321                                }
322                                _ => "require() expressions require at least 1 argument",
323                            };
324                            quote!(
325                                "(() => { throw new Error($message); })()" as Expr,
326                                message: Expr = Expr::Lit(Lit::Str(message.into()))
327                            )
328                        }
329                    };
330                }
331                // CjsRequireResolveAssetReference will only be used for Expr::Call.
332                // Due to eventual consistency the path might match something else,
333                // but we can ignore that as it will be recomputed anyway.
334            }
335        ));
336
337        Ok(CodeGeneration::visitors(visitors))
338    }
339}
340
341#[derive(
342    PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Debug, Hash, Encode, Decode,
343)]
344pub struct CjsRequireCacheAccess {
345    pub path: AstPath,
346}
347impl CjsRequireCacheAccess {
348    pub fn new(path: AstPath) -> Self {
349        CjsRequireCacheAccess { path }
350    }
351
352    pub async fn code_generation(
353        &self,
354        _chunking_context: Vc<Box<dyn ChunkingContext>>,
355    ) -> Result<CodeGeneration> {
356        let mut visitors = Vec::new();
357
358        visitors.push(create_visitor!(
359            self.path,
360            visit_mut_expr,
361            |expr: &mut Expr| {
362                if let Expr::Member(_) = expr {
363                    *expr = TURBOPACK_CACHE.into();
364                } else {
365                    unreachable!("`CjsRequireCacheAccess` is only created from `MemberExpr`");
366                }
367            }
368        ));
369
370        Ok(CodeGeneration::visitors(visitors))
371    }
372}
373
374impl From<CjsRequireCacheAccess> for CodeGen {
375    fn from(val: CjsRequireCacheAccess) -> Self {
376        CodeGen::CjsRequireCacheAccess(val)
377    }
378}