Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More optimizations #25

Open
2 of 4 tasks
kangax opened this issue Jun 1, 2016 · 26 comments
Open
2 of 4 tasks

More optimizations #25

kangax opened this issue Jun 1, 2016 · 26 comments

Comments

@kangax
Copy link
Member

kangax commented Jun 1, 2016

  • Remove unused args (e.g. bar in function foo(bar) { })
  • Remove space after return when possible (e.g. return [1,2,3]return[1,2,3])
  • var result = expression(); foo.bar = result;foo.bar = expression()
  • Better constant propagation (looks like it is in the dead-code-elimination plugin, but its a combination of both)
var b = () => {
  var x = 14;
  var y = 7 - x / 2;
  return y * (28 / x + 2);
};
var a = () => {
  var a = 14;

  return (7 - a / 2) * (28 / a + 2);
};
@sebmck
Copy link
Contributor

sebmck commented Jun 2, 2016

Removing unused arguments is an unsafe optimisation as it will effect runtime behaviour as length will be changed.

@kangax
Copy link
Member Author

kangax commented Jun 2, 2016

Yep.

This is one of those almost-safe optimizations which will "work" most of the time but could silently bite you. We'll need to have it as part of the advanced/unsafe category.

This is also somewhat similar to Number(...)+... transformation that we already do — it's correct unless native method was tampered with (which we can't check).

In practice, I'm seeing tons of stuff like __d("...",[],function(b,c,d,e,f,g){c.__markCompiled&&c.__markCompiled() /* stuff that doesn't use e,f,g */ },null); in our code and it's most certainly safe to strip those args, saving a lot of unnecessary boilerplate.

@amasad
Copy link
Member

amasad commented Jun 2, 2016

Maybe have it as an option. FWIW GCC does this by default.

On Wed, Jun 1, 2016 at 5:39 PM Juriy Zaytsev notifications@github.com
wrote:

Yep.

This is one of those almost-safe optimizations which will "work" most of
the time but could silently bite you. We'll need to have it as part of the
advanced/unsafe category.

This is also somewhat similar to Number(...) → +... transformation that
we already do — it's correct unless native method was tampered with (which
we can't check).

In practice, I'm seeing tons of stuff like __d("...",[],function(b,c,d,e,f,g){c.__markCompiled&&c.__markCompiled()
/* stuff that doesn't use e,f,g */ },null); in our code and it's most
certainly safe to strip those args, saving a lot of unnecessary boilerplate.


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
#25 (comment),
or mute the thread
https://github.com/notifications/unsubscribe/AAj2_tmK1DmSSLH89FgpwgO-Wc8flg1xks5qHiZdgaJpZM4Ir6IY
.

@fregante
Copy link
Contributor

fregante commented Jul 6, 2016

