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#[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 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 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 }
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}