1use std::{
2 borrow::Cow,
3 collections::{BTreeMap, hash_map::Entry},
4 fmt::Display,
5 sync::{Arc, LazyLock},
6};
7
8use anyhow::{Context, Result};
9use rustc_hash::{FxHashMap, FxHashSet};
10use smallvec::SmallVec;
11use swc_core::{
12 atoms::Wtf8Atom,
13 common::{BytePos, Mark, Span, Spanned, SyntaxContext, comments::Comments},
14 ecma::{
15 ast::*,
16 atoms::{Atom, atom},
17 utils::{IsDirective, find_pat_ids},
18 visit::{Visit, VisitWith},
19 },
20};
21use turbo_frozenmap::FrozenMap;
22use turbo_rcstr::{RcStr, rcstr};
23use turbo_tasks::{FxIndexMap, FxIndexSet, ResolvedVc};
24use turbopack_core::{loader::WebpackLoaderItem, resolve::ImportUsage};
25
26use super::{JsValue, ModuleValue, top_level_await::has_top_level_await};
27use crate::{
28 SpecifiedModuleType,
29 analyzer::{
30 ConstantValue, ObjectPart,
31 graph::{AssignmentScope, AssignmentScopes, EvalContext},
32 is_unresolved,
33 },
34 magic_identifier::{MAGIC_IDENTIFIER_DEFAULT_EXPORT, MAGIC_IDENTIFIER_DEFAULT_EXPORT_ATOM},
35 references::{
36 esm::{EsmAssetReference, EsmExport, Liveness},
37 util::{SpecifiedChunkingType, parse_chunking_type_annotation},
38 },
39 tree_shake::{PartId, find_turbopack_part_id_in_asserts},
40};
41
42#[turbo_tasks::value]
43#[derive(Default, Debug, Clone, Hash)]
44pub struct ImportAnnotations {
45 #[turbo_tasks(trace_ignore)]
47 #[bincode(with_serde)]
48 map: BTreeMap<Wtf8Atom, Wtf8Atom>,
49 #[turbo_tasks(trace_ignore)]
52 #[bincode(with_serde)]
53 turbopack_loader: Option<WebpackLoaderItem>,
54 turbopack_rename_as: Option<RcStr>,
55 turbopack_module_type: Option<RcStr>,
56 chunking_type: Option<SpecifiedChunkingType>,
57}
58
59static ANNOTATION_TRANSITION: LazyLock<Wtf8Atom> =
61 LazyLock::new(|| crate::annotations::ANNOTATION_TRANSITION.into());
62
63static ATTRIBUTE_MODULE_TYPE: LazyLock<Wtf8Atom> = LazyLock::new(|| atom!("type").into());
65
66impl ImportAnnotations {
67 pub fn parse(with: Option<&ObjectLit>) -> Option<ImportAnnotations> {
68 let with = with?;
69
70 let mut map = BTreeMap::new();
71 let mut turbopack_loader_name: Option<RcStr> = None;
72 let mut turbopack_loader_options: serde_json::Map<String, serde_json::Value> =
73 serde_json::Map::new();
74 let mut turbopack_rename_as: Option<RcStr> = None;
75 let mut turbopack_module_type: Option<RcStr> = None;
76 let mut chunking_type: Option<SpecifiedChunkingType> = None;
77
78 for prop in &with.props {
79 let Some(kv) = prop.as_prop().and_then(|p| p.as_key_value()) else {
80 continue;
81 };
82
83 let key_str = match &kv.key {
84 PropName::Ident(ident) => Cow::Borrowed(ident.sym.as_str()),
85 PropName::Str(str) => str.value.to_string_lossy(),
86 _ => continue,
87 };
88
89 match &*key_str {
91 "turbopackLoader" => {
92 if let Some(Lit::Str(s)) = kv.value.as_lit() {
93 turbopack_loader_name =
94 Some(RcStr::from(s.value.to_string_lossy().into_owned()));
95 }
96 }
97 "turbopackLoaderOptions" => {
98 if let Some(Lit::Str(s)) = kv.value.as_lit() {
99 let json_str = s.value.to_string_lossy();
100 if let Ok(serde_json::Value::Object(map)) = serde_json::from_str(&json_str)
101 {
102 turbopack_loader_options = map;
103 }
104 }
105 }
106 "turbopackAs" => {
107 if let Some(Lit::Str(s)) = kv.value.as_lit() {
108 turbopack_rename_as =
109 Some(RcStr::from(s.value.to_string_lossy().into_owned()));
110 }
111 }
112 "turbopackModuleType" => {
113 if let Some(Lit::Str(s)) = kv.value.as_lit() {
114 turbopack_module_type =
115 Some(RcStr::from(s.value.to_string_lossy().into_owned()));
116 }
117 }
118 "turbopack-chunking-type" => {
119 if let Some(Lit::Str(s)) = kv.value.as_lit() {
120 chunking_type = parse_chunking_type_annotation(
121 kv.value.span(),
122 &s.value.to_string_lossy(),
123 );
124 }
125 }
126 _ => {
127 if let Some(Lit::Str(str)) = kv.value.as_lit() {
129 let key: Wtf8Atom = match &kv.key {
130 PropName::Ident(ident) => ident.sym.clone().into(),
131 PropName::Str(s) => s.value.clone(),
132 _ => continue,
133 };
134 map.insert(key, str.value.clone());
135 }
136 }
137 }
138 }
139
140 let turbopack_loader = turbopack_loader_name.map(|name| WebpackLoaderItem {
141 loader: name,
142 options: turbopack_loader_options,
143 });
144
145 if !map.is_empty()
146 || turbopack_loader.is_some()
147 || turbopack_rename_as.is_some()
148 || turbopack_module_type.is_some()
149 || chunking_type.is_some()
150 {
151 Some(ImportAnnotations {
152 map,
153 turbopack_loader,
154 turbopack_rename_as,
155 turbopack_module_type,
156 chunking_type,
157 })
158 } else {
159 None
160 }
161 }
162
163 pub fn parse_dynamic(with: &JsValue) -> Option<ImportAnnotations> {
164 let mut map = BTreeMap::new();
165
166 let JsValue::Object { parts, .. } = with else {
167 return None;
168 };
169
170 for part in parts.iter() {
171 let ObjectPart::KeyValue(key, value) = part else {
172 continue;
173 };
174 let (
175 JsValue::Constant(ConstantValue::Str(key)),
176 JsValue::Constant(ConstantValue::Str(value)),
177 ) = (key, value)
178 else {
179 continue;
180 };
181
182 map.insert(
183 key.as_atom().into_owned().into(),
184 value.as_atom().into_owned().into(),
185 );
186 }
187
188 if !map.is_empty() {
189 Some(ImportAnnotations {
190 map,
191 turbopack_loader: None,
192 turbopack_rename_as: None,
193 turbopack_module_type: None,
194 chunking_type: None,
195 })
196 } else {
197 None
198 }
199 }
200
201 pub fn transition(&self) -> Option<Cow<'_, str>> {
203 self.get(&ANNOTATION_TRANSITION)
204 .map(|v| v.to_string_lossy())
205 }
206
207 pub fn chunking_type(&self) -> Option<SpecifiedChunkingType> {
209 self.chunking_type
210 }
211
212 pub fn module_type(&self) -> Option<&Wtf8Atom> {
214 self.get(&ATTRIBUTE_MODULE_TYPE)
215 }
216
217 pub fn turbopack_loader(&self) -> Option<&WebpackLoaderItem> {
219 self.turbopack_loader.as_ref()
220 }
221
222 pub fn turbopack_rename_as(&self) -> Option<&RcStr> {
224 self.turbopack_rename_as.as_ref()
225 }
226
227 pub fn turbopack_module_type(&self) -> Option<&RcStr> {
229 self.turbopack_module_type.as_ref()
230 }
231
232 pub fn has_turbopack_loader(&self) -> bool {
234 self.turbopack_loader.is_some()
235 }
236
237 pub fn get(&self, key: &Wtf8Atom) -> Option<&Wtf8Atom> {
238 self.map.get(key)
239 }
240}
241
242impl Display for ImportAnnotations {
243 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
244 let mut it = self.map.iter();
245 if let Some((k, v)) = it.next() {
246 write!(f, "{{ {}: {}", k.to_string_lossy(), v.to_string_lossy())?
247 } else {
248 return f.write_str("{}");
249 };
250 for (k, v) in it {
251 write!(f, ", {}: {}", k.to_string_lossy(), v.to_string_lossy())?
252 }
253 f.write_str(" }")
254 }
255}
256
257#[derive(Clone, Debug)]
258pub enum DeclUsage {
259 SideEffects,
260 Bindings(FxHashSet<Id>),
261}
262impl Default for DeclUsage {
263 fn default() -> Self {
264 DeclUsage::Bindings(Default::default())
265 }
266}
267impl DeclUsage {
268 fn add_usage(&mut self, user: &Id) {
269 match self {
270 Self::Bindings(set) => {
271 set.insert(user.clone());
272 }
273 Self::SideEffects => {}
274 }
275 }
276 fn make_side_effects(&mut self) {
277 *self = Self::SideEffects;
278 }
279}
280
281#[derive(Default, Debug)]
282pub(crate) struct ProgramDeclUsage {
283 pub(crate) decl_usages: FxHashMap<Id, DeclUsage>,
285 pub(crate) import_usages: FxHashMap<usize, DeclUsage>,
287 pub(crate) exports: FxHashMap<RcStr, Id>,
289}
290impl ProgramDeclUsage {
291 fn compute_import_usage(&self) -> FxHashMap<usize, ImportUsage> {
292 let mut import_usage =
293 FxHashMap::with_capacity_and_hasher(self.import_usages.len(), Default::default());
294 for (reference, usage) in &self.import_usages {
295 if let DeclUsage::Bindings(ids) = usage {
297 let mut visited = ids.clone();
299 let mut stack = ids.iter().collect::<Vec<_>>();
300 let mut has_global_usage = false;
301 while let Some(id) = stack.pop() {
302 match self.decl_usages.get(id) {
303 Some(DeclUsage::SideEffects) => {
304 has_global_usage = true;
305 break;
306 }
307 Some(DeclUsage::Bindings(callers)) => {
308 for caller in callers {
309 if visited.insert(caller.clone()) {
310 stack.push(caller);
311 }
312 }
313 }
314 _ => {}
315 }
316 }
317
318 import_usage.insert(
320 *reference,
321 if has_global_usage {
322 ImportUsage::TopLevel
323 } else {
324 ImportUsage::Exports(
325 self.exports
326 .iter()
327 .filter(|(_, id)| visited.contains(*id))
328 .map(|(exported, _)| exported.clone())
329 .collect(),
330 )
331 },
332 );
333 }
334 }
335 import_usage
336 }
337}
338
339#[derive(Debug)]
342pub enum Export {
343 LocalBinding(RcStr, bool),
347 ImportedBinding(usize, RcStr, bool),
351 ImportedNamespace(usize),
353 Error,
355}
356
357#[derive(Default, Debug)]
362pub(crate) struct ImportMap {
363 imports: FxIndexMap<Id, (usize, Atom)>,
365
366 namespace_imports: FxIndexMap<Id, usize>,
368
369 exports: BTreeMap<RcStr, Export>,
371
372 reexport_namespaces: Vec<usize>,
374
375 references: FxIndexSet<ImportMapReference>,
377
378 has_imports: bool,
381
382 has_exports: bool,
385
386 has_top_level_await: bool,
388
389 pub(crate) strict: bool,
391
392 attributes: FxHashMap<BytePos, ImportAttributes>,
399
400 full_star_imports: FxHashSet<Wtf8Atom>,
403
404 pub(super) assignment_scopes: FxHashMap<Id, AssignmentScopes>,
407
408 pub(crate) import_usage: FxHashMap<usize, ImportUsage>,
409
410 pub(crate) exports_ids: FxHashMap<RcStr, Id>,
412}
413
414#[derive(Debug)]
419pub struct ImportAttributes {
420 pub ignore: bool,
431 pub optional: bool,
441 pub export_names: Option<SmallVec<[RcStr; 1]>>,
455 pub chunking_type: Option<SpecifiedChunkingType>,
464}
465
466impl ImportAttributes {
467 pub const fn empty() -> Self {
468 ImportAttributes {
469 ignore: false,
470 optional: false,
471 export_names: None,
472 chunking_type: None,
473 }
474 }
475
476 pub fn empty_ref() -> &'static Self {
477 static DEFAULT_VALUE: ImportAttributes = ImportAttributes::empty();
479 &DEFAULT_VALUE
480 }
481}
482
483impl Default for ImportAttributes {
484 fn default() -> Self {
485 ImportAttributes::empty()
486 }
487}
488
489impl Default for &ImportAttributes {
490 fn default() -> Self {
491 ImportAttributes::empty_ref()
492 }
493}
494
495#[derive(Debug, Clone, PartialEq, Eq, Hash)]
496pub(crate) enum ImportedSymbol {
497 ModuleEvaluation,
498 Symbol(Atom),
499 Exports,
500 Part(u32),
501 PartEvaluation(u32),
502}
503
504#[derive(Debug, Clone, PartialEq, Eq, Hash)]
505pub(crate) struct ImportMapReference {
506 pub module_path: Wtf8Atom,
507 pub imported_symbol: ImportedSymbol,
508 pub annotations: Option<Arc<ImportAnnotations>>,
509 pub span: Span,
510}
511
512impl ImportMap {
513 pub fn is_esm(&self, specified_type: SpecifiedModuleType) -> bool {
514 if self.has_exports {
515 return true;
516 }
517
518 match specified_type {
519 SpecifiedModuleType::Automatic => {
520 self.has_exports || self.has_imports || self.has_top_level_await
521 }
522 SpecifiedModuleType::CommonJs => false,
523 SpecifiedModuleType::EcmaScript => true,
524 }
525 }
526
527 pub fn get_import(&self, id: &Id) -> Option<JsValue> {
528 if let Some((i, i_sym)) = self.imports.get(id) {
529 let r = &self.references[*i];
530 return Some(JsValue::member(
531 Box::new(JsValue::Module(ModuleValue {
532 module: r.module_path.clone(),
533 annotations: r.annotations.clone(),
534 })),
535 Box::new(i_sym.clone().into()),
536 ));
537 }
538 if let Some(i) = self.namespace_imports.get(id) {
539 let r = &self.references[*i];
540 return Some(JsValue::Module(ModuleValue {
541 module: r.module_path.clone(),
542 annotations: r.annotations.clone(),
543 }));
544 }
545 None
546 }
547
548 pub fn get_attributes(&self, span: Span) -> &ImportAttributes {
549 self.attributes.get(&span.lo).unwrap_or_default()
550 }
551
552 pub fn get_binding(&self, id: &Id) -> Option<(usize, Option<&Atom>)> {
553 if let Some((i, i_sym)) = self.imports.get(id) {
554 return Some((*i, Some(i_sym)));
555 }
556 if let Some(i) = self.namespace_imports.get(id) {
557 return Some((*i, None));
558 }
559 None
560 }
561
562 pub fn references(&self) -> impl ExactSizeIterator<Item = &ImportMapReference> {
563 self.references.iter()
564 }
565
566 pub fn reexports_reference_idxs(&self) -> impl Iterator<Item = usize> {
567 self.exports
568 .values()
569 .filter_map(|value| match value {
570 Export::ImportedBinding(i, ..) | Export::ImportedNamespace(i) => Some(*i),
571 Export::LocalBinding(..) | Export::Error => None,
572 })
573 .chain(self.reexport_namespaces.iter().copied())
574 }
575
576 pub fn as_esm_exports(
577 &self,
578 import_references: &[ResolvedVc<EsmAssetReference>],
579 eval_context: &EvalContext,
580 ) -> Result<FrozenMap<RcStr, EsmExport>> {
581 Ok(FrozenMap::from(
582 self.exports
583 .iter()
584 .map(|(name, value)| {
585 let value = match value {
586 Export::LocalBinding(local, is_fake_esm) => EsmExport::LocalBinding(
587 local.clone(),
588 if *is_fake_esm {
589 Liveness::Mutable
591 } else {
592 eval_context.imports.get_export_ident_liveness(
593 self.exports_ids.get(name).cloned().with_context(|| {
594 format!("Exported binding {name} not found in exports_ids")
595 })?,
596 )
597 },
598 ),
599 Export::ImportedBinding(i, name, is_fake_esm) => {
600 EsmExport::ImportedBinding(
601 ResolvedVc::upcast(import_references[*i]),
602 name.clone(),
603 *is_fake_esm,
604 )
605 }
606 Export::ImportedNamespace(i) => {
607 EsmExport::ImportedNamespace(ResolvedVc::upcast(import_references[*i]))
608 }
609 Export::Error => EsmExport::Error,
610 };
611 Ok((name.clone(), value))
612 })
613 .collect::<Result<Vec<_>>>()?,
614 ))
615 }
616
617 pub fn reexport_namespaces(&self) -> impl ExactSizeIterator<Item = usize> {
618 self.reexport_namespaces.iter().copied()
619 }
620
621 pub fn get_export_ident_liveness(&self, id: Id) -> Liveness {
624 if let Some(assignment_scopes) = self.assignment_scopes.get(&id) {
625 if *assignment_scopes != AssignmentScopes::AllInModuleEvalScope {
627 Liveness::Live
628 } else {
629 Liveness::Constant
630 }
631 } else {
632 Liveness::Live
637 }
638 }
639
640 pub(super) fn analyze(
642 unresolved_mark: Mark,
643 m: &Program,
644 comments: Option<&dyn Comments>,
645 ) -> Self {
646 let mut data = ImportMap::default();
647 let mut analyzer = Analyzer {
648 unresolved_mark,
649 data: &mut data,
650 comments,
651 namespace_imports_to_specifier: FxIndexMap::default(),
652 state: Default::default(),
653 program_decl_usage: Default::default(),
654 };
655
656 if let Program::Module(m) = m {
658 for stmt in &m.body {
659 match stmt {
660 ModuleItem::ModuleDecl(ModuleDecl::Import(import)) => {
661 if import.type_only {
662 continue;
663 }
664 analyzer.data.has_imports = true;
665 let annotations = ImportAnnotations::parse(import.with.as_deref());
666 let internal_symbol = parse_with(import.with.as_deref());
667 if internal_symbol.is_none() {
668 analyzer.ensure_reference(
669 import.span,
670 import.src.value.clone(),
671 ImportedSymbol::ModuleEvaluation,
672 annotations.clone(),
673 );
674 }
675
676 for s in &import.specifiers {
677 if s.is_type_only() {
678 continue;
679 }
680 let symbol = internal_symbol
681 .clone()
682 .unwrap_or_else(|| get_import_symbol_from_import(s));
683 let i = analyzer.ensure_reference(
684 import.span,
685 import.src.value.clone(),
686 symbol,
687 annotations.clone(),
688 );
689
690 let (local, orig_sym) = match s {
691 ImportSpecifier::Namespace(s) => {
692 analyzer
693 .namespace_imports_to_specifier
694 .insert(s.local.to_id(), import.src.value.clone());
695 analyzer.data.namespace_imports.insert(s.local.to_id(), i);
696 continue;
697 }
698 ImportSpecifier::Default(s) => (s.local.to_id(), atom!("default")),
699 ImportSpecifier::Named(s) => match &s.imported {
700 Some(imported) => {
701 (s.local.to_id(), imported.atom().into_owned())
702 }
703 _ => (s.local.to_id(), s.local.sym.clone()),
704 },
705 };
706 analyzer.data.imports.insert(local, (i, orig_sym));
707 }
708 if import.specifiers.is_empty()
709 && let Some(internal_symbol) = internal_symbol
710 {
711 analyzer.ensure_reference(
712 import.span,
713 import.src.value.clone(),
714 internal_symbol,
715 annotations,
716 );
717 }
718 }
719 ModuleItem::ModuleDecl(ModuleDecl::ExportAll(export)) => {
722 if export.type_only {
723 continue;
724 }
725 let annotations = ImportAnnotations::parse(export.with.as_deref());
726 analyzer.ensure_reference(
727 export.span,
728 export.src.value.clone(),
729 ImportedSymbol::ModuleEvaluation,
730 annotations.clone(),
731 );
732 }
733 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(export)) => {
734 if export.type_only {
735 continue;
736 }
737 if let Some(ref src) = export.src {
738 let annotations = ImportAnnotations::parse(export.with.as_deref());
739 let internal_symbol = parse_with(export.with.as_deref());
740 if internal_symbol.is_none() || export.specifiers.is_empty() {
741 analyzer.ensure_reference(
742 export.span,
743 src.value.clone(),
744 ImportedSymbol::ModuleEvaluation,
745 annotations.clone(),
746 );
747 }
748 }
749 }
750 _ => (),
751 }
752 }
753 }
754
755 m.visit_with(&mut analyzer);
756
757 data.import_usage = analyzer.program_decl_usage.compute_import_usage();
758
759 data
760 }
761
762 pub(crate) fn should_import_all(&self, esm_reference_index: usize) -> bool {
763 let r = &self.references[esm_reference_index];
764
765 self.full_star_imports.contains(&r.module_path)
766 }
767}
768
769mod analyzer_state {
770 use swc_core::ecma::ast::{Id, Ident};
771
772 use super::Analyzer;
773
774 #[derive(Default)]
775 pub(super) struct AnalyzerState {
776 is_in_fn: bool,
777 cur_top_level_decl_name: Option<Id>,
778 }
779
780 impl AnalyzerState {
781 pub(super) fn cur_top_level_decl_name(&self) -> &Option<Id> {
783 &self.cur_top_level_decl_name
784 }
785
786 pub(super) fn is_in_fn(&self) -> bool {
788 self.is_in_fn
789 }
790 }
791
792 impl Analyzer<'_> {
793 pub(super) fn enter_top_level_decl<T>(
795 &mut self,
796 name: &Ident,
797 visitor: impl FnOnce(&mut Self) -> T,
798 ) -> T {
799 let is_top_level_fn = self.state.cur_top_level_decl_name.is_none();
800 if is_top_level_fn {
801 self.state.cur_top_level_decl_name = Some(name.to_id());
802 }
803 let result = visitor(self);
804 if is_top_level_fn {
805 self.state.cur_top_level_decl_name = None;
806 }
807 result
808 }
809
810 pub(super) fn enter_fn<T>(&mut self, visitor: impl FnOnce(&mut Self) -> T) -> T {
812 let old_is_in_fn = self.state.is_in_fn;
813 self.state.is_in_fn = true;
814 let result = visitor(self);
815 self.state.is_in_fn = old_is_in_fn;
816 result
817 }
818 }
819}
820
821struct Analyzer<'a> {
822 unresolved_mark: Mark,
823 data: &'a mut ImportMap,
824 comments: Option<&'a dyn Comments>,
825 namespace_imports_to_specifier: FxIndexMap<Id, Wtf8Atom>,
828
829 program_decl_usage: ProgramDeclUsage,
830
831 state: analyzer_state::AnalyzerState,
832}
833
834impl Analyzer<'_> {
835 fn ensure_reference(
836 &mut self,
837 span: Span,
838 module_path: Wtf8Atom,
839 imported_symbol: ImportedSymbol,
840 annotations: Option<ImportAnnotations>,
841 ) -> usize {
842 let r = ImportMapReference {
843 module_path,
844 imported_symbol,
845 span,
846 annotations: annotations.map(Arc::new),
847 };
848 if let Some(i) = self.data.references.get_index_of(&r) {
849 i
850 } else {
851 let i = self.data.references.len();
852 self.data.references.insert(r);
853 i
854 }
855 }
856
857 fn register_assignment_scope(&mut self, id: Id) {
858 let scope = if self.state.is_in_fn() {
859 AssignmentScope::Function
860 } else {
861 AssignmentScope::ModuleEval
862 };
863
864 match self.data.assignment_scopes.entry(id) {
865 Entry::Occupied(mut e) => {
866 *e.get_mut() = e.get().merge(scope);
867 }
868 Entry::Vacant(e) => {
869 e.insert(AssignmentScopes::new(scope));
870 }
871 }
872 }
873}
874
875impl Visit for Analyzer<'_> {
876 fn visit_import_decl(&mut self, _: &ImportDecl) {
877 }
879
880 fn visit_export_all(&mut self, export: &ExportAll) {
881 if export.type_only {
882 return;
883 }
884
885 let annotations = ImportAnnotations::parse(export.with.as_deref());
886
887 let symbol = parse_with(export.with.as_deref());
888 let i = self.ensure_reference(
889 export.span,
890 export.src.value.clone(),
891 symbol.unwrap_or(ImportedSymbol::Exports),
892 annotations,
893 );
894 self.data.reexport_namespaces.push(i);
895 self.data.has_exports = true;
896 export.visit_children_with(self);
897 }
898
899 fn visit_named_export(&mut self, export: &NamedExport) {
900 if export.type_only {
901 return;
902 }
903
904 self.data.has_exports = true;
905
906 if let Some(ref src) = export.src {
907 let annotations = ImportAnnotations::parse(export.with.as_deref());
908 let internal_symbol = parse_with(export.with.as_deref());
909
910 for spec in export.specifiers.iter() {
911 let symbol = internal_symbol
912 .clone()
913 .unwrap_or_else(|| get_import_symbol_from_export(spec));
914
915 let i = self.ensure_reference(
916 export.span,
917 src.value.clone(),
918 symbol,
919 annotations.clone(),
920 );
921
922 match spec {
923 ExportSpecifier::Namespace(n) => {
924 self.data.exports.insert(
925 RcStr::from(n.name.atom().as_str()),
926 Export::ImportedNamespace(i),
927 );
928 }
929 ExportSpecifier::Default(d) => {
930 self.data.exports.insert(
931 RcStr::from(d.exported.sym.as_str()),
932 Export::ImportedBinding(i, rcstr!("default"), false),
933 );
934 }
935 ExportSpecifier::Named(n) => {
936 self.data.exports.insert(
937 RcStr::from(n.exported.as_ref().unwrap_or(&n.orig).atom().as_str()),
938 Export::ImportedBinding(i, RcStr::from(n.orig.atom().as_str()), false),
939 );
940 }
941 }
942 }
943 } else {
944 for spec in export.specifiers.iter() {
945 match spec {
946 ExportSpecifier::Namespace(_) => {
947 unreachable!(
948 "ExportNamespaceSpecifier will not happen in combination with src == \
949 None"
950 );
951 }
952 ExportSpecifier::Default(_) => {
953 unreachable!(
954 "ExportDefaultSpecifier will not happen in combination with src == \
955 None"
956 );
957 }
958 ExportSpecifier::Named(ExportNamedSpecifier {
959 orig,
960 exported,
961 is_type_only,
962 ..
963 }) => {
964 if *is_type_only {
965 continue;
966 }
967
968 let is_fake_esm = export
970 .with
971 .as_deref()
972 .map(find_turbopack_part_id_in_asserts)
973 .is_some();
974 let export = {
975 let imported_binding = if let ModuleExportName::Ident(ident) = orig {
976 self.data.get_binding(&ident.to_id())
977 } else {
978 None
979 };
980 if let Some((index, export)) = imported_binding {
981 if let Some(export) = export {
984 Export::ImportedBinding(
985 index,
986 RcStr::from(export.as_str()),
987 is_fake_esm,
988 )
989 } else {
990 Export::ImportedNamespace(index)
991 }
992 } else {
993 Export::LocalBinding(RcStr::from(orig.atom().as_str()), is_fake_esm)
994 }
995 };
996 self.data.exports.insert(
997 RcStr::from(exported.as_ref().unwrap_or(orig).atom().as_str()),
998 export,
999 );
1000 }
1001 }
1002 }
1003 export.visit_children_with(self);
1004 }
1005 }
1006
1007 fn visit_export_decl(&mut self, n: &ExportDecl) {
1008 self.data.has_exports = true;
1009 match &n.decl {
1010 Decl::Class(n) => {
1011 let name = RcStr::from(n.ident.sym.as_str());
1012 self.data
1013 .exports
1014 .insert(name.clone(), Export::LocalBinding(name.clone(), false));
1015 self.data.exports_ids.insert(name.clone(), n.ident.to_id());
1016 self.program_decl_usage
1017 .exports
1018 .insert(name, n.ident.to_id());
1019 }
1020 Decl::Fn(n) => {
1021 let name = RcStr::from(n.ident.sym.as_str());
1022 self.data
1023 .exports
1024 .insert(name.clone(), Export::LocalBinding(name.clone(), false));
1025 self.data.exports_ids.insert(name.clone(), n.ident.to_id());
1026 self.program_decl_usage
1027 .exports
1028 .insert(name, n.ident.to_id());
1029 }
1030 Decl::Var(..) => {
1031 let ids: Vec<Id> = find_pat_ids(&n.decl);
1032 for id in ids {
1033 let name = RcStr::from(id.0.as_str());
1034 self.data
1035 .exports
1036 .insert(name.clone(), Export::LocalBinding(name.clone(), false));
1037 self.data.exports_ids.insert(name.clone(), id.clone());
1038 self.program_decl_usage.exports.insert(name, id);
1039 }
1040 }
1041 Decl::Using(_) => {
1042 unreachable!("using declarations can not be exported");
1044 }
1045 Decl::TsInterface(_) | Decl::TsTypeAlias(_) | Decl::TsEnum(_) | Decl::TsModule(_) => {
1046 }
1048 }
1049
1050 n.visit_children_with(self);
1051 }
1052
1053 fn visit_export_default_decl(&mut self, n: &ExportDefaultDecl) {
1054 self.data.has_exports = true;
1055
1056 let id = match &n.decl {
1057 DefaultDecl::Class(ClassExpr { ident, .. }) | DefaultDecl::Fn(FnExpr { ident, .. }) => {
1058 ident.as_ref().map_or_else(
1061 || {
1062 (
1063 MAGIC_IDENTIFIER_DEFAULT_EXPORT_ATOM.clone(),
1064 SyntaxContext::empty(),
1065 )
1066 },
1067 |ident| ident.to_id(),
1068 )
1069 }
1070 DefaultDecl::TsInterfaceDecl(_) => {
1071 (
1073 MAGIC_IDENTIFIER_DEFAULT_EXPORT_ATOM.clone(),
1074 SyntaxContext::empty(),
1075 )
1076 }
1077 };
1078
1079 self.data.exports.insert(
1080 rcstr!("default"),
1081 Export::LocalBinding(RcStr::from(id.0.as_str()), false),
1082 );
1083 self.data.exports_ids.insert(rcstr!("default"), id.clone());
1084 self.program_decl_usage
1085 .exports
1086 .insert(rcstr!("default"), id);
1087 n.visit_children_with(self);
1088 }
1089
1090 fn visit_export_default_expr(&mut self, n: &ExportDefaultExpr) {
1091 self.data.has_exports = true;
1092
1093 self.data.exports.insert(
1094 rcstr!("default"),
1095 Export::LocalBinding(MAGIC_IDENTIFIER_DEFAULT_EXPORT.clone(), false),
1096 );
1097 self.data.exports_ids.insert(
1098 rcstr!("default"),
1099 (
1100 MAGIC_IDENTIFIER_DEFAULT_EXPORT_ATOM.clone(),
1102 SyntaxContext::empty(),
1103 ),
1104 );
1105 n.visit_children_with(self);
1106 }
1107
1108 fn visit_export_named_specifier(&mut self, n: &ExportNamedSpecifier) {
1109 self.data.has_exports = true;
1110
1111 let ModuleExportName::Ident(local) = &n.orig else {
1112 unreachable!("exporting a string should be impossible")
1113 };
1114 let exported = RcStr::from(n.exported.as_ref().unwrap_or(&n.orig).atom().as_str());
1115 self.data
1116 .exports_ids
1117 .insert(exported.clone(), local.to_id());
1118 self.program_decl_usage
1119 .exports
1120 .insert(exported, local.to_id());
1121 n.visit_children_with(self);
1122 }
1123
1124 fn visit_export_default_specifier(&mut self, n: &ExportDefaultSpecifier) {
1125 self.data.has_exports = true;
1126
1127 self.data
1128 .exports_ids
1129 .insert(rcstr!("default"), n.exported.to_id());
1130 n.visit_children_with(self);
1131 }
1132
1133 fn visit_program(&mut self, m: &Program) {
1134 self.data.has_top_level_await = has_top_level_await(m).is_some();
1135 self.data.strict = match m {
1136 Program::Module(module) => module
1137 .body
1138 .iter()
1139 .take_while(|s| s.directive_continue())
1140 .any(IsDirective::is_use_strict),
1141 Program::Script(script) => script
1142 .body
1143 .iter()
1144 .take_while(|s| s.directive_continue())
1145 .any(IsDirective::is_use_strict),
1146 };
1147
1148 m.visit_children_with(self);
1149 }
1150
1151 fn visit_call_expr(&mut self, n: &CallExpr) {
1165 if let Some(comments) = self.comments {
1166 let callee_span = match &n.callee {
1167 Callee::Import(Import { span, .. }) => Some(*span),
1168 Callee::Expr(e) => Some(e.span()),
1169 _ => None,
1170 };
1171
1172 if let Some(callee_span) = callee_span
1173 && let Some(attributes) = parse_directives(comments, n.args.first())
1174 {
1175 self.data.attributes.insert(callee_span.lo, attributes);
1176 }
1177 }
1178
1179 n.visit_children_with(self);
1180 }
1181
1182 fn visit_new_expr(&mut self, n: &NewExpr) {
1183 if let Some(comments) = self.comments {
1184 let callee_span = match &*n.callee {
1185 Expr::Ident(Ident { sym, .. }) if sym == "Worker" => Some(n.span),
1186 _ => None,
1187 };
1188
1189 if let Some(callee_span) = callee_span
1190 && let Some(attributes) = parse_directives(comments, n.args.iter().flatten().next())
1191 {
1192 self.data.attributes.insert(callee_span.lo, attributes);
1193 }
1194 }
1195
1196 n.visit_children_with(self);
1197 }
1198
1199 fn visit_getter_prop(&mut self, node: &GetterProp) {
1200 self.enter_fn(|this| {
1201 node.visit_children_with(this);
1202 });
1203 }
1204 fn visit_setter_prop(&mut self, node: &SetterProp) {
1205 self.enter_fn(|this| {
1206 node.visit_children_with(this);
1207 });
1208 }
1209 fn visit_function(&mut self, node: &Function) {
1210 self.enter_fn(|this| {
1211 node.visit_children_with(this);
1212 });
1213 }
1214 fn visit_constructor(&mut self, node: &Constructor) {
1215 self.enter_fn(|this| {
1216 node.visit_children_with(this);
1217 });
1218 }
1219 fn visit_arrow_expr(&mut self, node: &ArrowExpr) {
1220 self.enter_fn(|this| {
1221 node.visit_children_with(this);
1222 });
1223 }
1224
1225 fn visit_member_expr(&mut self, node: &MemberExpr) {
1226 if matches!(
1227 &node.prop,
1228 MemberProp::Ident(..) | MemberProp::PrivateName(..)
1229 ) && let Expr::Ident(ident) = &*node.obj
1230 {
1231 ident.visit_with(self);
1234 } else {
1235 node.visit_children_with(self);
1236 }
1237 }
1238
1239 fn visit_expr(&mut self, node: &Expr) {
1240 if let Expr::Ident(i) = node
1243 && let Some(module_path) = self.namespace_imports_to_specifier.get(&i.to_id())
1244 {
1245 self.data.full_star_imports.insert(module_path.clone());
1246 }
1247 node.visit_children_with(self);
1248 }
1249
1250 fn visit_pat(&mut self, pat: &Pat) {
1251 if let Pat::Ident(i) = pat {
1252 self.register_assignment_scope(i.to_id());
1253 if let Some(module_path) = self.namespace_imports_to_specifier.get(&i.to_id()) {
1254 self.data.full_star_imports.insert(module_path.clone());
1255 }
1256 }
1257 pat.visit_children_with(self);
1258 }
1259
1260 fn visit_simple_assign_target(&mut self, node: &SimpleAssignTarget) {
1261 if let SimpleAssignTarget::Ident(i) = node {
1262 self.register_assignment_scope(i.to_id());
1263 if let Some(module_path) = self.namespace_imports_to_specifier.get(&i.to_id()) {
1264 self.data.full_star_imports.insert(module_path.clone());
1265 }
1266 }
1267 node.visit_children_with(self);
1268 }
1269
1270 fn visit_ident(&mut self, node: &Ident) {
1271 let id = node.to_id();
1272 if let Some((esm_reference_index, _)) = self.data.get_binding(&id) {
1273 let usage = self
1275 .program_decl_usage
1276 .import_usages
1277 .entry(esm_reference_index)
1278 .or_default();
1279 if let Some(top_level) = self.state.cur_top_level_decl_name() {
1280 usage.add_usage(top_level);
1281 } else {
1282 usage.make_side_effects();
1283 }
1284 } else {
1285 if !is_unresolved(node, self.unresolved_mark) {
1287 if let Some(top_level) = self.state.cur_top_level_decl_name() {
1288 if &id != top_level {
1289 self.program_decl_usage
1290 .decl_usages
1291 .entry(id)
1292 .or_default()
1293 .add_usage(top_level);
1294 }
1295 } else {
1296 self.program_decl_usage
1297 .decl_usages
1298 .entry(id)
1299 .or_default()
1300 .make_side_effects();
1301 }
1302 }
1303 }
1304 }
1305
1306 fn visit_fn_expr(&mut self, node: &FnExpr) {
1307 if let Some(ident) = &node.ident {
1308 self.register_assignment_scope(ident.to_id());
1309 }
1310 node.visit_children_with(self);
1311 }
1312
1313 fn visit_fn_decl(&mut self, node: &FnDecl) {
1314 self.enter_top_level_decl(&node.ident, |this| {
1315 node.visit_children_with(this);
1316 });
1317 }
1318
1319 fn visit_decl(&mut self, node: &Decl) {
1320 match node {
1321 Decl::Class(c) => {
1322 self.register_assignment_scope(c.ident.to_id());
1323 }
1324 Decl::Fn(f) => {
1325 self.register_assignment_scope(f.ident.to_id());
1326 }
1327 Decl::Using(v) => {
1328 let ids: Vec<Id> = find_pat_ids(&v.decls);
1329 for id in ids {
1330 self.register_assignment_scope(id);
1331 }
1332 }
1333 Decl::Var(v) => {
1334 let ids: Vec<Id> = find_pat_ids(&v.decls);
1335 for id in ids {
1336 self.register_assignment_scope(id);
1337 }
1338 }
1339 Decl::TsInterface(_) | Decl::TsTypeAlias(_) | Decl::TsEnum(_) | Decl::TsModule(_) => {}
1340 }
1341 node.visit_children_with(self);
1342 }
1343
1344 fn visit_update_expr(&mut self, node: &UpdateExpr) {
1345 if let Some(key) = node.arg.as_ident() {
1346 self.register_assignment_scope(key.to_id());
1348 }
1349 node.visit_children_with(self);
1350 }
1351}
1352
1353fn parse_directives(
1356 comments: &dyn Comments,
1357 value: Option<&ExprOrSpread>,
1358) -> Option<ImportAttributes> {
1359 let value = value?;
1360 let leading_comments = comments.get_leading(value.span_lo())?;
1361
1362 let mut ignore = None;
1363 let mut optional = None;
1364 let mut export_names = None;
1365 let mut chunking_type = None;
1366
1367 for comment in leading_comments.iter() {
1369 if let Some((directive, val)) = comment.text.trim().split_once(':') {
1370 let val = val.trim();
1371 match directive.trim() {
1372 "webpackIgnore" | "turbopackIgnore" => match val {
1373 "true" => ignore = Some(true),
1374 "false" => ignore = Some(false),
1375 _ => {}
1376 },
1377 "turbopackOptional" => match val {
1378 "true" => optional = Some(true),
1379 "false" => optional = Some(false),
1380 _ => {}
1381 },
1382 "webpackExports" | "turbopackExports" => {
1383 export_names = Some(parse_export_names(val));
1384 }
1385 "turbopackChunkingType" => {
1386 chunking_type = parse_chunking_type_annotation(value.span(), val);
1387 }
1388 _ => {} }
1390 }
1391 }
1392
1393 if ignore.is_some() || optional.is_some() || export_names.is_some() || chunking_type.is_some() {
1395 Some(ImportAttributes {
1396 ignore: ignore.unwrap_or(false),
1397 optional: optional.unwrap_or(false),
1398 export_names,
1399 chunking_type,
1400 })
1401 } else {
1402 None
1403 }
1404}
1405
1406fn parse_export_names(val: &str) -> SmallVec<[RcStr; 1]> {
1412 let val = val.trim();
1413
1414 if let Ok(names) = serde_json::from_str::<Vec<String>>(val) {
1416 return names.into_iter().map(|s| s.into()).collect();
1417 }
1418
1419 if let Ok(name) = serde_json::from_str::<String>(val) {
1421 return SmallVec::from_buf([name.into()]);
1422 }
1423
1424 if !val.is_empty() {
1426 return SmallVec::from_buf([val.into()]);
1427 }
1428
1429 SmallVec::new()
1430}
1431
1432fn parse_with(with: Option<&ObjectLit>) -> Option<ImportedSymbol> {
1433 find_turbopack_part_id_in_asserts(with?).map(|v| match v {
1434 PartId::Internal(index, true) => ImportedSymbol::PartEvaluation(index),
1435 PartId::Internal(index, false) => ImportedSymbol::Part(index),
1436 PartId::ModuleEvaluation => ImportedSymbol::ModuleEvaluation,
1437 PartId::Export(e) => ImportedSymbol::Symbol(e.as_str().into()),
1438 PartId::Exports => ImportedSymbol::Exports,
1439 })
1440}
1441
1442fn get_import_symbol_from_import(specifier: &ImportSpecifier) -> ImportedSymbol {
1443 match specifier {
1444 ImportSpecifier::Named(ImportNamedSpecifier {
1445 local, imported, ..
1446 }) => ImportedSymbol::Symbol(match imported {
1447 Some(imported) => imported.atom().into_owned(),
1448 _ => local.sym.clone(),
1449 }),
1450 ImportSpecifier::Default(..) => ImportedSymbol::Symbol(atom!("default")),
1451 ImportSpecifier::Namespace(..) => ImportedSymbol::Exports,
1452 }
1453}
1454
1455fn get_import_symbol_from_export(specifier: &ExportSpecifier) -> ImportedSymbol {
1456 match specifier {
1457 ExportSpecifier::Named(ExportNamedSpecifier { orig, .. }) => {
1458 ImportedSymbol::Symbol(orig.atom().into_owned())
1459 }
1460 ExportSpecifier::Default(..) => ImportedSymbol::Symbol(atom!("default")),
1461 ExportSpecifier::Namespace(..) => ImportedSymbol::Exports,
1462 }
1463}
1464
1465#[cfg(test)]
1466mod tests {
1467 use swc_core::{atoms::Atom, common::DUMMY_SP, ecma::ast::*};
1468
1469 use super::*;
1470
1471 fn str_lit(s: &str) -> Box<Expr> {
1473 Box::new(Expr::Lit(Lit::Str(Str {
1474 span: DUMMY_SP,
1475 value: Atom::from(s).into(),
1476 raw: None,
1477 })))
1478 }
1479
1480 fn ident_key(s: &str) -> PropName {
1482 PropName::Ident(IdentName {
1483 span: DUMMY_SP,
1484 sym: Atom::from(s),
1485 })
1486 }
1487
1488 fn kv_prop(key: PropName, value: Box<Expr>) -> PropOrSpread {
1490 PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp { key, value })))
1491 }
1492
1493 #[test]
1494 fn test_parse_turbopack_loader_annotation() {
1495 let with = ObjectLit {
1497 span: DUMMY_SP,
1498 props: vec![kv_prop(ident_key("turbopackLoader"), str_lit("raw-loader"))],
1499 };
1500
1501 let annotations = ImportAnnotations::parse(Some(&with)).unwrap();
1502 assert!(annotations.has_turbopack_loader());
1503
1504 let loader = annotations.turbopack_loader().unwrap();
1505 assert_eq!(loader.loader.as_str(), "raw-loader");
1506 assert!(loader.options.is_empty());
1507 }
1508
1509 #[test]
1510 fn test_parse_turbopack_loader_with_options() {
1511 let with = ObjectLit {
1513 span: DUMMY_SP,
1514 props: vec![
1515 kv_prop(ident_key("turbopackLoader"), str_lit("my-loader")),
1516 kv_prop(
1517 ident_key("turbopackLoaderOptions"),
1518 str_lit(r#"{"flag":true}"#),
1519 ),
1520 ],
1521 };
1522
1523 let annotations = ImportAnnotations::parse(Some(&with)).unwrap();
1524 assert!(annotations.has_turbopack_loader());
1525
1526 let loader = annotations.turbopack_loader().unwrap();
1527 assert_eq!(loader.loader.as_str(), "my-loader");
1528 assert_eq!(loader.options["flag"], serde_json::Value::Bool(true));
1529 }
1530
1531 #[test]
1532 fn test_parse_without_turbopack_loader() {
1533 let with = ObjectLit {
1535 span: DUMMY_SP,
1536 props: vec![kv_prop(ident_key("type"), str_lit("json"))],
1537 };
1538
1539 let annotations = ImportAnnotations::parse(Some(&with)).unwrap();
1540 assert!(!annotations.has_turbopack_loader());
1541 assert!(annotations.module_type().is_some());
1542 }
1543
1544 #[test]
1545 fn test_parse_empty_with() {
1546 let annotations = ImportAnnotations::parse(None);
1547 assert!(annotations.is_none());
1548 }
1549}