How much overhead does ES2015 bring on the client?


#1

We know from @benjamn’s Meteor +=ECMAScript talk at DevShop July 2015 that ES6 polyfills aren’t shipped dynamically only to browsers that need them. I personally hope this gets implemented soon, because Chrome has over 40% market share, and an even higher piece of the pie in certain demographics, so we hit half our users with needless polyfill code.

But until then, has anyone tested the impact of using ES2015 on the client?

  • percentage increase in initial bundle size
  • hot code reload speed decrease

Other performance considerations?


#2

About performance, transpiled code seems sometimes to give a serious hit :
http://kpdecker.github.io/six-speed/


#3

I agree. In a new app that’s targeted towards tech users i’m only supporting IE >11, and the latest Safari, FF and Chrome. It would be helpful to opt out of this!

This is interesting! Some of the features may be too slow to use for speed critical parts but if creating a class is 1.5x slower than ES5 i’ll still use ES6 any day of the week.

Code maintainability/readability is a larger problem than a few cycles in most cases. At the end of the day the DOM has choked my app 99.9% of the time and not JS runtime speeds.

I didn’t realize that for of had such a perf. hit, that’s pretty significant!


#4

I’m happy to provide actual data (and I’m sorry I didn’t have it ready during my talk), but first I want to clear up some confusions that I see emerging in this thread.

Optionality

Both packages (ecmascript and es5-shim) will be removable, for those who do not wish to enable the new features, or who do not care about supporting older browsers. This was always a design constraint for the ES2015 project, precisely so that no one could complain we made their app slower or larger without giving them the opportunity to opt out. I do not mean to suggest we actually think the new features are too slow or too bloated for production use. In fact, if you let those unmeasured concerns keep you from using the new ecmascript package, you’ll just be cheating yourself out of a better development experience.

Caching

All compilation is cached, so the cost of incremental reloads is never more than the cost of compiling the files that changed, and only those files. If recompilation ever takes longer than switching from your text editor to your browser, something has gone terribly wrong, and you should file a GitHub issue (with a reproduction, if possible).

If you are writing a package that uses ecmascript but also contains large files that should not be compiled, you can selectively disable compilation when you call api.addFiles by passing the {transpile:false} option, e.g.

api.addFiles("backbone.js", "client", { transpile: false });

Runtime footprint

Here is the entire runtime library that the ecmascript package uses. It contains only the helper functions we need, so it’s smaller than Babel’s own library of external helpers. The benefit of using a runtime library is that we don’t have to keep injecting the same helper functions at the top of every compiled file, which is what Babel does by default.

Generated code size

As part of our due diligence in deciding which language features to enable, we examined the output of every candidate transformation.

In some cases we decided not to enable the transformation, either because its runtime was large, or because the generated code was verbose. Generator functions are a good example of both problems, and I should know, because I am the author of Regenerator. That said, if you have a good use case for generator functions in Meteor code, let us know, and we will consider enabling them.

In other cases we tweaked the Babel options so that the generated code would be smaller or faster. For example, we compile for-of loops in “loose” mode, so that code like this

for (let x of foo()) {
  console.log(x);
}

gets translated to this instead of this.

In other words, if you refer to that performance comparison table that @vjau mentioned above, make sure you look at the babel-loose rows instead of the babel rows. For example, for-of loops over arrays are much faster in loose mode.

Pay for what you use

Perhaps most importantly, any code that avoids using ES2015 features will have exactly the same generated code size and performance characteristics as ordinary ES5 code, because the compiler will just leave it untouched.

Since truly performance critical code tends to be isolated in relatively few hot spots, you always have the option of rewriting just those few bits of code in whatever style performs best, and otherwise reaping the benefits of the new language features throughout the overwhelming majority of your code.

When native support eventually catches up, your code will be ready to take full advantage of the native performance boost—but only if you’ve already been using the new language features via compilation!

Avoid relying on microbenchmarks

