From 7b9a781047ab833c4e8a8cf3e949596a53267bd4 Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Mon, 11 Aug 2025 15:51:26 +0000 Subject: [PATCH] feat(minifier): return total number of iterations for debugging (#12995) --- crates/oxc_minifier/src/compressor.rs | 5 +++-- crates/oxc_minifier/src/lib.rs | 28 +++++++++++++++---------- crates/oxc_minifier/src/peephole/mod.rs | 3 ++- tasks/minsize/minsize.snap | 26 +++++++++++------------ tasks/minsize/src/lib.rs | 24 ++++++++++++--------- 5 files changed, 49 insertions(+), 37 deletions(-) diff --git a/crates/oxc_minifier/src/compressor.rs b/crates/oxc_minifier/src/compressor.rs index 10b9be95d6cbd..577fccb2aa8ef 100644 --- a/crates/oxc_minifier/src/compressor.rs +++ b/crates/oxc_minifier/src/compressor.rs @@ -23,18 +23,19 @@ impl<'a> Compressor<'a> { self.build_with_scoping(program, scoping, options); } + /// Returns total number of iterations ran. pub fn build_with_scoping( self, program: &mut Program<'a>, scoping: Scoping, options: CompressOptions, - ) { + ) -> u8 { let state = MinifierState::new(program.source_type, options); let mut ctx = ReusableTraverseCtx::new(state, scoping, self.allocator); let normalize_options = NormalizeOptions { convert_while_to_fors: true, convert_const_to_let: true }; Normalize::new(normalize_options).build(program, &mut ctx); - PeepholeOptimizations::new().run_in_loop(program, &mut ctx); + PeepholeOptimizations::new().run_in_loop(program, &mut ctx) } pub fn dead_code_elimination(self, program: &mut Program<'a>, options: CompressOptions) { diff --git a/crates/oxc_minifier/src/lib.rs b/crates/oxc_minifier/src/lib.rs index 4adcd7b38abbd..039ecada50385 100644 --- a/crates/oxc_minifier/src/lib.rs +++ b/crates/oxc_minifier/src/lib.rs @@ -16,7 +16,7 @@ mod tester; use oxc_allocator::Allocator; use oxc_ast::ast::Program; use oxc_mangler::Mangler; -use oxc_semantic::{Scoping, SemanticBuilder, Stats}; +use oxc_semantic::{Scoping, SemanticBuilder}; pub use oxc_mangler::{MangleOptions, MangleOptionsKeepNames}; @@ -36,6 +36,9 @@ impl Default for MinifierOptions { pub struct MinifierReturn { pub scoping: Option, + + /// Total number of iterations ran. Useful for debugging performance issues. + pub iterations: u8, } pub struct Minifier { @@ -48,15 +51,18 @@ impl Minifier { } pub fn build<'a>(self, allocator: &'a Allocator, program: &mut Program<'a>) -> MinifierReturn { - let stats = if let Some(options) = self.options.compress { - let semantic = SemanticBuilder::new().build(program).semantic; - let stats = semantic.stats(); - let scoping = semantic.into_scoping(); - Compressor::new(allocator).build_with_scoping(program, scoping, options); - stats - } else { - Stats::default() - }; + let (stats, iterations) = self + .options + .compress + .map(|options| { + let semantic = SemanticBuilder::new().build(program).semantic; + let stats = semantic.stats(); + let scoping = semantic.into_scoping(); + let iterations = + Compressor::new(allocator).build_with_scoping(program, scoping, options); + (stats, iterations) + }) + .unwrap_or_default(); let scoping = self.options.mangle.map(|options| { let mut semantic = SemanticBuilder::new() .with_stats(stats) @@ -66,6 +72,6 @@ impl Minifier { Mangler::default().with_options(options).build_with_semantic(&mut semantic, program); semantic.into_scoping() }); - MinifierReturn { scoping } + MinifierReturn { scoping, iterations } } } diff --git a/crates/oxc_minifier/src/peephole/mod.rs b/crates/oxc_minifier/src/peephole/mod.rs index 6386b87247bac..a95e44d97402b 100644 --- a/crates/oxc_minifier/src/peephole/mod.rs +++ b/crates/oxc_minifier/src/peephole/mod.rs @@ -59,7 +59,7 @@ impl<'a> PeepholeOptimizations { &mut self, program: &mut Program<'a>, ctx: &mut ReusableTraverseCtx<'a, MinifierState<'a>>, - ) { + ) -> u8 { loop { self.changed = false; self.build(program, ctx); @@ -72,6 +72,7 @@ impl<'a> PeepholeOptimizations { } self.iteration += 1; } + self.iteration } pub fn commutative_pair<'x, A, F, G, RetF: 'x, RetG: 'x>( diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 7e0572229c4f0..cc57bf6d454d3 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -1,27 +1,27 @@ | Oxc | ESBuild | Oxc | ESBuild | -Original | minified | minified | gzip | gzip | Fixture +Original | minified | minified | gzip | gzip | Iterations | File ------------------------------------------------------------------------------------- -72.14 kB | 23.47 kB | 23.70 kB | 8.47 kB | 8.54 kB | react.development.js +72.14 kB | 23.47 kB | 23.70 kB | 8.47 kB | 8.54 kB | 2 | react.development.js -173.90 kB | 59.48 kB | 59.82 kB | 19.18 kB | 19.33 kB | moment.js +173.90 kB | 59.48 kB | 59.82 kB | 19.18 kB | 19.33 kB | 2 | moment.js -287.63 kB | 89.34 kB | 90.07 kB | 30.94 kB | 31.95 kB | jquery.js +287.63 kB | 89.34 kB | 90.07 kB | 30.94 kB | 31.95 kB | 2 | jquery.js -342.15 kB | 117.22 kB | 118.14 kB | 43.27 kB | 44.37 kB | vue.js +342.15 kB | 117.22 kB | 118.14 kB | 43.27 kB | 44.37 kB | 2 | vue.js -544.10 kB | 71.38 kB | 72.48 kB | 25.85 kB | 26.20 kB | lodash.js +544.10 kB | 71.38 kB | 72.48 kB | 25.85 kB | 26.20 kB | 2 | lodash.js -555.77 kB | 270.87 kB | 270.13 kB | 88.23 kB | 90.80 kB | d3.js +555.77 kB | 270.87 kB | 270.13 kB | 88.23 kB | 90.80 kB | 2 | d3.js -1.01 MB | 440.04 kB | 458.89 kB | 122.28 kB | 126.71 kB | bundle.min.js +1.01 MB | 440.04 kB | 458.89 kB | 122.28 kB | 126.71 kB | 2 | bundle.min.js -1.25 MB | 646.98 kB | 646.76 kB | 160.27 kB | 163.73 kB | three.js +1.25 MB | 646.98 kB | 646.76 kB | 160.27 kB | 163.73 kB | 2 | three.js -2.14 MB | 717.51 kB | 724.14 kB | 161.88 kB | 181.07 kB | victory.js +2.14 MB | 717.51 kB | 724.14 kB | 161.88 kB | 181.07 kB | 2 | victory.js -3.20 MB | 1.01 MB | 1.01 MB | 324.08 kB | 331.56 kB | echarts.js +3.20 MB | 1.01 MB | 1.01 MB | 324.08 kB | 331.56 kB | 2 | echarts.js -6.69 MB | 2.25 MB | 2.31 MB | 463.12 kB | 488.28 kB | antd.js +6.69 MB | 2.25 MB | 2.31 MB | 463.12 kB | 488.28 kB | 3 | antd.js -10.95 MB | 3.35 MB | 3.49 MB | 860.97 kB | 915.50 kB | typescript.js +10.95 MB | 3.35 MB | 3.49 MB | 860.97 kB | 915.50 kB | 2 | typescript.js diff --git a/tasks/minsize/src/lib.rs b/tasks/minsize/src/lib.rs index 995044e11d12c..950879b6f4182 100644 --- a/tasks/minsize/src/lib.rs +++ b/tasks/minsize/src/lib.rs @@ -93,12 +93,14 @@ pub fn run() -> Result<(), io::Error> { .unwrap(); writeln!( out, - "{:width$} | {:width$} | {:width$} | {:width$} | {:width$} | Fixture", + "{:width$} | {:width$} | {:width$} | {:width$} | {:width$} | {:width$} | {:width$}", "Original", "minified", "minified", "gzip", "gzip", + "Iterations", + "File", width = width, ) .unwrap(); @@ -116,18 +118,19 @@ pub fn run() -> Result<(), io::Error> { let save_path = Path::new("./target/minifier").join(marker); for file in files.files() { - let minified = minify_twice(file, options); + let (minified, iterations) = minify_twice(file, options); fs::create_dir_all(&save_path).unwrap(); fs::write(save_path.join(&file.file_name), &minified).unwrap(); let s = format!( - "{:width$} | {:width$} | {:width$} | {:width$} | {:width$} | {:width$}\n\n", + "{:width$} | {:width$} | {:width$} | {:width$} | {:width$} | {:width$} | {:width$} \n\n", format_size(file.source_text.len(), DECIMAL), format_size(minified.len(), DECIMAL), targets[file.file_name.as_str()], format_size(gzip_size(&minified), DECIMAL), gzip_targets[file.file_name.as_str()], + iterations, &file.file_name, width = width ); @@ -144,15 +147,15 @@ pub fn run() -> Result<(), io::Error> { Ok(()) } -fn minify_twice(file: &TestFile, options: Options) -> String { +fn minify_twice(file: &TestFile, options: Options) -> (String, u8) { let source_type = SourceType::cjs(); - let code1 = minify(&file.source_text, source_type, options); - let code2 = minify(&code1, source_type, options); + let (code1, iterations) = minify(&file.source_text, source_type, options); + let (code2, _) = minify(&code1, source_type, options); assert_eq_minified_code(&code1, &code2, &file.file_name); - code2 + (code2, iterations) } -fn minify(source_text: &str, source_type: SourceType, options: Options) -> String { +fn minify(source_text: &str, source_type: SourceType, options: Options) -> (String, u8) { let allocator = Allocator::default(); let ret = Parser::new(&allocator, source_text, source_type).parse(); let mut program = ret.program; @@ -167,11 +170,12 @@ fn minify(source_text: &str, source_type: SourceType, options: Options) -> Strin compress: Some(CompressOptions::default()), }) .build(&allocator, &mut program); - Codegen::new() + let code = Codegen::new() .with_options(CodegenOptions { minify: !options.compress_only, ..CodegenOptions::minify() }) .with_scoping(ret.scoping) .build(&program) - .code + .code; + (code, ret.iterations) } fn gzip_size(s: &str) -> usize {