Edit 1: added more

  • Use arrow functions (in ES6 browsers)

  • Minify Array() and Object() type constructors: Minify more type constructors #45 🎉

  • Minify RegExp type constructor

  • Use assignment operators where possible

  • Merge object additions near object creation (a={};a.a=1 => a={a:1})

  • Don't assign or return undefined (var a = undefined and return undefined)

  • Math.abs can be generated

    Unsafe with -0

    Math.abs(x)
    
    (ideal) 0>x?-x:x
  • Resolve simple functions

    Examples...
    var q = 'hello'.charAt(2);
    
    
    babel   var q='hello'.charAt(2);
    uglify  var q="hello".charAt(2);
    closure var q="l";
    (function () {
        function add(a, b) { return a + b; }
        function by3(num) { return num * 3; }
        window.q = add(4, by3(2)) + by3(add(4, 5));
    } ());
    
    
    babel   (function(){function c(d,a){return d+a}function a(b){return b*3}window.q=c(4,a(2))+a(c(4,5))})();
    uglify  !function(){function n(n,t){return n+t}function t(n){return 3*n}window.q=n(4,t(2))+t(n(4,5))}();
    closure (function(){window.q=37})();
    // Edit 3, added July 7th
    function foo (num) {
        function cube(num) {
            return num * 2;
        }
        function quarter(num) {
            return num / 4;
        }
        return cube(quarter(num));
    }
    
    uglify  function foo(n){function r(n){return 2*n}function t(n){return n/4}return r(t(n))}
    babel   function foo(a){return function(a){return 2*a}(function(a){return a/4}(a))}
    closure function foo(a){return a/4*2};

    boopathi/babel-minify has some

  • Remove unnecessary IIFE

    (function () {
        window.foo = 2;
    } ());
    
    
    (all)   (function(){window.foo=2})();    
    (ideal) window.foo=2;
  • Remove shadowed+unused variables/functions (unless they access properties)

    var a = 1;a = 2;
    
    
    (all)   var a=1;a=2;
    (ideal) var a=2;
    function foo() {}
    function foo(bar) {
        return bar;
    }
    
    (all)   function foo(){}function foo(a){return a}
    (ideal) function foo(c){return c};
  • Minify Function type constructor

    Unsafe because new Function's functions are created in the global scope; They'd have to be created there.

    new Function('a', 'b', 'return a + b');
    
    (all)   new Function('a','b','return a + b');
    (safe)? function anonymous(a,b) {return a+b}
    (ideal) function(a,b) {return a+b}
  • constant-folding isn't applied after minify-type-constructors

    console.log(1+String("1"));
    
    
    uglify  console.log(1+String("1"));
    babel   console.log(1+"1");
    closure console.log("11");
  • dead-code-elimination isn't applied after minify-type-constructors

    1+"1"; // this is removed
    1+String("1") // this isn't
    
    
    uglify  1+String("1");
    closure "11";"11";
    babel   1+"1";
  • Math.pow -> Exponentiation Operator in ES2016 browsers

    var cube = Math.pow(2, 3);
    num = Math.pow(num, 3);
    
    
    (ideal) var cube=2**3;num**=3;
  • Dead code elimination: pure functions

    (function () {} ());
    
    
    babel   (function(){})();
    closure (function(){})();
    uglify  // 0 bytes, totally eliminated
    (function () {
        function twice(num) {
            return num * 2;
        }
        var a=twice(1);
    } ());
    
    
    babel   (function(){(function(a){return 2*a})(1)})();
    uglify  !function(){function n(n){return 2*n}n(1)}();
    closure (function(){})();
    (ideal) // 0 bytes, totally eliminated
  • Remove window where possible (unsafe if output is pasted inside another function)

    window.jQuery = window.$ = function() {};
    
    
    (ideal) jQuery=$=function(){}
  • Merge multiple assignments

    (function () {
        function on() {}
        function off() {}
        module.exports = on;
        module.exports.on = on;
        module.exports.off = off;
    } ());
    
    
    uglify  !function(){function o(){}function n(){}module.exports=o,module.exports.on=o,module.exports.off=n}();
    babel   (function(){function a(){}module.exports=a,module.exports.on=a,module.exports.off=function(){}})();
    closure (function(){function a(){}module.exports=a;module.exports.on=a;module.exports.off=function(){}})();
    (ideal) (function(){function a(){}module.exports.on=module.exports=a;module.exports.off=function(){}})(); // this transform
    (next) (function(){module.exports.on=module.exports=function(){},module.exports.off=function(){}})(); // would allow for this
    (next) module.exports.on=module.exports=function(){},module.exports.off=function(){}; // and then this
  • Remove boolean casting in conditions

    if (!!foo) {
        log(1);
    }
    
    
    babel   !foo||log(1);
    uglify  foo&&log(1);
    closure foo&&log(1);
  • Math.min and Math.max with two parameters
    http://codegolf.stackexchange.com/a/60293/21413

    Unsafe with -0

    Math.min(a,b)
    Math.max(a,b)
    
    
    (ideal) 
    a<b?a:b
    a>b?a:b
  • Various possible indexOf optimizations

    [1].indexOf(1)!==-1; // contains
    
    
    (ideal) 
    [1].indexOf(1)>=0;
    ~[1].indexOf(1); // only works when a truthy value is enough, e.g. if(...)
    [1].indexOf(1)===-1; // doesn't contain
    
    
    (ideal) 
    [1].indexOf(1)<0;
  • Only when accessing properties, compare to undefined instead of using typeof

    Removed because accessing a.bar could have side effects

    function isBarUndefined(foo) {
        return typeof foo.bar === 'undefined'
    }
    
    (all)   function isBarUndefined(a){return'undefined'==typeof a.bar}
    (ideal) function isBarUndefined(a){return void 0===a.bar}
  • Generate the string 'undefined' (I think constant-folding conflicts with this by transforming ''+void 0 back into 'undefined')

    typeof foo === 'undefined'
    
    (ideal)
    typeof foo==''+void 0
  • undefined-to-void -> shorten-undefined http://codegolf.stackexchange.com/a/42155/21413

    undefined;
    
    
    (all)   void 0;
    (ideal) 1..a;
    undefined;undefined;
    
    
    (all)   void 0;void 0
    (ideal) var u;u;u // assign undefined variable, use twice

