David Walsh Blog

curl.js: Incredible AMD Loader

Today there are dozens of AMD JavaScript loaders available, the most popular being RequireJS. There are also lesser known JavaScript loaders like YepNope, $script.js, LABjs, and Dojo’s new native loader. My favorite JavaScript loader, however, is John Hann (unscriptable)’s curl. While allowing for maximum configuration and reliable loading, curl also allows for loading of simple JavaScript files as well as CSS files. Let me show you how to use it!

Super Quick AMD Primer

If you aren’t familiar with AMD structure, I’m going to give you the most oversimplified explanation you’ll ever hear. AMD is a system by which you define and require modules asynchronously. A define returns one or zero objects. The first argument of both define and require is (usually) an array of dependencies. The second argument is a function; the define returns the result, the require executes a basic callback:


// "define" a module
define(["namespace/dependencyA", "namespace/dependencyB"], function(depA, depB) {
	// Whole bunch of processing
	
	
	// Return what this module defines
	return function() {
		// Or an object, or whatever
	}
});

// "require" to use modules:
require(["namespace/dependencyC"], function(depC) {
	
	// depC can be used in here only
	// Yay for modularity!
	
});

The slashes in the dependency array items represent paths to module JavaScript files. Once dependencies are loaded, the action is allowed to begin.

As I said, this is a very simple, vanilla example; there are exceptions to every rule, so don’t bother pointing out what-ifs.

Configuring Module Loading with curl

And of course I start out with a few of the exceptions to the rule. Instead of a require function, curl.js defines curl in its place. Additionally, curl.js allows for an object literal as a first parameter, allowing for configuration of loaded modules:


curl({
		baseUrl: "/path/to/js",
		pluginPath: "curl/src/curl/plugin"
	}, 
	["namespace/depC", "namespace/otherDep"],
	function(depC, otherDep) {
		// Do stuff
	}
);

This configuration allows you to provide plugin paths, modules paths, and more.

Basic define and require with curl.js

Basic usage of curl.js is as you would expect from a JavaScript loader; dependency array as the first argument, callback with the second:


define(["namespace/depA", "namespace/depB"], function(depA, depB) {
	// Do something with the dependencies
	
	// Pump out a return obj
	return myFinalObject;
});

With a module defined, the same syntax requires and works with the dependencies:


curl(["namespace/depC"], function(depC) {
	// Do some stuff!
});

This is the same syntax you will have used with any JS loader, with the obvious exception of require being replaced by curl.

curl.js with next

The next method allows for chaining of module loading:


curl(["js!someFile.js"])
	.next(["dep1", "dep2", "dep3"], function (dep1, dep2, dep3) {
		// Execute regardless of domReady status
	})
	.next(["domReady!"])
	.then(
		function () {
		// do something after the dom is ready
		},
		function (ex) {
		// show an error to the user
		}
	);

This syntax may suit your fancy more than others.

curl.js with Deferred Syntax

If you work with the Dojo Toolkit, or more recently with jQuery, Deferreds are becoming more prevalent and incredibly useful; curl.js provides you the ability to write your loader JavaScript in the same fashion:


curl(["namespace/depA"]).then(
	function(depA) { // success callback
	
	},
	function(depB) { // errback
	
	}
);

The deferred format and ability to pass the result of an XHR pool can be very powerful.

Loading Non-AMD JavaScript Files

Sometimes you need to load JavaScript files that aren’t in AMD format, like loading MooTools or jQuery from CDN. curl.js makes that easy:


curl(
	["js!https://ajax.googleapis.com/ajax/libs/mootools/1.4.1/mootools-yui-compressed.js"]
).next(["namespace/MooModule"], function() {
	// We loaded Moo first, then once loaded, loaded a dependency that requires MooTools
	// At this point, both are loaded and we can work with them!
	
});

All you need to do add the js! prefix to the dependency string and you’re set; your callback will be fire when the basic JavaScript file is loaded. Note that you can mix AMD modules with basic JavaScript files:


curl(
	[
		"js!https://ajax.googleapis.com/ajax/libs/mootools/1.4.1/mootools-yui-compressed.js",
		"js!https://davidwalsh.name/mootools-ftw.js",
		"namespace/independentModule"
	]
).next(["namespace/MooModule"], function() {
	// We loaded Moo first, then once loaded, loaded a dependency that requires MooTools
	// At this point, both are loaded and we can work with them!
	
});	

Loading CSS Files

Of course one of the strengths of AMD is modularity, so why not load your stylesheets with your scripts?


curl(
	[
		"namespace/MyWidget",
		"css!namespace/resources/MyWidget.css"
	], 
	function(MyWidget) {
		// Do something with MyWidget
		// The CSS reference isn't in the signature because we don't care about it;
		// we just care that it is now in the page
	}
});

LINK tags don’t provide an onLoad event in all browsers, but curl.js’ shim provides a reliable method of detecting stylesheet load. Since stylesheets are a large part of UI-driven, JavaScript-powered widgets, creating modules with stylesheet dependencies is becoming much more abundant.

More curl Plugins

curl is much more than just a basic JS loader. I’ve already mentioned the JS and CSS plugins above, but curl has a few more. curl features a domReady plugin, as well as a text plugin and an internationalization plugin:


curl(
	[
		"i18n!stuff/nls/strings", // Load string content for the user's namespace
		"text!myWidget/resources/template.html", // Loads a file as text,
		"domReady!" // Don't fire the callback until the DOM is ready
	],
	function(nlsStringObject, template) { // Callback
		// Do something now that we have the NLS object, template, and domContentLoaded has fired
	}
);

These plugins are quick and easy enhancers to existing functionality!

curl is an absolute beast of a JavaScript loader. Beyond simple AMD loading, curl is fit with numerous configuration options, plugins, and multiple syntax structures to all the developer to code the way they want. This blog uses curl.js to asynchronously load JavaScript modules and stylesheets, manage domReady, and more; the best endorsement I can give!