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 06e98791e46a0..39c0ccc3765ee 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::{ToInt32, ToJsString}; use oxc_semantic::IsGlobalReference; use oxc_span::{GetSpan, SPAN}; use oxc_syntax::{ + identifier::is_identifier_name, number::NumberBase, operator::{BinaryOperator, UnaryOperator}, }; @@ -86,9 +87,7 @@ impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax { // Change syntax match expr { - Expression::ArrowFunctionExpression(arrow_expr) => { - self.try_compress_arrow_expression(arrow_expr, ctx); - } + Expression::ArrowFunctionExpression(e) => self.try_compress_arrow_expression(e, ctx), Expression::ChainExpression(e) => self.try_compress_chain_call_expression(e, ctx), Expression::BinaryExpression(e) => self.try_compress_type_of_equal_string(e, ctx), _ => {} @@ -101,12 +100,15 @@ impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax { Expression::AssignmentExpression(e) => Self::try_compress_assignment_expression(e, ctx), Expression::LogicalExpression(e) => Self::try_compress_is_null_or_undefined(e, ctx), Expression::NewExpression(e) => Self::try_fold_new_expression(e, ctx), + Expression::TemplateLiteral(t) => Self::try_fold_template_literal(t, ctx), + Expression::BinaryExpression(e) => Self::try_compress_typeof_undefined(e, ctx), + Expression::ComputedMemberExpression(e) => { + self.try_compress_computed_member_expression(e, ctx) + } Expression::CallExpression(e) => { Self::try_fold_literal_constructor_call_expression(e, ctx) .or_else(|| Self::try_fold_simple_function_call(e, ctx)) } - Expression::TemplateLiteral(t) => Self::try_fold_template_literal(t, ctx), - Expression::BinaryExpression(e) => Self::try_compress_typeof_undefined(e, ctx), _ => None, } { *expr = folded_expr; @@ -714,7 +716,9 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax { // 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>) { - use oxc_syntax::identifier::is_identifier_name; + if self.in_fixed_loop { + return; + } let PropertyKey::StringLiteral(s) = key else { return }; if match ctx.parent() { Ancestor::ObjectPropertyKey(key) => *key.computed(), @@ -743,6 +747,26 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax { } } } + + /// `foo['bar']` -> `foo.bar` + fn try_compress_computed_member_expression( + &self, + e: &mut ComputedMemberExpression<'a>, + ctx: Ctx<'a, 'b>, + ) -> Option> { + if self.in_fixed_loop { + return None; + } + let Expression::StringLiteral(s) = &e.expression else { return None }; + if !is_identifier_name(&s.value) { + return None; + } + let property = ctx.ast.identifier_name(s.span, s.value.clone()); + let object = ctx.ast.move_expression(&mut e.object); + Some(Expression::StaticMemberExpression( + ctx.ast.alloc_static_member_expression(e.span, object, property, false), + )) + } } /// Port from @@ -1240,4 +1264,10 @@ mod test { test("({ '0': _, 'a': _ })", "({ 0: _, a: _ })"); test_same("({ '1.1': _, '😊': _, 'a.a': _ })"); } + + #[test] + fn test_computed_to_member_expression() { + test("x['true']", "x.true"); + test_same("x['😊']"); + } } diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index f453e18993034..4a350420714eb 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -3,25 +3,25 @@ Original | minified | minified | gzip | gzip | Fixture ------------------------------------------------------------------------------------- 72.14 kB | 23.74 kB | 23.70 kB | 8.61 kB | 8.54 kB | react.development.js -173.90 kB | 60.16 kB | 59.82 kB | 19.49 kB | 19.33 kB | moment.js +173.90 kB | 60.16 kB | 59.82 kB | 19.48 kB | 19.33 kB | moment.js -287.63 kB | 90.59 kB | 90.07 kB | 32.18 kB | 31.95 kB | jquery.js +287.63 kB | 90.70 kB | 90.07 kB | 32.21 kB | 31.95 kB | jquery.js -342.15 kB | 118.61 kB | 118.14 kB | 44.54 kB | 44.37 kB | vue.js +342.15 kB | 118.59 kB | 118.14 kB | 44.53 kB | 44.37 kB | vue.js -544.10 kB | 72.04 kB | 72.48 kB | 26.18 kB | 26.20 kB | lodash.js +544.10 kB | 72.49 kB | 72.48 kB | 26.22 kB | 26.20 kB | lodash.js -555.77 kB | 273.89 kB | 270.13 kB | 91.18 kB | 90.80 kB | d3.js +555.77 kB | 274.10 kB | 270.13 kB | 91.25 kB | 90.80 kB | d3.js -1.01 MB | 461.09 kB | 458.89 kB | 126.91 kB | 126.71 kB | bundle.min.js +1.01 MB | 461.06 kB | 458.89 kB | 126.91 kB | 126.71 kB | bundle.min.js -1.25 MB | 656.66 kB | 646.76 kB | 164.14 kB | 163.73 kB | three.js +1.25 MB | 656.99 kB | 646.76 kB | 164.18 kB | 163.73 kB | three.js -2.14 MB | 735.26 kB | 724.14 kB | 180.97 kB | 181.07 kB | victory.js +2.14 MB | 728.22 kB | 724.14 kB | 180.43 kB | 181.07 kB | victory.js -3.20 MB | 1.01 MB | 1.01 MB | 332.23 kB | 331.56 kB | echarts.js +3.20 MB | 1.01 MB | 1.01 MB | 332.24 kB | 331.56 kB | echarts.js -6.69 MB | 2.36 MB | 2.31 MB | 494.89 kB | 488.28 kB | antd.js +6.69 MB | 2.34 MB | 2.31 MB | 493.51 kB | 488.28 kB | antd.js -10.95 MB | 3.51 MB | 3.49 MB | 910.90 kB | 915.50 kB | typescript.js +10.95 MB | 3.51 MB | 3.49 MB | 910.88 kB | 915.50 kB | typescript.js diff --git a/tasks/minsize/src/lib.rs b/tasks/minsize/src/lib.rs index 91eda08f1e8df..638a48287c8da 100644 --- a/tasks/minsize/src/lib.rs +++ b/tasks/minsize/src/lib.rs @@ -5,6 +5,7 @@ use std::{ path::Path, }; +use cow_utils::CowUtils; use flate2::{write::GzEncoder, Compression}; use humansize::{format_size, DECIMAL}; use oxc_allocator::Allocator; @@ -16,7 +17,6 @@ use oxc_span::SourceType; use oxc_tasks_common::{project_root, TestFile, TestFiles}; use oxc_transformer::{ReplaceGlobalDefines, ReplaceGlobalDefinesConfig}; use rustc_hash::FxHashMap; -use similar_asserts::assert_eq; // #[test] // #[cfg(any(coverage, coverage_nightly))] @@ -24,21 +24,6 @@ use similar_asserts::assert_eq; // run().unwrap(); // } -macro_rules! assert_eq_minified_code { - ($left:expr, $right:expr, $($arg:tt)*) => { - if $left != $right { - let normalized_left = $crate::normalize_minified_code($left); - let normalized_right = $crate::normalize_minified_code($right); - assert_eq!(normalized_left, normalized_right, $($arg)*); - } - }; -} - -fn normalize_minified_code(code: &str) -> String { - use cow_utils::CowUtils; - code.cow_replace(";", ";\n").cow_replace(",", ",\n").into_owned() -} - /// # Panics /// # Errors pub fn run() -> Result<(), io::Error> { @@ -147,12 +132,7 @@ fn minify_twice(file: &TestFile) -> String { }; let source_text1 = minify(&file.source_text, source_type, options); let source_text2 = minify(&source_text1, source_type, options); - assert_eq_minified_code!( - &source_text1, - &source_text2, - "Minification failed for {}", - &file.file_name - ); + assert_eq_minified_code(&source_text1, &source_text2, &file.file_name); source_text2 } @@ -181,3 +161,19 @@ fn gzip_size(s: &str) -> usize { let s = e.finish().unwrap(); s.len() } + +fn assert_eq_minified_code(s1: &str, s2: &str, filename: &str) { + if s1 != s2 { + let normalized_left = normalize_minified_code(s1); + let normalized_right = normalize_minified_code(s2); + similar_asserts::assert_eq!( + normalized_left, + normalized_right, + "Minification failed for {filename}" + ); + } +} + +fn normalize_minified_code(code: &str) -> String { + code.cow_replace(";", ";\n").cow_replace(",", ",\n").into_owned() +}