From 00626ca568b59ed49f1503ed8c1f0ff54a0b426f Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Thu, 9 Jan 2025 00:18:06 +0900 Subject: [PATCH] feat(minifier): compress `a.b || (a.b = c)` to `a.b ||= c` --- .../peephole_substitute_alternate_syntax.rs | 52 ++++++++++++++++--- tasks/minsize/minsize.snap | 18 +++---- 2 files changed, 54 insertions(+), 16 deletions(-) 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 469d2468957835..3a02a892611bc8 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 @@ -481,13 +481,39 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax { let new_op = expr.operator.to_assignment_operator(); - let AssignmentTarget::AssignmentTargetIdentifier(write_id_ref) = &mut assignment_expr.left - else { - return None; - }; - let Expression::Identifier(read_id_ref) = &mut expr.left else { return None }; - if write_id_ref.name != read_id_ref.name { - return None; + match (&assignment_expr.left, &expr.left) { + // `a || (a = b)` -> `a ||= b` + ( + AssignmentTarget::AssignmentTargetIdentifier(write_id_ref), + Expression::Identifier(read_id_ref), + ) => { + if write_id_ref.name != read_id_ref.name { + return None; + } + } + // `a.b || (a.b = c)` -> `a.b ||= c` + // `a.#b || (a.#b = c)` -> `a.#b ||= c` + ( + AssignmentTarget::StaticMemberExpression(_), + Expression::StaticMemberExpression(_), + ) + | ( + AssignmentTarget::PrivateFieldExpression(_), + Expression::PrivateFieldExpression(_), + ) => { + let write_expr = assignment_expr.left.to_member_expression(); + let read_expr = expr.left.to_member_expression(); + let Expression::Identifier(write_expr_object_id) = &write_expr.object() else { + return None; + }; + // It should also return None when the reference might refer to a reference value created by a with statement + // when the minifier supports with statements + if ctx.is_global_reference(write_expr_object_id) || write_expr.content_ne(read_expr) + { + return None; + } + } + _ => return None, } assignment_expr.span = expr.span; @@ -1592,6 +1618,18 @@ mod test { test_same("x || (y = 3)"); + // GetValue(x) has no sideeffect when x is a resolved identifier + test("var x; x.y || (x.y = 3)", "var x; x.y ||= 3"); + test("var x; x.#y || (x.#y = 3)", "var x; x.#y ||= 3"); + test_same("x.y || (x.y = 3)"); + // this can be compressed if `y` does not have side effect + test_same("var x; x[y] || (x[y] = 3)"); + // GetValue(x) has a side effect in this case + // Example case: `var a = { get b() { console.log('b'); return { get c() { console.log('c') } } } }; a.b.c || (a.b.c = 1)` + test_same("var x; x.y.z || (x.y.z = 3)"); + // This case is not supported, since the minifier does not support with statements + // test_same("var x; with (z) { x.y || (x.y = 3) }"); + // foo() might have a side effect test_same("foo().a || (foo().a = 3)"); diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 827137a019df6b..013f70c8088ee7 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -5,23 +5,23 @@ Original | minified | minified | gzip | gzip | Fixture 173.90 kB | 59.80 kB | 59.82 kB | 19.41 kB | 19.33 kB | moment.js -287.63 kB | 90.13 kB | 90.07 kB | 32.05 kB | 31.95 kB | jquery.js +287.63 kB | 90.10 kB | 90.07 kB | 32.04 kB | 31.95 kB | jquery.js -342.15 kB | 118.36 kB | 118.14 kB | 44.52 kB | 44.37 kB | vue.js +342.15 kB | 118.11 kB | 118.14 kB | 44.45 kB | 44.37 kB | vue.js 544.10 kB | 71.74 kB | 72.48 kB | 26.14 kB | 26.20 kB | lodash.js -555.77 kB | 273.19 kB | 270.13 kB | 90.92 kB | 90.80 kB | d3.js +555.77 kB | 273.19 kB | 270.13 kB | 90.91 kB | 90.80 kB | d3.js -1.01 MB | 460.47 kB | 458.89 kB | 126.83 kB | 126.71 kB | bundle.min.js +1.01 MB | 460.18 kB | 458.89 kB | 126.76 kB | 126.71 kB | bundle.min.js -1.25 MB | 652.88 kB | 646.76 kB | 163.52 kB | 163.73 kB | three.js +1.25 MB | 652.84 kB | 646.76 kB | 163.52 kB | 163.73 kB | three.js -2.14 MB | 726.28 kB | 724.14 kB | 180.14 kB | 181.07 kB | victory.js +2.14 MB | 726.27 kB | 724.14 kB | 180.14 kB | 181.07 kB | victory.js -3.20 MB | 1.01 MB | 1.01 MB | 331.93 kB | 331.56 kB | echarts.js +3.20 MB | 1.01 MB | 1.01 MB | 331.80 kB | 331.56 kB | echarts.js -6.69 MB | 2.32 MB | 2.31 MB | 492.68 kB | 488.28 kB | antd.js +6.69 MB | 2.32 MB | 2.31 MB | 492.65 kB | 488.28 kB | antd.js -10.95 MB | 3.50 MB | 3.49 MB | 908.82 kB | 915.50 kB | typescript.js +10.95 MB | 3.49 MB | 3.49 MB | 907.50 kB | 915.50 kB | typescript.js