mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
feat(minifier): fold string concat chaining (#8441)
Compress `"".concat(a).concat(b)` into `"".concat(a, b)`. **References** - [Spec of `String::concat`](https://tc39.es/ecma262/multipage/text-processing.html#sec-string.prototype.concat)
This commit is contained in:
parent
7ab14cc41c
commit
3dc2d8b8e9
2 changed files with 25 additions and 8 deletions
|
|
@ -26,7 +26,7 @@ impl<'a> CompressorPass<'a> for PeepholeReplaceKnownMethods {
|
|||
|
||||
impl<'a> Traverse<'a> for PeepholeReplaceKnownMethods {
|
||||
fn exit_expression(&mut self, node: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
self.try_fold_array_concat(node, ctx);
|
||||
self.try_fold_concat_chain(node, ctx);
|
||||
self.try_fold_known_string_methods(node, ctx);
|
||||
}
|
||||
}
|
||||
|
|
@ -341,7 +341,8 @@ impl<'a> PeepholeReplaceKnownMethods {
|
|||
}
|
||||
|
||||
/// `[].concat(a).concat(b)` -> `[].concat(a, b)`
|
||||
fn try_fold_array_concat(&mut self, node: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
/// `"".concat(a).concat(b)` -> `"".concat(a, b)`
|
||||
fn try_fold_concat_chain(&mut self, node: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
if matches!(ctx.parent(), Ancestor::StaticMemberExpressionObject(_)) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -368,7 +369,7 @@ impl<'a> PeepholeReplaceKnownMethods {
|
|||
|
||||
// We don't need to check if the arguments has a side effect here.
|
||||
//
|
||||
// The only side effect Array::concat can cause is throwing an error when the created array is too long.
|
||||
// The only side effect Array::concat / String::concat can cause is throwing an error when the created array is too long.
|
||||
// With the compressor assumption, that error can be moved.
|
||||
//
|
||||
// For example, if we have `[].concat(a).concat(b)`, the steps before the compression is:
|
||||
|
|
@ -387,10 +388,13 @@ impl<'a> PeepholeReplaceKnownMethods {
|
|||
let CallExpression { callee, arguments, .. } = ce.as_mut();
|
||||
collected_arguments.push(arguments);
|
||||
|
||||
// [].concat()
|
||||
// [].concat() or "".concat()
|
||||
let is_root_expr_concat = {
|
||||
let Expression::StaticMemberExpression(member) = callee else { unreachable!() };
|
||||
matches!(&member.object, Expression::ArrayExpression(_))
|
||||
matches!(
|
||||
&member.object,
|
||||
Expression::ArrayExpression(_) | Expression::StringLiteral(_)
|
||||
)
|
||||
};
|
||||
if is_root_expr_concat {
|
||||
new_root_callee = callee;
|
||||
|
|
@ -1162,6 +1166,7 @@ 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])");
|
||||
|
||||
|
|
@ -1170,6 +1175,18 @@ mod test {
|
|||
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("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)");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,11 +17,11 @@ Original | minified | minified | gzip | gzip | Fixture
|
|||
|
||||
1.25 MB | 652.88 kB | 646.76 kB | 163.54 kB | 163.73 kB | three.js
|
||||
|
||||
2.14 MB | 725.56 kB | 724.14 kB | 180.06 kB | 181.07 kB | victory.js
|
||||
2.14 MB | 724.06 kB | 724.14 kB | 179.94 kB | 181.07 kB | victory.js
|
||||
|
||||
3.20 MB | 1.01 MB | 1.01 MB | 332.01 kB | 331.56 kB | echarts.js
|
||||
|
||||
6.69 MB | 2.32 MB | 2.31 MB | 492.65 kB | 488.28 kB | antd.js
|
||||
6.69 MB | 2.32 MB | 2.31 MB | 492.44 kB | 488.28 kB | antd.js
|
||||
|
||||
10.95 MB | 3.49 MB | 3.49 MB | 907.34 kB | 915.50 kB | typescript.js
|
||||
10.95 MB | 3.49 MB | 3.49 MB | 907.09 kB | 915.50 kB | typescript.js
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue