1use anyhow::{Context, Error};
2use rustc_hash::{FxHashMap, FxHashSet};
3use swc_atoms::Atom;
4use swc_common::{sync::Lrc, FileName, Mark, Spanned, SyntaxContext, DUMMY_SP};
5use swc_ecma_ast::*;
6use swc_ecma_utils::find_pat_ids;
7use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
8
9use super::Bundler;
10use crate::{load::Load, resolve::Resolve, util::ExportMetadata};
11
12#[cfg(test)]
13mod tests;
14
15impl<L, R> Bundler<'_, L, R>
16where
17 L: Load,
18 R: Resolve,
19{
20 pub(super) fn extract_import_info(
22 &self,
23 path: &FileName,
24 module: &mut Module,
25 module_local_mark: Mark,
26 ) -> RawImports {
27 self.run(|| {
28 let mut v = ImportHandler {
29 module_ctxt: SyntaxContext::empty().apply_mark(module_local_mark),
30 path,
31 bundler: self,
32 top_level: false,
33 info: Default::default(),
34 usages: Default::default(),
35 imported_idents: Default::default(),
36 deglob_phase: false,
37 idents_to_deglob: Default::default(),
38 in_obj_of_member: false,
39 };
40 module.body.visit_mut_with(&mut v);
41 v.deglob_phase = true;
42 module.body.visit_mut_with(&mut v);
43
44 v.info
45 })
46 }
47
48 pub(super) fn resolve(
49 &self,
50 base: &FileName,
51 module_specifier: &str,
52 ) -> Result<Lrc<FileName>, Error> {
53 self.run(|| {
54 let path = self
55 .resolver
56 .resolve(base, module_specifier)
57 .map(|v| v.filename)
58 .with_context(|| format!("failed to resolve {module_specifier} from {base}"))?;
59
60 let path = Lrc::new(path);
61
62 Ok(path)
63 })
64 }
65}
66
67#[derive(Debug, Default)]
68pub(super) struct RawImports {
69 pub imports: Vec<ImportDecl>,
71
72 pub lazy_imports: Vec<ImportDecl>,
83 pub dynamic_imports: Vec<Str>,
84
85 pub forced_ns: FxHashSet<Atom>,
96}
97
98struct ImportHandler<'a, 'b, L, R>
101where
102 L: Load,
103 R: Resolve,
104{
105 module_ctxt: SyntaxContext,
108 path: &'a FileName,
109 bundler: &'a Bundler<'b, L, R>,
110 top_level: bool,
111 info: RawImports,
112
113 usages: FxHashMap<Id, Vec<Id>>,
116
117 imported_idents: FxHashMap<Id, SyntaxContext>,
119
120 deglob_phase: bool,
121 idents_to_deglob: FxHashSet<Id>,
122
123 in_obj_of_member: bool,
128}
129
130impl RawImports {
131 fn insert(&mut self, import: &ImportDecl) {
132 for prev in self.imports.iter_mut() {
133 if prev.src.value == import.src.value {
134 prev.specifiers.extend(import.specifiers.clone());
135 return;
136 }
137 }
138
139 self.imports.push(import.clone());
140 }
141}
142
143impl<L, R> ImportHandler<'_, '_, L, R>
144where
145 L: Load,
146 R: Resolve,
147{
148 fn ctxt_for(&self, src: &Atom) -> Option<(SyntaxContext, SyntaxContext)> {
150 if self.bundler.is_external(src) {
152 return None;
153 }
154 let path = self.bundler.resolve(self.path, src).ok()?;
155 let (_, local_mark, export_mark) = self.bundler.scope.module_id_gen.gen(&path);
156
157 Some((
158 SyntaxContext::empty().apply_mark(local_mark),
159 SyntaxContext::empty().apply_mark(export_mark),
160 ))
161 }
162
163 fn mark_as_wrapping_required(&self, src: &Atom) {
164 if self.bundler.is_external(src) {
166 return;
167 }
168 let path = self.bundler.resolve(self.path, src);
169 let path = match path {
170 Ok(v) => v,
171 Err(_) => return,
172 };
173 let (id, _, _) = self.bundler.scope.module_id_gen.gen(&path);
174
175 self.bundler.scope.mark_as_wrapping_required(id);
176 }
177
178 fn mark_as_cjs(&self, src: &Atom) {
179 let path = self.bundler.resolve(self.path, src);
180 let path = match path {
181 Ok(v) => v,
182 Err(_) => return,
183 };
184 let (id, _, _) = self.bundler.scope.module_id_gen.gen(&path);
185
186 self.bundler.scope.mark_as_cjs(id);
187 }
188
189 fn add_forced_ns_for(&mut self, id: Id) {
190 self.idents_to_deglob.remove(&id);
191 self.imported_idents.remove(&id);
192 self.usages.remove(&id);
193
194 if let Some(src) = self
195 .info
196 .imports
197 .iter()
198 .find(|import| {
199 import.specifiers.iter().any(|specifier| match specifier {
200 ImportSpecifier::Namespace(ns) => ns.local.sym == id.0 && ns.local.ctxt == id.1,
201 _ => false,
202 })
203 })
204 .map(|import| import.src.value.clone())
205 {
206 self.info.forced_ns.insert(src.to_atom_lossy().into_owned());
207 }
208 }
209
210 fn find_require(&mut self, e: &mut Expr) {
211 match e {
212 Expr::Call(e) if e.args.len() == 1 => {
213 let src = match e.args.first().unwrap() {
214 ExprOrSpread { spread: None, expr } => match &**expr {
215 Expr::Lit(Lit::Str(s)) => s,
216 _ => return,
217 },
218 _ => return,
219 };
220
221 match &mut e.callee {
222 Callee::Expr(callee)
223 if self.bundler.config.require && callee.is_ident_ref_to("require") =>
224 {
225 let src_atom = src.value.to_atom_lossy();
226 if self.bundler.is_external(src_atom.as_ref()) {
227 return;
228 }
229 if let Expr::Ident(i) = &mut **callee {
230 self.mark_as_cjs(src_atom.as_ref());
231 if let Some((_, export_ctxt)) = self.ctxt_for(src_atom.as_ref()) {
232 i.ctxt = export_ctxt;
233 }
234 }
235
236 let span = callee.span();
237
238 let decl = ImportDecl {
239 span,
240 specifiers: Vec::new(),
241 src: Box::new(src.clone()),
242 type_only: false,
243 with: None,
244 phase: Default::default(),
245 };
246
247 if self.top_level {
248 self.info.insert(&decl);
249 return;
250 }
251
252 self.info.lazy_imports.push(decl);
253 }
254
255 _ => {}
269 }
270 }
271 _ => {}
272 }
273 }
274
275 fn analyze_usage(&mut self, e: &mut Expr) {
276 if let Expr::Member(e) = e {
277 if let Expr::Ident(obj) = &*e.obj {
278 if !self.imported_idents.contains_key(&obj.to_id()) {
279 return;
281 }
282
283 if e.prop.is_computed() {
284 self.add_forced_ns_for(obj.to_id());
287 return;
288 }
289
290 let import = self.info.imports.iter().find(|import| {
292 for s in &import.specifiers {
293 if let ImportSpecifier::Namespace(n) = s {
294 return obj.sym == n.local.sym
295 && (obj.ctxt == self.module_ctxt || obj.ctxt == n.local.ctxt);
296 }
297 }
298
299 false
300 });
301 let import = match import {
302 Some(v) => v,
303 None => return,
304 };
305
306 let src_atom = import.src.value.to_atom_lossy();
307 let mark = self.ctxt_for(src_atom.as_ref());
308 let exported_ctxt = match mark {
309 None => return,
310 Some(ctxts) => ctxts.1,
311 };
312 let prop = match &e.prop {
313 MemberProp::Ident(i) => {
314 let mut i = Ident::from(i.clone());
315 i.ctxt = exported_ctxt;
316 i
317 }
318 _ => unreachable!(
319 "Non-computed member expression with property other than ident is invalid"
320 ),
321 };
322
323 self.usages
324 .entry(obj.to_id())
325 .or_default()
326 .push(prop.to_id());
327 }
328 }
329 }
330
331 fn try_deglob(&mut self, e: &mut Expr) {
332 let me = match e {
333 Expr::Member(e) => e,
334 _ => return,
335 };
336 if me.prop.is_computed() {
337 return;
338 }
339
340 let obj = match &*me.obj {
341 Expr::Ident(obj) => obj,
342 _ => return,
343 };
344
345 let usages = self.usages.get(&obj.to_id());
346
347 match usages {
348 Some(..) => {}
349 _ => return,
350 };
351
352 let mut prop = match &me.prop {
353 MemberProp::Ident(v) => Ident::from(v.clone()),
354 _ => return,
355 };
356 prop.ctxt = self.imported_idents.get(&obj.to_id()).copied().unwrap();
357
358 *e = prop.into();
359 }
360}
361
362impl<L, R> VisitMut for ImportHandler<'_, '_, L, R>
363where
364 L: Load,
365 R: Resolve,
366{
367 noop_visit_mut_type!(fail);
368
369 fn visit_mut_export_named_specifier(&mut self, s: &mut ExportNamedSpecifier) {
370 let orig = match &s.orig {
371 ModuleExportName::Ident(ident) => ident,
372 ModuleExportName::Str(..) => unimplemented!("module string names unimplemented"),
373 #[cfg(swc_ast_unknown)]
374 _ => panic!("unable to access unknown nodes"),
375 };
376
377 self.add_forced_ns_for(orig.to_id());
378
379 match &mut s.exported {
380 Some(ModuleExportName::Ident(exported)) => {
381 exported.ctxt = self.module_ctxt;
383 }
384 Some(ModuleExportName::Str(..)) => unimplemented!("module string names unimplemented"),
385 #[cfg(swc_ast_unknown)]
386 Some(_) => panic!("unable to access unknown nodes"),
387 None => {
388 let exported = Ident::new(orig.sym.clone(), orig.span, self.module_ctxt);
389 s.exported = Some(ModuleExportName::Ident(exported));
390 }
391 }
392 }
393
394 fn visit_mut_expr(&mut self, e: &mut Expr) {
395 e.visit_mut_children_with(self);
396
397 if !self.deglob_phase {
398 if !self.in_obj_of_member {
405 if let Expr::Ident(i) = &e {
406 if !self.in_obj_of_member {
407 self.add_forced_ns_for(i.to_id());
408 return;
409 }
410 }
411 }
412
413 self.analyze_usage(e);
414 self.find_require(e);
415 } else {
416 self.try_deglob(e);
417 }
418 }
419
420 fn visit_mut_import_decl(&mut self, import: &mut ImportDecl) {
421 let src_atom = import.src.value.to_atom_lossy().into_owned();
422 if self.bundler.is_external(&src_atom) {
424 return;
425 }
426
427 if !self.deglob_phase {
428 if let Some((_, export_ctxt)) = self.ctxt_for(&src_atom) {
429 ExportMetadata {
431 export_ctxt: Some(export_ctxt),
432 ..Default::default()
433 }
434 .encode(&mut import.with);
435
436 for specifier in &mut import.specifiers {
438 match specifier {
439 ImportSpecifier::Named(n) => {
440 self.imported_idents.insert(n.local.to_id(), export_ctxt);
441 match &mut n.imported {
442 Some(ModuleExportName::Ident(imported)) => {
443 imported.ctxt = export_ctxt;
444 }
445 Some(ModuleExportName::Str(..)) => {
446 unimplemented!("module string names unimplemented")
447 }
448 #[cfg(swc_ast_unknown)]
449 Some(_) => panic!("unable to access unknown nodes"),
450 None => {
451 let mut imported: Ident = n.local.clone();
452 imported.ctxt = export_ctxt;
453 n.imported = Some(ModuleExportName::Ident(imported));
454 }
455 }
456 }
457 ImportSpecifier::Default(n) => {
458 self.imported_idents.insert(n.local.to_id(), n.local.ctxt);
459 }
460 ImportSpecifier::Namespace(n) => {
461 self.imported_idents.insert(n.local.to_id(), export_ctxt);
462 }
463 #[cfg(swc_ast_unknown)]
464 _ => panic!("unable to access unknown nodes"),
465 }
466 }
467 }
468
469 self.info.insert(import);
470 return;
471 }
472
473 if self.info.forced_ns.contains(&import.src.value) {
477 return;
478 }
479
480 if import.specifiers.len() == 1 {
482 if let ImportSpecifier::Namespace(ns) = &import.specifiers[0] {
483 let specifiers = self
485 .usages
486 .get(&ns.local.to_id())
487 .cloned()
488 .map(|ids| {
489 let specifiers: Vec<_> = ids
491 .into_iter()
492 .map(|id| {
493 self.idents_to_deglob.insert(id.clone());
494 ImportSpecifier::Named(ImportNamedSpecifier {
495 span: DUMMY_SP,
496 local: Ident::new(id.0, DUMMY_SP, id.1),
497 imported: None,
498 is_type_only: false,
499 })
500 })
501 .collect();
502
503 for import_info in &mut self.info.imports {
504 if import_info.src != import.src {
505 continue;
506 }
507
508 import_info.specifiers.extend(specifiers.clone());
509 }
510
511 specifiers
512 })
513 .unwrap_or_else(Vec::new);
514
515 if !specifiers.is_empty() {
516 import.specifiers = specifiers;
517 return;
518 }
519
520 self.info
522 .forced_ns
523 .insert(import.src.value.clone().to_atom_lossy().into_owned());
524 }
525 }
526 }
527
528 fn visit_mut_member_expr(&mut self, e: &mut MemberExpr) {
529 let old = self.in_obj_of_member;
530 self.in_obj_of_member = true;
531 e.obj.visit_mut_with(self);
532
533 if let MemberProp::Computed(c) = &mut e.prop {
534 self.in_obj_of_member = false;
535 c.visit_mut_with(self);
536 }
537
538 self.in_obj_of_member = old;
539 }
540
541 fn visit_mut_module_items(&mut self, items: &mut Vec<ModuleItem>) {
542 self.top_level = true;
543 items.visit_mut_children_with(self);
544
545 items.retain_mut(|item| match item {
546 ModuleItem::Stmt(Stmt::Empty(..)) => false,
547 ModuleItem::Stmt(Stmt::Decl(Decl::Var(var))) => {
548 var.decls.retain(|d| !matches!(d.name, Pat::Invalid(..)));
549
550 !var.decls.is_empty()
551 }
552
553 _ => true,
554 });
555
556 if self.deglob_phase {
557 let mut wrapping_required = Vec::new();
558 for import in self.info.imports.iter_mut() {
559 let src_atom = import.src.value.to_atom_lossy().into_owned();
560 let use_ns = self.info.forced_ns.contains(&import.src.value)
561 || self.bundler.config.external_modules.contains(&src_atom);
562
563 if use_ns {
564 wrapping_required.push(import.src.value.clone());
565 } else {
566 import
568 .specifiers
569 .retain(|s| !matches!(s, ImportSpecifier::Namespace(_)));
570 }
571 }
572
573 for id in wrapping_required {
574 let id_atom = id.to_atom_lossy();
575 self.mark_as_wrapping_required(id_atom.as_ref());
576 }
577 }
578 }
579
580 fn visit_mut_stmts(&mut self, items: &mut Vec<Stmt>) {
581 self.top_level = false;
582 items.visit_mut_children_with(self)
583 }
584
585 fn visit_mut_super_prop_expr(&mut self, e: &mut SuperPropExpr) {
586 let old = self.in_obj_of_member;
587
588 if let SuperProp::Computed(c) = &mut e.prop {
589 self.in_obj_of_member = false;
590 c.visit_mut_with(self);
591 }
592
593 self.in_obj_of_member = old;
594 }
595
596 fn visit_mut_var_declarator(&mut self, node: &mut VarDeclarator) {
606 node.visit_mut_children_with(self);
607
608 if let Some(init) = &mut node.init {
609 match &mut **init {
610 Expr::Call(CallExpr {
611 span,
612 callee: Callee::Expr(ref mut callee),
613 ref args,
614 ..
615 }) if self.bundler.config.require
616 && callee.is_ident_ref_to("require")
617 && args.len() == 1 =>
618 {
619 let span = *span;
620 let src = match args.first().unwrap() {
621 ExprOrSpread { spread: None, expr } => match &**expr {
622 Expr::Lit(Lit::Str(s)) => s.clone(),
623 _ => return,
624 },
625 _ => return,
626 };
627 let src_atom = src.value.to_atom_lossy();
629 if self
630 .bundler
631 .config
632 .external_modules
633 .contains(src_atom.as_ref())
634 {
635 return;
636 }
637
638 self.mark_as_cjs(src_atom.as_ref());
639
640 if let Expr::Ident(i) = &mut **callee {
641 if let Some((_, export_ctxt)) = self.ctxt_for(src_atom.as_ref()) {
642 i.ctxt = export_ctxt;
643 }
644 }
645
646 let ids: Vec<Ident> = find_pat_ids(&node.name);
647
648 let decl = ImportDecl {
649 span,
650 specifiers: ids
651 .into_iter()
652 .map(|ident| {
653 ImportSpecifier::Named(ImportNamedSpecifier {
654 span,
655 local: ident,
656 imported: None,
657 is_type_only: false,
658 })
659 })
660 .collect(),
661 src: Box::new(src),
662 type_only: false,
663 with: None,
664 phase: Default::default(),
665 };
666
667 self.info.lazy_imports.push(decl);
675 }
676
677 _ => {}
678 }
679 }
680 }
681}