Regressions in Ember's loading due to ES6 modules


#1

With Ember.js 1.6.0 release the codebase was converted to use ES6 modules. While this was great for the design of the project there was a known regression announced alongside the release:

Unfortunately, adding the additional loader overhead on boot has a fairly significant performance impact on boot speed of older mobile clients (approximately 5-10% boot time penalty).

As of the 1.8.0 beta, there is unfortunately still no resolution for this issue. We are noticing ~250ms of time spent in requireModule in Discourse right now (we are on the 1.6, cleaning up some deprecations before 1.8).

The goal of this topic is to capture what caused the regression, and determine what is involved to fix it. There are probably many people who would love to help fix it if we could articulate what the issue is and a plan forward.

The Cause (as I understand it)

  • The issue is that transpiling ES6 modules into ES5 format introduces overhead for each dependency in the load graph.

  • There is a new bundle format in the ES6 transpiler that seeks to address this. Rather than using a loader, you can concatenate all your JS together in one bundle that uses a bunch of $ characters to isolate variables.

  • However, the bundle format has issues with circular dependencies. It’s not simple to just switch Ember to the bundle format and have everything work.

The Fix

This is where I need some guidance. How could we go about solving the problem? If people such as myself want to help out in getting Ember ready to be bundled, what is involved?

In particular:

  • What needs to be done to the Ember code?
  • Can we divide and conquer the files to address the issues? How?

/cc @stefan @fivetanley @rwjblue


#2

I’d be curious to know if the bundle format will allow for isolated module importing (or whatever it is being called) that many of the core team have mentioned as being a goal with ember-cli. So that whatever I import is only what will be finally compiled. Or is this not a concern?


#3

@bcardarella once in place, if we bundle you app and ember together (and sufficiently refactor ember) it will more easily drop unused parts of ember. Both ember-cli, ember and the bundle formatter will need to be improved to support this.

@eviltrout honestly the best bet is for someone to resurrect

and continue fixing embers es6 usage.

The bundle formatter changes the logical ordering of the modules, which means things that would normally resolve with a loader, now will not. I would love for the bundle formatter to instead layout the modules in top sort order of the dep graph. I suspect this will sort out many issues I encountered and make the format more user friendly with larger graphs.

We can and should also improve embers internal es6 usage to support this nicely.

Another downside is the bundle format is (for now) extremely slow to build, so it will likely not be useful during development. So one of the other formatters must be used. System/CJS etc.

This does mean that we will likely have differences that may only surface when the bundle formatter is used. I feel that the change it bundle layout may mitigate this.

Im just throwing these thoughts together as they come to mind, i will try to find some time later this week (likely weekend) to further flesh out the current state of things.

Ultimately I think the only blocker is time, some changes to embers build system, and likely some improvements to the now transpiler.

/me ends brain dump

Also for reference RSVP is using the new formatter, which helped reduce file size and improve performance slightly. As of recently @fivetanley has migrated ember-data. So we know the tech works, for simple projects. Next up is ember and ember-cli apps.

Another aside, to do what @bcardarella mentioned, will likely require much extra work but will lead us down of a path of more optimized builds.


#4

more info on the differences between bundle and loader based.

Also for those curious, here is RSVP formatted with the “bundle” format -> http://rsvpjs-builds.s3.amazonaws.com/rsvp-latest.js As you notice, there are no internal modules rather aggressive renaming. This results in very nice minification and no overhead of a loader.

Anyways the differences this produces:

Given loader require ordering x -> y > z, but the order of the modules on disk is z -> y -> x. The loader absorbs this at runtime regardless of the entry point. As it will re-order based on usage.

Currently with bundle format, we do not have this dynamic flexibility, there will be one “ordering”. Currently that ordering is based on “first scene” although maybe (i advocate) it should be the result of a top sort of the deep graph to more closely resemble how the modules work when used with a loader.

Luckily the bundle format handles a large number of cycle and ordering issues by virtue of just utilizing JavaScripts built in hoisting. Unfortunately some scenarios in ember exist that “break” these bindings, or implicitly rely on the loader require ordering. Typically this is some “runtime” code invoking during module load rather then lazily.

Unrelated to the differences mention above, but work that needs to be done: Ember has some invalid usage of modules that “Break” bindings. Luckily these are very easy to fix, and I would welcome PR’s fixing them.

An example of binding breakages:

import Ember from 'ember';

var get = Ember.get; 

function foo() {
  get(....); // the binding to get will likely break.
}

the solution is very easy, and part of the es6 module api

import { get }  from 'ember';

function foo() {
  get(....); // the binding will work correctly.
}

#5

I don’t have a whole lot to add to this except to say that if someone can come up with a minimal test case that illustrates (i.e. with console.log()s) a case where the bundle and commonjs formats differ in load order with the same entry point I’d be happy to dig in and fix this.


#6

@stefan and @eviltrout I would like to squash this regression as soon as possible. We recently ran some performance numbers on a low powered device (Moto G) and got some less then stellar benchmarks. We saw about ~3sec just to eval Ember. Anything that will reduce boot time is desired.


#7

I decided to grep through Ember master and I can’t seem to find any of these! Does that mean they’ve been removed or am I not searching hard enough?


#8

Looked like @eventualbuddha has been working on this, just posting a link to his github branch in case anyone is interested in following along or helping!


#9

Working with @eventualbuddha on this today. Hopefully we can make some more progress on it.


#10

I would love an update once you get a bit done! I was hoping to look at it myself soon.


#11

Going to revive this, seeing an large amount of cost in the require code (in production) … as it stands on my macbook pro

  • 200ms in require stuff for ember
  • 320ms for our own application js file
  • 80ms for our admin js

This is on a desktop so on android you can multiply that by 5…

Been looking at the “state of the art” at the moment, should we start looking into using something like https://github.com/rollup/rollup here? Are there any plans for tackling the Ember part of the problem? Are there any better bundlers around that can make this problem go away?


#12

some related POC: https://github.com/emberjs/ember.js/pull/11576


#13

A big concern I have that will not be addressed by Ember is that we are already sitting at 320ms of work for Discourse application. No Ember optimisations will improve this, we need to figure out a clean pattern for deferred cost we can use for es6 import/export. Use better transpiler options and so on.

Any ideas on what we could do?


#14

Much of this has been addressed, v8 has mitigated many of these issues. As it had to do with lazy parse/compile optimization backfiring when encountering AMD modules which themselves contain deeply nested functions.

Kris Selden’s ember-cli-optimize was used for some time, but I believe at this point (post v8 work) that is no longer super relevant as the negative feedback loop has been largely corrected.