Let's Make a Framework: Asynchronous Resource Loading

2011-09-22 00:00:00 +0100 by Alex R. Young
*Let's Make a Framework* is an ongoing series about building a JavaScript framework from the ground up. These articles are tagged with [lmaf](http://dailyjs.com/tags.html#lmaf). The project we're creating is called [Turing](http://github.com/alexyoung/turing.js). Documentation is available at [turingjs.com](http://turingjs.com/).

The use of asynchronous script loaders has become very popular over the
last few years. Rather than bundling assets into monolithic files to
improve performance, it's possible to coax browsers to load several
files at once. There are different approaches to this, but in general
JavaScript APIs are used rather than script elements.

I want to look at how asynchronous script loaders are built, what HTML5
provides, and then build a new asynchronous script loader as an advanced
client-side tutorial. This tutorial series will be split over several

Popular Libraries

There are a huge amount of asynchronous script loaders out there. The
diversity is largely due to the complexity of the problem space --
different situations can be optimised, and browser support requirements
play a huge role in the internal design of these libraries.

The first library I found and used actively was
LABjs (GitHub: getify / LABjs, License: MIT) by Kyle
Simpson. To understand what LABjs does, consider the following HTML:

In the HTML4
it says:

All SCRIPT elements are evaluated in order as the document is loaded

That means if a framework like jQuery is specified first, then
subsequent scripts can safely depend on it being loaded. The downside of
this, however, is downloading each of these scripts will block the
loading of other page components like images and stylesheets. This can
create a noticeable visual delay when the page is loaded.

Using LABjs can solve this problem:


Here, every script will be loaded in parallel. The wait()
method will cause a execution to delay until the script has finished
loading and has been parsed.

Technically, code within the application's client-side JavaScript could
call \$LAB.script() whenever a dependency is needed. This
means a single page app could have a lower footprint until certain
functionality is required.

Another popular library is RequireJS (GitHub:
jrburke / requirejs, License: new BSD and MIT) by James Burke. RequireJS is very different to LABjs, with a large feature set:

The basic usage looks a bit like LABjs:

require(['script1.js', 'script2.js'], function(someModule) {
  // This optional callback runs when all dependencies are loaded

By embracing concepts like CommonJS Modules and the Asynchronous Module
API, RequireJS
offers a way to not only avoid blocking when loading scripts, but also
add structure to projects.

Yet another approach used by
Modernizr and yepnope.js is to use a test to see if a script should be loaded:

  test: window.JSON,
  nope: 'json2.js',
  complete: function () {
    var data = window.JSON.parse('{ "json" : "string" }');

The power here, is that you were able to parse a JSON string, but if your browser already had built in support for JSON parsing, it didn't require you to load anything extra!


JavaScript-powered asynchronous loading isn't the end of the story. In
HTML5, the
element has been updated to support async and
defer attributes. This is already available in recent
WebKit-based browsers and Firefox 3.6.

These attributes effectively let us do what LABjs offers:

From Mozilla's documentation on the script

In older browsers that don't support the async attribute, parser-inserted scripts block the parser; script-inserted scripts execute asynchronously in IE and WebKit, but synchronously in Opera and pre-4.0 Firefox.

Tony Gentilcore compared this approach to JavaScript libraries on the
Surfin' Safari Blog:

There are many clever techniques for working around this performance bottleneck, but they all involve extra code and browser-specific hacks. Instead, scripts which do not require synchronous execution can now be marked as either async or defer.


As well as HTML5, asynchronous script loading is addressed by the
Asynchronous Module
understand why this is useful, consider CommonJS

var fn = require('my-module').fn;

How would this work in a browser? Assuming 'my-module' is
loaded through script tags, it would be difficult to control execution
and parsing. With AMD it's possible to do this instead:

define('my-module', ['require', 'exports', 'mydep'], function (require, exports, mydep) {
  exports.fn = function() {
    return 'hello from my-module:fn';