ES6 transpilation via babel

Does that rely on the underscore meaning super?

Thatā€™ be great to post onto the Babel GitHub issue tracker. If itā€™s transpiling to ES5, it should only be marginally slower, one would think. EDIT: https://github.com/babel/babel/issues/2399

Yeah, right! Thatā€™s not happening. x}

After seeing your findings, Iā€™m totally down to go the pure ES5 route.

The loss of the satisfaction of how writing ES6 classes feels will be tiny compared to the gains in satisfaction from making the our library performant.

I never realized how under performing ES6 transpiling is today! I am kinda embarrassed that I never really thought about it until now. In truth, there has never been a need to look at it before now. Interesting to me is I even asked "why did WillyBoat not use ES6" to the willyboat team and not one answer from them.

We really need to think about how we are going to set this up to eventually go to the the faster code base between ES5 and ES6.

  • Is it safe to use ES6 modules?
  • should we still maintain an ES6 code base. Let the transpiler ignore the ES5 code and change when supported browser versions support native ES6?
  • would it be too much work to create the ES5 code from the transpile by stripping the wrappers? Always having the ES6 version ready?

I probably know the answers to most of these, but just want to throw them out in the case someone has a better idea than we come up with.

Edit: I have to take back some of my concern, because I was basing it on the tests above. Please see my test below. There should be no unexpected slow down of an extended class if written properly.

I asked Michael Obrien in person one time, and he said he loves ES6, but that performance would be out of their control.

Good to know that was the reason. Makes me feel better they came up with the same conclusion. Another example of documenting and being VERY clear about decisions, so we can just link to the explanation and not have to explain it :stuck_out_tongue:

I think so, because that only affects the code wrappers prior to any code running, so even if it takes a looooong time to load those modules, it wonā€™t actually affect the runtime performance of the app.

Yeah, I donā€™t see why not. Certain things should be just fine (ES6 modules for example). We can write classes in a forward-compatible manner by using ES5 classes and never calling the super contructor after the use of this.

Which wrappers? The module wrappers? I donā€™t that really matters because they are all evaluated at the beginning (unless weā€™re using HTTP/2 with JSPM, but when that becomes an official way to load modules ES6 modules may already be native, thatā€™s my guess).

In general, I think we can continue using ES6 for certain things, just not for class definitions. Some things compile to really simple ES5 with no function wrappers. Weā€™ve gotta experiment/research to find out what exactly.

No. I meant the class wrappers in this case.

Yeah, I agree with supporting as much as possible and doing the rest as it goes native. Could even have both versions, to show the benchmark for why we are still there. That way we know when to make the change also.

I personally would first write my code in ES6, create my benchmark tests, then convert to the performance enhanced equivalent.

I donā€™t think the wrappers can be removed easily, but itā€™s possible. Someone would have to maintain that against Babel. Thatā€™d be cool to write ES6 classes and have it transpile to pure ES5 with no wrappers. Itā€™s totally doable.

I donā€™t see why

class Foo extends Bar {
  someMethod() {...}
}

canā€™t simply become

function Foo() {
    Bar.apply(this, arguments)
}
Foo.prototype = Object.create(Bar.prototype)
Foo.prototype.constructor = Foo
Foo.prototype.someMethod = function someMethod() {...}

(in the simple case)

Thatā€™s a lot of extra work, unless we have a conversion script that converts to pure ES5 classes.

1 Like

@gadicc This was really bugging me. So I was trying to figure out why your tests would be so far off. 17x on a call to an extended function just made no sense to me. :stuck_out_tongue: Transpiling should not have affected it like it did.

I see now that the first test over complicated the use of super to create a false positive test. I kept telling myself these two instances should be returning exactly the same code values and getting the exact same reference values in the parent class. The only slow down would have been in the instantiating of the classes, IF we were testing that case.

new test returning exact same values:
http://jsperf.com/es6-classes-super-es5-vs-transpiled/8

ES6 code

    class Base {
      constructor(data) {
        this.data = data;
      }
      getData() {
        return this.data;
      }
    }

    class Sub extends Base {
       constructor(data) {
         super(data);
      }
    }

ES6 Transpiled

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var Base = (function () {
  function Base(data) {
    _classCallCheck(this, Base);

    this.data = data;
  }

  Base.prototype.getData = function getData() {
    return this.data;
  };

  return Base;
})();

