Some sites can defer most, if not all, of their JavaScript-driven progressive enhancements until well after the page has loaded. Even so-called “lightweight” libraries like JQuery are far too heavy for some environments…not because they (like Dojo) pull in all the code needed to use them, but because they do it all up-front. Often the best time to pay the expense of loading, parsing, and executing JavaScript code is when the user takes an action that needs the enhancement to run. Dojo already gives you the best tools available anywhere to defer loading modules until you actually use them; other than those provided by dojo.js itself…but what about dojo.js? What if even the small size of Dojo is too big for your page? This need to load as fast as possible and defer work is never greater than in mobile applications. The best-performing thing to do is always to hand-write all the code you’ll need and never use more script than that…but there are complications. Not being able to share your code between developers (let alone between pages) can be a huge disadvantage. The code is also likely to be slow, buggy, and unmaintainable. There’s a real reason why toolkits like Dojo are so incredibly popular: they make much of this pain go away at the cost of initial download size. Why would anyone want to invite the pain back?

So we need some middle ground: a common, expected API surface area combined with a trivial initial footprint to ensure that pages load as fast as possible. How small could we make such a thing? Turns out, 6K.

Dojo has, for many versions now, provided the ability to use the build system to cut out parts of dojo.js which weren’t in use by the application and load them later. In fact, everything but the package loader and the few functions it needs to operate are located in source packages in the dojo/_base/ directory. When running Dojo from source, these files get pulled in with dojo.require() the same way that any other module might. One possible option then is to use the build system to create a custom version of dojo.js which includes just the modules your application uses at page load. But we’re still faced with the essential problem created by hand-rolling everything: it becomes much harder to work with Dojo. You no longer know what functions and classes might be provided by the dojo.js file loaded in the header of your page, meaning that the advantages of maintainability, shared team knowledge, and dependability are reduced. Code developed for this situation is littered with statements like dojo.require("dojo._base.array"); in order to ensure that the right modules are available.

A better solution might be one which preserves the super-lightweight foot-print of including just the Dojo loader but which also provides access to the full, rich API that is provided by the stock dojo.js without developers needing to spend a lot of time worrying whether a particular base module has already been loaded.

Enter the stub loader.

I’ve prototyped an alternate dojo.js which you can download as part of a build (256K tarball, includes tests) of just Core (the stuff in the dojo.* namespace). When served gzipped, this new version of dojo.js weighs in at roughly 6K, yet it provides the full API of the normal dojo.js, provided off of the AOL CDN at roughly 24K, or 4x the size. Instead of including all of the modules in Dojo’s baseline set of functionality, this new version instead pulls in just the bootstrapping code for the module system and then creates stub functions and constructors for all of the functionality which isn’t included. When a function is first called, the stub loader ensures that dojo.require() is called to pull in the appropriate functionality synchronously. The result is an API which behaves exactly as though all of dojo.js had been loaded up front but which defers loading until the code is actually used.

On an iPhone with a clean cache the stubbed-out dojo.js cut in half the time required to load and evaluate. Sure, it’ll take more time on the network when parts of the toolkit are actually used (say, in response to a click event), but for mobile device scenarios, it’s going to be hard to beat the flexibility and speed of the stub loader when pulling Dojo into a page.

The code to implement this strategy can now be viewed. If you view that file, you’ll note that the first several lines read like this:

dojo.provide("dojo._base");
dojo.provide("dojo._base.browser");
dojo.provide("dojo._base._stubs");

This implies that if some other code subsequently calls dojo.require("dojo._base");, as the build system normally would, then no code will be loaded. In a source version of Dojo, dojo/_base.js reads as such:

dojo.provide("dojo._base");
dojo.require("dojo._base.lang");
dojo.require("dojo._base.declare");
dojo.require("dojo._base.connect");
dojo.require("dojo._base.Deferred");
dojo.require("dojo._base.json");
dojo.require("dojo._base.array");
dojo.require("dojo._base.Color");
dojo.requireIf(dojo.isBrowser, "dojo._base.browser");

By loading our stub loader instead, all of the modules in dojo._base.* which would normally be included are left out of the build and replaced with their stub counterparts. To get some bit of code included in a build instead of the stub, we’d then just add the corresponding require (e.g., dojo.require("dojo._base.lang")) at the top of dojo/_base/_stubs.js, at which point the corresponding stub functions won’t be generated.

In a build which uses dojo/_base/stubs.js, extra code is not loaded until the functions and constructors which are stubbed out there are actually called. You can drop this file into your dojo/_base/ directory if you’re working with Dojo from source and then use the following profile definition to create your own stubbed-out dojo.js. I placed mine in util/buildscripts/profiles/stubs.profile.js:

dependencies = {
	layers: [
		{
			name: "dojo.js",
			customBase: true,
			dependencies: [
				"dojo._base._stubs"
			]
		},
		// your layers here!
	],

	prefixes: [
		// uncomment these to copy dijit and dojox into your build output
		// [ "dijit", "../dijit" ],
		// [ "dojox", "../dojox" ]
	]
};

And would normally build with:


%> cd dojo_repo/util/buildscripts/
%> ./build.sh profile=stubs action=clean,release copyTests=false cssOptimize=comments optimize=shrinksafe
clean: Deleting: ../../release/dojo
release: Using profile: profiles/stubs.profile.js
...