1use rustc_hash::FxHashMap;
2use swc_common::{util::take::Take, DUMMY_SP};
3use swc_ecma_ast::*;
4
5use crate::option::CompressOptions;
6
7pub fn postcompress_optimizer(program: &mut Program, options: &CompressOptions) {
8 let Some(module) = program.as_mut_module() else {
9 return;
10 };
11
12 for item in &mut module.body {
13 let Some(m) = item.as_mut_module_decl() else {
14 continue;
15 };
16
17 if let ModuleDecl::ExportDefaultExpr(e) = m {
18 match &mut *e.expr {
19 Expr::Fn(f) => {
20 if f.ident.is_some() {
21 if options.top_level() {
22 *m = ExportDefaultDecl {
23 span: e.span,
24 decl: DefaultDecl::Fn(f.take()),
25 }
26 .into()
27 }
28 } else {
29 *m = ExportDefaultDecl {
30 span: e.span,
31 decl: DefaultDecl::Fn(f.take()),
32 }
33 .into()
34 }
35 }
36 Expr::Class(c) => {
37 if c.ident.is_some() {
38 if options.top_level() {
39 *m = ExportDefaultDecl {
40 span: e.span,
41 decl: DefaultDecl::Class(c.take()),
42 }
43 .into()
44 }
45 } else {
46 *m = ExportDefaultDecl {
47 span: e.span,
48 decl: DefaultDecl::Class(c.take()),
49 }
50 .into()
51 }
52 }
53 _ => (),
54 }
55 }
56 }
57
58 if options.merge_imports {
60 merge_imports_in_module(module);
61 }
62}
63
64#[derive(Debug, Clone, PartialEq, Eq, Hash)]
65struct ImportKey {
66 src: String,
67 type_only: bool,
68 phase: ImportPhase,
69 with_hash: Option<u64>,
71}
72
73impl ImportKey {
74 fn from_import_decl(decl: &ImportDecl) -> Self {
75 use std::{
76 collections::hash_map::DefaultHasher,
77 hash::{Hash, Hasher},
78 };
79
80 let with_hash = decl.with.as_ref().map(|w| {
81 let mut hasher = DefaultHasher::new();
82 format!("{w:?}").hash(&mut hasher);
84 hasher.finish()
85 });
86
87 Self {
88 src: decl.src.value.to_string_lossy().to_string(),
89 type_only: decl.type_only,
90 phase: decl.phase,
91 with_hash,
92 }
93 }
94}
95
96#[derive(Debug, Clone, PartialEq, Eq, Hash)]
98enum SpecifierKey {
99 Named(String, String, bool),
101 Default(String),
103 Namespace(String),
105}
106
107impl SpecifierKey {
108 fn from_specifier(spec: &ImportSpecifier) -> Self {
109 match spec {
110 ImportSpecifier::Named(named) => {
111 let imported = named
112 .imported
113 .as_ref()
114 .map(|n| match n {
115 ModuleExportName::Ident(id) => id.sym.to_string(),
116 ModuleExportName::Str(s) => s.value.to_string_lossy().to_string(),
117 })
118 .unwrap_or_else(|| named.local.sym.to_string());
119
120 SpecifierKey::Named(imported, named.local.sym.to_string(), named.is_type_only)
121 }
122 ImportSpecifier::Default(default) => {
123 SpecifierKey::Default(default.local.sym.to_string())
124 }
125 ImportSpecifier::Namespace(ns) => SpecifierKey::Namespace(ns.local.sym.to_string()),
126 }
127 }
128}
129
130fn merge_imports_in_module(module: &mut Module) {
135 let mut import_groups: FxHashMap<ImportKey, Vec<ImportDecl>> = FxHashMap::default();
137
138 for item in module.body.iter() {
139 if let ModuleItem::ModuleDecl(ModuleDecl::Import(import_decl)) = item {
140 if import_decl.specifiers.is_empty() {
142 continue;
143 }
144
145 let key = ImportKey::from_import_decl(import_decl);
146 import_groups
147 .entry(key)
148 .or_default()
149 .push(import_decl.clone());
150 }
151 }
152
153 module.body.retain(|item| {
155 if let ModuleItem::ModuleDecl(ModuleDecl::Import(import_decl)) = item {
156 if import_decl.specifiers.is_empty() {
158 return true;
159 }
160
161 let key = ImportKey::from_import_decl(import_decl);
162 import_groups.get(&key).map_or(true, |v| v.len() <= 1)
164 } else {
165 true
166 }
167 });
168
169 for (key, import_decls) in import_groups.iter() {
171 if import_decls.len() <= 1 {
172 continue;
174 }
175
176 let merged_imports = merge_import_decls(import_decls, key);
177 for merged in merged_imports {
178 module
179 .body
180 .push(ModuleItem::ModuleDecl(ModuleDecl::Import(merged)));
181 }
182 }
183}
184
185fn merge_import_decls(decls: &[ImportDecl], key: &ImportKey) -> Vec<ImportDecl> {
189 let mut default_spec: Option<ImportSpecifier> = None;
190 let mut namespace_spec: Option<ImportSpecifier> = None;
191 let mut named_specs: Vec<ImportSpecifier> = Vec::new();
192 let mut seen_named: FxHashMap<SpecifierKey, ()> = FxHashMap::default();
193
194 let first_decl = &decls[0];
195 let span = first_decl.span;
196
197 for decl in decls {
199 for spec in &decl.specifiers {
200 match spec {
201 ImportSpecifier::Default(_) => {
202 if default_spec.is_none() {
203 default_spec = Some(spec.clone());
204 }
205 }
206 ImportSpecifier::Namespace(_) => {
207 if namespace_spec.is_none() {
208 namespace_spec = Some(spec.clone());
209 }
210 }
211 ImportSpecifier::Named(_) => {
212 let spec_key = SpecifierKey::from_specifier(spec);
213 if let std::collections::hash_map::Entry::Vacant(e) = seen_named.entry(spec_key)
214 {
215 e.insert(());
216 named_specs.push(spec.clone());
217 }
218 }
219 }
220 }
221 }
222
223 let mut result = Vec::new();
224
225 if let Some(namespace) = namespace_spec {
235 if default_spec.is_some() {
236 if named_specs.is_empty() {
237 result.push(ImportDecl {
239 span,
240 specifiers: vec![default_spec.unwrap(), namespace],
241 src: Box::new(Str {
242 span: DUMMY_SP,
243 value: key.src.clone().into(),
244 raw: None,
245 }),
246 type_only: key.type_only,
247 with: first_decl.with.clone(),
248 phase: key.phase,
249 });
250 } else {
251 let mut specs = vec![default_spec.unwrap()];
254 specs.extend(named_specs);
255 result.push(ImportDecl {
256 span,
257 specifiers: specs,
258 src: Box::new(Str {
259 span: DUMMY_SP,
260 value: key.src.clone().into(),
261 raw: None,
262 }),
263 type_only: key.type_only,
264 with: first_decl.with.clone(),
265 phase: key.phase,
266 });
267 result.push(ImportDecl {
269 span,
270 specifiers: vec![namespace],
271 src: Box::new(Str {
272 span: DUMMY_SP,
273 value: key.src.clone().into(),
274 raw: None,
275 }),
276 type_only: key.type_only,
277 with: first_decl.with.clone(),
278 phase: key.phase,
279 });
280 }
281 } else if named_specs.is_empty() {
282 result.push(ImportDecl {
284 span,
285 specifiers: vec![namespace],
286 src: Box::new(Str {
287 span: DUMMY_SP,
288 value: key.src.clone().into(),
289 raw: None,
290 }),
291 type_only: key.type_only,
292 with: first_decl.with.clone(),
293 phase: key.phase,
294 });
295 } else {
296 result.push(ImportDecl {
299 span,
300 specifiers: vec![namespace],
301 src: Box::new(Str {
302 span: DUMMY_SP,
303 value: key.src.clone().into(),
304 raw: None,
305 }),
306 type_only: key.type_only,
307 with: first_decl.with.clone(),
308 phase: key.phase,
309 });
310 result.push(ImportDecl {
312 span,
313 specifiers: named_specs,
314 src: Box::new(Str {
315 span: DUMMY_SP,
316 value: key.src.clone().into(),
317 raw: None,
318 }),
319 type_only: key.type_only,
320 with: first_decl.with.clone(),
321 phase: key.phase,
322 });
323 }
324 } else {
325 let mut specs = Vec::new();
327 if let Some(default) = default_spec {
328 specs.push(default);
329 }
330 specs.extend(named_specs);
331
332 result.push(ImportDecl {
333 span,
334 specifiers: specs,
335 src: Box::new(Str {
336 span: DUMMY_SP,
337 value: key.src.clone().into(),
338 raw: None,
339 }),
340 type_only: key.type_only,
341 with: first_decl.with.clone(),
342 phase: key.phase,
343 });
344 }
345
346 result
347}