Skip to content

Commit 2b0ecd5

Browse files
committed
feat(minifier): compress Object(expr)(args) to (0, expr)(args)
1 parent 3bfb235 commit 2b0ecd5

File tree

2 files changed

+56
-2
lines changed

2 files changed

+56
-2
lines changed

crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ impl<'a> PeepholeOptimizations {
131131

132132
pub fn substitute_call_expression(expr: &mut CallExpression<'a>, ctx: &mut Ctx<'a, '_>) {
133133
Self::try_flatten_arguments(&mut expr.arguments, ctx);
134+
Self::try_rewrite_object_callee_indirect_call(expr, ctx);
134135
}
135136

136137
pub fn substitute_new_expression(expr: &mut NewExpression<'a>, ctx: &mut Ctx<'a, '_>) {
@@ -885,6 +886,49 @@ impl<'a> PeepholeOptimizations {
885886
ctx.state.changed = true;
886887
}
887888

889+
/// `Object(expr)(args)` -> `(0, expr)(args)`
890+
///
891+
/// If `expr` is `null` or `undefined`, both before and after throws an TypeError ("something is not a function").
892+
/// It is because `Object(expr)` returns `{}`.
893+
///
894+
/// If `expr` is other primitive values, both before and after throws an TypeError ("something is not a function").
895+
/// It is because `Object(expr)` returns the Object wrapped values (e.g. `new Boolean()`).
896+
///
897+
/// If `expr` is an object / function, `Object(expr)` returns `expr` as-is.
898+
/// Note that we need to wrap `expr` as `(0, expr)` so that the `this` value is preserved.
899+
///
900+
/// <https://tc39.es/ecma262/2025/multipage/fundamental-objects.html#sec-object-value>
901+
fn try_rewrite_object_callee_indirect_call(
902+
expr: &mut CallExpression<'a>,
903+
ctx: &mut Ctx<'a, '_>,
904+
) {
905+
let Expression::CallExpression(inner_call) = &mut expr.callee else { return };
906+
if inner_call.optional || inner_call.arguments.len() != 1 {
907+
return;
908+
}
909+
let Expression::Identifier(callee) = &inner_call.callee else {
910+
return;
911+
};
912+
if callee.name != "Object" || !ctx.is_global_reference(callee) {
913+
return;
914+
}
915+
916+
let span = inner_call.span;
917+
let Some(arg_expr) = inner_call.arguments[0].as_expression_mut() else {
918+
return;
919+
};
920+
921+
let new_callee = ctx.ast.expression_sequence(
922+
span,
923+
ctx.ast.vec_from_array([
924+
ctx.ast.expression_numeric_literal(span, 0.0, None, NumberBase::Decimal),
925+
arg_expr.take_in(ctx.ast),
926+
]),
927+
);
928+
expr.callee = new_callee;
929+
ctx.state.changed = true;
930+
}
931+
888932
/// Remove name from function expressions if it is not used.
889933
///
890934
/// e.g. `var a = function f() {}` -> `var a = function () {}`
@@ -1832,4 +1876,14 @@ mod test {
18321876
test("var {y: z, 'z': y} = x", "var {y: z, z: y} = x");
18331877
test("var {y: y, 'z': z} = x", "var {y, z} = x");
18341878
}
1879+
1880+
#[test]
1881+
fn test_object_callee_indirect_call() {
1882+
test("Object(f)(1,2)", "f(1, 2)");
1883+
test("(Object(g))(a)", "g(a)");
1884+
test("Object(a.b)(x)", "(0, a.b)(x)");
1885+
test_same("Object?.(f)(1)");
1886+
test_same("function Object(x){return x} Object(f)(1)");
1887+
test_same("Object(...a)(1)");
1888+
}
18351889
}

tasks/minsize/minsize.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ Original | minified | minified | gzip | gzip | Iterations | Fi
1717

1818
1.25 MB | 646.98 kB | 646.76 kB | 160.27 kB | 163.73 kB | 2 | three.js
1919

20-
2.14 MB | 717.51 kB | 724.14 kB | 161.88 kB | 181.07 kB | 2 | victory.js
20+
2.14 MB | 715.66 kB | 724.14 kB | 161.78 kB | 181.07 kB | 2 | victory.js
2121

2222
3.20 MB | 1.01 MB | 1.01 MB | 324.08 kB | 331.56 kB | 2 | echarts.js
2323

24-
6.69 MB | 2.25 MB | 2.31 MB | 463.12 kB | 488.28 kB | 3 | antd.js
24+
6.69 MB | 2.23 MB | 2.31 MB | 462.26 kB | 488.28 kB | 3 | antd.js
2525

2626
10.95 MB | 3.35 MB | 3.49 MB | 860.97 kB | 915.50 kB | 2 | typescript.js
2727

0 commit comments

Comments
 (0)