var Sub = (function (_Base) {
  _inherits(Sub, _Base);

  function Sub(data) {
    _classCallCheck(this, Sub);

    _Base.call(this, data);
  }

  return Sub;
})(Base);

ES5 code

    function ES5_Base(data) {
      this.data = data;
    }

    ES5_Base.prototype.getData = function() {
      return this.data;
    }

    function ES5_Sub(data) {
      ES5_Base.call(this, data);
    }

    ES5_Sub.prototype = Object.create(ES5_Base.prototype);
    ES5_Sub.prototype.constructor = ES5_Sub;

###Instances

this.es6t = new Sub(5);
this.es5 = new ES5_Sub(5);

###Test Cases

this.es6t.getData();
this.es5.getData();

You will be glad to know, I also increased the performance of the ES5 code at the same time as the ES6 code and they should be performing about twice as fast as the first example test.

I am going back to writing ES6 in most cases :smile:

Had anyone tested traceur compiler vs Babel? Traceur-runtime may not have some of these perf problems.

I havenā€™t tested, but check out how Traceur transpiles a simple ES6 class. It seems like a lot of overhead.

I went ahead and rolled my own class lib too: http://npmjs.com/lowclass. :relaxed: I tried to make it as performant as possible, generating pure ES5 classes.

@talves, sorry, my test wasnā€™t 100% clearā€¦ the important super was the one inside the overriden getData, since thatā€™s what weā€™re testing :> The one inside the constructor was actually superfluous and was left over from something else I was trying, but wasnā€™t counted in the test time anyway, since we werenā€™t timing instantiation.

In any case, I really feel I have to stress again, I am only talking about using ES5 for components and other high traffic functions. We can benefit from beautiful ES6 classes everywhere except for things that are running 10,000 times a second. Even though itā€™s quite a big difference, I donā€™t think itā€™s something youā€™ll feel anywhere else.

Super also was obviously one of the worst performing examples. I did link it before, but hereā€™s the time comparison matrix including babel vs babel-loose vs traceur. http://kpdecker.github.io/six-speed/. (I added it to the resource section too).

One of the reasons why some things in babel are so slow is because of strict ES6 compliance. If you run it in ā€œlooseā€ mode, it will be much faster, but then when the day comes for native ES6, (potentially) some of the code you wrote before that did work, wonā€™t work anymore. So I donā€™t think thatā€™s the best route. ES5 will always work.

Iā€™m going to see how easy it is to move the browser version/adoption conversation into a new thread, I think itā€™s an important topic: Browser support, share, usage, adoption, upgrades, etc

4 posts were merged into an existing topic: Browser support, share, usage, adoption, upgrades, etc

Edit: sorry this is an older post that I moved accidentally and now is here out of order since I moved it back :confused:

No, I was only looking at the running time of the method of an already instanced class.

Weā€™d need to do some proper research into browser usage (including native webviews on mobile) to make a final call, but my main point was just that, if some optimization will solve some problem for us and is expected to land in ā€œthe next chrome-betaā€, we should not assume that this fix is acceptable for all our users (i.e. our usersā€™ users) most of whom probably wonā€™t be keeping their browsers constantly up-to-date.

e.g. according to https://kangax.github.io/compat-table/es6/, no current browser (including google chrome beta and canary) has native ES6 class supportā€¦ some do if you enable flags for experimental javascript (which is unrealistic to expect all users to do), and some implementations arenā€™t final. So I think weā€™ll still be relying on transpiled code for some time to come (thereā€™s also the issue of intelligently detecting the browser environemt and serving raw ES6 if itā€™s fully supported).

Yeah, I said both things made sense, it was just annoying come from the more permissive ES5 :slight_smile: That said, great news about the ES6 Java-like properties; thatā€™s exactly what I was looking forā€¦ inheritable properties that could be overriden per subclass or instance. Shot!

I think we can assume it will be at least as fast or possibly even faster since itā€™s more restrictive. On the performance grid, native ES6 is marked as ā€œidenticalā€ for all browsers for classes. But as per above, I think it will be a while before we can bank on all our users (and their users) having native ES6 browsers.

Yeah, I just rolled my own for now :> It looks like this:

var subComponent = Component.extend({

   doSomething() {
     this._doSomething();  // easy syntax for super's method
   }  

});

