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