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