Avoiding Reflows using Ember's Run Loop

Most of the work I do day-to-day is targeted at mobile devices. While it's no secret that avoiding excessive reflows is important for performance, it is absolutely essential on mobile, particularly on older devices.

Simply put, document changes that affect element geometry invalidate some or all of the page layout. If an invalidating change is followed by a read from the computed style of an affected node, the browser is forced to synchronously recalculate the layout. This takes time. For example:

1
2
3
4
for (var i = 0; i < elements.length; i++) {
  var height = elements[i].clientHeight;
  elements[i].style.height = (height % 20) + 'px';
}

The change to element is height invalidates the layout. When the clientHeight of element i+1 is requested the browser must recalculate the layout before returning a value. Depending on the number of elements and complexity of the DOM and styling, this loop can chew up hundreds of milliseconds and ruin the user experience.

While it's fairly easy to de-interleave the reads and writes to avoid forced layouts in the above example, it is slightly more complex when working with a collection of disparate Ember views.

Take for example a CollectionView with many child views, each which reads and then modifies a few element style properties on didInsertElement. A naive approach would be to orchestrate batching reads and writes using the parent view, but this would break encapsulation. Another approach would be to allow each view to read computed styles on insertion but defer writes using setTimeout(..., 0). However, this would return control to the browser before the changes are applied and could cause a flash of partially/incorrectly styled content.

A simple and effective approach uses the Ember run loop. First, do all necessary calculated style reads during didInsertElement. It executes in the "render" queue of the run loop. Second, defer all writes into a later queue, say the "afterRender" queue.

1
2
3
4
5
6
7
didInsertElement: function() {
  var element = this.get('element');
  var originalHeight = element.clientHeight;
  Ember.run.scheduleOnce('afterRender', function() {
    element.style.height = (originalHeight % 20) + 'px';
  });
}

This is an easy way to perform all reads followed by all invalidating writes while remaining in a single browser event loop execution. It can optimize what normally would be one reflow per view down to a single reflow for all views.

TL;DR

To avoid excessive reflows across your Ember views, perform computed style reads as needed but defer all writes to a later stage of the run loop.

Ember's {{outlet}}

While Ember's convention over configuration often provides the desired result out of the box, I often find myself needing to dig into the Ember source to find out what's really happening behind the wizard's curtain.

The handlebars {{outlet}} helper is typically used to specify the location at which the views of child routes will render into a parent route's template:

1
2
3
4
5
6
7
<header>
  Some content
</header>
{{outlet}}
<footer>
  Some other content
</footer>

Behind the scenes, the {{outlet}} helper does the following:

  1. Finds the top-level view associated with the route - the outletSource.

    For example, if the outlet is somewhere within the content rendered in the application template, the helper must traverse the view hierarchy to find the instance of the ApplicationView.

  2. Inserts a new ContainerView at the location of the {{outlet}}.

    In reality, Ember uses a Ember._Metamorph extension of a ContainerView. The metamorph view is virtual, preventing the view from appearing in the childViews / parentView hierarchy, and it doesn't create a DOM element (it uses a metamorph), so any template rendered into the outlet appears precisely in place of {{outlet}} helper without a wrapping element. Here's a fiddle that shows how mixing in _Metamorph affects how a ContainerView works

  3. Binds the currentView property of this ContainerView to the view under a named key in the outletSource's _outlets hash.

    Unless the outlet is explicitly given a name it gets the name main. In this case, the currentView is bound to outletSource._outlets.main. When the view the _outlets hash is changed, Ember automatically updates the view in the outlet through the magic of bindings.

Building simple JavaScript libraries with Rake-Pipeline

Cross-posted from blog.nulayer.com

At Nulayer we try to write very modular JavaScript, which allows us to be flexible and re-use those libraries in other projects. But we’ve had difficulty structuring a build process for these small- to medium-sized JavaScript library projects, while meeting the following objectives:

  1. Break it up: While it’s common to write smaller libraries as one big file, we like the mental separation provided by dividing the source into multiple files.
  2. Inline dependency management: We’d rather define each file’s dependencies right in the code than define the concatenation order in some sort of makefile.
  3. Keep files separate while debugging: When we’re setting breakpoints and stepping through code in the debugger we want to see our individual source files, not have to wade through one big file.
  4. A transparent solution: We need the production library to be free from dependency management code.

A year ago, I tried to satisfy our objectives using @jrburke’s require.js. It gave me the multiple source files with dependency management and excellent seamless development (with separate files) and production (combined and minified) environments that I was looking for. Unfortunately at the time, and things may have changed in a year, compiling requirejs out for the production build was not easy. I could get it down to a bare-bones set of require and define functions but it was still there. Requirejs is excellent for large JS projects and I ended up using it extensively for other work but it wasn’t quite the right fit for making a smaller library.

Via the Ember.js project I was introduced to livingsocal’s rake-pipeline with @wycats’ rake-pipeline-web-filters and minispade. With this combination I can prefix my source files with minispade “require” calls and satisfy objectives #1 and #2. The minispade filter for rake-pipeline can optionally encode and your source as strings, add a @sourceURL annotation and eval at run-time. In Webkit and Firefox the script debugger will show each eval’d block as a separate file, satisfying objective #3. But minispade is still required.

@wagenet’s addition of the neuter filter goes a long way to satisfying all four requirements. Neuter will determine the correct ordering of all source files and concatenate them into a single file. This is great for a production build as it means minispade is not required. So by using minispade in development and neuter in production, I can meet all four of our project organization objectives… almost.

One of the side-effects of using minispade is that all modules are evaluated within their own closure. This is great if everything that the module needs to share with other modules is exposed through a common global namespace (as in Ember.js) but it’s problematic if I want to define library-wide functions and variables that I don’t want to expose publicly. In my case, I had some helper functions that I put in a common module that was required by a number of other modules in my library. In the neutered production output everything is fine since all modules are appended as-is and can be wrapped in one big shared closure. But in development I was out of luck.

My solution was to use a modified version of the neuter filter that either outputs the raw code (in production) or outputs eval’d strings of the code with @sourceURL annotations (in development). This simple change allows me to satisfy all four of my requirements with very little work.

Here is a snippet of a rake-pipeline Assetfile that can be switched between production vs. development followed by the modified neuter filter (my changes mostly in output_source):

Using rake-pipeline with a modified neuter filter makes it easy to structure, build, and debug JS projects that use multiple source files with dependencies specified right in the code.