@kangax
Copy link
Member Author

kangax commented Jul 6, 2016

Wow, closure is really nailing it, huh...

@fregante
Copy link
Contributor

fregante commented Jul 6, 2016

I wanted to expand on that first "Resolve identity/simple functions" for CC since it does it quite nicely:

(click here to expand <details>)

(function () {
    function repeatTwice(text) {
        return [text, text].join('');
    }

    foo = repeatTwice('hello')
} ());

closure output: (function(){foo="hellohello"})();
(function () {
    function identity(text) {
        return text;
    }

    foo = identity('hello')
    bar = identity('hello')
    baz = identity('hello')
} ());

closure output: (function(){baz=bar=foo="hello"})();

And look at this crazy thing:

(function () {
    function multiple(text) {
        return [text, text, text, text, text, text];
    }

    foo = multiple('w')
} ());

closure output: (function(){foo="wwwwww".split("")})();

Which at times fails too:

(function () {
    var text = 'hello, it’s me';
    foo = [text, text, text, text, text, text]
} ());

closure (function(){foo="hello, it\u2019s me;hello, it\u2019s me;hello, it\u2019s me;hello, it\u2019s me;hello, it\u2019s me;hello, it\u2019s me".split(";")})();
babel   (function(){var a='hello, it’s me';foo=[a,a,a,a,a,a]})();
uglify  !function(){var o="hello, it’s me";foo=[o,o,o,o,o,o]}();

This is being solved

(function () {
    function twice(text) {
        return [text, text];
    }

    foo = twice('w')
    foo = twice('w')
    foo = twice('w')
} ());

closure output: (function(){foo=["w","w"];foo=["w","w"];foo=["w","w"]})();

But a slight change in the length of the text breaks it, even though the output would still be smaller, especially factoring gzip in

(function () {
    function twice(text) {
        return [text, text];
    }

    foo = twice('ww') // <---
    foo = twice('ww') // <---
    foo = twice('ww') // <---
} ());

expected output: (function(){foo=["ww","ww"];foo=["ww","ww"];foo=["ww","ww"]})();
closure output:  (function(){function a(a){return[a,a]}foo=a("ww");foo=a("ww");foo=a("ww")})();

This was a wrong repeat implementation, but look at that output

(function () {
    function repeat(text, repeat) {
        for (var i = 0; i < repeat; i++) {
            text += text;
        }
        return repeat; // mistake here, it should return text
    }

    foo = repeat('hello', 1)
} ());


closure output: (function(){for(var a=0;1>a;a++);foo=1})();

Notes: foo is resolved. The loop is truncated, but it's still there.

Making two calls no longer resolves foo

    ...
    foo = repeat('hello', 1)
    foo = repeat('hello', 1)
    ...

closure output: (function(){function a(a,b){for(var c=0;c<b;c++);return b}foo=a("hello",1);foo=a("hello",1)})();

@jamiebuilds
Copy link
Contributor

re: Use arrow functions (in ES6 browsers)

How should babel-minify handle minifying using modern language features that are normally compiled down to ES5 by Babel?

@sebmck
Copy link
Contributor

