diff --git a/crates/oxc_minifier/examples/minifier.rs b/crates/oxc_minifier/examples/minifier.rs index 591cbd12811b7..ca478e1ecef5e 100644 --- a/crates/oxc_minifier/examples/minifier.rs +++ b/crates/oxc_minifier/examples/minifier.rs @@ -17,6 +17,7 @@ //! - `--nospace`: Remove extra whitespace //! - `--twice`: Test idempotency by running twice //! - `--sourcemap`: Generate source maps +//! - `--max-iterations `: Set the maximum number of compress pass iterations use std::path::{Path, PathBuf}; @@ -41,6 +42,9 @@ fn main() -> std::io::Result<()> { let nospace = args.contains("--nospace"); let twice = args.contains("--twice"); let sourcemap = args.contains("--sourcemap"); + let max_iterations = args + .opt_value_from_str::<&str, u8>("--max-iterations") + .expect("Invalid number for --max-iterations"); let name = args.free_from_str().unwrap_or_else(|_| "test.js".to_string()); let path = Path::new(&name); @@ -49,7 +53,15 @@ fn main() -> std::io::Result<()> { let source_map_path = sourcemap.then(|| path.to_path_buf()); let mut allocator = Allocator::default(); - let ret = minify(&allocator, &source_text, source_type, source_map_path, mangle, nospace); + let ret = minify( + &allocator, + &source_text, + source_type, + source_map_path, + mangle, + nospace, + max_iterations, + ); let printed = ret.code; println!("{printed}"); @@ -61,7 +73,7 @@ fn main() -> std::io::Result<()> { if twice { allocator.reset(); - let printed2 = minify(&allocator, &printed, source_type, None, mangle, nospace).code; + let printed2 = minify(&allocator, &printed, source_type, None, mangle, nospace, None).code; println!("{printed2}"); println!("same = {}", printed == printed2); } @@ -76,12 +88,13 @@ fn minify( source_map_path: Option, mangle: bool, nospace: bool, + max_iterations: Option, ) -> CodegenReturn { let ret = Parser::new(allocator, source_text, source_type).parse(); let mut program = ret.program; let options = MinifierOptions { mangle: mangle.then(MangleOptions::default), - compress: Some(CompressOptions::smallest()), + compress: Some(CompressOptions { max_iterations, ..CompressOptions::smallest() }), }; let ret = Minifier::new(options).minify(allocator, &mut program); Codegen::new() diff --git a/crates/oxc_minifier/src/compressor.rs b/crates/oxc_minifier/src/compressor.rs index 154710ca6e051..1f21715118aa6 100644 --- a/crates/oxc_minifier/src/compressor.rs +++ b/crates/oxc_minifier/src/compressor.rs @@ -30,12 +30,13 @@ impl<'a> Compressor<'a> { scoping: Scoping, options: CompressOptions, ) -> u8 { + let max_iterations = options.max_iterations; 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(max_iterations).run_in_loop(program, &mut ctx) } pub fn dead_code_elimination(self, program: &mut Program<'a>, options: CompressOptions) -> u8 { diff --git a/crates/oxc_minifier/src/options.rs b/crates/oxc_minifier/src/options.rs index 70cfcaa7d7c12..fe8405e6a1c61 100644 --- a/crates/oxc_minifier/src/options.rs +++ b/crates/oxc_minifier/src/options.rs @@ -45,6 +45,9 @@ pub struct CompressOptions { /// Treeshake Options . /// pub treeshake: TreeShakeOptions, + + /// Limit the maximum number of iterations for debugging purpose. + pub max_iterations: Option, } impl Default for CompressOptions { @@ -64,6 +67,7 @@ impl CompressOptions { sequences: true, unused: CompressOptionsUnused::Remove, treeshake: TreeShakeOptions::default(), + max_iterations: None, } } @@ -77,6 +81,7 @@ impl CompressOptions { sequences: true, unused: CompressOptionsUnused::Keep, treeshake: TreeShakeOptions::default(), + max_iterations: None, } } @@ -90,6 +95,7 @@ impl CompressOptions { sequences: false, unused: CompressOptionsUnused::Remove, treeshake: TreeShakeOptions::default(), + max_iterations: None, } } } diff --git a/crates/oxc_minifier/src/peephole/mod.rs b/crates/oxc_minifier/src/peephole/mod.rs index 0f4b7b3c9770e..9a9cfb017e55b 100644 --- a/crates/oxc_minifier/src/peephole/mod.rs +++ b/crates/oxc_minifier/src/peephole/mod.rs @@ -32,6 +32,7 @@ use crate::{ pub use self::normalize::{Normalize, NormalizeOptions}; pub struct PeepholeOptimizations { + max_iterations: Option, /// Walk the ast in a fixed point loop until no changes are made. /// `prev_function_changed`, `functions_changed` and `current_function` track changes /// in top level and each function. No minification code are run if the function is not changed @@ -41,8 +42,8 @@ pub struct PeepholeOptimizations { } impl<'a> PeepholeOptimizations { - pub fn new() -> Self { - Self { iteration: 0, changed: false } + pub fn new(max_iterations: Option) -> Self { + Self { max_iterations, iteration: 0, changed: false } } fn run_once( @@ -64,7 +65,11 @@ impl<'a> PeepholeOptimizations { if !self.changed { break; } - if self.iteration > 10 { + if let Some(max_iterations) = self.max_iterations { + if self.iteration >= max_iterations { + break; + } + } else if self.iteration > 10 { debug_assert!(false, "Ran loop more than 10 times."); break; } diff --git a/napi/minify/src/options.rs b/napi/minify/src/options.rs index 24288646682e1..af8a91672c87b 100644 --- a/napi/minify/src/options.rs +++ b/napi/minify/src/options.rs @@ -63,6 +63,7 @@ impl TryFrom<&CompressOptions> for oxc_minifier::CompressOptions { unused: oxc_minifier::CompressOptionsUnused::Keep, keep_names: o.keep_names.as_ref().map(Into::into).unwrap_or_default(), treeshake: TreeShakeOptions::default(), + max_iterations: None, }) } }