dbg_swc/es/minifier/
ensure_size.rs

1use std::{
2    path::{Path, PathBuf},
3    sync::Arc,
4};
5
6use anyhow::{Context, Result};
7use clap::Args;
8use par_iter::prelude::*;
9use swc_common::{
10    errors::{ColorConfig, Handler, HANDLER},
11    SourceFile, SourceMap, GLOBALS,
12};
13use tracing::info;
14
15use crate::util::{
16    all_js_files, gzipped_size,
17    minifier::{get_esbuild_output, get_minified, get_terser_output},
18    print_js, wrap_task,
19};
20
21/// [Experimental] Ensure that we are performing better than other minification
22/// tools.
23#[derive(Debug, Args)]
24pub struct EnsureSize {
25    #[clap(long)]
26    pub no_terser: bool,
27
28    #[clap(long)]
29    pub no_esbuild: bool,
30
31    /// This can be a directyory or a file.
32    ///
33    /// If this is a directory, all `.js` files in it will be verified.
34    pub path: PathBuf,
35}
36
37impl EnsureSize {
38    pub fn run(self, cm: Arc<SourceMap>) -> Result<()> {
39        let all_files = all_js_files(&self.path)?;
40
41        info!("Using {} files", all_files.len());
42
43        let mut results = GLOBALS.with(|globals| {
44            all_files
45                .par_iter()
46                .map(|js_file| GLOBALS.set(globals, || self.check_file(cm.clone(), js_file)))
47                .filter_map(|v| v.transpose())
48                .collect::<Result<Vec<_>>>()
49        })?;
50
51        results.sort_by_key(|f| {
52            if let Some(terser) = &f.terser {
53                f.swc.mangled_size as isize - terser.mangled_size as isize
54            } else {
55                0
56            }
57        });
58
59        for f in &results {
60            if let Some(terser) = &f.terser {
61                if f.swc.mangled_size > terser.mangled_size
62                    || f.swc.no_mangle_size > terser.no_mangle_size
63                    || f.swc.gzipped_size > terser.gzipped_size
64                {
65                    println!();
66                    println!("{}", f.fm.name);
67                }
68                if f.swc.gzipped_size > terser.gzipped_size {
69                    println!("  Gzipped");
70                    println!("    swc: {} bytes", f.swc.gzipped_size);
71                    println!("    terser: {} bytes", terser.gzipped_size);
72                }
73
74                if f.swc.mangled_size > terser.mangled_size {
75                    println!("  Mangled");
76                    println!("    swc: {} bytes", f.swc.mangled_size);
77                    println!("    terser: {} bytes", terser.mangled_size);
78                }
79
80                if f.swc.no_mangle_size > terser.no_mangle_size {
81                    println!("  No-mangle");
82                    println!("    swc: {} bytes", f.swc.no_mangle_size);
83                    println!("    terser: {} bytes", terser.no_mangle_size);
84                }
85            }
86        }
87        {
88            let swc_total = results.iter().map(|f| f.swc.mangled_size).sum::<usize>();
89            let terser_total = results
90                .iter()
91                .flat_map(|f| f.terser.map(|v| v.mangled_size))
92                .sum::<usize>();
93
94            println!("Total");
95            println!("  swc: {swc_total} bytes");
96            println!("  terser: {terser_total} bytes");
97            println!("  Size ratio: {}", swc_total as f64 / terser_total as f64);
98
99            let swc_smaller_file_count = results
100                .iter()
101                .filter(|f| {
102                    if let Some(terser) = &f.terser {
103                        f.swc.mangled_size <= terser.mangled_size
104                    } else {
105                        false
106                    }
107                })
108                .count();
109            println!(
110                "swc produced smaller or equal output for {} files out of {} files, {:.2}%",
111                swc_smaller_file_count,
112                all_files.len(),
113                100.0 * swc_smaller_file_count as f64 / results.len() as f64
114            );
115        }
116        {
117            let swc_total = results.iter().map(|f| f.swc.gzipped_size).sum::<usize>();
118            let terser_total = results
119                .iter()
120                .flat_map(|f| f.terser.map(|v| v.gzipped_size))
121                .sum::<usize>();
122
123            println!("Total (gzipped)");
124            println!("  swc: {swc_total} bytes");
125            println!("  terser: {terser_total} bytes");
126            println!("  Size ratio: {}", swc_total as f64 / terser_total as f64);
127
128            let swc_smaller_file_count = results
129                .iter()
130                .filter(|f| {
131                    if let Some(terser) = &f.terser {
132                        f.swc.gzipped_size <= terser.gzipped_size
133                    } else {
134                        false
135                    }
136                })
137                .count();
138            println!(
139                "swc produced smaller or equal output for {} files out of {} files, {:.2}%",
140                swc_smaller_file_count,
141                all_files.len(),
142                100.0 * swc_smaller_file_count as f64 / results.len() as f64
143            );
144        }
145
146        Ok(())
147    }
148
149    fn check_file(&self, cm: Arc<SourceMap>, js_file: &Path) -> Result<Option<FileSize>> {
150        wrap_task(|| {
151            info!("Checking {}", js_file.display());
152
153            let fm = cm.load_file(js_file).context("failed to load file")?;
154            let handler =
155                Handler::with_tty_emitter(ColorConfig::Never, true, false, Some(cm.clone()));
156            HANDLER.set(&handler, || {
157                let code_mangled = {
158                    let minified_mangled = get_minified(cm.clone(), js_file, true, true)?;
159
160                    print_js(cm.clone(), &minified_mangled.module, true)
161                        .context("failed to convert ast to code")?
162                };
163
164                let swc_no_mangle = {
165                    let minified_no_mangled = get_minified(cm.clone(), js_file, true, false)?;
166
167                    print_js(cm, &minified_no_mangled.module, true)
168                        .context("failed to convert ast to code")?
169                };
170
171                // eprintln!("The output size of swc minifier: {}", code_mangled.len());
172
173                let mut file_size = FileSize {
174                    fm,
175                    swc: MinifierOutput {
176                        mangled_size: code_mangled.len(),
177                        no_mangle_size: swc_no_mangle.len(),
178                        gzipped_size: gzipped_size(&code_mangled),
179                    },
180                    terser: Default::default(),
181                    esbuild: Default::default(),
182                };
183
184                if !self.no_terser {
185                    let terser_mangled = get_terser_output(js_file, true, true)?;
186                    let terser_no_mangle = get_terser_output(js_file, true, false)?;
187
188                    file_size.terser = Some(MinifierOutput {
189                        mangled_size: terser_mangled.len(),
190                        no_mangle_size: terser_no_mangle.len(),
191                        gzipped_size: gzipped_size(&terser_mangled),
192                    });
193                }
194
195                if !self.no_esbuild {
196                    let esbuild_mangled = get_esbuild_output(js_file, true)?;
197                    let esbuild_no_mangle = get_esbuild_output(js_file, false)?;
198
199                    file_size.esbuild = Some(MinifierOutput {
200                        mangled_size: esbuild_mangled.len(),
201                        no_mangle_size: esbuild_no_mangle.len(),
202                        gzipped_size: gzipped_size(&esbuild_mangled),
203                    });
204                }
205
206                if file_size.terser.is_none() && file_size.esbuild.is_none() {
207                    return Ok(None);
208                }
209
210                Ok(Some(file_size))
211            })
212        })
213        .with_context(|| format!("failed to check file: {}", js_file.display()))
214    }
215}
216
217#[allow(unused)]
218#[derive(Debug)]
219struct FileSize {
220    fm: Arc<SourceFile>,
221
222    swc: MinifierOutput,
223
224    terser: Option<MinifierOutput>,
225
226    esbuild: Option<MinifierOutput>,
227}
228
229#[allow(unused)]
230#[derive(Debug, Clone, Copy)]
231struct MinifierOutput {
232    mangled_size: usize,
233    no_mangle_size: usize,
234
235    /// Minify + mangle + gzip
236    gzipped_size: usize,
237}