sebmck commented Jul 6, 2016

We can't turn function functions into arrow functions. It's an unsafe optimisation. Arrow functions have no prototype and cannot be used as constructors so they aren't the same.

@amasad
Copy link
Member

amasad commented Jul 6, 2016

James: I don't think that's necessary because you can always add the
plugins you need before the minify step

On Wed, Jul 6, 2016, 2:26 PM Sebastian McKenzie notifications@github.com
wrote:

We can't turn function functions into arrow functions. It's an unsafe
optimisation. Arrow functions have no prototype and cannot be used as
constructors.


You are receiving this because you commented.

Reply to this email directly, view it on GitHub
#25 (comment),
or mute the thread
https://github.com/notifications/unsubscribe/AAj2_nWIPRCQPIfQt-5mt-TEYeHAXyBOks5qTB1ngaJpZM4Ir6IY
.

@jamiebuilds
Copy link
Contributor

Yes, but there are places where ES5 code could be optimized more by using ES6+ features. I'm sure there are cases where we'd find arrow functions acceptable to use. Should optimizations like that be inferred? configured?

@fregante
Copy link
Contributor

fregante commented Jul 7, 2016

@thejameskyle Should optimizations like that be inferred?

Ideally the user would either be able to enable/disable each transform or use browserlist to figure out which ES6 transforms are safe. But maybe a target:"es6" could work.

@kittens then maybe the function-to-arrow transform could only apply to functions that are verified to never user the prototype.

@fregante
Copy link
Contributor

fregante commented Jul 7, 2016

It may be early for this, but should babel-minify have a repo dedicated to transforms/plugin suggestions? One-suggestion-per-issue would make it easier to discuss, track and "rate" each transform (difficulty vs. how common it is vs. savings)

Like: https://github.com/postcss/postcss-plugin-suggestion-box and https://github.com/sindresorhus/module-requests

Perhaps babel itself could benefit from one

@boopathi
Copy link
Member

boopathi commented Jul 19, 2016

A few more optimizations for conditionals -

  • propagate condition to the diff between consequent and alternate

    if (x) { doSomething(a) } else { doSomething(b) }
    
    // to:
    doSomething(x ? a : b);
  • conditional operator

    a < b ? false : true
    a() ? false : true
    b() ? true : false
    a ? b : true
    a ? b : false
    
    // out:
    x === y
    !(a < b)
    !a()
    !!b()
    !a || b

@kangax
Copy link
Member Author

kangax commented Jul 19, 2016

For the 1st case, I guess we would need to "fold" all the same-named methods as long as they're the only ones present in blocks and are in the same order?

if (x) { foo(a); bar(y); } else { foo(b); bar(z); }
// becomes
foo(x ? a : b);
bar(x ? y : z);

but these won't work:

if (x) { bar(y); foo(a); } else { foo(b); bar(z); }
if (x) { foo(a); } else { foo(b); bar(z); }

@napa3um
Copy link

napa3um commented Sep 3, 2016

May be undefined -> ''.$ instead undefined -> void 0? ([].$, ({}).$, eval.$, Date.$, Map.$, etc., or simple var u; as global symbol.)

@fregante
Copy link
Contributor

fregante commented Sep 3, 2016

@napa3um Already suggested near the end of #25 (comment)

@hzoo Would it make sense to open a repo for the suggestions as suggested earlier? Or maybe just one-enhancement-per-issue so they can be easily discussed and tracked.

@jokeyrhyme
Copy link

Came across this one, too: https://github.com/nolanlawson/optimize-js
Is there already a babel plugin for this?

@kangax
Copy link
Member Author

kangax commented Oct 19, 2016

We should also turn new Array to Array() no matter which arguments since they're identical and there's no need for new /cc @shinew

@fregante
Copy link
Contributor

fregante commented Oct 19, 2016

It should happen already: source + repl

@kangax
Copy link
Member Author

kangax commented Oct 19, 2016

So strange, I could swear it wasn't happening for me in repl.

@kangax
Copy link
Member Author

