mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
feat(minifier): compress a.b || (a.b = c) to a.b ||= c (#8366)
As noted in https://github.com/oxc-project/oxc/pull/8315, the only difference of `x ||= y` and `x || (x = y)` is that `Let lRef be ? Evaluation of LeftHandSideExpression` is done twice. If the LeftHandSideExpression is a MemberExpression, `GetValue(baseReference)` is the only place that might have a side effect. Further more, if the `baseReference` is an IdentifierReference that is not a global reference (and not a reference created by a `with` statement), `GetValue(baseReference)` does not have a side effect. When those conditions are met, `a.b || (a.b = c)` can be compress to `a.b ||= c`. **References** - [Spec of `a.b`](<https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-property-accessors-runtime-semantics-evaluation:~:text=%2C%20strict).-,MemberExpression,4.%20Return%20MakePrivateReference(baseValue%2C%20fieldNameString).,-CallExpression>) - [Spec of `GetValue`](https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#sec-getvalue) - [Spec of `IdentifierReference`](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-identifiers-runtime-semantics-evaluation) - [Spec of `DeclarativeEnvironmentRecord.GetBindingValue`](https://tc39.es/ecma262/multipage/executable-code-and-execution-contexts.html#sec-declarative-environment-records-getbindingvalue-n-s)
This commit is contained in:
parent
aa5e65ff3c
commit
579eb603d5
2 changed files with 54 additions and 16 deletions
|
|
@ -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)");
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue