From e84f267a398c03d45e501d234c89186aea703698 Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Sun, 5 Jan 2025 07:20:56 +0000 Subject: [PATCH] feat(minifier): compress more property keys (#8253) --- crates/oxc_ast/src/ast/js.rs | 3 - .../convert_to_dotted_properties.rs | 48 +------- crates/oxc_minifier/src/ast_passes/mod.rs | 45 ++++++- .../peephole_substitute_alternate_syntax.rs | 111 ++++++++++++++++++ 4 files changed, 154 insertions(+), 53 deletions(-) diff --git a/crates/oxc_ast/src/ast/js.rs b/crates/oxc_ast/src/ast/js.rs index 48a1330bef39e..fe0c5e21b8767 100644 --- a/crates/oxc_ast/src/ast/js.rs +++ b/crates/oxc_ast/src/ast/js.rs @@ -1477,7 +1477,6 @@ pub struct AssignmentPattern<'a> { pub right: Expression<'a>, } -// See serializer in serialize.rs #[ast(visit)] #[derive(Debug)] #[generate_derive(CloneIn, GetSpan, GetSpanMut, ContentEq, ContentHash, ESTree)] @@ -1499,7 +1498,6 @@ pub struct BindingProperty<'a> { pub computed: bool, } -// See serializer in serialize.rs #[ast(visit)] #[derive(Debug)] #[generate_derive(CloneIn, GetSpan, GetSpanMut, ContentEq, ContentHash, ESTree)] @@ -1644,7 +1642,6 @@ pub enum FunctionType { } /// -// See serializer in serialize.rs #[ast(visit)] #[derive(Debug)] #[generate_derive(CloneIn, GetSpan, GetSpanMut, ContentEq, ContentHash, ESTree)] diff --git a/crates/oxc_minifier/src/ast_passes/convert_to_dotted_properties.rs b/crates/oxc_minifier/src/ast_passes/convert_to_dotted_properties.rs index 600abcbe685cb..2068c7a148334 100644 --- a/crates/oxc_minifier/src/ast_passes/convert_to_dotted_properties.rs +++ b/crates/oxc_minifier/src/ast_passes/convert_to_dotted_properties.rs @@ -1,6 +1,6 @@ use oxc_ast::ast::*; use oxc_syntax::identifier::is_identifier_name; -use oxc_traverse::{traverse_mut_with_ctx, Ancestor, ReusableTraverseCtx, Traverse, TraverseCtx}; +use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, TraverseCtx}; use crate::{node_util::Ctx, CompressorPass}; @@ -21,12 +21,6 @@ impl<'a> CompressorPass<'a> for ConvertToDottedProperties { } impl<'a> Traverse<'a> for ConvertToDottedProperties { - fn exit_property_key(&mut self, key: &mut PropertyKey<'a>, ctx: &mut TraverseCtx<'a>) { - if !self.in_fixed_loop { - self.try_compress_property_key(key, ctx); - } - } - fn exit_member_expression( &mut self, expr: &mut MemberExpression<'a>, @@ -43,38 +37,6 @@ impl<'a> ConvertToDottedProperties { Self { changed: false, in_fixed_loop } } - // https://github.com/swc-project/swc/blob/4e2dae558f60a9f5c6d2eac860743e6c0b2ec562/crates/swc_ecma_minifier/src/compress/pure/properties.rs - #[allow(clippy::cast_lossless)] - fn try_compress_property_key(&mut self, key: &mut PropertyKey<'a>, ctx: &mut TraverseCtx<'a>) { - let PropertyKey::StringLiteral(s) = key else { return }; - if match ctx.parent() { - Ancestor::ObjectPropertyKey(key) => *key.computed(), - Ancestor::BindingPropertyKey(key) => *key.computed(), - Ancestor::MethodDefinitionKey(key) => *key.computed(), - Ancestor::PropertyDefinitionKey(key) => *key.computed(), - Ancestor::AccessorPropertyKey(key) => *key.computed(), - _ => true, - } { - return; - } - if is_identifier_name(&s.value) { - self.changed = true; - *key = PropertyKey::StaticIdentifier( - ctx.ast.alloc_identifier_name(s.span, s.value.clone()), - ); - } else if (!s.value.starts_with('0') && !s.value.starts_with('+')) || s.value.len() <= 1 { - if let Ok(value) = s.value.parse::() { - self.changed = true; - *key = PropertyKey::NumericLiteral(ctx.ast.alloc_numeric_literal( - s.span, - value as f64, - None, - NumberBase::Decimal, - )); - } - } - } - /// `foo['bar']` -> `foo.bar` /// `foo?.['bar']` -> `foo?.bar` fn try_compress_computed_member_expression( @@ -113,12 +75,6 @@ mod test { test(source_text, source_text); } - #[test] - fn test_object_key() { - test("({ '0': _, 'a': _ })", "({ 0: _, a: _ })"); - test_same("({ '1.1': _, '😊': _, 'a.a': _ })"); - } - #[test] fn test_computed_to_member_expression() { test("x['true']", "x.true"); @@ -175,7 +131,7 @@ mod test { fn test_convert_to_dotted_properties_quoted_props() { test_same("({'':0})"); test_same("({'1.0':0})"); - test("({'\\u1d17A':0})", "({ \u{1d17}A: 0 })"); + test_same("({'\\u1d17A':0})"); test_same("({'a\\u0004b':0})"); } diff --git a/crates/oxc_minifier/src/ast_passes/mod.rs b/crates/oxc_minifier/src/ast_passes/mod.rs index 5916b09808a29..b61ed00d61e2b 100644 --- a/crates/oxc_minifier/src/ast_passes/mod.rs +++ b/crates/oxc_minifier/src/ast_passes/mod.rs @@ -170,10 +170,6 @@ impl<'a> Traverse<'a> for PeepholeOptimizations { self.x6_peephole_substitute_alternate_syntax.exit_call_expression(expr, ctx); } - fn exit_property_key(&mut self, key: &mut PropertyKey<'a>, ctx: &mut TraverseCtx<'a>) { - self.x9_convert_to_dotted_properties.exit_property_key(key, ctx); - } - fn exit_member_expression( &mut self, expr: &mut MemberExpression<'a>, @@ -181,6 +177,47 @@ impl<'a> Traverse<'a> for PeepholeOptimizations { ) { self.x9_convert_to_dotted_properties.exit_member_expression(expr, ctx); } + + fn exit_object_property(&mut self, prop: &mut ObjectProperty<'a>, ctx: &mut TraverseCtx<'a>) { + self.x6_peephole_substitute_alternate_syntax.exit_object_property(prop, ctx); + } + + fn exit_assignment_target_property_property( + &mut self, + prop: &mut AssignmentTargetPropertyProperty<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + self.x6_peephole_substitute_alternate_syntax + .exit_assignment_target_property_property(prop, ctx); + } + + fn exit_binding_property(&mut self, prop: &mut BindingProperty<'a>, ctx: &mut TraverseCtx<'a>) { + self.x6_peephole_substitute_alternate_syntax.exit_binding_property(prop, ctx); + } + + fn exit_method_definition( + &mut self, + prop: &mut MethodDefinition<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + self.x6_peephole_substitute_alternate_syntax.exit_method_definition(prop, ctx); + } + + fn exit_property_definition( + &mut self, + prop: &mut PropertyDefinition<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + self.x6_peephole_substitute_alternate_syntax.exit_property_definition(prop, ctx); + } + + fn exit_accessor_property( + &mut self, + prop: &mut AccessorProperty<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + self.x6_peephole_substitute_alternate_syntax.exit_accessor_property(prop, ctx); + } } pub struct DeadCodeElimination { diff --git a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs index 0f43f4ab85f11..7c48e1ec21e43 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs @@ -4,6 +4,7 @@ use oxc_ecmascript::{constant_evaluation::ConstantEvaluation, ToInt32, ToJsStrin use oxc_semantic::IsGlobalReference; use oxc_span::{GetSpan, SPAN}; use oxc_syntax::{ + identifier::is_identifier_name, number::NumberBase, operator::{BinaryOperator, UnaryOperator}, }; @@ -35,6 +36,46 @@ impl<'a> CompressorPass<'a> for PeepholeSubstituteAlternateSyntax { } impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax { + fn exit_object_property(&mut self, prop: &mut ObjectProperty<'a>, ctx: &mut TraverseCtx<'a>) { + self.try_compress_property_key(&mut prop.key, &mut prop.computed, ctx); + } + + fn exit_assignment_target_property_property( + &mut self, + prop: &mut AssignmentTargetPropertyProperty<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + self.try_compress_property_key(&mut prop.name, &mut prop.computed, ctx); + } + + fn exit_binding_property(&mut self, prop: &mut BindingProperty<'a>, ctx: &mut TraverseCtx<'a>) { + self.try_compress_property_key(&mut prop.key, &mut prop.computed, ctx); + } + + fn exit_method_definition( + &mut self, + prop: &mut MethodDefinition<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + self.try_compress_property_key(&mut prop.key, &mut prop.computed, ctx); + } + + fn exit_property_definition( + &mut self, + prop: &mut PropertyDefinition<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + self.try_compress_property_key(&mut prop.key, &mut prop.computed, ctx); + } + + fn exit_accessor_property( + &mut self, + prop: &mut AccessorProperty<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + self.try_compress_property_key(&mut prop.key, &mut prop.computed, ctx); + } + fn exit_return_statement( &mut self, stmt: &mut ReturnStatement<'a>, @@ -691,6 +732,42 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax { fn empty_array_literal(ctx: Ctx<'a, 'b>) -> Expression<'a> { Self::array_literal(ctx.ast.vec(), ctx) } + + // https://github.com/swc-project/swc/blob/4e2dae558f60a9f5c6d2eac860743e6c0b2ec562/crates/swc_ecma_minifier/src/compress/pure/properties.rs + #[allow(clippy::cast_lossless)] + fn try_compress_property_key( + &mut self, + key: &mut PropertyKey<'a>, + computed: &mut bool, + ctx: &mut TraverseCtx<'a>, + ) { + if self.in_fixed_loop { + return; + } + let PropertyKey::StringLiteral(s) = key else { return }; + if s.value == "__proto__" || s.value == "constructor" { + return; + } + if *computed { + *computed = false; + } + if is_identifier_name(&s.value) { + self.changed = true; + *key = PropertyKey::StaticIdentifier( + ctx.ast.alloc_identifier_name(s.span, s.value.clone()), + ); + } else if (!s.value.starts_with('0') && !s.value.starts_with('+')) || s.value.len() <= 1 { + if let Ok(value) = s.value.parse::() { + self.changed = true; + *key = PropertyKey::NumericLiteral(ctx.ast.alloc_numeric_literal( + s.span, + value as f64, + None, + NumberBase::Decimal, + )); + } + } + } } /// Port from @@ -1185,4 +1262,38 @@ mod test { test("typeof foo !== `number`", "typeof foo != 'number'"); test("`number` !== typeof foo", "typeof foo != 'number'"); } + + #[test] + fn test_property_key() { + // Object Property + test( + "({ '0': _, 'a': _, ['1']: _, ['b']: _, ['c.c']: _, '1.1': _, '😊': _, 'd.d': _ })", + "({ 0: _, a: _, 1: _, b: _, 'c.c': _, '1.1': _, '😊': _, 'd.d': _ })", + ); + // AssignmentTargetPropertyProperty + test( + "({ '0': _, 'a': _, ['1']: _, ['b']: _, ['c.c']: _, '1.1': _, '😊': _, 'd.d': _ } = {})", + "({ 0: _, a: _, 1: _, b: _, 'c.c': _, '1.1': _, '😊': _, 'd.d': _ } = {})", + ); + // Binding Property + test( + "var { '0': _, 'a': _, ['1']: _, ['b']: _, ['c.c']: _, '1.1': _, '😊': _, 'd.d': _ } = {}", + "var { 0: _, a: _, 1: _, b: _, 'c.c': _, '1.1': _, '😊': _, 'd.d': _ } = {}", + ); + // Method Definition + test( + "class F { '0'(){}; 'a'(){}; ['1'](){}; ['b'](){}; ['c.c'](){}; '1.1'(){}; '😊'(){}; 'd.d'(){} }", + "class F { 0(){}; a(){}; 1(){}; b(){}; 'c.c'(){}; '1.1'(){}; '😊'(){}; 'd.d'(){} }" + ); + // Property Definition + test( + "class F { '0' = _; 'a' = _; ['1'] = _; ['b'] = _; ['c.c'] = _; '1.1' = _; '😊' = _; 'd.d' = _ }", + "class F { 0 = _; a = _; 1 = _; b = _; 'c.c' = _; '1.1' = _; '😊' = _; 'd.d' = _ }" + ); + // Accessor Property + test( + "class F { accessor '0' = _; accessor 'a' = _; accessor ['1'] = _; accessor ['b'] = _; accessor ['c.c'] = _; accessor '1.1' = _; accessor '😊' = _; accessor 'd.d' = _ }", + "class F { accessor 0 = _; accessor a = _; accessor 1 = _; accessor b = _; accessor 'c.c' = _; accessor '1.1' = _; accessor '😊' = _; accessor 'd.d' = _ }" + ); + } }