kangax commented Oct 23, 2016

  • Ouch, just noticed we don't even do 10001e3 and so on

@pitaj
Copy link

pitaj commented Nov 22, 2016

I'd like to see unnecessary, single-use variables get inlined and also functions like this:

(function ([, a, b]) {
  return (function (c, d) {
    return c + d;
  })(a, b);
})(thing);

(function ([, a, b]) {
  var e = (function (c, d) {
    return c + d;
  })(a, b);
  return e;
})(thing);

(function ([, a, b]) {
  (function (c, d) {
    log(c + d);
  })(a, b);
})(thing);

var a = b.something();
run(a.other());

// become

(function ([, c, d]) {
  return c + d;
})(thing);

(function ([, c, d]) {
  return c + d;
})(thing);

(function ([, c, d]) {
  log(c + d);
})(thing);

run(b.something().other())

@fregante
Copy link
Contributor

That'd be great! Anything that isn't used more than once should be inlined. I often declare variables for readability but I could just as well write one-liners instead.

@j-f1
Copy link
Contributor

j-f1 commented Aug 8, 2017

Various possible indexOf optimizations

These are shorter:

[1].indexOf(1)!==-1; // contains


(ideal) 
[1].includes(1);
[1].indexOf(1)===-1; // doesn't contain

(ideal) 
![1].includes(1)

Remove shadowed+unused variables/functions (unless they access properties)

For property access:

var a = foo.bar;a = foo;


(all)   var a=foo.bar;a=foo;
(ideal) foo.bar;var a=foo;

Also, a couple other thoughts:

  • Just like how Babel creates a _classCallCheck helper variable that makes sure new is used when constructing ES6 classes, Babili could create a _babiliUndefined variable that could get mangled down to a single character, which is significantly shorter than void 0. By taking advantage of the same mechanism that Babel uses to generate a non-conflicting variable name, we could avoid problems when people name a variable _babiliUndefined. Example:
if (foo === undefined) {
  return undefined
}

// becomes

var _babiliUndefined
if (foo === _babiliUndefined) {
  return _babiliUndefined
}

// after mangling
var a
if (b === a) {
  return a
}

Some of the things presented in https://codegolf.stackexchange.com/q/2682/44689:

  • Replace parseInt/parseFloat with +:

    parseInt(foo(),16);
    parseInt(foo(),10);
    parseInt(foo());
    parseInt(foo(),8);
    parseInt(foo(),2);
    
    // ideal:
    +("0x"+foo()|0);
    +foo()|0;
    +foo()|0;
    +("0o"+foo()|0);
    +("0b"+foo()|0);
  • Math.{floor,ceil,round}:

    Math.floor(foo);
    Math.round(bar());
    Math.ceil(baz.quux);
    
    // ideal
    
    0|foo;
    0|bar()+.5;
    0|baz.quux+1-1e-9;
  • boolean contexts: !=^:

    x != 3 ? a : b;
    foo.bar() != -99 ? a : b;
    if (theAnswer != 92) {
      foo();
    }
    
    // ideal
    
    x^3?a:b
    foo.bar()^-99?a:b
    if (theAnswer^92) {
      foo();
    }
  • boolean contexts: <thing>.length0 in <thing>

    arr.length ? 'foo' : 'bar';
    "foo".length ? 'foo' : 'bar';
    
    // ideal
    
    
    0 in arr ? 'foo' : 'bar';
    0 in "foo" ? 'foo' : 'bar';

@j-f1
Copy link
Contributor

j-f1 commented Aug 22, 2017

Coming from #591, where @loganfsmyth suggested (after some discussion) that babel-minify place a _void0 or similar variable at the top, then replace all uses of undefined with it, allowing for mangling:

It should be possible to either put the var _void0 variable at the top of sourceType: module scripts or ones that are node modules. In other cases, they can be put at the top of function scopes, or the entire code could be wrapped in a block:

{
let _void0
// code here
}

This would increase code size if void 0 only appeared once, but would save 5 characters if used twice and 11 if used thrice.

@shanimal
Copy link

shanimal commented May 2, 2018

Replace () => {} with global noop?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests