diff --git a/crates/oxc_minifier/src/ast_passes/mod.rs b/crates/oxc_minifier/src/ast_passes/mod.rs index 77cc7b0bc2d5f..a1cf75018f63c 100644 --- a/crates/oxc_minifier/src/ast_passes/mod.rs +++ b/crates/oxc_minifier/src/ast_passes/mod.rs @@ -4,6 +4,7 @@ use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, Travers mod collapse_variable_declarations; mod exploit_assigns; +mod normalize; mod peephole_fold_constants; mod peephole_minimize_conditions; mod peephole_remove_dead_code; @@ -14,6 +15,7 @@ mod statement_fusion; pub use collapse_variable_declarations::CollapseVariableDeclarations; pub use exploit_assigns::ExploitAssigns; +pub use normalize::Normalize; pub use peephole_fold_constants::PeepholeFoldConstants; pub use peephole_minimize_conditions::PeepholeMinimizeConditions; pub use peephole_remove_dead_code::PeepholeRemoveDeadCode; diff --git a/crates/oxc_minifier/src/ast_passes/normalize.rs b/crates/oxc_minifier/src/ast_passes/normalize.rs new file mode 100644 index 0000000000000..cb25270742053 --- /dev/null +++ b/crates/oxc_minifier/src/ast_passes/normalize.rs @@ -0,0 +1,67 @@ +use oxc_ast::ast::*; +use oxc_syntax::scope::ScopeFlags; +use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, TraverseCtx}; + +use crate::CompressorPass; + +/// Normalize AST +/// +/// Make subsequent AST passes easier to analyze: +/// +/// * convert whiles to fors +/// +/// +pub struct Normalize; + +impl<'a> CompressorPass<'a> for Normalize { + fn build(&mut self, program: &mut Program<'a>, ctx: &mut ReusableTraverseCtx<'a>) { + traverse_mut_with_ctx(self, program, ctx); + } +} + +impl<'a> Traverse<'a> for Normalize { + fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { + if matches!(stmt, Statement::WhileStatement(_)) { + Self::convert_while_to_for(stmt, ctx); + } + } +} + +impl<'a> Normalize { + pub fn new() -> Self { + Self + } + + fn convert_while_to_for(stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { + let Statement::WhileStatement(while_stmt) = ctx.ast.move_statement(stmt) else { return }; + let while_stmt = while_stmt.unbox(); + let for_stmt = ctx.ast.alloc_for_statement_with_scope_id( + while_stmt.span, + None, + Some(while_stmt.test), + None, + while_stmt.body, + ctx.create_child_scope_of_current(ScopeFlags::empty()), + ); + *stmt = Statement::ForStatement(for_stmt); + } +} + +#[cfg(test)] +mod test { + use oxc_allocator::Allocator; + + use crate::tester; + + fn test(source_text: &str, expected: &str) { + let allocator = Allocator::default(); + let mut pass = super::Normalize::new(); + tester::test(&allocator, source_text, expected, &mut pass); + } + + #[test] + fn test_while() { + // Verify while loops are converted to FOR loops. + test("while(c < b) foo()", "for(; c < b;) foo()"); + } +} diff --git a/crates/oxc_minifier/src/compressor.rs b/crates/oxc_minifier/src/compressor.rs index d0dd762b61b7f..e284ee5a66368 100644 --- a/crates/oxc_minifier/src/compressor.rs +++ b/crates/oxc_minifier/src/compressor.rs @@ -5,8 +5,8 @@ use oxc_traverse::ReusableTraverseCtx; use crate::{ ast_passes::{ - CollapsePass, DeadCodeElimination, LatePeepholeOptimizations, PeepholeOptimizations, - RemoveSyntax, + CollapsePass, DeadCodeElimination, LatePeepholeOptimizations, Normalize, + PeepholeOptimizations, RemoveSyntax, }, CompressOptions, CompressorPass, }; @@ -35,6 +35,7 @@ impl<'a> Compressor<'a> { ) { let mut ctx = ReusableTraverseCtx::new(scopes, symbols, self.allocator); RemoveSyntax::new(self.options).build(program, &mut ctx); + Normalize::new().build(program, &mut ctx); PeepholeOptimizations::new().build(program, &mut ctx); CollapsePass::new().build(program, &mut ctx); LatePeepholeOptimizations::new().run_in_loop(program, &mut ctx); diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 93646406847ed..c7682131d35eb 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -5,23 +5,23 @@ Original | minified | minified | gzip | gzip | Fixture 173.90 kB | 61.52 kB | 59.82 kB | 19.54 kB | 19.33 kB | moment.js -287.63 kB | 92.47 kB | 90.07 kB | 32.30 kB | 31.95 kB | jquery.js +287.63 kB | 92.42 kB | 90.07 kB | 32.32 kB | 31.95 kB | jquery.js -342.15 kB | 121.36 kB | 118.14 kB | 44.67 kB | 44.37 kB | vue.js +342.15 kB | 121.31 kB | 118.14 kB | 44.69 kB | 44.37 kB | vue.js -544.10 kB | 73.32 kB | 72.48 kB | 26.13 kB | 26.20 kB | lodash.js +544.10 kB | 73.22 kB | 72.48 kB | 26.22 kB | 26.20 kB | lodash.js -555.77 kB | 275.77 kB | 270.13 kB | 91.12 kB | 90.80 kB | d3.js +555.77 kB | 275.67 kB | 270.13 kB | 91.19 kB | 90.80 kB | d3.js -1.01 MB | 466.38 kB | 458.89 kB | 126.72 kB | 126.71 kB | bundle.min.js +1.01 MB | 466.33 kB | 458.89 kB | 126.76 kB | 126.71 kB | bundle.min.js -1.25 MB | 660.41 kB | 646.76 kB | 163.99 kB | 163.73 kB | three.js +1.25 MB | 660.39 kB | 646.76 kB | 164.00 kB | 163.73 kB | three.js -2.14 MB | 740.08 kB | 724.14 kB | 181.34 kB | 181.07 kB | victory.js +2.14 MB | 739.97 kB | 724.14 kB | 181.42 kB | 181.07 kB | victory.js -3.20 MB | 1.02 MB | 1.01 MB | 332.26 kB | 331.56 kB | echarts.js +3.20 MB | 1.02 MB | 1.01 MB | 332.30 kB | 331.56 kB | echarts.js -6.69 MB | 2.39 MB | 2.31 MB | 495.65 kB | 488.28 kB | antd.js +6.69 MB | 2.39 MB | 2.31 MB | 495.67 kB | 488.28 kB | antd.js -10.95 MB | 3.54 MB | 3.49 MB | 909.94 kB | 915.50 kB | typescript.js +10.95 MB | 3.54 MB | 3.49 MB | 910.07 kB | 915.50 kB | typescript.js