swc_bundler/bundler/
export.rs

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    /// TODO: Support pattern like
21    ///     export const [a, b] = [1, 2]
22    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    /// Key is None if it's exported from the module itself.
46    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    /// Returns `(local, export)`.
72    fn ctxt_for(&self, src: &Atom) -> Option<(SyntaxContext, SyntaxContext)> {
73        // Don't apply mark if it's a core module.
74        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        // Don't apply mark if it's a core module.
94        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            // TODO: Optimize pure constants
124            //            ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
125            //                decl: Decl::Var(v),
126            //                ..
127            //            })) if v.kind == VarDeclKind::Const
128            //                && v.decls.iter().all(|v| {
129            //                    (match v.name {
130            //                        Pat::Ident(..) => true,
131            //                        _ => false,
132            //                    }) && (match v.init {
133            //                        Some(box Expr::Lit(..)) => true,
134            //                        _ => false,
135            //                    })
136            //                }) =>
137            //            {
138            //                self.info
139            //                    .pure_constants
140            //                    .extend(v.decls.into_iter().map(|decl| {
141            //                        let id = match decl.name {
142            //                            Pat::Ident(i) => i.into(),
143            //                            _ => unreachable!(),
144            //                        };
145            //                        let lit = match decl.init {
146            //                            Some(box Expr::Lit(l)) => l,
147            //                            _ => unreachable!(),
148            //                        };
149            //                        (id, lit)
150            //                    }));
151            //                return ModuleItem::Stmt(Stmt::Empty(EmptyStmt { span: DUMMY_SP }));
152            //            }
153            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}