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