mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
feat(minifier): fold Array::concat into literal (#8442)
Compress `[].concat(a, [b])` into `[a, b]`. **References** - [Spec of `Array::concat`](https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.concat)
This commit is contained in:
parent
3dc2d8b8e9
commit
991a22f907
2 changed files with 77 additions and 21 deletions
|
|
@ -62,6 +62,7 @@ impl<'a> PeepholeReplaceKnownMethods {
|
|||
"indexOf" | "lastIndexOf" => Self::try_fold_string_index_of(ce, name, object, ctx),
|
||||
"charAt" => Self::try_fold_string_char_at(ce, object, ctx),
|
||||
"charCodeAt" => Self::try_fold_string_char_code_at(ce, object, ctx),
|
||||
"concat" => Self::try_fold_concat(ce, ctx),
|
||||
"replace" | "replaceAll" => Self::try_fold_string_replace(ce, name, object, ctx),
|
||||
"fromCharCode" => Self::try_fold_string_from_char_code(ce, object, ctx),
|
||||
"toString" => Self::try_fold_to_string(ce, object, ctx),
|
||||
|
|
@ -420,6 +421,68 @@ impl<'a> PeepholeReplaceKnownMethods {
|
|||
);
|
||||
self.changed = true;
|
||||
}
|
||||
|
||||
/// `[].concat(1, 2)` -> `[1, 2]`
|
||||
fn try_fold_concat(
|
||||
ce: &mut CallExpression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Option<Expression<'a>> {
|
||||
// let concat chaining reduction handle it first
|
||||
if let Ancestor::StaticMemberExpressionObject(parent_member) = ctx.parent() {
|
||||
if parent_member.property().name.as_str() == "concat" {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
let Expression::StaticMemberExpression(member) = &mut ce.callee else { unreachable!() };
|
||||
let Expression::ArrayExpression(array_expr) = &mut member.object else { return None };
|
||||
|
||||
let can_merge_until = ce
|
||||
.arguments
|
||||
.iter()
|
||||
.enumerate()
|
||||
.take_while(|(_, argument)| match argument {
|
||||
Argument::SpreadElement(_) => false,
|
||||
match_expression!(Argument) => {
|
||||
let argument = argument.to_expression();
|
||||
if argument.is_literal() {
|
||||
true
|
||||
} else {
|
||||
matches!(argument, Expression::ArrayExpression(_))
|
||||
}
|
||||
}
|
||||
})
|
||||
.map(|(i, _)| i)
|
||||
.last();
|
||||
|
||||
if let Some(can_merge_until) = can_merge_until {
|
||||
for argument in ce.arguments.drain(..=can_merge_until) {
|
||||
let argument = argument.into_expression();
|
||||
if argument.is_literal() {
|
||||
array_expr.elements.push(ArrayExpressionElement::from(argument));
|
||||
} else {
|
||||
let Expression::ArrayExpression(mut argument_array) = argument else {
|
||||
unreachable!()
|
||||
};
|
||||
array_expr.elements.append(&mut argument_array.elements);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ce.arguments.is_empty() {
|
||||
Some(ctx.ast.move_expression(&mut member.object))
|
||||
} else if can_merge_until.is_some() {
|
||||
Some(ctx.ast.expression_call(
|
||||
ce.span,
|
||||
ctx.ast.move_expression(&mut ce.callee),
|
||||
Option::<TSTypeParameterInstantiation>::None,
|
||||
ctx.ast.move_vec(&mut ce.arguments),
|
||||
false,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Port from: <https://github.com/google/closure-compiler/blob/v20240609/test/com/google/javascript/jscomp/PeepholeReplaceKnownMethodsTest.java>
|
||||
|
|
@ -1167,52 +1230,45 @@ mod test {
|
|||
#[test]
|
||||
fn test_fold_concat_chaining() {
|
||||
// array
|
||||
fold("[1,2].concat(1).concat(2,['abc']).concat('abc')", "[1,2].concat(1,2,['abc'],'abc')");
|
||||
fold("[].concat(['abc']).concat(1).concat([2,3])", "[].concat(['abc'],1,[2,3])");
|
||||
fold("[1,2].concat(1).concat(2,['abc']).concat('abc')", "[1,2,1,2,'abc','abc']");
|
||||
fold("[].concat(['abc']).concat(1).concat([2,3])", "['abc',1,2,3]");
|
||||
|
||||
fold("var x, y; [1].concat(x).concat(y)", "var x, y; [1].concat(x, y)");
|
||||
fold("var y; [1].concat(x).concat(y)", "var y; [1].concat(x, y)"); // x might have a getter that updates y, but that side effect is preserved correctly
|
||||
fold("var x; [1].concat(x.a).concat(x)", "var x; [1].concat(x.a, x)"); // x.a might have a getter that updates x, but that side effect is preserved correctly
|
||||
|
||||
fold_same("[].concat(1)");
|
||||
|
||||
// string
|
||||
fold("'1'.concat(1).concat(2,['abc']).concat('abc')", "'1'.concat(1,2,['abc'],'abc')");
|
||||
fold("''.concat(['abc']).concat(1).concat([2,3])", "''.concat(['abc'],1,[2,3])");
|
||||
fold_same("''.concat(1)");
|
||||
|
||||
fold("var x, y; ''.concat(x).concat(y)", "var x, y; ''.concat(x, y)");
|
||||
fold("var y; ''.concat(x).concat(y)", "var y; ''.concat(x, y)"); // x might have a getter that updates y, but that side effect is preserved correctly
|
||||
fold("var x; ''.concat(x.a).concat(x)", "var x; ''.concat(x.a, x)"); // x.a might have a getter that updates x, but that side effect is preserved correctly
|
||||
|
||||
fold_same("''.concat(1)");
|
||||
|
||||
// other
|
||||
fold_same("obj.concat([1,2]).concat(1)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_remove_array_literal_from_front_of_concat() {
|
||||
// enableTypeCheck();
|
||||
fold("[].concat([1,2,3],1)", "[1,2,3,1]");
|
||||
|
||||
fold("[].concat([1,2,3],1)", "[1,2,3].concat(1)");
|
||||
|
||||
fold_same("[1,2,3].concat(returnArrayType())");
|
||||
fold_same("[1,2,3].concat(foo())");
|
||||
// Call method with the same name as Array.prototype.concat
|
||||
fold_same("obj.concat([1,2,3])");
|
||||
|
||||
fold_same("[].concat(1,[1,2,3])");
|
||||
fold_same("[].concat(1)");
|
||||
fold("[].concat([1])", "[1].concat()");
|
||||
fold("[].concat(1,[1,2,3])", "[1,1,2,3]");
|
||||
fold("[].concat(1)", "[1]");
|
||||
fold("[].concat([1])", "[1]");
|
||||
|
||||
// Chained folding of empty array lit
|
||||
fold("[].concat([], [1,2,3], [4])", "[1,2,3].concat([4])");
|
||||
fold("[].concat([]).concat([1]).concat([2,3])", "[1].concat([2,3])");
|
||||
fold("[].concat([], [1,2,3], [4])", "[1,2,3,4]");
|
||||
fold("[].concat([]).concat([1]).concat([2,3])", "[1,2,3]");
|
||||
|
||||
// Cannot fold based on type information
|
||||
fold_same("[].concat(returnArrayType(),1)");
|
||||
fold_same("[].concat(returnArrayType())");
|
||||
fold_same("[].concat(returnUnionType())");
|
||||
fold("[].concat(1, x)", "[1].concat(x)"); // x might be an array or an object with `Symbol.isConcatSpreadable`
|
||||
fold("[].concat(1, ...x)", "[1].concat(...x)");
|
||||
fold_same("[].concat(x, 1)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ Original | minified | minified | gzip | gzip | Fixture
|
|||
|
||||
544.10 kB | 71.76 kB | 72.48 kB | 26.15 kB | 26.20 kB | lodash.js
|
||||
|
||||
555.77 kB | 273.15 kB | 270.13 kB | 90.92 kB | 90.80 kB | d3.js
|
||||
555.77 kB | 272.91 kB | 270.13 kB | 90.90 kB | 90.80 kB | d3.js
|
||||
|
||||
1.01 MB | 460.17 kB | 458.89 kB | 126.76 kB | 126.71 kB | bundle.min.js
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue