Over the past several weeks, I’ve been writing an AMD-compatible javascript loader called curl.js. AMD stands for Asynchronous Module Definition and is a CommonJS proposed standard for writing javascript modules. The module format closely follows the proposed migration path set out by ECMA’s proposed ES-Harmony javascript modules.
curl stands for Cujo Resource Loader since it’s an integral part of the re-engineering of cujo.js.
An AMD-compatible javascript loader is (surprise, surprise) an asynchronous javascript loader that is savvy about AMD-formatted javascript modules.
[update]
Version 0.3.2 is out! fork it!
What is an asynchronous loader?
Web apps, especially large ones, require many modules and resources. Most of these modules and resources need to be loaded at page load, but some may be loaded later, either in the background or “just in time”. They also need to be loaded as quickly as possible.
The traditional way to load javascript modules is via a <SCRIPT>
element in an HTML page. Similarly, CSS files are loaded via a <LINK>
element, and text resources are either loaded in the page or via XHR calls.
The problem with <SCRIPT>
and <LINK>
elements is that a browser must execute them sequentially since it has no idea if one may depend on another. It just assumes the developer has placed them in the correct order and that there are dependencies. (The term “synchronous loading” is used to describe this process since the elements are executed in a single timeline. I think “sequential” is a much better word, but nobody asked me.)
If there are no dependencies between two files, loading them sequentially is a waste of time. These files could be loaded and executed in parallel (i.e at the same time).
An asynchronous loader does just that: it loads javascript files (and other types of files) in parallel as much as possible.
curl.js has lots of company. Other async loaders include LABjs, Steal.js, yepnope.js, $script.js, bdLoad, and RequireJS.
(a more complete list can be found here)
What is AMD?
Asynchronous Module Definition is the CommonJS proposed standard for javascript modules that can be loaded by asynchronous loaders. It defines a simple API that developers can use to write their javascript modules so that they may be loaded by any AMD-compliant loader.
The AMD proposal follows the CommonJS Modules proposal as much as possible. Because of the way browsers load and evaluate scripts, AMD can’t follow it completely without causing significant processing overhead (think serious regex action and eval()). Instead, AMD allows us to place a lightweight wrapper around javascript modules to help work around the shortcomings.
Ultimately, both proposals (AMD and Modules 1.1) are in preparation for an official javascript modules specification and eventual implementation in browsers. (Hopefully!)
If you don’t want to wait for official javascript modules, then don’t. The future is now. AMD works now — and it’s awesome.
AMD’s API focuses on two globally-available functions: require()
and define()
. require()
specifies a list of dependent modules or resources that must be loaded before running a set of code. This code resides in a callback function that is executed asynchronously, i.e. it runs later, not in the current “thread”. Specifically, it executes when all of the dependencies are loaded and ready.
Actually, the proposal says that the require()
function could have a different name — or could even be implemented differently. To keep with convention — and to better integrate with non-AMD CommonJS modules — we’re using require()
, but curl()
is also an alias to require()
.
It’s more important that the define()
method be consistent. This is the method that tells the loader what modules have been loaded by a script. define()
also specifies a list of dependencies and a callback function that defines and/or creates the resource when the dependencies are ready. Optionally, define()
also takes a name parameter, but this is mainly for build tools and optimizers.
AMD’s API also helps code reuse by providing compatibility with CommonJS server modules. AMD-compliant loaders support the same require()
syntax and argument signatures as server-side javascript (ssjs) modules.
Not all async loaders are AMD-compliant. Of the list above, only the following are AMD-compliant:
curl.js http://github.com/unscriptable/curl
RequireJS http://requirejs.org/
bdLoad http://bdframework.org/bdLoad
(there are a few others in that google spreadsheet link)
The beauty of AMD loaders is their ability to remove the drudgery of manually managing dependencies. Since all dependencies are listed within the modules, the loader will ensure that everything is loaded into the browser — and in the right order.
Even better: the modules are always loaded in parallel without blocking the loading process of the rest of the page.
Most of the current AMD loaders also support plugins. Plugins allow you to load CSS, HTML templates, i18n/L10n bundles, CSS generated from LESS, plain vanilla javascript files, etc. Again, these are loaded without blocking so the performance is optimal.
So how can I get my hands on curl.js?
curl.js is up on github! Fork it or clone it to your local disk and check out the Very Simple Example in the README file.
At the time of this blog post, curl.js is at version 0.3.1 and is not 100% compatible with CommonJS Modules 1.1 (only missing the exports
and module
parameters). However, I am currently unit-testing 0.3.2, which is 100% compatible!
Compared to other AMD loaders, curl.js is tiny. At 4.5KB (2.1KB gzipped using Google Closure Compiler), it’s half the size of the others. It also employs some wicked cool techniques using promises.
Version 0.3.2 will be ready for production use, despite the zero-dot version number. Please check it out and let me know what you think!
Pingback: Essential jQuery Plugin Patterns - Smashing Coding
Pingback: jQuery 1.7 Released | Sean Walther’s Blog