December 8, 2012Infra · Platform · JavaScript · Front End

Under the Hood: The JavaScript SDK – Truly Asynchronous Loading

Stoyan Stefanov

This post is part of a series that looks under the hood of our JavaScript SDK. The previous parts are here and here.

Every time you add a JavaScript tag on your page, it's important how you load it, and it becomes even more important when the JavaScript comes from a third party. I'm happy to announce that the Facebook JavaScript SDK now supports a truly asynchronous way of loading, which virtually removes the effect of loading this script (and any plugins or other Facebook content it loads) on your page. In other words, even if Facebook is inaccessible (e.g. blocked by a firewall), your page should load just as fast.

The problem

You always need to take care of reducing the effect of third party content on your page. This is why you should always load third-party JavaScript files (such as the Facebook JavaScript SDK) asynchronously. If this topic is new to you, you can start here.

Loading a JavaScript file asynchronously takes that file off the critical path of downloading other components and rendering the page. However, in most browsers (except Internet Explorer), asynchronous scripts still block the window.onload event.

This may not be a problem for many web applications, which commonly instantiate JavaScript when the DOMContentLoaded event fires. But there still might be cases where firing window.onload as soon as possible is important. For example:

  • If you have important functionality kicking "lazily" after window.onload
  • If you run third party analytics or performance analysis tools that don't know anything about your application and approximate "done" state with window.onload

The solution: FIF

Luckily, there's a solution to loading JavaScript without blocking the onload event. It's a technique pioneered by Meebo, and also known as "Friendly iFrames" or simply "frame-in-frame", both of which abbreviate to FIF.

In essence, the FIF technique is about creating an iframe and loading the JavaScript after onload of this iframe. Since onload of the parent page waits for onload of each frame, we need to fire onload of the iframe as soon as possible and only after that load the JavaScript. The end result is a truly non-blocking JavaScript load, while at the same time the script will still load as soon and as quickly as possible (as opposed to, say, loading the JavaScript after the full parent page has loaded).

There are some browser-specific considerations to implementing this properly, so here's the working version:

(function() {
  var url = 'http://example.org/js.js';
  var iframe = document.createElement('iframe');
  (iframe.frameElement || iframe).style.cssText =
    "width: 0; height: 0; border: 0";
  iframe.src = "javascript:false";
  var where = document.getElementsByTagName('script')[0];
  where.parentNode.insertBefore(iframe, where);
  var doc = iframe.contentWindow.document;
  doc.open().write('<body onload="'+
    'var js = document.createElement(\'script\');'+
    'js.src = \''+ url +'\';'+
    'document.body.appendChild(js);">');
  doc.close();
}());

A drawback to loading a script this way (other than being a little more complicated) is that the script is now loaded in a frame, meaning the commonly used objects such as window and document are now pointing to the iframe and not to the main parent page. Luckily this is easy to solve.

All you have to do is wrap all the code in a closure (which is a pattern you may already be following) and pass the appropriate window object.

Before:

(function() {
  // fun with window
  // and document
}());  

After:

(function(window) {
  var document = window.document;
  // fun with window
  // and document
}(parent.window));

And the script should work just as well as it did before! (Although there might be a few globals you need to fix by accessing with <code>window, e.g. window.MYAPP instead of MYAPP.)

This assumes you have control over the script you're loading. What if you don't? What if you want to load a third party script this way? Well, you need your third party provider to support it.

FB JS SDK support

I'm happy to report that the Facebook JavaScript SDK now supports FIF loading. And since we want to support both FIF and regular in-page (async or not) loading with the same file (all.js), you need to tell the SDK how you're loading it. There are several approaches to this problem, and we chose to use a global variable that has been de-facto standardized.

This global variable is called inDapIF and is defined by IAB (Interactive Advertising Bureau) in this document. The same document also defines the "Friendly iFrames" term.

In this JSBin you'll find a working version of how you would include the FB JS SDK in a FIF manner to load a social plugin. Here's only the inDapIF part:

doc.open().write('>body onload="'+
  'window.inDapIF = true;' +
  'var js = document.createElement('script');'+
  'js.src = ''+ url +'';'+
  'document.body.appendChild(js);">');  

RFC

This feature is still experimental and we need your comments and feedback. Do you like it? Will you use it? Does inDapIF make sense? Please use the comment feature below to let us know.

Keep Updated

Stay up-to-date via RSS with the latest open source project releases from Facebook, news from our Engineering teams, and upcoming events.

Subscribe
Facebook © 2017