It was quite handy because it let me do a bunch of other tweaks and per component code that would be an extra few lines per class otherwise.

I donā€™t know so much. Most end users wonā€™t know things about ā€œoh I need to upgrade my browser for ES6 speed improvementsā€. The whole idea of transpilation is the assumption that it will take users much longer to upgrade than developers. People are going to look at an infamous demo, or a product using infamous, and say ā€œwow, this feels like crapā€¦ it doesnā€™t deliverā€, not ā€œoh, wait, let me upgrade my browser firstā€.

Again, Iā€™m not saying donā€™t use ES6ā€¦ my main point is for high traffic code (e.g. components) As per:

So letā€™s look at components againā€¦ Say 100 nodes with 5 components each, thatā€™s potentially 500 updates a frame, and at 60fps, 30,000 updates/second. Even a 33% difference can make a huge different with these numbers. To be clear though, Iā€™m not talking about load time, class definition time, or instantiation time (which as @talves correctly said, is addressed by proper pooling). Iā€™m only talking about function calls.

Hereā€™s a jsperf I did for the use of super() in ES6 transpiled classes:

http://jsperf.com/es6-classes-super-es5-vs-transpiled/2

To be clear, after definitions, the only thing Iā€™m testing/timing is:

someInstance.getData();

which calls itā€™s superā€™s getData to return the result (actual test code available in the link).

In Chrome, native ES5 was 27.5x faster, and in Firefox, 56.4x faster. That is a pretty huge deal, and was very noticeable when I was profiling components. Apparently it has to do with ES6 strictnessā€¦ thereā€™s a more permissive option which is only 1.7x slower (Chrome) and 5x slower (Firefox) , which is still way too much in my mind for components, but also:

  1. It means youā€™ll be able to write code that will work transpiled but break in native.
  2. It means weā€™ll have to explain to our users to use a non-ES6 version of our code or to enable this option which affects all their code too (assuming they have a babel setup).

Obviously also affects anything components use, such as utility classes, and probably Node methods.

You might say ā€œok so donā€™t use super()ā€, but well, is our approach then ā€œuse ES6 classes but donā€™t use their features?ā€. Yes, although instead of super, we could do the prototype call ourself, this is still just similar to the babel-loose example where thereā€™s still a lot of overhead from all the wrapped code (weā€™re still calling the prototype of an ES6 transpiled class).

And then we have to do this kind of research for everything we use. I just think itā€™s much safer to do components (and probably anything they use) in ES5. Weā€™re going to be judged on performance, and I think we should assume most of our users wonā€™t be using ES6 browsers by the time we have something out and for quite some time after.

@gadicc Donā€™t be sorry, it is a valiant point you are making. Although I disagreed with how you ran the test, I am starting to see ES6 ā€œtranspiledā€ classes as an unnecessary and maybe evil code sugar.

Here are two articles I will point to for my decision, until they are fixed either by going native or a better prototypical design in ES7. I have been going down the rabbit hole based on my belief that classes will solve other problems when in reality they are just creating new ones.

The Two Pillars of JavaScript
How to Fix the ES6 ā€˜classā€™ keyword

I agree with the following quote 100%. We know we can add performance to the statement when transpiled.

P.S. Donā€™t use super unless you enjoy stepping through the debugger into multiple layers of inheritance abstraction. 1

Even when ES6 classes are native, I donā€™t think that can be done, at least not yet (not without evaling everything really slooooooooowly). But, if a way to make it happen become reality in browsers, then we could stick with a single syntax if we do like in the following examples.

Use ES5 for components.

// --------------- Component.js - an ES5 class?

export default
Class ('Component', {
    // No constructor here, so it'll have a default one, the same as in ES6.
    someMethod() {
        let something = 'something'
    }
}, 'es5')

// --------------- SomeComponent.js
import Component from './Component'

export default
Class('SomeComponent') .extends ('Component', {
    SomeComponent() { // constructor
        this.super.call(this)
    },

    aMethod() {},
})

Nodes to ES6?

// --------------- Node.js - an ES6 class?

export default
Class ('Node', {
    someMethod() {
        let something = 'something'
    }
}, 'es6')

// --------------- SomeComponent.js
import Component from './Component'

export default
Class('SomeWorldlyThing') .extends ('Node', {
    SomeWorldlyThing() { // constructor
        this.super.call(this)
    },

    aMethod() {},
})