Goin' Full Require: Moving To AMD
MD ( [Asyncronous Module Definition](http://wiki.commonjs.org/wiki/CommonJS) ) is a apart of the [CommonJS](http://www.commonjs.org/) movement. It aims to bring something to javasscript is very much needed in todays world of browser based applications. And that is the idea of modules. A logical way of breaking your apps into smaller, more logical peices and import dependencies only in the situations that you actually need them. [RequireJS](http://www.requirejs.org) is at the fore front of JavaScript Module loaders, and for good reason. When you start to dig into RequireJS, you'll find that it isn't just a fancy script loader, but more of a micro-framework for managing large JavaSscript Apps.
Make The Move
Its pretty simple to get started with requireJS - just include the script on the base page / template of your site! When you include Require on your page, you'll get two "free" functions, define and require. And they pretty much do what you would think - define creates a new modules and require loads one or more modules. Their are multiple ways you can define a module.
Object Literal
You can pass a simple object literal to define and it will be used as the module. This is perfect for helper / utility modules that don't contain a lot of logic or complexity.
// foobar.js
define({
foo:"bar"
});
Callback Function
You can also pass the define function a callback function, which will get called when that module is required. The object you return from that function will be used as the module itself.
define(function(){
return {
foo:'bar'
};
});
Module Dependencies
It is also very common for modules to have a set of dependencies that need to be loaded prior to their execution. RequireJS allows you to do this by passing an array of module names ( or paths to modules ) that you need to load.
define(['moduleA'], function( moduleA ){
// do fancy setup work...
return {
foo:"bar"
};
});
Each module you specify will be passed into the callback function as arguments, in the same order the were in the dependencies array. One thing to keep in mind here, is that dependencies are loaded asynchronously and make not load in the order you specified. If the order is important, you may need to find a work around.
JSONP Service
You can even specify a JSONP service as a dependency if you want to load data from a different domain. The server must return valid json ( a top level object ) for this to work
define(["http://domain.com/data.json?calback=define"], function( data ){
return {
foo:data.bar
};
});
CommonJS Wrapper
Sometimes the list of dependencies in the array can get quite large and make for messy code. Luckily RequireJS allows you to require modules inside of a module definition just like regular var statements, much like NodeJS does. You just need to make sure that your callback function accepts require as the first argument
define(function( require, exports){
var moduleA = require("moduleA");
return{
foo:moduleA.bar;
}
});
Magic Exports
Now you don't have to always return an object from a module. Keeping inline with the CommonJS specification, you can attach anything you want to expose to the public from your module on to the exports which you can specify as the second depenency for your module after require
define(['require', 'exports'], function( require, exports ){
exports.foo = "bar";
});
Module Naming
Modules are often named the same as the file they are found in. For example, by defining a module in foo.js, your will require your modules by the name foo. However, it is also possible to name your module specifically by passing the name as the first argument to define
define("some/module",["moduleB"], function( moduleB){
return{
foo:moduleB.bar
};
});
One of the great things that I like about RequireJS and AMD modules is the isolation. Your module shouldn't assume anything about the outside world. If you need to use some other big chunk of functionality, you should have to require it. Although, it is just javascript, and their is nothing preventing you from accessing the global namespace, it really defeats the purpose. I personally think that this practice keeps you honest. Your modules should have a narrow focus, and if you find yourself requiring dozens of dependencies for a single module, you are probably trying to do to much.
Return Anything
One of the things that trips a lot of people up when they first start working with AMD modules, is what to return and how to make sure you actually get the stuff you want out into the public. Take this code for example:
// foobar.js
define(function( require, exports ){
// export class function called foobar
exports.foobar = function(){
this.name ="foobar"
};
// add a method called foo
exports.foobar.prototype.foo = function(){
return "Bar!"
};
});
In this situation, you would end up requiring and using this module like this
require(["foobar"], function( foobar ){
var myFooBar = new foobar.foobar();
console.log( myFooBar.foo() );
});
This is less then ideal and can lead to confusion and often leads developers to call their modules and variable strange things to avoid the double naming. Developers from other languages tend to be used to the idea of "One Class Per File". Either by way of convention or restriction of the language, it tends to be a good practice. The easiest way to accomplish this, is to just remember that you don't have to use exports, and that you can return whatever you want out of a module.
// foobar.js
define(function( require, exports ){
var Class = require("class")
,MyClass;
// define a new class
MyClass = new Class({
initialize:function( value ){
this.value = value;
}
});
// return the clas!
return MyClass;
});
For large complicated applications, or even large sites that have to manage large collections of files on different pages, RequireJS is awesome. Being able to keep your code in neat modules that can be loaded only when you need them goes along way in not only maintainability, but helps minimize learning curves when someone inherits your code.