Modular JavaScript with require.js

JavaScript based projects are getting larger with more code, they are now quite often complete applications. One way to organise and divide your code into manageable chunks is to split the classes into separate files. This soon creates problems of dependency as each file might rely on another and how do you know when it's loaded and ready.

Asynchronous Module Definition (AMD) is a standard way to define each JavaScript module into a separate file. You then register each class using a generic 'define' function which ensures they are loaded in the correct order and dependencies are handled. The added bonus of this is that your classes are name spaced which reduces chances of conflicts and they support crossdomain loading.

The main advantages are
- modular code (easier to maintain, in separate files)
- asynchronous (modules can load in different orders, once their dependencies load they init with options)
- code is loaded on demand (if the user doesn't request a feature, it isn't loaded!)
- modules are crossdomain (load a module from another server!)
- no global variables means no chance of conflicts with other libraries/code
- easily minified (if we want one file minified we can do that automatically too)
it's a standard!! (potentially future proof?)

So now you probably want to know how complicated it is to set up? Well it is actually so simple you will be amazed:

AMD MVC Example

models/Fields.js
define('models/Fields', function(require) {
// return your object/function class code here
return function(name) {
this.name = name || 'default';
this.getName = function() {
return this.name;
}

};
});

views/Inputs.js
define('views/Inputs', function(require) {
// return your object/function class code here
return {
init: function(value) {
var el = document.getElementById('form');
 el .innerHTML = '<input id="'+value+'" />';
}
}
});

controllers/Form.js
define('controllers/Form', function(require) {

// require two modules are loaded before this class will load
var view = require('views/Inputs');
var model = require('models/Fields');

// return your object/function class code here
return {
init: function(name) {
var item = new model(name);
view.init( item.getName());
}
};
});

Main.js
define('Main', function(require) {
var form = require('controllers/Form');
form.init();
});

Once you have created your Module classes, you then add a loader script to load the module classes into the browser dynamically. There are lots of options because they use the same standard:
Download and include the loader file in the head of your html page, pointing to the Main controller:
<script src="js/plugins/require.js" data-main="js/app/Main"></script>

Note you will have to get your paths correct as require.js uses the path of your Main file as the root of your app. If you don't specify a root path then it will default to the directory of require.js

If you want to minify and compact down to one file you can use r.js with java rhino and java google compiler. You have to define the module name as I have in the other code examples e.g. define('controllers/Form' as when they are compiled they will lose their filenames/paths.
- Create a shell script which points to the java files with your build options e.g:
java -classpath plugins/js.jar;plugins/compiler.jar org.mozilla.javascript.tools.shell.Main plugins/r.js -o baseUrl=app name=Main out=app.min.js
pause

How does it look when you put it all together?

Example MVC Todo list:

Minified example:

Download it all including minify setup files:
https://github.com/kmturley/require-js-mvc/zipball/master

Further Reading, Articles & Examples:

No comments:

Post a Comment