next_custom_transforms/transforms/
cjs_optimizer.rs1use rustc_hash::{FxHashMap, FxHashSet};
2use serde::Deserialize;
3use swc_core::{
4 atoms::{Wtf8Atom, atom},
5 common::{DUMMY_SP, SyntaxContext, util::take::Take},
6 ecma::{
7 ast::{
8 CallExpr, Callee, Decl, Expr, Id, Ident, IdentName, Lit, MemberExpr, MemberProp,
9 Module, ModuleItem, Pat, Script, Stmt, VarDecl, VarDeclKind, VarDeclarator,
10 },
11 atoms::Atom,
12 utils::{ExprFactory, IdentRenamer, prepend_stmts, private_ident},
13 visit::{Visit, VisitMut, VisitMutWith, VisitWith, noop_visit_mut_type, noop_visit_type},
14 },
15};
16
17pub fn cjs_optimizer(config: Config, unresolved_ctxt: SyntaxContext) -> CjsOptimizer {
18 CjsOptimizer {
19 data: State::default(),
20 packages: config.packages,
21 unresolved_ctxt,
22 }
23}
24
25#[derive(Clone, Debug, Deserialize)]
26pub struct Config {
27 pub packages: FxHashMap<Atom, PackageConfig>,
28}
29
30#[derive(Clone, Debug, Deserialize)]
31#[serde(rename_all = "camelCase")]
32pub struct PackageConfig {
33 pub transforms: FxHashMap<Atom, Atom>,
34}
35
36pub struct CjsOptimizer {
37 data: State,
38 packages: FxHashMap<Atom, PackageConfig>,
39 unresolved_ctxt: SyntaxContext,
40}
41
42#[derive(Debug, Default)]
43struct State {
44 imports: FxHashMap<Id, ImportRecord>,
48
49 replaced: FxHashMap<(Wtf8Atom, Atom), Id>,
51
52 extra_stmts: Vec<Stmt>,
53
54 rename_map: FxHashMap<Id, Id>,
55
56 ignored: FxHashSet<Id>,
58
59 is_prepass: bool,
60}
61
62#[derive(Debug)]
63struct ImportRecord {
64 module_specifier: Wtf8Atom,
65}
66
67impl CjsOptimizer {
68 fn should_rewrite(&self, module_specifier: &Wtf8Atom) -> Option<&FxHashMap<Atom, Atom>> {
69 self.packages.get(module_specifier).map(|v| &v.transforms)
70 }
71}
72
73impl VisitMut for CjsOptimizer {
74 noop_visit_mut_type!();
75
76 fn visit_mut_module_items(&mut self, stmts: &mut Vec<ModuleItem>) {
77 self.data.is_prepass = true;
78 stmts.visit_mut_children_with(self);
79 self.data.is_prepass = false;
80 stmts.visit_mut_children_with(self);
81 }
82
83 fn visit_mut_expr(&mut self, e: &mut Expr) {
84 e.visit_mut_children_with(self);
85
86 if let Expr::Member(n) = e
87 && let MemberProp::Ident(prop) = &n.prop
88 && let Expr::Ident(obj) = &*n.obj
89 {
90 let key = obj.to_id();
91 if self.data.ignored.contains(&key) {
92 return;
93 }
94
95 if let Some(record) = self.data.imports.get(&key) {
96 let mut replaced = false;
97
98 let new_id = self
99 .data
100 .replaced
101 .entry((record.module_specifier.clone(), prop.sym.clone()))
102 .or_insert_with(|| private_ident!(prop.sym.clone()).to_id())
103 .clone();
104
105 if let Some(map) = self.should_rewrite(&record.module_specifier)
106 && let Some(renamed) = map.get(&prop.sym)
107 {
108 replaced = true;
109 if !self.data.is_prepass {
110 let var = VarDeclarator {
112 span: DUMMY_SP,
113 name: Pat::Ident(new_id.clone().into()),
114 init: Some(Box::new(Expr::Member(MemberExpr {
115 span: DUMMY_SP,
116 obj: Box::new(Expr::Call(CallExpr {
117 span: DUMMY_SP,
118 callee: Ident::new(
119 atom!("require"),
120 DUMMY_SP,
121 self.unresolved_ctxt,
122 )
123 .as_callee(),
124 args: vec![
125 Expr::Lit(Lit::Str(renamed.clone().into())).as_arg(),
126 ],
127 ..Default::default()
128 })),
129 prop: MemberProp::Ident(IdentName::new(prop.sym.clone(), DUMMY_SP)),
130 }))),
131 definite: false,
132 };
133
134 if !self.data.extra_stmts.iter().any(|s| {
135 if let Stmt::Decl(Decl::Var(v)) = &s {
136 v.decls.iter().any(|d| d.name == var.name)
137 } else {
138 false
139 }
140 }) {
141 self.data
142 .extra_stmts
143 .push(Stmt::Decl(Decl::Var(Box::new(VarDecl {
144 span: DUMMY_SP,
145 kind: VarDeclKind::Const,
146 decls: vec![var],
147 ..Default::default()
148 }))));
149 }
150
151 *e = Expr::Ident(new_id.into());
152 }
153 }
154
155 if !replaced {
156 self.data.ignored.insert(key);
157 }
158 }
159 }
160 }
161
162 fn visit_mut_module(&mut self, n: &mut Module) {
163 n.visit_children_with(&mut Analyzer {
164 data: &mut self.data,
165 in_member_or_var: false,
166 });
167
168 n.visit_mut_children_with(self);
169
170 prepend_stmts(
171 &mut n.body,
172 self.data.extra_stmts.drain(..).map(ModuleItem::Stmt),
173 );
174
175 n.visit_mut_children_with(&mut IdentRenamer::new(&self.data.rename_map));
176 }
177
178 fn visit_mut_script(&mut self, n: &mut Script) {
179 n.visit_children_with(&mut Analyzer {
180 data: &mut self.data,
181 in_member_or_var: false,
182 });
183
184 n.visit_mut_children_with(self);
185
186 prepend_stmts(&mut n.body, self.data.extra_stmts.drain(..));
187
188 n.visit_mut_children_with(&mut IdentRenamer::new(&self.data.rename_map));
189 }
190
191 fn visit_mut_stmt(&mut self, n: &mut Stmt) {
192 n.visit_mut_children_with(self);
193
194 if let Stmt::Decl(Decl::Var(v)) = n
195 && v.decls.is_empty()
196 {
197 n.take();
198 }
199 }
200
201 fn visit_mut_var_declarator(&mut self, n: &mut VarDeclarator) {
202 n.visit_mut_children_with(self);
203
204 if let Some(Expr::Call(CallExpr {
206 callee: Callee::Expr(callee),
207 args,
208 ..
209 })) = n.init.as_deref()
210 && let Expr::Ident(ident) = &**callee
211 && ident.ctxt == self.unresolved_ctxt
212 && ident.sym == *"require"
213 && let Some(arg) = args.first()
214 && let Expr::Lit(Lit::Str(v)) = &*arg.expr
215 {
216 if let Pat::Ident(name) = &n.name
219 && self.should_rewrite(&v.value).is_some()
220 {
221 let key = name.to_id();
222
223 if !self.data.is_prepass {
224 if !self.data.ignored.contains(&key) {
225 n.name.take();
227 }
228 } else {
229 self.data.imports.insert(
230 key,
231 ImportRecord {
232 module_specifier: v.value.clone(),
233 },
234 );
235 }
236 }
237 }
238 }
239
240 fn visit_mut_var_declarators(&mut self, n: &mut Vec<VarDeclarator>) {
241 n.visit_mut_children_with(self);
242
243 n.retain(|v| !v.name.is_invalid());
245 }
246}
247
248struct Analyzer<'a> {
249 in_member_or_var: bool,
250 data: &'a mut State,
251}
252
253impl Visit for Analyzer<'_> {
254 noop_visit_type!();
255
256 fn visit_var_declarator(&mut self, n: &VarDeclarator) {
257 let mut safe_to_ignore = false;
258
259 if let Some(Expr::Call(CallExpr {
261 callee: Callee::Expr(callee),
262 ..
263 })) = n.init.as_deref()
264 && let Expr::Ident(ident) = &**callee
265 && ident.sym == *"require"
266 {
267 safe_to_ignore = true;
268 }
269
270 if safe_to_ignore {
271 self.in_member_or_var = true;
272 n.visit_children_with(self);
273 self.in_member_or_var = false;
274 } else {
275 n.visit_children_with(self);
276 }
277 }
278
279 fn visit_member_expr(&mut self, e: &MemberExpr) {
280 self.in_member_or_var = true;
281 e.visit_children_with(self);
282 self.in_member_or_var = false;
283
284 if let (Expr::Ident(obj), MemberProp::Computed(..)) = (&*e.obj, &e.prop) {
285 self.data.ignored.insert(obj.to_id());
286 }
287 }
288
289 fn visit_ident(&mut self, i: &Ident) {
290 i.visit_children_with(self);
291 if !self.in_member_or_var {
292 self.data.ignored.insert(i.to_id());
293 }
294 }
295}