swc_ecma_transforms_optimization/
const_modules.rs

1use std::{
2    collections::{HashMap, HashSet},
3    sync::Arc,
4};
5
6use dashmap::DashMap;
7use once_cell::sync::Lazy;
8use rustc_hash::{FxBuildHasher, FxHashMap};
9use swc_atoms::{atom, Atom};
10use swc_common::{
11    errors::HANDLER,
12    sync::Lrc,
13    util::{move_map::MoveMap, take::Take},
14    FileName, SourceMap,
15};
16use swc_ecma_ast::*;
17use swc_ecma_parser::parse_file_as_expr;
18use swc_ecma_utils::drop_span;
19use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith};
20
21pub fn const_modules(
22    cm: Lrc<SourceMap>,
23    globals: FxHashMap<Atom, FxHashMap<Atom, String>>,
24) -> impl Pass {
25    visit_mut_pass(ConstModules {
26        globals: globals
27            .into_iter()
28            .map(|(src, map)| {
29                let map = map
30                    .into_iter()
31                    .map(|(key, value)| {
32                        let value = parse_option(&cm, &key, value);
33
34                        (key, value)
35                    })
36                    .collect();
37
38                (src, map)
39            })
40            .collect(),
41        scope: Default::default(),
42    })
43}
44
45fn parse_option(cm: &SourceMap, name: &str, src: String) -> Arc<Expr> {
46    static CACHE: Lazy<DashMap<String, Arc<Expr>, FxBuildHasher>> = Lazy::new(DashMap::default);
47
48    let fm = cm.new_source_file(
49        FileName::Internal(format!("<const-module-{}.js>", name)).into(),
50        src,
51    );
52    if let Some(expr) = CACHE.get(&**fm.src) {
53        return expr.clone();
54    }
55
56    let expr = parse_file_as_expr(
57        &fm,
58        Default::default(),
59        Default::default(),
60        None,
61        &mut Vec::new(),
62    )
63    .map_err(|e| {
64        if HANDLER.is_set() {
65            HANDLER.with(|h| e.into_diagnostic(h).emit())
66        }
67    })
68    .map(drop_span)
69    .unwrap_or_else(|()| {
70        panic!(
71            "failed to parse jsx option {}: '{}' is not an expression",
72            name, fm.src,
73        )
74    });
75
76    let expr = Arc::new(*expr);
77
78    CACHE.insert((*fm.src).clone(), expr.clone());
79
80    expr
81}
82
83struct ConstModules {
84    globals: HashMap<Atom, HashMap<Atom, Arc<Expr>>>,
85    scope: Scope,
86}
87
88#[derive(Default)]
89struct Scope {
90    namespace: HashSet<Id>,
91    imported: HashMap<Atom, Arc<Expr>>,
92}
93
94impl VisitMut for ConstModules {
95    noop_visit_mut_type!(fail);
96
97    fn visit_mut_module_items(&mut self, n: &mut Vec<ModuleItem>) {
98        *n = n.take().move_flat_map(|item| match item {
99            ModuleItem::ModuleDecl(ModuleDecl::Import(import)) => {
100                let entry = self.globals.get(&import.src.value);
101                if let Some(entry) = entry {
102                    for s in &import.specifiers {
103                        match *s {
104                            ImportSpecifier::Named(ref s) => {
105                                let imported = s
106                                    .imported
107                                    .as_ref()
108                                    .map(|m| match m {
109                                        ModuleExportName::Ident(id) => &id.sym,
110                                        ModuleExportName::Str(s) => &s.value,
111                                    })
112                                    .unwrap_or(&s.local.sym);
113                                let value = entry.get(imported).cloned().unwrap_or_else(|| {
114                                    panic!(
115                                        "The requested const_module `{}` does not provide an \
116                                         export named `{}`",
117                                        import.src.value, imported
118                                    )
119                                });
120                                self.scope.imported.insert(imported.clone(), value);
121                            }
122                            ImportSpecifier::Namespace(ref s) => {
123                                self.scope.namespace.insert(s.local.to_id());
124                            }
125                            ImportSpecifier::Default(ref s) => {
126                                let imported = &s.local.sym;
127                                let default_import_key = atom!("default");
128                                let value =
129                                    entry.get(&default_import_key).cloned().unwrap_or_else(|| {
130                                        panic!(
131                                            "The requested const_module `{}` does not provide \
132                                             default export",
133                                            import.src.value
134                                        )
135                                    });
136                                self.scope.imported.insert(imported.clone(), value);
137                            }
138                        };
139                    }
140
141                    None
142                } else {
143                    Some(import.into())
144                }
145            }
146            _ => Some(item),
147        });
148
149        if self.scope.imported.is_empty() && self.scope.namespace.is_empty() {
150            return;
151        }
152
153        n.visit_mut_children_with(self);
154    }
155
156    fn visit_mut_expr(&mut self, n: &mut Expr) {
157        match n {
158            Expr::Ident(ref id @ Ident { ref sym, .. }) => {
159                if let Some(value) = self.scope.imported.get(sym) {
160                    *n = (**value).clone();
161                    return;
162                }
163
164                if self.scope.namespace.contains(&id.to_id()) {
165                    panic!(
166                        "The const_module namespace `{}` cannot be used without member accessor",
167                        sym
168                    )
169                }
170            }
171            Expr::Member(MemberExpr { obj, prop, .. }) if obj.is_ident() => {
172                if let Some(module_name) = obj
173                    .as_ident()
174                    .filter(|member_obj| self.scope.namespace.contains(&member_obj.to_id()))
175                    .map(|member_obj| &member_obj.sym)
176                {
177                    let imported_name = match prop {
178                        MemberProp::Ident(ref id) => &id.sym,
179                        MemberProp::Computed(ref p) => match &*p.expr {
180                            Expr::Lit(Lit::Str(s)) => &s.value,
181                            _ => return,
182                        },
183                        MemberProp::PrivateName(..) => return,
184                    };
185
186                    let value = self
187                        .globals
188                        .get(module_name)
189                        .and_then(|entry| entry.get(imported_name))
190                        .unwrap_or_else(|| {
191                            panic!(
192                                "The requested const_module `{}` does not provide an export named \
193                                 `{}`",
194                                module_name, imported_name
195                            )
196                        });
197
198                    *n = (**value).clone();
199                } else {
200                    n.visit_mut_children_with(self);
201                }
202            }
203            _ => {
204                n.visit_mut_children_with(self);
205            }
206        };
207    }
208
209    fn visit_mut_prop(&mut self, n: &mut Prop) {
210        match n {
211            Prop::Shorthand(id) => {
212                if let Some(value) = self.scope.imported.get(&id.sym) {
213                    *n = Prop::KeyValue(KeyValueProp {
214                        key: id.take().into(),
215                        value: Box::new((**value).clone()),
216                    });
217                    return;
218                }
219
220                if self.scope.namespace.contains(&id.to_id()) {
221                    panic!(
222                        "The const_module namespace `{}` cannot be used without member accessor",
223                        id.sym
224                    )
225                }
226            }
227            _ => n.visit_mut_children_with(self),
228        }
229    }
230}