1use indexmap::IndexMap;
2use rustc_hash::FxBuildHasher;
3use swc_atoms::{atom, Atom};
4use swc_common::{FileName, SyntaxContext};
5use swc_ecma_ast::*;
6use swc_ecma_utils::find_pat_ids;
7use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
8
9use super::{
10 load::{Source, Specifier},
11 Bundler,
12};
13use crate::{id::Id, load::Load, resolve::Resolve, util::ExportMetadata};
14
15impl<L, R> Bundler<'_, L, R>
16where
17 L: Load,
18 R: Resolve,
19{
20 pub(super) fn extract_export_info(
23 &self,
24 file_name: &FileName,
25 module: &mut Module,
26 export_ctxt: SyntaxContext,
27 ) -> RawExports {
28 self.run(|| {
29 let mut v = ExportFinder {
30 info: Default::default(),
31 file_name,
32 bundler: self,
33 export_ctxt,
34 };
35
36 module.visit_mut_with(&mut v);
37
38 v.info
39 })
40 }
41}
42
43#[derive(Debug, Default)]
44pub(super) struct RawExports {
45 pub items: IndexMap<Option<Str>, Vec<Specifier>, FxBuildHasher>,
47}
48
49#[derive(Debug, Default)]
50pub(crate) struct Exports {
51 pub items: Vec<Specifier>,
52 pub reexports: Vec<(Source, Vec<Specifier>)>,
53}
54
55struct ExportFinder<'a, 'b, L, R>
56where
57 L: Load,
58 R: Resolve,
59{
60 info: RawExports,
61 file_name: &'a FileName,
62 bundler: &'a Bundler<'b, L, R>,
63 export_ctxt: SyntaxContext,
64}
65
66impl<L, R> ExportFinder<'_, '_, L, R>
67where
68 L: Load,
69 R: Resolve,
70{
71 fn ctxt_for(&self, src: &Atom) -> Option<(SyntaxContext, SyntaxContext)> {
73 if self
75 .bundler
76 .config
77 .external_modules
78 .iter()
79 .any(|v| v == src)
80 {
81 return None;
82 }
83 let path = self.bundler.resolve(self.file_name, src).ok()?;
84 let (_, local_mark, export_mark) = self.bundler.scope.module_id_gen.gen(&path);
85
86 Some((
87 SyntaxContext::empty().apply_mark(local_mark),
88 SyntaxContext::empty().apply_mark(export_mark),
89 ))
90 }
91
92 fn mark_as_wrapping_required(&self, src: &Atom) {
93 if self
95 .bundler
96 .config
97 .external_modules
98 .iter()
99 .any(|v| v == src)
100 {
101 return;
102 }
103 let path = self.bundler.resolve(self.file_name, src);
104 let path = match path {
105 Ok(v) => v,
106 _ => return,
107 };
108 let (id, _, _) = self.bundler.scope.module_id_gen.gen(&path);
109
110 self.bundler.scope.mark_as_wrapping_required(id);
111 }
112}
113
114impl<L, R> VisitMut for ExportFinder<'_, '_, L, R>
115where
116 L: Load,
117 R: Resolve,
118{
119 noop_visit_mut_type!(fail);
120
121 fn visit_mut_module_item(&mut self, item: &mut ModuleItem) {
122 match item {
123 ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(decl)) => {
154 let v = self.info.items.entry(None).or_default();
155 v.push({
156 let i = match decl.decl {
157 Decl::Class(ref c) => &c.ident,
158 Decl::Fn(ref f) => &f.ident,
159 Decl::Var(ref var) => {
160 let ids: Vec<Id> = find_pat_ids(&var.decls);
161 for id in ids {
162 v.push(Specifier::Specific {
163 local: id,
164 alias: None,
165 });
166 }
167 return;
168 }
169 Decl::TsEnum(ref e) => &e.id,
170 Decl::TsInterface(ref i) => &i.id,
171 Decl::TsTypeAlias(ref a) => &a.id,
172 _ => unreachable!("Decl in ExportDecl: {:?}", decl.decl),
173 };
174 Specifier::Specific {
175 local: i.into(),
176 alias: None,
177 }
178 });
179 }
180
181 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(_decl)) => {
182 self.info
183 .items
184 .entry(None)
185 .or_default()
186 .push(Specifier::Specific {
187 local: Id::new(atom!("default"), SyntaxContext::empty()),
188 alias: None,
189 });
190 }
191
192 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(_expr)) => {
193 self.info
194 .items
195 .entry(None)
196 .or_default()
197 .push(Specifier::Specific {
198 local: Id::new(atom!("default"), SyntaxContext::empty()),
199 alias: None,
200 });
201 }
202
203 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(named)) => {
204 let ctxt = named.src.as_ref().and_then(|s| {
205 let src_atom = s.value.to_atom_lossy();
206 self.ctxt_for(src_atom.as_ref())
207 });
208 let mut need_wrapping = false;
209
210 let v = self
211 .info
212 .items
213 .entry(named.src.clone().map(|v| *v))
214 .or_default();
215 for s in &mut named.specifiers {
216 match s {
217 ExportSpecifier::Namespace(n) => {
218 match &mut n.name {
219 ModuleExportName::Ident(name) => {
220 name.ctxt = self.export_ctxt;
221
222 need_wrapping = true;
223 v.push(Specifier::Namespace {
224 local: name.clone().into(),
225 all: true,
226 })
227 }
228 ModuleExportName::Str(..) => {
229 unimplemented!("module string names unimplemented")
230 }
231 #[cfg(swc_ast_unknown)]
232 _ => panic!("unable to access unknown nodes"),
233 };
234 }
235 ExportSpecifier::Default(d) => {
236 v.push(Specifier::Specific {
237 local: d.exported.clone().into(),
238 alias: Some(Id::new(atom!("default"), SyntaxContext::empty())),
239 });
240 }
241 ExportSpecifier::Named(n) => {
242 let orig = match &mut n.orig {
243 ModuleExportName::Ident(ident) => ident,
244 ModuleExportName::Str(..) => {
245 unimplemented!("module string names unimplemented")
246 }
247 #[cfg(swc_ast_unknown)]
248 _ => panic!("unable to access unknown nodes"),
249 };
250 if let Some((_, export_ctxt)) = ctxt {
251 orig.ctxt = export_ctxt;
252 }
253
254 match &mut n.exported {
255 Some(ModuleExportName::Ident(exported)) => {
256 exported.ctxt = self.export_ctxt;
257 }
258 Some(ModuleExportName::Str(..)) => {
259 unimplemented!("module string names unimplemented")
260 }
261 #[cfg(swc_ast_unknown)]
262 Some(_) => panic!("unable to access unknown nodes"),
263 None => {
264 let mut exported: Ident = orig.clone();
265 exported.ctxt = self.export_ctxt;
266 n.exported = Some(ModuleExportName::Ident(exported));
267 }
268 }
269
270 match &n.exported {
271 Some(ModuleExportName::Ident(exported)) => {
272 v.push(Specifier::Specific {
273 local: exported.clone().into(),
274 alias: Some(orig.clone().into()),
275 });
276 }
277 Some(ModuleExportName::Str(..)) => {
278 unimplemented!("module string names unimplemented")
279 }
280 _ => {
281 v.push(Specifier::Specific {
282 local: orig.clone().into(),
283 alias: None,
284 });
285 }
286 }
287 }
288 #[cfg(swc_ast_unknown)]
289 _ => panic!("unable to access unknown nodes"),
290 }
291 }
292
293 if need_wrapping {
294 let wrap_atom = named.src.as_ref().unwrap().value.to_atom_lossy();
295 self.mark_as_wrapping_required(wrap_atom.as_ref());
296 }
297 }
298
299 ModuleItem::ModuleDecl(ModuleDecl::ExportAll(all)) => {
300 let ctxt = {
301 let src_atom = all.src.value.to_atom_lossy();
302 self.ctxt_for(src_atom.as_ref())
303 };
304 if let Some((_, export_ctxt)) = ctxt {
305 ExportMetadata {
306 export_ctxt: Some(export_ctxt),
307 ..Default::default()
308 }
309 .encode(&mut all.with);
310 }
311
312 self.info.items.entry(Some(*all.src.clone())).or_default();
313 }
314 _ => {}
315 }
316 }
317}