mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
feat(minifier): merge assign expression in conditional expression (#8345)
compresses `a ? b = 0 : b = 1` into `b = a ? 0 : 1` This can be done when `b` is an IdentifierReference and the assignment operator is `=`. In this circumstance, the evaluation of `b = a ? 0 : 1` is: 1. Let lref be ? Evaluation of LeftHandSideExpression. (this does not have a side effect when LeftHandSideExpression is an IdentifierReference) 2. Let rref be ? Evaluation of AssignmentExpression. (ConditionalExpression is evaluated here) 3. Let rval be ? GetValue(rref). 4. Perform ? PutValue(lref, rval). 5. Return rval. **References** - [spec of `=`](https://262.ecma-international.org/15.0/index.html#sec-assignment-operators-runtime-semantics-evaluation) - [spec of `? :`](https://262.ecma-international.org/15.0/index.html#sec-conditional-operator-runtime-semantics-evaluation)
This commit is contained in:
parent
5a648bc3cd
commit
8d52cd0a5e
3 changed files with 96 additions and 12 deletions
|
|
@ -263,6 +263,18 @@ impl<'a> Expression<'a> {
|
|||
matches!(self, Expression::FunctionExpression(_) | Expression::ArrowFunctionExpression(_))
|
||||
}
|
||||
|
||||
/// Returns `true` if this [`Expression`] is an anonymous function definition.
|
||||
/// Note that this includes [`Class`]s.
|
||||
/// <https://262.ecma-international.org/15.0/#sec-isanonymousfunctiondefinition>
|
||||
pub fn is_anonymous_function_definition(&self) -> bool {
|
||||
match self {
|
||||
Self::ArrowFunctionExpression(_) => true,
|
||||
Self::FunctionExpression(func) => func.name().is_none(),
|
||||
Self::ClassExpression(class) => class.name().is_none(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if this [`Expression`] is a [`CallExpression`].
|
||||
pub fn is_call_expression(&self) -> bool {
|
||||
matches!(self, Expression::CallExpression(_))
|
||||
|
|
@ -1160,7 +1172,13 @@ impl<'a> ArrowFunctionExpression<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl Class<'_> {
|
||||
impl<'a> Class<'a> {
|
||||
/// Returns this [`Class`]'s name, if it has one.
|
||||
#[inline]
|
||||
pub fn name(&self) -> Option<Atom<'a>> {
|
||||
self.id.as_ref().map(|id| id.name.clone())
|
||||
}
|
||||
|
||||
/// `true` if this [`Class`] is an expression.
|
||||
///
|
||||
/// For example,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use oxc_ecmascript::{
|
|||
constant_evaluation::{ConstantEvaluation, ValueType},
|
||||
ToInt32, ToJsString, ToNumber,
|
||||
};
|
||||
use oxc_span::cmp::ContentEq;
|
||||
use oxc_span::{GetSpan, SPAN};
|
||||
use oxc_syntax::{
|
||||
es_target::ESTarget,
|
||||
|
|
@ -153,6 +154,9 @@ impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax {
|
|||
Expression::TemplateLiteral(t) => Self::try_fold_template_literal(t, ctx),
|
||||
Expression::BinaryExpression(e) => Self::try_fold_loose_equals_undefined(e, ctx)
|
||||
.or_else(|| Self::try_compress_typeof_undefined(e, ctx)),
|
||||
Expression::ConditionalExpression(e) => {
|
||||
Self::try_merge_conditional_expression_inside(e, ctx)
|
||||
}
|
||||
Expression::NewExpression(e) => Self::get_fold_constructor_name(&e.callee, ctx)
|
||||
.and_then(|name| {
|
||||
Self::try_fold_object_or_array_constructor(e.span, name, &mut e.arguments, ctx)
|
||||
|
|
@ -623,6 +627,46 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax {
|
|||
}
|
||||
}
|
||||
|
||||
/// Merge `consequent` and `alternate` of `ConditionalExpression` inside.
|
||||
///
|
||||
/// - `x ? a = 0 : a = 1` -> `a = x ? 0 : 1`
|
||||
fn try_merge_conditional_expression_inside(
|
||||
expr: &mut ConditionalExpression<'a>,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) -> Option<Expression<'a>> {
|
||||
let (
|
||||
Expression::AssignmentExpression(consequent),
|
||||
Expression::AssignmentExpression(alternate),
|
||||
) = (&mut expr.consequent, &mut expr.alternate)
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
if !matches!(consequent.left, AssignmentTarget::AssignmentTargetIdentifier(_)) {
|
||||
return None;
|
||||
}
|
||||
if consequent.right.is_anonymous_function_definition() {
|
||||
return None;
|
||||
}
|
||||
if consequent.operator != AssignmentOperator::Assign
|
||||
|| consequent.operator != alternate.operator
|
||||
|| consequent.left.content_ne(&alternate.left)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(ctx.ast.expression_assignment(
|
||||
SPAN,
|
||||
consequent.operator,
|
||||
ctx.ast.move_assignment_target(&mut alternate.left),
|
||||
ctx.ast.expression_conditional(
|
||||
SPAN,
|
||||
ctx.ast.move_expression(&mut expr.test),
|
||||
ctx.ast.move_expression(&mut consequent.right),
|
||||
ctx.ast.move_expression(&mut alternate.right),
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
/// Fold `Boolean`, `Number`, `String`, `BigInt` constructors.
|
||||
///
|
||||
/// `Boolean(a)` -> `!!a`
|
||||
|
|
@ -1125,6 +1169,28 @@ mod test {
|
|||
test_same("x += -1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compress_conditional_expression_inside() {
|
||||
test("x ? a = 0 : a = 1", "a = x ? 0 : 1");
|
||||
test(
|
||||
"x ? a = function foo() { return 'a' } : a = function bar() { return 'b' }",
|
||||
"a = x ? function foo() { return 'a' } : function bar() { return 'b' }",
|
||||
);
|
||||
|
||||
// a.b might have a side effect
|
||||
test_same("x ? a.b = 0 : a.b = 1");
|
||||
// `a = x ? () => 'a' : () => 'b'` does not set the name property of the function
|
||||
test_same("x ? a = () => 'a' : a = () => 'b'");
|
||||
test_same("x ? a = function () { return 'a' } : a = function () { return 'b' }");
|
||||
test_same("x ? a = class { foo = 'a' } : a = class { foo = 'b' }");
|
||||
|
||||
// for non `=` operators, `GetValue(lref)` is called before `Evaluation of AssignmentExpression`
|
||||
// so cannot be fold to `a += x ? 0 : 1`
|
||||
// example case: `(()=>{"use strict"; (console.log("log"), 1) ? a += 0 : a += 1; })()`
|
||||
test_same("x ? a += 0 : a += 1");
|
||||
test_same("x ? a &&= 0 : a &&= 1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fold_literal_object_constructors() {
|
||||
test("x = new Object", "x = ({})");
|
||||
|
|
|
|||
|
|
@ -3,25 +3,25 @@ Original | minified | minified | gzip | gzip | Fixture
|
|||
-------------------------------------------------------------------------------------
|
||||
72.14 kB | 23.68 kB | 23.70 kB | 8.61 kB | 8.54 kB | react.development.js
|
||||
|
||||
173.90 kB | 59.78 kB | 59.82 kB | 19.42 kB | 19.33 kB | moment.js
|
||||
173.90 kB | 59.77 kB | 59.82 kB | 19.41 kB | 19.33 kB | moment.js
|
||||
|
||||
287.63 kB | 90.04 kB | 90.07 kB | 32.06 kB | 31.95 kB | jquery.js
|
||||
287.63 kB | 90.02 kB | 90.07 kB | 32.05 kB | 31.95 kB | jquery.js
|
||||
|
||||
342.15 kB | 118.14 kB | 118.14 kB | 44.51 kB | 44.37 kB | vue.js
|
||||
342.15 kB | 118.12 kB | 118.14 kB | 44.51 kB | 44.37 kB | vue.js
|
||||
|
||||
544.10 kB | 71.75 kB | 72.48 kB | 26.16 kB | 26.20 kB | lodash.js
|
||||
544.10 kB | 71.72 kB | 72.48 kB | 26.15 kB | 26.20 kB | lodash.js
|
||||
|
||||
555.77 kB | 272.82 kB | 270.13 kB | 90.92 kB | 90.80 kB | d3.js
|
||||
555.77 kB | 272.81 kB | 270.13 kB | 90.92 kB | 90.80 kB | d3.js
|
||||
|
||||
1.01 MB | 460.22 kB | 458.89 kB | 126.83 kB | 126.71 kB | bundle.min.js
|
||||
1.01 MB | 460.21 kB | 458.89 kB | 126.83 kB | 126.71 kB | bundle.min.js
|
||||
|
||||
1.25 MB | 652.56 kB | 646.76 kB | 163.52 kB | 163.73 kB | three.js
|
||||
1.25 MB | 652.53 kB | 646.76 kB | 163.51 kB | 163.73 kB | three.js
|
||||
|
||||
2.14 MB | 726.04 kB | 724.14 kB | 180.15 kB | 181.07 kB | victory.js
|
||||
2.14 MB | 726 kB | 724.14 kB | 180.13 kB | 181.07 kB | victory.js
|
||||
|
||||
3.20 MB | 1.01 MB | 1.01 MB | 331.83 kB | 331.56 kB | echarts.js
|
||||
3.20 MB | 1.01 MB | 1.01 MB | 331.78 kB | 331.56 kB | echarts.js
|
||||
|
||||
6.69 MB | 2.32 MB | 2.31 MB | 492.72 kB | 488.28 kB | antd.js
|
||||
6.69 MB | 2.32 MB | 2.31 MB | 492.61 kB | 488.28 kB | antd.js
|
||||
|
||||
10.95 MB | 3.50 MB | 3.49 MB | 908.28 kB | 915.50 kB | typescript.js
|
||||
10.95 MB | 3.50 MB | 3.49 MB | 908.24 kB | 915.50 kB | typescript.js
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue