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