When and where performance optimization will be necessary is highly dependent on the details of your specific application. I’m reluctant to talk about performance without referring to real-world examples, because microbenchmarks are misleading. Without actually looking at someone’s code, it’s difficult to give any meaningful advice about performance optimization, and so I would strongly discourage anyone from presuming to give that kind of unspecific advice in this forum.


Let’s have some numbers!

Using the latest devel branch version of Meteor, I created a new app, then built it for production with all four combinations of enabling/disabling the ecmascript and es5-shim packages.

Listed below are the resulting sizes of the bundle/programs/web.browser/<hash>.js file (which is the file that gets sent to the browser):

Without ecmascript, without es5-shim: 327.80KB (+0KB, 1x)
With ecmascript, without es5-shim: 335.95KB (+8.15KB, 1.025x)
Without ecmascript, with es5-shim: 342.40KB (+14.60KB, 1.045x)
With ecmascript, with es5-shim: 350.55KB (+22.75KB, 1.069x)

If you use gzip (and you do, unless you use meteor run for production, in which case you have bigger problems!), the difference between the largest and smallest versions is only 7.66KB (or just 2.63KB with ecmascript only). Is that enough to cause concern? We can’t make that call for you, but now you have enough information to decide for yourself.


#5

Note: if you decide to remove the es5-shim package, you should be prepared either to replace it with something equivalent, or to drop support for older browsers. According to its author, es5-shim actually fixes bugs in all major browsers, not just IE8 and earlier, so removing it is not a decision to be taken lightly, regardless of your audience. Please do not recommend removing es5-shim to other developers without including a disclaimer like this one, or you may be partly responsible for compatibility bugs that they discover later!


#6

Great answer, Ben.

A note on app rebuilding times: The ecmascript package uses the new ability for build plugins to do file-level caching in Meteor 1.2 (preview release of that work). If you’re using Meteor 1.1 (or devel before a couple weeks ago), we hope you’ll find with the new version (more RCs coming soon) that rebuilding your app is now much, much faster.

With file-level caching, Meteor provides as fast an ES6 compilation experience as you can really get. And it’s pretty fast.


#7

Brilliant post! Thank you so much for taking the time to address the concerns in this thread. Please consider making this a meteor.com blog as well - it’s always good to point prospective developers at an official statement which shows the attention to detail you guys bring to the platform.


#8

+1000 we should copy-paste that post into the blog


#9

Done! http://info.meteor.com/blog/how-much-does-es2015-cost


#10

@benjamn Does this also include the babel polyfill for ES6 Number methods? gregio:babel had to use a custom build of core-js that excluded the es6.number.constructor option.

The root issue was that you couldn’t do check(123, Number) because core-js overrides the constructor. Just wondering how you got around this for the ecmascript package? The meteor-webpack-react project also suffers from this as well.


#11

I know this is a relatively old post… That said, the vast majority of features for ES2015 are already in Chrome and Firefox… Assuming you’re using a build tool (Webpack, Browserify, Rollup etc) with babel, you can create two build version… one with the es2015-minimal preset and babel-polyfill, and another with just es2015-modern, the modern one can be used for recent browsers… Firefox 49+, Chrome 52+, Safari 10+ and Edge 14+ … old browsers getting the full polyfill built version, new browsers getting the slim built version(s). You should observe the kangax[1] tables as a reference to the handful of areas not fully supported (mostly proper tail calls and some symbols not well defined).

With only the babel-polyfill via webpack -p the output is about 70K uglified, not gzipped… gzipped transport is closer to 30k, which isn’t too bad. Noting that this includes regenerator + core-js polyfills which is pretty big. That’s said, it’s still al lot less than say jQuery and moment combined… less than angular or react. The benefits, imho far outweigh the overhead. All of that said, ymmv, it depends on your needs… If you target modern browsers for a very small react-like app… you can use es2015-modern + stage-0, without the polyfill(s) with preact + redux, and get in well under 30kb gzipped.

[1] http://kangax.github.io/compat-table/es6/