1use anyhow::Result;
2use bincode::{Decode, Encode};
3use swc_core::{
4 common::{DUMMY_SP, util::take::Take},
5 ecma::{
6 ast::{
7 CallExpr, Expr, ExprOrSpread, Lit, ObjectLit, Prop, PropName, PropOrSpread,
8 SpreadElement,
9 },
10 utils::prop_name_eq,
11 },
12 quote,
13};
14use turbo_rcstr::{RcStr, rcstr};
15use turbo_tasks::{
16 NonLocalValue, ResolvedVc, ValueToString, Vc, debug::ValueDebugFormat, trace::TraceRawVcs,
17};
18use turbopack_core::{
19 chunk::{ChunkingContext, ChunkingType},
20 issue::IssueSource,
21 module::Module,
22 reference::ModuleReference,
23 reference_type::CommonJsReferenceSubType,
24 resolve::{
25 BindingUsage, ExportUsage, ImportUsage, ModuleResolveResult, ResolveErrorMode,
26 origin::ResolveOrigin, parse::Request,
27 },
28};
29use turbopack_resolve::ecmascript::cjs_resolve;
30
31use crate::{
32 chunk::{EcmascriptChunkPlaceable, EcmascriptExports},
33 code_gen::{CodeGen, CodeGeneration, IntoCodeGenReference},
34 create_visitor,
35 references::{
36 AstPath,
37 pattern_mapping::{PatternMapping, ResolveType},
38 util::SpecifiedChunkingType,
39 },
40 runtime_functions::TURBOPACK_CACHE,
41};
42
43#[turbo_tasks::value]
45#[derive(Hash, Debug, ValueToString)]
46#[value_to_string("generic commonjs {request}")]
47pub struct CjsAssetReference {
48 pub origin: ResolvedVc<Box<dyn ResolveOrigin>>,
49 pub request: ResolvedVc<Request>,
50 pub issue_source: IssueSource,
51 pub error_mode: ResolveErrorMode,
52}
53
54#[turbo_tasks::value_impl]
55impl CjsAssetReference {
56 #[turbo_tasks::function]
57 pub fn new(
58 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
59 request: ResolvedVc<Request>,
60 issue_source: IssueSource,
61 error_mode: ResolveErrorMode,
62 ) -> Result<Vc<Self>> {
63 Ok(Self::cell(CjsAssetReference {
64 origin,
65 request,
66 issue_source,
67 error_mode,
68 }))
69 }
70}
71
72#[turbo_tasks::value_impl]
73impl ModuleReference for CjsAssetReference {
74 #[turbo_tasks::function]
75 fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
76 cjs_resolve(
77 *self.origin,
78 *self.request,
79 CommonJsReferenceSubType::Undefined,
80 Some(self.issue_source),
81 self.error_mode,
82 )
83 }
84
85 fn chunking_type(&self) -> Option<ChunkingType> {
86 Some(ChunkingType::Parallel {
87 inherit_async: false,
88 hoisted: false,
89 })
90 }
91
92 fn source(&self) -> Option<IssueSource> {
93 Some(self.issue_source)
94 }
95}
96
97#[turbo_tasks::value(shared)]
98#[derive(Hash, Debug, ValueToString)]
99#[value_to_string("require {request}")]
100pub struct CjsRequireAssetReference {
101 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
102 request: ResolvedVc<Request>,
103 issue_source: IssueSource,
104 error_mode: ResolveErrorMode,
105 chunking_type_attribute: Option<SpecifiedChunkingType>,
106 resolve_override: Option<ResolvedVc<Box<dyn Module>>>,
107 usage: ExportUsage,
108 cjs_tree_shaking: bool,
109}
110
111impl CjsRequireAssetReference {
112 pub fn new(
113 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
114 request: ResolvedVc<Request>,
115 issue_source: IssueSource,
116 error_mode: ResolveErrorMode,
117 chunking_type_attribute: Option<SpecifiedChunkingType>,
118 resolve_override: Option<ResolvedVc<Box<dyn Module>>>,
119 usage: ExportUsage,
120 cjs_tree_shaking: bool,
121 ) -> Self {
122 CjsRequireAssetReference {
123 origin,
124 request,
125 issue_source,
126 error_mode,
127 chunking_type_attribute,
128 resolve_override,
129 usage,
130 cjs_tree_shaking,
131 }
132 }
133}
134
135#[turbo_tasks::value_impl]
136impl ModuleReference for CjsRequireAssetReference {
137 #[turbo_tasks::function]
138 fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
139 if let Some(resolved) = &self.resolve_override {
140 return *ModuleResolveResult::module(*resolved);
141 }
142
143 cjs_resolve(
144 *self.origin,
145 *self.request,
146 CommonJsReferenceSubType::Undefined,
147 Some(self.issue_source),
148 self.error_mode,
149 )
150 }
151
152 fn chunking_type(&self) -> Option<ChunkingType> {
153 self.chunking_type_attribute.map_or_else(
154 || {
155 Some(ChunkingType::Parallel {
156 inherit_async: false,
157 hoisted: false,
158 })
159 },
160 |c| c.as_chunking_type(false, false),
161 )
162 }
163
164 fn binding_usage(&self) -> BindingUsage {
165 BindingUsage {
166 import: ImportUsage::TopLevel,
167 export: self.usage.clone(),
168 }
169 }
170
171 fn source(&self) -> Option<IssueSource> {
172 Some(self.issue_source)
173 }
174}
175
176impl IntoCodeGenReference for CjsRequireAssetReference {
177 fn into_reference(self) -> ResolvedVc<Box<dyn ModuleReference>> {
178 ResolvedVc::upcast(self.resolved_cell())
179 }
180
181 fn into_code_gen_reference(
182 self,
183 path: AstPath,
184 ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
185 let reference = self.resolved_cell();
186 (
187 ResolvedVc::upcast(reference),
188 CodeGen::CjsRequireAssetReferenceCodeGen(CjsRequireAssetReferenceCodeGen {
189 reference,
190 path,
191 }),
192 )
193 }
194}
195
196#[derive(
197 PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode,
198)]
199pub struct CjsRequireAssetReferenceCodeGen {
200 reference: ResolvedVc<CjsRequireAssetReference>,
201 path: AstPath,
202}
203
204impl CjsRequireAssetReferenceCodeGen {
205 pub async fn code_generation(
206 &self,
207 chunking_context: Vc<Box<dyn ChunkingContext>>,
208 ) -> Result<CodeGeneration> {
209 let reference = self.reference.await?;
210
211 let pm = PatternMapping::resolve_request(
212 *reference.request,
213 *reference.origin,
214 chunking_context,
215 self.reference.resolve_reference(),
216 ResolveType::ChunkItem,
217 Some(Vc::upcast(*self.reference)),
218 )
219 .await?;
220 let mut visitors = Vec::new();
221
222 visitors.push(create_visitor!(
223 self.path,
224 visit_mut_expr,
225 |expr: &mut Expr| {
226 let old_expr = expr.take();
227 let message = if let Expr::Call(CallExpr { args, .. }) = old_expr {
228 match args.into_iter().next() {
229 Some(ExprOrSpread {
230 spread: None,
231 expr: key_expr,
232 }) => {
233 *expr = pm.create_require(*key_expr);
234 return;
235 }
236 Some(ExprOrSpread {
237 spread: Some(_),
238 expr: _,
239 }) => "spread operator is not analyze-able in require() expressions.",
240 _ => "require() expressions require at least 1 argument",
241 }
242 } else {
243 "visitor must be executed on a CallExpr"
244 };
245 *expr = quote!(
246 "(() => { throw new Error($message); })()" as Expr,
247 message: Expr = Expr::Lit(Lit::Str(message.into()))
248 );
249 }
250 ));
251
252 Ok(CodeGeneration::visitors(visitors))
253 }
254}
255
256#[turbo_tasks::value]
257#[derive(Hash, Debug, ValueToString)]
258#[value_to_string("require.resolve {request}")]
259pub struct CjsRequireResolveAssetReference {
260 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
261 request: ResolvedVc<Request>,
262 issue_source: IssueSource,
263 error_mode: ResolveErrorMode,
264 chunking_type_attribute: Option<SpecifiedChunkingType>,
265 resolve_override: Option<ResolvedVc<Box<dyn Module>>>,
266}
267
268impl CjsRequireResolveAssetReference {
269 pub fn new(
270 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
271 request: ResolvedVc<Request>,
272 issue_source: IssueSource,
273 error_mode: ResolveErrorMode,
274 chunking_type_attribute: Option<SpecifiedChunkingType>,
275 resolve_override: Option<ResolvedVc<Box<dyn Module>>>,
276 ) -> Self {
277 CjsRequireResolveAssetReference {
278 origin,
279 request,
280 issue_source,
281 error_mode,
282 chunking_type_attribute,
283 resolve_override,
284 }
285 }
286}
287
288#[turbo_tasks::value_impl]
289impl ModuleReference for CjsRequireResolveAssetReference {
290 #[turbo_tasks::function]
291 fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
292 if let Some(resolved) = &self.resolve_override {
293 return *ModuleResolveResult::module(*resolved);
294 }
295
296 cjs_resolve(
297 *self.origin,
298 *self.request,
299 CommonJsReferenceSubType::Undefined,
300 Some(self.issue_source),
301 self.error_mode,
302 )
303 }
304
305 fn chunking_type(&self) -> Option<ChunkingType> {
306 self.chunking_type_attribute.map_or_else(
307 || {
308 Some(ChunkingType::Parallel {
309 inherit_async: false,
310 hoisted: false,
311 })
312 },
313 |c| c.as_chunking_type(false, false),
314 )
315 }
316
317 fn source(&self) -> Option<IssueSource> {
318 Some(self.issue_source)
319 }
320}
321
322impl IntoCodeGenReference for CjsRequireResolveAssetReference {
323 fn into_reference(self) -> ResolvedVc<Box<dyn ModuleReference>> {
324 ResolvedVc::upcast(self.resolved_cell())
325 }
326
327 fn into_code_gen_reference(
328 self,
329 path: AstPath,
330 ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
331 let reference = self.resolved_cell();
332 (
333 ResolvedVc::upcast(reference),
334 CodeGen::CjsRequireResolveAssetReferenceCodeGen(
335 CjsRequireResolveAssetReferenceCodeGen { reference, path },
336 ),
337 )
338 }
339}
340
341#[derive(
342 PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode,
343)]
344pub struct CjsRequireResolveAssetReferenceCodeGen {
345 reference: ResolvedVc<CjsRequireResolveAssetReference>,
346 path: AstPath,
347}
348
349impl CjsRequireResolveAssetReferenceCodeGen {
350 pub async fn code_generation(
351 &self,
352 chunking_context: Vc<Box<dyn ChunkingContext>>,
353 ) -> Result<CodeGeneration> {
354 let reference = self.reference.await?;
355
356 let pm = PatternMapping::resolve_request(
357 *reference.request,
358 *reference.origin,
359 chunking_context,
360 self.reference.resolve_reference(),
361 ResolveType::ChunkItem,
362 Some(Vc::upcast(*self.reference)),
363 )
364 .await?;
365 let mut visitors = Vec::new();
366
367 visitors.push(create_visitor!(
369 self.path,
370 visit_mut_expr,
371 |expr: &mut Expr| {
372 if let Expr::Call(call_expr) = expr {
373 let args = std::mem::take(&mut call_expr.args);
374 *expr = match args.into_iter().next() {
375 Some(ExprOrSpread { expr, spread: None }) => pm.create_id(*expr),
376 other => {
377 let message = match other {
378 Some(ExprOrSpread {
380 spread: Some(_),
381 expr: _,
382 }) => {
383 "spread operator is not analyze-able in require() expressions."
384 }
385 _ => "require() expressions require at least 1 argument",
386 };
387 quote!(
388 "(() => { throw new Error($message); })()" as Expr,
389 message: Expr = Expr::Lit(Lit::Str(message.into()))
390 )
391 }
392 };
393 }
394 }
398 ));
399
400 Ok(CodeGeneration::visitors(visitors))
401 }
402}
403
404#[derive(
405 PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Debug, Hash, Encode, Decode,
406)]
407pub struct CjsRequireCacheAccess {
408 pub path: AstPath,
409}
410impl CjsRequireCacheAccess {
411 pub fn new(path: AstPath) -> Self {
412 CjsRequireCacheAccess { path }
413 }
414
415 pub async fn code_generation(
416 &self,
417 _chunking_context: Vc<Box<dyn ChunkingContext>>,
418 ) -> Result<CodeGeneration> {
419 let mut visitors = Vec::new();
420
421 visitors.push(create_visitor!(
422 self.path,
423 visit_mut_expr,
424 |expr: &mut Expr| {
425 if let Expr::Member(_) = expr {
426 *expr = TURBOPACK_CACHE.into();
427 } else {
428 unreachable!("`CjsRequireCacheAccess` is only created from `MemberExpr`");
429 }
430 }
431 ));
432
433 Ok(CodeGeneration::visitors(visitors))
434 }
435}
436
437impl From<CjsRequireCacheAccess> for CodeGen {
438 fn from(val: CjsRequireCacheAccess) -> Self {
439 CodeGen::CjsRequireCacheAccess(val)
440 }
441}
442
443#[derive(
447 PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode,
448)]
449pub struct CjsExportsDropCodeGen {
450 drops: Vec<DroppableCjsExportAssignment>,
451 dead_writes: Vec<DroppableCjsExportAssignment>,
453 has_es_module: bool,
456}
457
458#[derive(
460 PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode,
461)]
462pub enum DroppableCjsExportAssignment {
463 Write { name: RcStr, path: AstPath },
466 ObjectLiteral { names: Vec<RcStr>, path: AstPath },
469}
470
471impl CjsExportsDropCodeGen {
472 pub fn new(
473 drops: Vec<DroppableCjsExportAssignment>,
474 dead_writes: Vec<DroppableCjsExportAssignment>,
475 has_es_module: bool,
476 ) -> Self {
477 CjsExportsDropCodeGen {
478 drops,
479 dead_writes,
480 has_es_module,
481 }
482 }
483
484 pub async fn code_generation(
485 &self,
486 chunking_context: Vc<Box<dyn ChunkingContext>>,
487 module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
488 _exports: ResolvedVc<EcmascriptExports>,
489 ) -> Result<CodeGeneration> {
490 let export_usage_info = chunking_context
491 .module_export_usage(*ResolvedVc::upcast(module))
492 .await?;
493 let export_usage_info = export_usage_info.export_usage.await?;
494
495 let drops = if !self.has_es_module && export_usage_info.is_export_used(&rcstr!("default")) {
498 &[]
499 } else {
500 &self.drops[..]
501 };
502
503 let mut visitors = Vec::new();
508 for (drop, dead) in (self.dead_writes.iter().map(|drop| (drop, true)))
509 .chain(drops.iter().map(|drop| (drop, false)))
510 {
511 match drop {
512 DroppableCjsExportAssignment::Write { name, path } => {
513 if !dead && export_usage_info.is_export_used(name) {
514 continue;
515 }
516 visitors.push(create_visitor!(path, visit_mut_expr, |expr: &mut Expr| {
517 match expr {
518 Expr::Assign(assign) => {
520 let value = assign.right.take();
521 *expr = *value;
522 }
523 Expr::Call(call) => {
526 *expr = match take_define_property_value(call) {
527 Some(value) => *value,
528 None => quote!("0" as Expr),
529 };
530 }
531 _ => {}
532 }
533 }));
534 }
535 DroppableCjsExportAssignment::ObjectLiteral { names, path } => {
536 let unused = names
537 .iter()
538 .filter(|name| dead || !export_usage_info.is_export_used(name))
539 .cloned()
540 .collect::<Vec<_>>();
541 if unused.is_empty() {
542 continue;
543 }
544 visitors.push(create_visitor!(path, visit_mut_expr, |expr: &mut Expr| {
547 if let Expr::Assign(assign) = expr
548 && let Expr::Object(obj) = &mut *assign.right
549 {
550 drop_object_literal_exports(obj, &unused);
551 }
552 }));
553 }
554 }
555 }
556
557 Ok(CodeGeneration::visitors(visitors))
558 }
559}
560
561fn take_define_property_value(call: &mut CallExpr) -> Option<Box<Expr>> {
564 let descriptor = call.args.get_mut(2)?;
565 let Expr::Object(descriptor) = &mut *descriptor.expr else {
566 return None;
567 };
568 descriptor.props.iter_mut().find_map(|prop| {
569 let PropOrSpread::Prop(prop) = prop else {
570 return None;
571 };
572 let Prop::KeyValue(kv) = &mut **prop else {
573 return None;
574 };
575 prop_name_eq(&kv.key, "value").then(|| kv.value.take())
576 })
577}
578
579fn drop_object_literal_exports(obj: &mut ObjectLit, names: &[RcStr]) {
581 let is_dropped = |key: &PropName| names.iter().any(|n| prop_name_eq(key, n));
582 obj.props = obj
583 .props
584 .take()
585 .into_iter()
586 .filter_map(|prop| {
587 let PropOrSpread::Prop(p) = &prop else {
588 return Some(prop);
589 };
590 match &**p {
591 Prop::KeyValue(kv) if is_dropped(&kv.key) => {
593 Some(PropOrSpread::Spread(SpreadElement {
594 dot3_token: DUMMY_SP,
595 expr: Box::new(quote!("void ($e)" as Expr, e: Expr = *kv.value.clone())),
596 }))
597 }
598 Prop::Shorthand(id) if names.iter().any(|n| id.sym.as_str() == &**n) => None,
599 Prop::Getter(g) if is_dropped(&g.key) => None,
600 Prop::Setter(s) if is_dropped(&s.key) => None,
601 Prop::Method(m) if is_dropped(&m.key) => None,
602 _ => Some(prop),
604 }
605 })
606 .collect();
607}
608
609impl From<CjsExportsDropCodeGen> for CodeGen {
610 fn from(val: CjsExportsDropCodeGen) -> Self {
611 CodeGen::CjsExportsDropCodeGen(val)
612 }
613}