diff --git a/crates/oxc_minifier/src/ast_passes/collapse_variable_declarations.rs b/crates/oxc_minifier/src/ast_passes/collapse_variable_declarations.rs index 0bb721909..cf8ed2260 100644 --- a/crates/oxc_minifier/src/ast_passes/collapse_variable_declarations.rs +++ b/crates/oxc_minifier/src/ast_passes/collapse_variable_declarations.rs @@ -36,65 +36,75 @@ impl<'a> CollapseVariableDeclarations { Self { options, changed: false } } - /// Join consecutive var statements + fn is_require_call(var_decl: &VariableDeclaration) -> bool { + var_decl + .declarations + .first() + .and_then(|d| d.init.as_ref()) + .is_some_and(Expression::is_require_call) + } + + fn is_valid_var_decl( + stmt: &Statement, + kind: Option, + ) -> Option { + if let Statement::VariableDeclaration(cur_decl) = stmt { + let is_not_require_call = !Self::is_require_call(cur_decl); + if kind.map_or(true, |k| cur_decl.kind == k) && is_not_require_call { + return Some(cur_decl.kind); + } + } + None + } + fn join_vars(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) { - if self.options.join_vars { + if !self.options.join_vars || stmts.len() < 2 { return; } - // Collect all the consecutive ranges that contain joinable vars. - // This is required because Rust prevents in-place vec mutation. - let mut ranges = vec![]; - let mut range = 0..0; - let mut i = 1usize; - let mut capacity = 0usize; - for window in stmts.windows(2) { - let [prev, cur] = window else { unreachable!() }; - if let ( - Statement::VariableDeclaration(cur_decl), - Statement::VariableDeclaration(prev_decl), - ) = (cur, prev) + + let mut prev: usize = stmts.len() - 1; + let mut items = std::vec::Vec::::new(); + + while prev > 0 { + prev -= 1; + + let cur: usize = prev + 1; + + if !Self::is_valid_var_decl(&stmts[cur], None) + .is_some_and(|kind| Self::is_valid_var_decl(&stmts[prev], Some(kind)).is_some()) { - // Do not join `require` calls for cjs-module-lexer. - if cur_decl - .declarations - .first() - .and_then(|d| d.init.as_ref()) - .is_some_and(Expression::is_require_call) - { - break; - } - if cur_decl.kind == prev_decl.kind { - if i - 1 != range.end { - range.start = i - 1; - } - range.end = i + 1; - } + continue; } - if (range.end != i || i == stmts.len() - 1) && range.start < range.end { - capacity += range.end - range.start - 1; - ranges.push(range.clone()); - range = 0..0; + let Some(Statement::VariableDeclaration(cur_decl)) = stmts.get_mut(cur) else { + continue; + }; + + let mut decls = ctx.ast.move_vec(&mut cur_decl.declarations); + if let Some(Statement::VariableDeclaration(prev_decl)) = stmts.get_mut(prev) { + items.push(cur); + prev_decl.declarations.append(&mut decls); } - i += 1; } - if ranges.is_empty() { + if items.is_empty() { return; } - // Reconstruct the stmts array by joining consecutive ranges - let mut new_stmts = ctx.ast.vec_with_capacity(stmts.len() - capacity); - for (i, stmt) in stmts.drain(..).enumerate() { - if i > 0 && ranges.iter().any(|range| range.contains(&(i - 1)) && range.contains(&i)) { - if let Statement::VariableDeclaration(prev_decl) = new_stmts.last_mut().unwrap() { - if let Statement::VariableDeclaration(mut cur_decl) = stmt { - prev_decl.declarations.append(&mut cur_decl.declarations); - } + let mut item_iter = items.iter().rev(); + let mut next_item = item_iter.next(); + + let mut new_stmts = ctx.ast.vec_with_capacity(stmts.len() - items.len()); + + for (index, stmt) in stmts.drain(..).enumerate() { + if let Some(item) = next_item { + if *item == index { + next_item = item_iter.next(); + continue; } - } else { - new_stmts.push(stmt); } + new_stmts.push(stmt); } + *stmts = new_stmts; self.changed = true; } @@ -131,7 +141,6 @@ mod test { } #[test] - #[ignore] fn test_collapsing() { // Basic collapsing test("var a;var b;", "var a,b;"); @@ -177,7 +186,6 @@ mod test { } #[test] - #[ignore] fn test_issue397() { test_same("var x; x = 5; var z = 7;"); test("var x; var y = 3; x = 5;", "var x, y = 3; x = 5;"); @@ -192,7 +200,6 @@ mod test { // ES6 Tests #[test] - #[ignore] fn test_collapsing_let_const() { // Basic collapsing test("let a;let b;", "let a,b;"); @@ -229,7 +236,6 @@ mod test { } #[test] - #[ignore] fn test_redeclaration_let_in_function() { test( "function f() { let x = 1; let y = 2; let z = 3; x + y + z; }", @@ -248,7 +254,6 @@ mod test { } #[test] - #[ignore] fn test_arrow_function() { test("() => {let x = 1; let y = 2; x + y; }", "() => {let x = 1, y = 2; x + y; }"); @@ -264,7 +269,6 @@ mod test { } #[test] - #[ignore] fn test_mixed_declaration_types() { // lets, vars, const declarations consecutive test("let x = 1; let z = 3; var y = 2;", "let x = 1, z = 3; var y = 2;"); diff --git a/crates/oxc_minifier/src/compressor.rs b/crates/oxc_minifier/src/compressor.rs index f722a2266..326571bc2 100644 --- a/crates/oxc_minifier/src/compressor.rs +++ b/crates/oxc_minifier/src/compressor.rs @@ -42,9 +42,6 @@ impl<'a> Compressor<'a> { return; } - ExploitAssigns::new().build(program, &mut ctx); - CollapseVariableDeclarations::new(self.options).build(program, &mut ctx); - // See `latePeepholeOptimizations` let mut passes: [&mut dyn CompressorPass; 6] = [ &mut StatementFusion::new(), @@ -74,6 +71,10 @@ impl<'a> Compressor<'a> { } i += 1; } + + // Passes listed in `getFinalization` in `DefaultPassConfig` + ExploitAssigns::new().build(program, &mut ctx); + CollapseVariableDeclarations::new(self.options).build(program, &mut ctx); } fn dead_code_elimination(program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 635e557b4..f97500940 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -1,26 +1,26 @@ Original | Minified | esbuild | Gzip | esbuild -72.14 kB | 24.46 kB | 23.70 kB | 8.65 kB | 8.54 kB | react.development.js +72.14 kB | 24.12 kB | 23.70 kB | 8.62 kB | 8.54 kB | react.development.js -173.90 kB | 61.68 kB | 59.82 kB | 19.53 kB | 19.33 kB | moment.js +173.90 kB | 61.67 kB | 59.82 kB | 19.53 kB | 19.33 kB | moment.js -287.63 kB | 92.83 kB | 90.07 kB | 32.29 kB | 31.95 kB | jquery.js +287.63 kB | 92.70 kB | 90.07 kB | 32.27 kB | 31.95 kB | jquery.js -342.15 kB | 124.06 kB | 118.14 kB | 44.79 kB | 44.37 kB | vue.js +342.15 kB | 121.90 kB | 118.14 kB | 44.59 kB | 44.37 kB | vue.js -544.10 kB | 74.13 kB | 72.48 kB | 26.23 kB | 26.20 kB | lodash.js +544.10 kB | 73.49 kB | 72.48 kB | 26.13 kB | 26.20 kB | lodash.js -555.77 kB | 278.22 kB | 270.13 kB | 91.36 kB | 90.80 kB | d3.js +555.77 kB | 276.31 kB | 270.13 kB | 91.08 kB | 90.80 kB | d3.js -1.01 MB | 470.07 kB | 458.89 kB | 126.95 kB | 126.71 kB | bundle.min.js +1.01 MB | 467.63 kB | 458.89 kB | 126.75 kB | 126.71 kB | bundle.min.js -1.25 MB | 670.94 kB | 646.76 kB | 164.72 kB | 163.73 kB | three.js +1.25 MB | 662.90 kB | 646.76 kB | 164.00 kB | 163.73 kB | three.js -2.14 MB | 756.31 kB | 724.14 kB | 182.74 kB | 181.07 kB | victory.js +2.14 MB | 741.41 kB | 724.14 kB | 181.41 kB | 181.07 kB | victory.js -3.20 MB | 1.05 MB | 1.01 MB | 334.08 kB | 331.56 kB | echarts.js +3.20 MB | 1.02 MB | 1.01 MB | 331.95 kB | 331.56 kB | echarts.js -6.69 MB | 2.44 MB | 2.31 MB | 498.86 kB | 488.28 kB | antd.js +6.69 MB | 2.39 MB | 2.31 MB | 496.10 kB | 488.28 kB | antd.js -10.95 MB | 3.59 MB | 3.49 MB | 913.91 kB | 915.50 kB | typescript.js +10.95 MB | 3.56 MB | 3.49 MB | 911.24 kB | 915.50 kB | typescript.js