swc_bundler/bundler/chunk/merge.rs
1use std::sync::atomic::Ordering;
2
3use anyhow::Error;
4use indexmap::IndexSet;
5use petgraph::EdgeDirection;
6use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
7use swc_atoms::atom;
8use swc_common::{sync::Lock, FileName, SyntaxContext, DUMMY_SP};
9use swc_ecma_ast::*;
10use swc_ecma_transforms_base::helpers::Helpers;
11use swc_ecma_utils::{find_pat_ids, prepend_stmt, private_ident, quote_ident, ExprFactory};
12use swc_ecma_visit::{VisitMut, VisitMutWith};
13use EdgeDirection::Outgoing;
14
15use crate::{
16 bundler::{keywords::KeywordRenamer, load::TransformedModule},
17 dep_graph::ModuleGraph,
18 id::{Id, ModuleId},
19 inline::inline,
20 load::Load,
21 modules::Modules,
22 resolve::Resolve,
23 util::{CloneMap, ExportMetadata, ExprExt, VarDeclaratorExt},
24 Bundler, Hook, ModuleRecord,
25};
26
27pub(super) struct Ctx {
28 /// Full dependency graph.
29 pub graph: ModuleGraph,
30 pub cycles: Vec<Vec<ModuleId>>,
31 pub transitive_remap: CloneMap<SyntaxContext, SyntaxContext>,
32 pub export_stars_in_wrapped: Lock<FxHashMap<ModuleId, Vec<SyntaxContext>>>,
33}
34
35impl Ctx {
36 pub fn is_exported_ctxt(
37 &self,
38 ctxt_to_check: SyntaxContext,
39 entry_export_ctxt: SyntaxContext,
40 ) -> bool {
41 if ctxt_to_check == entry_export_ctxt {
42 return true;
43 }
44
45 if let Some(v) = self.transitive_remap.get(&ctxt_to_check) {
46 if v == ctxt_to_check {
47 return false;
48 }
49
50 return self.is_exported_ctxt(v, entry_export_ctxt);
51 }
52
53 false
54 }
55}
56
57impl<L, R> Bundler<'_, L, R>
58where
59 L: Load,
60 R: Resolve,
61{
62 pub(super) fn get_for_merging(
63 &self,
64 ctx: &Ctx,
65 id: ModuleId,
66 is_entry: bool,
67 ) -> Result<Modules, Error> {
68 self.run(|| {
69 let info = self
70 .scope
71 .get_module(id)
72 .unwrap_or_else(|| unreachable!("Module {} is not registered", id));
73 let mut module = self.apply_hooks(id, is_entry)?;
74 module = self.prepare_for_merging(ctx, &info, module)?;
75
76 if !is_entry {
77 module = self.wrap_cjs_module(ctx, &info, module)?;
78 }
79 self.replace_cjs_require_calls(&info, &mut module, is_entry);
80
81 Ok(module)
82 })
83 }
84
85 /// This method sort modules.
86 pub(super) fn merge_into_entry(
87 &self,
88 ctx: &Ctx,
89 entry_id: ModuleId,
90 entry: &mut Modules,
91 all: &mut FxHashMap<ModuleId, Modules>,
92 ) {
93 self.run(|| {
94 let injected_ctxt = self.injected_ctxt;
95
96 let entry_info = self.scope.get_module(entry_id).unwrap();
97
98 let all_deps_of_entry =
99 self.collect_all_deps(&ctx.graph, entry_id, &mut Default::default());
100
101 tracing::debug!("Merging dependencies: {:?}", all_deps_of_entry);
102
103 let deps = all_deps_of_entry.iter().map(|id| {
104 let dep_info = self.scope.get_module(*id).unwrap();
105 entry_info.helpers.extend(&dep_info.helpers);
106
107 {
108 let helpers = *entry_info.swc_helpers.lock();
109 let dep_helpers = *dep_info.swc_helpers.lock();
110
111 let helpers = Helpers::from_data(helpers);
112 let dep_helpers = Helpers::from_data(dep_helpers);
113
114 helpers.extend_from(&dep_helpers);
115
116 *entry_info.swc_helpers.lock() = helpers.data();
117 }
118
119 if *id == entry_id {
120 return Modules::empty(injected_ctxt);
121 }
122
123 all.remove(id).unwrap_or_else(|| {
124 unreachable!(
125 "failed to merge into {}: module {} does not exist in the map",
126 entry_id, id
127 )
128 })
129 });
130
131 for dep in deps {
132 entry.add_dep(dep);
133 }
134
135 self.replace_import_specifiers(&entry_info, entry);
136 self.finalize_merging_of_entry(ctx, entry_id, entry);
137 self.remove_wrong_exports(ctx, &entry_info, entry);
138 })
139 }
140
141 #[allow(clippy::only_used_in_recursion)]
142 fn collect_all_deps(
143 &self,
144 graph: &ModuleGraph,
145 start: ModuleId,
146 dejavu: &mut FxHashSet<ModuleId>,
147 ) -> IndexSet<ModuleId, FxBuildHasher> {
148 let mut set = IndexSet::default();
149
150 for dep in graph.neighbors_directed(start, Outgoing) {
151 if !dejavu.insert(dep) {
152 continue;
153 }
154 set.insert(dep);
155 set.extend(self.collect_all_deps(graph, dep, dejavu));
156 }
157
158 set
159 }
160
161 pub(crate) fn apply_hooks(
162 &self,
163 module_id: ModuleId,
164 is_entry: bool,
165 ) -> Result<Modules, Error> {
166 self.run(|| {
167 let info = self.scope.get_module(module_id).unwrap();
168
169 let mut entry: Module = (*info.module).clone();
170 entry.visit_mut_with(&mut ImportMetaHandler {
171 file: &info.fm.name,
172 hook: &self.hook,
173 is_entry,
174 inline_ident: private_ident!("importMeta"),
175 occurred: false,
176 err: None,
177 });
178
179 let module = Modules::from(module_id, entry, self.injected_ctxt);
180
181 Ok(module)
182 })
183 }
184
185 /// This should only be called after everything is merged.
186 ///
187 /// This method does not care about orders of statement, and it's expected
188 /// to be called before `sort`.
189 fn inject_reexports(&self, ctx: &Ctx, _entry_id: ModuleId, entry: &mut Modules) {
190 // dbg!(&ctx.transitive_remap);
191 let injected_ctxt = self.injected_ctxt;
192
193 {
194 // Handle `export *` for non-wrapped modules.
195
196 let mut vars = Vec::new();
197 /// We recurse if `export *` is nested.
198 fn add_var(
199 injected_ctxt: SyntaxContext,
200 vars: &mut Vec<(ModuleId, ModuleItem)>,
201 declared: &mut FxHashSet<Id>,
202 map: &CloneMap<SyntaxContext, SyntaxContext>,
203 module_id: ModuleId,
204 id: Id,
205 ) {
206 let remapped = match map.get(&id.ctxt()) {
207 Some(v) => v,
208 _ => return,
209 };
210 if remapped == id.ctxt() {
211 return;
212 }
213 let reexported = id.clone().with_ctxt(remapped);
214
215 add_var(
216 injected_ctxt,
217 vars,
218 declared,
219 map,
220 module_id,
221 reexported.clone(),
222 );
223
224 if !declared.insert(reexported.clone()) {
225 return;
226 }
227
228 vars.push((
229 module_id,
230 id.assign_to(reexported)
231 .into_module_item(injected_ctxt, "export_star_replacer"),
232 ));
233 }
234
235 // We have to exclude some ids because there are already declared.
236 // See https://github.com/denoland/deno/issues/8725
237 //
238 // Let's say D is a dependency which contains export * from './foo';
239 // If an user import and export from D, the transitive syntax context map
240 // contains a entry from D to foo because it's reexported and
241 // the variable (reexported from D) exist because it's imported.
242 let mut declared_ids = FxHashSet::<_>::default();
243
244 for (_, stmt) in entry.iter() {
245 if let ModuleItem::Stmt(Stmt::Decl(Decl::Var(decl))) = stmt {
246 if decl.ctxt == injected_ctxt {
247 let ids: Vec<Id> = find_pat_ids(decl);
248 declared_ids.extend(ids);
249 }
250 }
251 }
252
253 for (module_id, stmt) in entry.iter() {
254 match stmt {
255 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(export)) => {
256 for s in &export.specifiers {
257 match s {
258 ExportSpecifier::Namespace(_) => {}
259 ExportSpecifier::Default(_) => {}
260 ExportSpecifier::Named(named) => {
261 if let Some(exported) = &named.exported {
262 let exported = match exported {
263 ModuleExportName::Ident(ident) => ident,
264 ModuleExportName::Str(..) => {
265 unimplemented!("module string names unimplemented")
266 }
267 #[cfg(swc_ast_unknown)]
268 _ => panic!("unable to access unknown nodes"),
269 };
270
271 let id: Id = exported.into();
272 if declared_ids.contains(&id) {
273 continue;
274 }
275
276 match &named.orig {
277 ModuleExportName::Ident(orig) => {
278 vars.push((
279 module_id,
280 orig.clone()
281 .assign_to(exported.clone())
282 .into_module_item(
283 injected_ctxt,
284 "finalize -> export to var",
285 ),
286 ));
287 }
288 ModuleExportName::Str(..) => {
289 unimplemented!("module string names unimplemented")
290 }
291 #[cfg(swc_ast_unknown)]
292 _ => panic!("unable to access unknown nodes"),
293 }
294 }
295 }
296 #[cfg(swc_ast_unknown)]
297 _ => panic!("unable to access unknown nodes"),
298 }
299 }
300 }
301
302 ModuleItem::Stmt(Stmt::Decl(Decl::Var(decl))) => {
303 let ids: Vec<Id> = find_pat_ids(decl);
304
305 for id in ids {
306 if *id.sym() == "default" {
307 continue;
308 }
309
310 add_var(
311 injected_ctxt,
312 &mut vars,
313 &mut declared_ids,
314 &ctx.transitive_remap,
315 module_id,
316 id,
317 );
318 }
319 }
320
321 _ => {}
322 }
323 }
324
325 entry.append_all(vars);
326 }
327
328 {
329 let mut map = ctx.export_stars_in_wrapped.lock();
330 let mut additional_props = FxHashMap::<_, Vec<_>>::default();
331 // Handle `export *` for wrapped modules.
332 for (module_id, ctxts) in map.drain() {
333 for (_, stmt) in entry.iter() {
334 if let ModuleItem::Stmt(Stmt::Decl(Decl::Var(decl))) = stmt {
335 let ids: Vec<Id> = find_pat_ids(decl);
336
337 for id in ids {
338 if *id.sym() == "default" {
339 continue;
340 }
341
342 if ctxts.contains(&id.ctxt()) {
343 additional_props.entry(module_id).or_default().push(
344 PropOrSpread::Prop(Box::new(Prop::Shorthand(id.into_ident()))),
345 );
346 }
347 }
348 }
349 }
350 }
351
352 for (module_id, props) in additional_props {
353 let id = match self.scope.wrapped_esm_id(module_id) {
354 Some(v) => v,
355 None => continue,
356 };
357
358 for (_, stmt) in entry.iter_mut() {
359 let var = match stmt {
360 ModuleItem::Stmt(Stmt::Decl(Decl::Var(var)))
361 if matches!(
362 &**var,
363 VarDecl {
364 kind: VarDeclKind::Const,
365 ..
366 }
367 ) =>
368 {
369 var
370 }
371 _ => continue,
372 };
373
374 if var.decls.len() != 1 {
375 continue;
376 }
377
378 let var_decl = &mut var.decls[0];
379 match &var_decl.name {
380 Pat::Ident(i) if id == i.id => {}
381 _ => continue,
382 }
383
384 let callee = match &mut var_decl.init {
385 Some(init) => match &mut **init {
386 Expr::Call(CallExpr { callee, .. }) => match callee {
387 Callee::Super(_) | Callee::Import(_) => continue,
388 Callee::Expr(v) => v,
389 #[cfg(swc_ast_unknown)]
390 _ => panic!("unable to access unknown nodes"),
391 },
392 Expr::Await(AwaitExpr { arg, .. }) => match &mut **arg {
393 Expr::Call(CallExpr { callee, .. }) => match callee {
394 Callee::Super(_) | Callee::Import(_) => continue,
395 Callee::Expr(v) => v,
396 #[cfg(swc_ast_unknown)]
397 _ => panic!("unable to access unknown nodes"),
398 },
399 _ => continue,
400 },
401 _ => continue,
402 },
403 None => continue,
404 };
405
406 let f = match &mut **callee {
407 Expr::Fn(f) => f,
408 _ => continue,
409 };
410
411 let body = match &mut f.function.body {
412 Some(body) => body,
413 None => continue,
414 };
415
416 let last_stmt = body.stmts.last_mut();
417
418 let return_stmt = match last_stmt {
419 Some(Stmt::Return(s)) => s,
420 _ => continue,
421 };
422
423 let ret_val = match &mut return_stmt.arg {
424 Some(arg) => arg,
425 None => continue,
426 };
427
428 let obj = match &mut **ret_val {
429 Expr::Object(obj) => obj,
430 _ => continue,
431 };
432
433 obj.props.extend(props);
434 break;
435 }
436 }
437 }
438 }
439
440 fn finalize_merging_of_entry(&self, ctx: &Ctx, id: ModuleId, entry: &mut Modules) {
441 tracing::trace!("All modules are merged");
442
443 tracing::debug!("Injecting reexports");
444 self.inject_reexports(ctx, id, entry);
445
446 // entry.print(&self.cm, "before inline");
447
448 tracing::debug!("Inlining injected variables");
449
450 inline(self.injected_ctxt, entry);
451
452 entry.sort(id, &ctx.graph, &ctx.cycles, &self.cm);
453
454 // crate::debug::print_hygiene("done", &self.cm, &entry.clone().into());
455
456 entry.retain_mut(|_, item| {
457 match item {
458 ModuleItem::ModuleDecl(ModuleDecl::ExportAll(export)) => {
459 let src_atom = export.src.value.to_atom_lossy().into_owned();
460 if self.config.external_modules.contains(&src_atom) {
461 return true;
462 }
463
464 return false;
465 }
466
467 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(export)) => {
468 if let Some(src) = &export.src {
469 let src_atom = src.value.to_atom_lossy().into_owned();
470 if self.config.external_modules.contains(&src_atom) {
471 return true;
472 }
473 }
474
475 export
476 .specifiers
477 .retain(|s| !matches!(s, ExportSpecifier::Namespace(_)));
478
479 export.src = None;
480 }
481
482 ModuleItem::ModuleDecl(ModuleDecl::Import(import)) => {
483 let src_atom = import.src.value.to_atom_lossy().into_owned();
484 if self.config.external_modules.contains(&src_atom) {
485 return true;
486 }
487
488 // Drop import statements.
489 return false;
490 }
491
492 _ => {}
493 }
494
495 true
496 });
497
498 tracing::debug!("Renaming keywords");
499
500 entry.visit_mut_with(&mut KeywordRenamer::default());
501
502 // print_hygiene(
503 // "done-clean",
504 // &self.cm,
505 // &entry
506 // .clone()
507 // .fold_with(&mut hygiene())
508 // .fold_with(&mut fixer(None)),
509 // );
510 }
511
512 /// Remove exports with wrong syntax context
513 fn remove_wrong_exports(&self, ctx: &Ctx, info: &TransformedModule, module: &mut Modules) {
514 tracing::debug!("Removing wrong exports");
515
516 let item_count = module.iter().count();
517 tracing::trace!("Item count = {}", item_count);
518
519 module.retain_mut(|_, item| {
520 if let ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(NamedExport {
521 specifiers, ..
522 })) = item
523 {
524 specifiers.retain(|s| match s {
525 ExportSpecifier::Named(ExportNamedSpecifier {
526 exported: Some(exported),
527 ..
528 }) => {
529 let exported = match exported {
530 ModuleExportName::Ident(ident) => ident,
531 ModuleExportName::Str(..) => {
532 unimplemented!("module string names unimplemented")
533 }
534 #[cfg(swc_ast_unknown)]
535 _ => panic!("unable to access unknown nodes"),
536 };
537 // Default is not exported via `export *`
538 if &*exported.sym == "default" {
539 exported.ctxt == info.export_ctxt()
540 } else {
541 ctx.is_exported_ctxt(exported.ctxt, info.export_ctxt())
542 }
543 }
544 _ => true,
545 });
546
547 if specifiers.is_empty() {
548 return false;
549 }
550 }
551
552 true
553 });
554
555 tracing::debug!("Removed wrong exports");
556 }
557
558 /// This method handles imports and exports.
559 ///
560 ///
561 /// Basically one module have two top-level contexts. One is for it's codes
562 /// and another is for exporting. This method connects two module by
563 /// injecting `const local_A = exported_B_from_foo;`
564 ///
565 ///
566 /// We convert all exports to variable at here.
567 pub(super) fn prepare_for_merging(
568 &self,
569 ctx: &Ctx,
570 info: &TransformedModule,
571 mut module: Modules,
572 ) -> Result<Modules, Error> {
573 self.handle_imports_and_exports(ctx, info, &mut module);
574
575 let wrapped = self.scope.should_be_wrapped_with_a_fn(info.id);
576 if wrapped {
577 module = self.wrap_esm(ctx, info.id, module)?;
578 }
579
580 Ok(module)
581 }
582
583 fn handle_imports_and_exports(
584 &self,
585 ctx: &Ctx,
586 info: &TransformedModule,
587 module: &mut Modules,
588 ) {
589 let injected_ctxt = self.injected_ctxt;
590
591 if !info.is_es6 {
592 return;
593 }
594
595 let mut extra = Vec::new();
596
597 module.map_any_items(|_, items| {
598 let mut new = Vec::with_capacity(items.len() * 11 / 10);
599
600 for item in items {
601 match item {
602 ModuleItem::ModuleDecl(ModuleDecl::Import(mut import)) => {
603 let src_atom = import.src.value.to_atom_lossy().into_owned();
604 // Preserve imports from node.js builtin modules.
605 if self.config.external_modules.contains(&src_atom) {
606 new.push(import.into());
607 continue;
608 }
609
610 if let Some((src, _)) = info
611 .imports
612 .specifiers
613 .iter()
614 .find(|s| s.0.src.value == import.src.value)
615 {
616 if !self.scope.get_module(src.module_id).unwrap().is_es6 {
617 new.push(import.into());
618 continue;
619 }
620 }
621
622 // Imports are easy to handle.
623 for s in &import.specifiers {
624 match s {
625 ImportSpecifier::Named(s) => {
626 if let Some(imported) = &s.imported {
627 let imported = match imported {
628 ModuleExportName::Ident(ident) => ident,
629 ModuleExportName::Str(..) => {
630 unimplemented!("module string names unimplemented")
631 }
632 #[cfg(swc_ast_unknown)]
633 _ => panic!("unable to access unknown nodes"),
634 };
635 new.push(
636 imported
637 .clone()
638 .assign_to(s.local.clone())
639 .into_module_item(
640 injected_ctxt,
641 "prepare -> named import -> aliased",
642 ),
643 );
644 }
645 }
646 ImportSpecifier::Default(s) => {
647 new.push(
648 Ident::new(
649 atom!("default"),
650 import.span,
651 ExportMetadata::decode(import.with.as_deref())
652 .export_ctxt
653 .unwrap(),
654 )
655 .assign_to(s.local.clone())
656 .into_module_item(
657 injected_ctxt,
658 "prepare -> default import",
659 ),
660 );
661 }
662 ImportSpecifier::Namespace(s) => {
663 if let Some((src, _)) = info
664 .imports
665 .specifiers
666 .iter()
667 .find(|s| s.0.src.value == import.src.value)
668 {
669 let esm_id =
670 self.scope.wrapped_esm_id(src.module_id).expect(
671 "If a namespace import specifier is preserved, it \
672 means failure of deblobbing and as a result \
673 module should be marked as wrapped esm",
674 );
675 new.push(
676 esm_id
677 .clone()
678 .assign_to(s.local.clone())
679 .into_module_item(
680 injected_ctxt,
681 "prepare -> import -> namespace",
682 ),
683 );
684 }
685 }
686 #[cfg(swc_ast_unknown)]
687 _ => panic!("unable to access unknown nodes"),
688 }
689 }
690
691 import.specifiers.clear();
692 new.push(import.into());
693 }
694 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(export)) => {
695 // At here, we create multiple items.
696 //
697 // One item is `const local_default = expr` and another one is
698 // `export { local_default as default }`.
699 //
700 // To allow using identifier of the declaration in the original module, we
701 // create `const local_default = orig_ident` if original identifier exists.
702
703 let local = Ident::new(atom!("default"), DUMMY_SP, info.local_ctxt());
704
705 match export.decl {
706 DefaultDecl::Class(c) => {
707 //
708 match c.ident {
709 Some(ident) => {
710 new.push(
711 ClassDecl {
712 ident: ident.clone(),
713 class: c.class,
714 declare: false,
715 }
716 .into(),
717 );
718
719 new.push(ident.assign_to(local.clone()).into_module_item(
720 injected_ctxt,
721 "prepare -> export default decl -> class -> with ident",
722 ))
723 }
724 None => {
725 let init = c;
726 new.push(init.assign_to(local.clone()).into_module_item(
727 injected_ctxt,
728 "prepare -> export default decl -> class -> without \
729 ident",
730 ));
731 }
732 }
733 }
734 DefaultDecl::Fn(f) => {
735 //
736 match f.ident {
737 Some(ident) => {
738 new.push(
739 FnDecl {
740 ident: ident.clone(),
741 function: f.function,
742 declare: false,
743 }
744 .into(),
745 );
746
747 new.push(ident.assign_to(local.clone()).into_module_item(
748 injected_ctxt,
749 "prepare -> export default decl -> function -> with \
750 ident",
751 ))
752 }
753 None => {
754 // We should inject a function declaration because of
755 // dependencies.
756 //
757 // See: https://github.com/denoland/deno/issues/9346
758 let ident = private_ident!("default");
759 new.push(
760 FnDecl {
761 ident: ident.clone(),
762 function: f.function,
763 declare: false,
764 }
765 .into(),
766 );
767
768 new.push(ident.assign_to(local.clone()).into_module_item(
769 injected_ctxt,
770 "prepare -> export default decl -> function -> \
771 without ident",
772 ));
773 }
774 }
775 }
776 DefaultDecl::TsInterfaceDecl(_) => continue,
777 #[cfg(swc_ast_unknown)]
778 _ => panic!("unable to access unknown nodes"),
779 }
780
781 // Create `export { local_default as default }`
782 tracing::trace!(
783 "Exporting `default` with `export default decl` ({})",
784 local.sym
785 );
786
787 let exported = Ident::new(atom!("default"), DUMMY_SP, info.export_ctxt());
788
789 new.push(
790 local
791 .clone()
792 .assign_to(exported.clone())
793 .into_module_item(injected_ctxt, "prepare -> export default decl"),
794 );
795
796 let specifier = ExportSpecifier::Named(ExportNamedSpecifier {
797 span: DUMMY_SP,
798 orig: ModuleExportName::Ident(local),
799 exported: Some(ModuleExportName::Ident(exported)),
800 is_type_only: false,
801 });
802 extra.push(
803 NamedExport {
804 span: export.span,
805 specifiers: vec![specifier],
806 src: None,
807 type_only: false,
808 with: Some(
809 ExportMetadata {
810 injected: true,
811 ..Default::default()
812 }
813 .into_with(),
814 ),
815 }
816 .into(),
817 );
818 }
819
820 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(export)) => {
821 // At here, we create two items.
822 //
823 // One item is `const local_default = expr` and the
824 // other is `export { local_default as default }`.
825
826 // TODO: Check if we really need this.
827
828 let local = Ident::new(atom!("default"), DUMMY_SP, info.local_ctxt());
829
830 // Create `const local_default = expr`
831 new.push(
832 export
833 .expr
834 .assign_to(local.clone())
835 .into_module_item(injected_ctxt, "prepare -> export default expr"),
836 );
837
838 let exported = Ident::new(atom!("default"), DUMMY_SP, info.export_ctxt());
839
840 new.push(
841 local
842 .clone()
843 .assign_to(exported.clone())
844 .into_module_item(injected_ctxt, "prepare -> export default expr"),
845 );
846
847 // Create `export { local_default as default }`
848 let specifier = ExportSpecifier::Named(ExportNamedSpecifier {
849 span: DUMMY_SP,
850 orig: ModuleExportName::Ident(local),
851 exported: Some(ModuleExportName::Ident(exported)),
852 is_type_only: false,
853 });
854 tracing::trace!("Exporting `default` with `export default expr`");
855 extra.push(
856 NamedExport {
857 span: export.span,
858 specifiers: vec![specifier],
859 src: None,
860 type_only: false,
861 with: Some(
862 ExportMetadata {
863 injected: true,
864 ..Default::default()
865 }
866 .into_with(),
867 ),
868 }
869 .into(),
870 );
871 }
872
873 ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export)) => {
874 // Idea is almost same as above. But we uses symbol of the declaration
875 // instead of using `default`.
876
877 let local = match export.decl {
878 Decl::Class(c) => {
879 let i = c.ident.clone();
880 new.push(c.into());
881
882 i
883 }
884 Decl::Fn(f) => {
885 let i = f.ident.clone();
886 new.push(f.into());
887
888 i
889 }
890 Decl::Var(v) => {
891 let ids: Vec<Ident> = find_pat_ids(&v);
892 //
893
894 new.push(v.into());
895
896 let export = NamedExport {
897 span: export.span,
898 specifiers: ids
899 .into_iter()
900 .map(|id| {
901 let exported = Ident::new(
902 id.sym.clone(),
903 id.span,
904 info.export_ctxt(),
905 );
906
907 tracing::trace!(
908 "Exporting `{}{:?}` with `export decl`",
909 id.sym,
910 id.ctxt
911 );
912
913 new.push(
914 id.clone()
915 .assign_to(exported.clone())
916 .into_module_item(
917 injected_ctxt,
918 "prepare -> export decl -> var",
919 ),
920 );
921
922 ExportNamedSpecifier {
923 span: DUMMY_SP,
924 orig: ModuleExportName::Ident(id),
925 exported: Some(ModuleExportName::Ident(exported)),
926 is_type_only: false,
927 }
928 })
929 .map(ExportSpecifier::Named)
930 .collect(),
931 src: None,
932 type_only: false,
933 with: Some(
934 ExportMetadata {
935 injected: true,
936 ..Default::default()
937 }
938 .into_with(),
939 ),
940 }
941 .into();
942 extra.push(export);
943 continue;
944 }
945
946 Decl::TsInterface(_)
947 | Decl::TsTypeAlias(_)
948 | Decl::TsEnum(_)
949 | Decl::TsModule(_)
950 | Decl::Using(..) => continue,
951 #[cfg(swc_ast_unknown)]
952 _ => panic!("unable to access unknown nodes"),
953 };
954
955 tracing::trace!(
956 "Exporting `default` with `export default decl` ({})",
957 local.sym
958 );
959
960 // Create `export { local_ident as exported_ident }`
961 let exported =
962 Ident::new(local.sym.clone(), local.span, info.export_ctxt());
963
964 new.push(
965 local
966 .clone()
967 .assign_to(exported.clone())
968 .into_module_item(injected_ctxt, "prepare -> export decl -> var"),
969 );
970
971 let specifier = ExportSpecifier::Named(ExportNamedSpecifier {
972 span: DUMMY_SP,
973 orig: ModuleExportName::Ident(local),
974 exported: Some(ModuleExportName::Ident(exported)),
975 is_type_only: false,
976 });
977
978 extra.push(
979 NamedExport {
980 span: export.span,
981 specifiers: vec![specifier],
982 src: None,
983 type_only: false,
984 with: Some(
985 ExportMetadata {
986 injected: true,
987 ..Default::default()
988 }
989 .into_with(),
990 ),
991 }
992 .into(),
993 );
994 }
995
996 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(NamedExport {
997 ref specifiers,
998 ref src,
999 ..
1000 })) => {
1001 if let Some(export_src) = src {
1002 if let Some((src, _)) = info
1003 .exports
1004 .reexports
1005 .iter()
1006 .find(|s| s.0.src.value == export_src.value)
1007 {
1008 let dep = self.scope.get_module(src.module_id).unwrap();
1009 if !dep.is_es6 {
1010 dep.helpers.require.store(true, Ordering::SeqCst);
1011
1012 let mut vars = Vec::new();
1013 let mod_var = private_ident!("_cjs_module_");
1014
1015 vars.push(VarDeclarator {
1016 span: DUMMY_SP,
1017 name: mod_var.clone().into(),
1018 init: Some(
1019 CallExpr {
1020 span: DUMMY_SP,
1021 callee: Ident::new(
1022 atom!("load"),
1023 DUMMY_SP,
1024 dep.export_ctxt(),
1025 )
1026 .as_callee(),
1027 ..Default::default()
1028 }
1029 .into(),
1030 ),
1031 definite: Default::default(),
1032 });
1033 for s in specifiers {
1034 match s {
1035 ExportSpecifier::Namespace(s) => {
1036 match &s.name {
1037 ModuleExportName::Ident(name) => {
1038 vars.push(VarDeclarator {
1039 span: s.span,
1040 name: name.clone().into(),
1041 init: Some(mod_var.clone().into()),
1042 definite: Default::default(),
1043 });
1044 }
1045 ModuleExportName::Str(..) => {
1046 unimplemented!(
1047 "module string names unimplemented"
1048 )
1049 }
1050 #[cfg(swc_ast_unknown)]
1051 _ => panic!("unable to access unknown nodes"),
1052 };
1053 }
1054 ExportSpecifier::Default(s) => {
1055 vars.push(VarDeclarator {
1056 span: DUMMY_SP,
1057 name: s.exported.clone().into(),
1058 init: Some(
1059 mod_var
1060 .clone()
1061 .make_member(quote_ident!("default"))
1062 .into(),
1063 ),
1064 definite: Default::default(),
1065 });
1066 }
1067 ExportSpecifier::Named(s) => {
1068 let exp = s.exported.clone();
1069 let exported = match exp {
1070 Some(ModuleExportName::Ident(ident)) => {
1071 Some(ident)
1072 }
1073 Some(ModuleExportName::Str(..)) => {
1074 unimplemented!(
1075 "module string names unimplemented"
1076 )
1077 }
1078 _ => None,
1079 };
1080 let orig = match &s.orig {
1081 ModuleExportName::Ident(ident) => ident,
1082 _ => unimplemented!(
1083 "module string names unimplemented"
1084 ),
1085 };
1086 vars.push(VarDeclarator {
1087 span: s.span,
1088 name: exported.clone().unwrap().into(),
1089 init: Some(
1090 mod_var
1091 .clone()
1092 .make_member(orig.clone().into())
1093 .into(),
1094 ),
1095 definite: Default::default(),
1096 });
1097 }
1098 #[cfg(swc_ast_unknown)]
1099 _ => panic!("unable to access unknown nodes"),
1100 }
1101 }
1102
1103 if !vars.is_empty() {
1104 new.push(
1105 VarDecl {
1106 span: DUMMY_SP,
1107 kind: VarDeclKind::Const,
1108 declare: Default::default(),
1109 decls: vars,
1110 ..Default::default()
1111 }
1112 .into(),
1113 );
1114 }
1115 continue;
1116 }
1117 }
1118 }
1119
1120 for s in specifiers {
1121 match s {
1122 ExportSpecifier::Named(ExportNamedSpecifier {
1123 orig,
1124 exported: Some(exported),
1125 ..
1126 }) => {
1127 let exported_ident = match exported {
1128 ModuleExportName::Ident(ident) => ident,
1129 ModuleExportName::Str(..) => {
1130 unimplemented!("module string names unimplemented")
1131 }
1132 #[cfg(swc_ast_unknown)]
1133 _ => panic!("unable to access unknown nodes"),
1134 };
1135 let orig_ident = match orig {
1136 ModuleExportName::Ident(ident) => ident,
1137 _ => unimplemented!("module string names unimplemented"),
1138 };
1139 new.push(
1140 orig_ident
1141 .clone()
1142 .assign_to(exported_ident.clone())
1143 .into_module_item(
1144 injected_ctxt,
1145 "prepare -> export named -> aliased",
1146 ),
1147 );
1148 }
1149
1150 ExportSpecifier::Default(ExportDefaultSpecifier {
1151 exported,
1152 ..
1153 }) => {
1154 new.push(
1155 Ident::new(atom!("default"), DUMMY_SP, info.local_ctxt())
1156 .clone()
1157 .assign_to(exported.clone())
1158 .into_module_item(
1159 injected_ctxt,
1160 "prepare -> export named -> aliased",
1161 ),
1162 );
1163 }
1164
1165 ExportSpecifier::Namespace(ns) => {
1166 if let Some((src, _)) = info
1167 .exports
1168 .reexports
1169 .iter()
1170 .find(|s| s.0.src.value == src.as_ref().unwrap().value)
1171 {
1172 match &ns.name {
1173 ModuleExportName::Ident(name) => {
1174 if !self
1175 .scope
1176 .get_module(src.module_id)
1177 .unwrap()
1178 .is_es6
1179 {
1180 continue;
1181 }
1182
1183 let wrapped_esm_id =
1184 self.scope.wrapped_esm_id(src.module_id);
1185 match wrapped_esm_id {
1186 Some(module_var) => {
1187 // Create variable for the namespaced
1188 // export.
1189 extra.push(
1190 module_var
1191 .clone()
1192 .assign_to(name.clone())
1193 .into_module_item(
1194 injected_ctxt,
1195 "prepare -> namespaced \
1196 reexport",
1197 ),
1198 );
1199 let specifier = ExportSpecifier::Named(
1200 ExportNamedSpecifier {
1201 span: ns.span,
1202 orig: ModuleExportName::Ident(
1203 module_var.into(),
1204 ),
1205 exported: Some(ns.name.clone()),
1206 is_type_only: false,
1207 },
1208 );
1209 extra.push(
1210 NamedExport {
1211 span: ns.span,
1212 specifiers: vec![specifier],
1213 src: None,
1214 with: None,
1215 type_only: false,
1216 }
1217 .into(),
1218 );
1219 }
1220 None => {
1221 unreachable!(
1222 "Modules reexported with `export * as \
1223 foo from './foo'` should be marked \
1224 as a wrapped esm"
1225 )
1226 }
1227 }
1228
1229 // Remove `export * as foo from ''`
1230 continue;
1231 }
1232 ModuleExportName::Str(..) => {
1233 unimplemented!("module string names unimplemented")
1234 }
1235 #[cfg(swc_ast_unknown)]
1236 _ => panic!("unable to access unknown nodes"),
1237 }
1238 }
1239 }
1240 _ => {}
1241 }
1242 }
1243
1244 new.push(item);
1245 }
1246
1247 ModuleItem::ModuleDecl(ModuleDecl::ExportAll(ref export)) => {
1248 let metadata = ExportMetadata::decode(export.with.as_deref());
1249
1250 if let Some(export_ctxt) = metadata.export_ctxt {
1251 let reexport = self.scope.get_module(info.id).unwrap().export_ctxt();
1252 ctx.transitive_remap.insert(export_ctxt, reexport);
1253 }
1254
1255 new.push(item);
1256 }
1257
1258 _ => {
1259 new.push(item);
1260 }
1261 }
1262 }
1263
1264 new
1265 });
1266
1267 for item in extra {
1268 module.append(info.id, item);
1269 }
1270
1271 // module.print(&self.cm, "prepare");
1272 }
1273
1274 pub(super) fn replace_import_specifiers(&self, info: &TransformedModule, module: &mut Modules) {
1275 let injected_ctxt = self.injected_ctxt;
1276
1277 let mut vars = Vec::new();
1278 module.map_any_items(|module_id, stmts| {
1279 let mut new = Vec::with_capacity(stmts.len() + 32);
1280
1281 for stmt in stmts {
1282 if let ModuleItem::ModuleDecl(ModuleDecl::Import(import)) = &stmt {
1283 let src_atom = import.src.value.to_atom_lossy().into_owned();
1284 if self.config.external_modules.contains(&src_atom) {
1285 new.push(stmt);
1286 continue;
1287 }
1288
1289 for specifier in &import.specifiers {
1290 match specifier {
1291 ImportSpecifier::Named(named) => {
1292 if let Some(imported) = &named.imported {
1293 let imporeted_ident = match imported {
1294 ModuleExportName::Ident(ident) => ident,
1295 ModuleExportName::Str(..) => {
1296 unimplemented!("module string names unimplemented")
1297 }
1298 #[cfg(swc_ast_unknown)]
1299 _ => panic!("unable to access unknown nodes"),
1300 };
1301 vars.push((
1302 module_id,
1303 imporeted_ident
1304 .clone()
1305 .assign_to(named.local.clone())
1306 .into_module_item(
1307 injected_ctxt,
1308 "from_replace_import_specifiers",
1309 ),
1310 ));
1311 continue;
1312 }
1313 }
1314 ImportSpecifier::Default(default) => {
1315 if let Some((src, _)) = info
1316 .imports
1317 .specifiers
1318 .iter()
1319 .find(|s| s.0.src.value == import.src.value)
1320 {
1321 let imported =
1322 Ident::new(atom!("default"), DUMMY_SP, src.export_ctxt);
1323 vars.push((
1324 module_id,
1325 imported.assign_to(default.local.clone()).into_module_item(
1326 injected_ctxt,
1327 "from_replace_import_specifiers",
1328 ),
1329 ));
1330 continue;
1331 }
1332 }
1333 ImportSpecifier::Namespace(s) => {
1334 if let Some((src, _)) = info
1335 .imports
1336 .specifiers
1337 .iter()
1338 .find(|s| s.0.src.value == import.src.value)
1339 {
1340 let esm_id = self.scope.wrapped_esm_id(src.module_id).expect(
1341 "If a namespace import specifier is preserved, it means \
1342 failure of deblobbing and as a result module should be \
1343 marked as wrapped esm",
1344 );
1345 vars.push((
1346 module_id,
1347 esm_id.clone().assign_to(s.local.clone()).into_module_item(
1348 injected_ctxt,
1349 "from_replace_import_specifiers: namespaced",
1350 ),
1351 ));
1352 continue;
1353 }
1354 }
1355 #[cfg(swc_ast_unknown)]
1356 _ => panic!("unable to access unknown nodes"),
1357 }
1358 }
1359
1360 // We should remove imports
1361 continue;
1362 }
1363
1364 new.push(stmt);
1365 }
1366
1367 new
1368 });
1369
1370 module.append_all(vars)
1371 }
1372}
1373
1374struct ImportMetaHandler<'a, 'b> {
1375 file: &'a FileName,
1376 #[allow(clippy::borrowed_box)]
1377 hook: &'a Box<dyn 'b + Hook>,
1378 is_entry: bool,
1379 inline_ident: Ident,
1380 occurred: bool,
1381 /// TODO: Use this
1382 #[allow(dead_code)]
1383 err: Option<Error>,
1384}
1385
1386impl VisitMut for ImportMetaHandler<'_, '_> {
1387 fn visit_mut_expr(&mut self, e: &mut Expr) {
1388 e.visit_mut_children_with(self);
1389
1390 if let Expr::MetaProp(MetaPropExpr {
1391 kind: MetaPropKind::ImportMeta,
1392 ..
1393 }) = e
1394 {
1395 *e = self.inline_ident.clone().into();
1396 self.occurred = true;
1397 }
1398 }
1399
1400 fn visit_mut_module(&mut self, n: &mut Module) {
1401 n.visit_mut_children_with(self);
1402
1403 if self.occurred {
1404 match self.hook.get_import_meta_props(
1405 n.span,
1406 &ModuleRecord {
1407 file_name: self.file.to_owned(),
1408 is_entry: self.is_entry,
1409 },
1410 ) {
1411 Ok(key_value_props) => {
1412 prepend_stmt(
1413 &mut n.body,
1414 VarDecl {
1415 span: n.span,
1416 kind: VarDeclKind::Const,
1417 declare: false,
1418 decls: vec![VarDeclarator {
1419 span: n.span,
1420 name: Pat::Ident(self.inline_ident.clone().into()),
1421 init: Some(Box::new(Expr::Object(ObjectLit {
1422 span: n.span,
1423 props: key_value_props
1424 .iter()
1425 .cloned()
1426 .map(|kv| PropOrSpread::Prop(Box::new(Prop::KeyValue(kv))))
1427 .collect(),
1428 }))),
1429 definite: false,
1430 }],
1431 ..Default::default()
1432 }
1433 .into(),
1434 );
1435 }
1436 Err(err) => self.err = Some(err),
1437 }
1438 }
1439 }
1440}