Open kangax opened 8 years ago
Removing unused arguments is an unsafe optimisation as it will effect runtime behaviour as length
will be changed.
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.
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 .
[x] Use arrow functions (in ES6 browsers)
[x] Minify Array()
and Object()
type constructors: #45 π
[x] Minify RegExp
type constructor
[x] Use assignment operators where possible
[x] Merge object additions near object creation (a={};a.a=1
=> a={a:1}
)
[x] 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
[ ] 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
Wow, closure is really nailing it, huh...
I wanted to expand on that first "Resolve identity/simple functions" for CC since it does it quite nicely:
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?
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.
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 .
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?
@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.
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
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);
[x] 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
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); }
May be undefined
-> ''.$
instead undefined
-> void 0
? ([].$
, ({}).$
, eval.$
, Date.$
, Map.$
, etc., or simple var u;
as global symbol.)
@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.
Came across this one, too: https://github.com/nolanlawson/optimize-js Is there already a babel plugin for this?
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
So strange, I could swear it wasn't happening for me in repl.
1000
β 1e3
and so onI'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())
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.
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:
_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>.length
β 0 in <thing>
arr.length ? 'foo' : 'bar';
"foo".length ? 'foo' : 'bar';
// ideal
0 in arr ? 'foo' : 'bar';
0 in "foo" ? 'foo' : 'bar';
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.
Replace () => {}
with global noop
?
bar
infunction foo(bar) { }
)return
when possible (e.g.return [1,2,3]
→return[1,2,3]
)var result = expression(); foo.bar = result;
→foo.bar = expression()