babel / minify

:scissors: An ES6+ aware minifier based on the Babel toolchain (beta)
https://babeljs.io/repl
MIT License
4.39k stars 225 forks source link

More optimizations #25

Open kangax opened 8 years ago

kangax commented 8 years ago
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 commented 8 years ago

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

kangax commented 8 years ago

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 commented 8 years ago

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 https://github.com/amasad/babel-minify/issues/25#issuecomment-223166015, or mute the thread https://github.com/notifications/unsubscribe/AAj2_tmK1DmSSLH89FgpwgO-Wc8flg1xks5qHiZdgaJpZM4Ir6IY .

fregante commented 8 years ago

Edit 1: added more

kangax commented 8 years ago

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

fregante commented 8 years ago

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

(click here to expand <details>) ``` js (function () { function repeatTwice(text) { return [text, text].join(''); } foo = repeatTwice('hello') } ()); closure output: (function(){foo="hellohello"})(); ``` ``` js (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: ``` js (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: ``` js (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 ``` js (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 ``` js (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 ``` js (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` ``` js ... foo = repeat('hello', 1) foo = repeat('hello', 1) ... closure output: (function(){function a(a,b){for(var c=0;c
jamiebuilds commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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 https://github.com/amasad/babel-minify/issues/25#issuecomment-230911983, or mute the thread https://github.com/notifications/unsubscribe/AAj2_nWIPRCQPIfQt-5mt-TEYeHAXyBOks5qTB1ngaJpZM4Ir6IY .

jamiebuilds commented 8 years ago

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 commented 8 years ago

@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 commented 8 years ago

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 commented 8 years ago

A few more optimizations for conditionals -

kangax commented 8 years ago

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 commented 8 years ago

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

fregante commented 8 years ago

@napa3um Already suggested near the end of https://github.com/babel/babili/issues/25#issuecomment-230671685

@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 commented 8 years ago

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

kangax commented 8 years ago

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 commented 8 years ago

It should happen already: source + repl

kangax commented 8 years ago

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

kangax commented 8 years ago
pitaj commented 7 years ago

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 commented 7 years ago

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 commented 7 years ago

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:

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:

j-f1 commented 7 years ago

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 commented 6 years ago

Replace () => {} with global noop?