Loader Usage at Quorus

By YUI TeamMarch 24th, 2011

Today, I’d like to talk about YUI Loader and how we at Quorus, Inc., use it to provide third-party websites with new features on demand.

Quorus screenshot

The code we write powers features on other peoples’ pages, meaning we’re in the unenviable position of having not only no control over the browser environment, but heavy restrictions in how we use the document itself. Our customers put a Quorus bootstrap script on their pages; everything else needed for our functionality is loaded dynamically and on demand. We go to heroic lengths to make sure that our elements, styles, and scripts do not alter the behavior of anything we’re not responsible for.

We started our present code base two years ago, when YUI 3 was just taking shape. It was a risky decision at the time to commit to a codebase that wouldn’t hit beta for several months. In retrospect I can’t imagine how we would have accomplished what we have without it. I haven’t seen any other framework that has components approaching the power of Loader, Attribute, and CustomEvent.

The Quorus bootstrap script we provide to our customers does almost nothing. Its job is just to load the core of our platform without blocking the rest of page load, and to queue any API calls until we’ve done so. This core script file, called stage2, inlines yui, loader, and oop, as well as enough smarts to load additional libraries to respond to API calls, user clicks, and other conditions in the operating environment. Most other resources are served by a custom combo server that serves custom Quorus and stock YUI modules.

Bootstrap queues up API calls made in the host site’s code between when it loads and when we’re ready to go in an array on our global object, QUORUS:

QUORUS._callbacks = [];
QUORUS.use = function () {
  // turn the arguments object into a regular array,
  // so that it can be stored safely
  var args = Array.prototype.slice.call(arguments, 0);
  QUORUS._callbacks.push(args);
};

Once we’re ready to process API calls, stage2 runs them one by one in timeouts. This ensures we yield control regularly back to the browser, which makes the user experience more responsive. The behavior is a lot like Y.AsyncQueue, but simpler and doesn’t require YUI to be loaded:

// Put the real 'use' function in place for any subsequent calls:
QUORUS.use = function (feature, callback) {
  YUI.use('module-that-provides-the-feature', function (Y) {
    // find the API for the requested feature, and pass it to the callback
    callback(Y.APIs[feature]);
    // process another pending API call, if any:
    setTimeout(processAPICall, 0);
  });
};

// Play catch-up, running each callback in sequence:
function processAPICall () {
  var callback = QUORUS._callbacks.shift();
  if (callback) {
    QUORUS.use.apply(QUORUS, callback);
  }
}

// Start processing the queue:
processAPICall();

The bootstrap file is, by this point, mostly immutable: it’s something we hand off to a customer, who might require a month or more to deploy any new version we gave them—an impossibly long time for an agile startup company. The stage2 file, meanwhile, is small, loads from our own servers, and has a short cache lifetime. This ensures that no end user will have an old version for more than a few minutes. Nearly all the other resources we need are in permanently cacheable JavaScript libraries and CSS files.

When we release a new version of our code, stage2 automatically directs browsers to start downloading from a new location, ensuring that they use only the newest code. This setup allows us to deploy changes quickly without serving up assets more often than necessary. Not only does this keep our bandwidth costs low, but it provides a better user experience: the cached resources load very quickly while the page is loading.

Quorus JS loading flow diagram

If we were starting our codebase today, with the benefit of the YUI Gallery, there are a number of components we might use to make our lives easier. One of them is Eric Ferraiuolo’s Base Component Manager, which assists with wiring up components (typically Widgets) on demand. Another might be Storage Lite, to help us retain application state across page loads.

Many thanks to the YUI team for their great work, and to the community for their contributions. If you would like to read about our approaches to sandboxing or to coordinating asynchronously loaded components, please let me know in the comments!

Peter AbrahamsenAbout the author: Peter Abrahamsen writes Ruby and JavaScript, manages server infrastructure, and studies user-centered design in Seattle, Washington, USA. He can be found on IRC as Rainhead.

2 Comments

  1. Hi Peter,
    I’m actually pretty curious about your QUORUS.use call. You seem to be swapping it out with a YUI use call that loads up one specific module.
    Im not seeing how this correlates to specific calls to the API. How does your passing of “feature” to the QUORUS.use tie to the module for the YUI use, unless it is one global module (though that doesn’t fit with the graphic you have showing menubar and widget)?

    Also, have you done any testing using the queued settimeout method you’ve shown vs just placing all of the modules in one use statement?
    It seems, in theory like the setTimeout would be better at releasing control to the browser, but a generally higher TTI with the page (since it’s calling a new use every 10-15ms and doing a different set of dependency tree calculations for each module), but I’m just curious if you guys have tested one vs the other?

    Otherwise, thanks for a great article. I love seeing examples like this showing how people are using the loader for their specific app :)

  2. Peter,
    I would like to extend this framework for html element extraction from a web page. It looks like I can accomplish that with this approach.

    If you can drop me a line, that will be great – so we have offline conversations.

    Thanks,
    gS