esbuild icon indicating copy to clipboard operation
esbuild copied to clipboard

Minify suggestions

Open pushkine opened this issue 1 year ago • 5 comments

Some suggestions from looking at my minified bundle:

- return w(46) ? new lt() : new gn()
+ return new (w(46) ? lt : gn)

- var X1 = () => gc; fi(X1());
+ fi(gc);

- var Wa = () => ++Rs; y(Wa());
+ y(++Rs);

- Q.length=0,H.length=0,he.length=0,ge.length=0
+ ge.length=he.length=H.length=Q.length=0

- var y = (e) => { M(), Te() };
+ var y = ( ) => { M(), Te() };
y();

function Ya(e) { return Ls(H) ? t(e()) : Yt(he, s), i; }
- var xc = (s) => { Ya(() => s()) }
+ var xc = (s) => { Ya(s) }

- var V1 = !1;
- function It() { return (V1 = !0), new E() }
+ function It() { return new E }

- var S1 = (e) => { Ns(e.length) }
- S1("raw")
+ Ns(3)

function Gt() {
    switch ($()) {
        case 20: return Cc(() => new le());
        case 1: return Le(new E());
-       default: return;
    }
}

- Wa(); e: for (;;)
+ e: for (Wa();;)
    switch (h()) { case 41: break e; ... }

$a(
    () => {
        switch ((S1("raw"), $())) {
            case 31: return f(), new ss("mut");
            case 20: return f(), new ss("const");
-           default: return;
        }
    },
    () => new ts()
)

switch (h()) {
-   default: return !1;
-   case 63:
-       return !0;
+   case 63:
    case 43:
        return !0;
}
+ return !1;

- return W1(t) ? (G1(t === 4 ? 4 - 1 : t), Ns(s), e) : 0;
+ return W1(t) ? (G1(t === 4 ? 3 : t), Ns(s), e) : 0;

From experience benchmarking v8 some years ago, I found that eq, bin and math ops against literals reliably scored ~0.5-2.5% better when the literal was evaluated first

- array.length === 0
+ 0 === array.length

pushkine avatar Jul 04 '22 23:07 pushkine

I was also going to suggest this:

- "hello world".split(" ",void 0);
+ "hello world".split(" ");

But as stated in the documentation:

The JavaScript minification algorithm in esbuild usually generates output that is very close to the minified output size of industry-standard JavaScript minification tools. This benchmark has an example comparison of output sizes between different minifiers. While esbuild is not the optimal JavaScript minifier in all cases (and doesn't try to be), it strives to generate minified output within a few percent of the size of dedicated minification tools for most code, and of course to do so much faster than other tools.

cf https://esbuild.github.io/api/#minify

It's just a trade-off, speed vs optimal minification. I'm fine with it 🙂

hugoattal avatar Jul 05 '22 17:07 hugoattal

Thanks for the suggestions. Some of these make sense for esbuild, and I can add them. However, I think I won't do all of them. For example:

- var y = (e) => { M(), Te() };
+ var y = ( ) => { M(), Te() };

This transform isn't safe in all cases because it changes the value of y.length.

- "hello world".split(" ",void 0);
+ "hello world".split(" ");

This transform isn't safe in all cases because it changes arguments.length.

And some of the other transforms related to function inlining aren't really something esbuild is intended for because the internal architecture isn't designed for it. You would need a worklist-style algorithm that iterates until a fixed point to solve these while esbuild's current internal architecture is a pipeline that just does a single round of optimizations before finishing (since it's designed for performance). As the docs quoted in the previous post say, being optimal in all cases is a non-goal. If you want some of the advanced optimizations listed here then you may be better off with another tool.

evanw avatar Jul 06 '22 05:07 evanw

- var y = (e) => { M(), Te() };
+ var y = ( ) => { M(), Te() };
y();

much better do this:

- var y = (e) => { M(), Te() };
+ var y = _ => { M(), Te() };
y(orig);
// or y() if `arguments` doesn't present as side effect

MaxGraey avatar Jul 06 '22 05:07 MaxGraey

It's called eta-reduction:

- var xc = (s) => { Ya(() => s()) }
+ var xc = (s) => { Ya(s) }

Probably make sense, create some lambda interpreter / evaluator / simplificator?

It also should be done carefully in JS. For example:

boo((x)=>y.foo(x))

should be reduced to:

boo(y.foo.bind(y))

Or just skipped

MaxGraey avatar Jul 06 '22 05:07 MaxGraey

much better do this:

- var y = (e) => { M(), Te() };
+ var y = _ => { M(), Te() };
y(orig);
// or y() if `arguments` doesn't present as side effect

This is already implemented. You have to use --minify to get this (specifically --minify-whitespace, which means "print as small as possible but don't change the AST or rename identifiers").

It's called eta-reduction:

- var xc = (s) => { Ya(() => s()) }
+ var xc = (s) => { Ya(s) }

Probably make sense, create some lambda interpreter / evaluator / simplificator?

This is not safe in all cases because JavaScript has the concept of object identity and s === (() => s()) is false in JavaScript. Doing what you describe would break things.

It also should be done carefully in JS. For example:

boo((x)=>y.foo(x))

should be reduced to:

boo(y.foo.bind(y))

Or just skipped

This would also break things. Not only is the length different ((x)=>y.foo(x) has length 1 while y.foo.bind(y) has length 0) but this also changes the function object into something that can be constructed via new ((x)=>y.foo(x) throws an error when constructed while y.foo.bind(y) doesn't).

evanw avatar Jul 06 '22 06:07 evanw