Saturday, June 15, 2013

Simplify Loading Backbone Objects with the RequireJS Namespace Plugin

Generally, your Backbone project is going to broken out into different files and directories to keep things organized and more modular. If you weren't using RequireJS to load everything, you might declare a namespace in the global scope and, in each file, add the object definition to its place in the namespace object so it can be used when necessary. If you're using RequireJS, then your probably aware of some of the issues of hard-coding the namespace and cluttering the global scope. However, as you avoid those issues, you still want to build a local object to map all your view, collection, and model definitions into a similar namespace so they're readily available. The RequireJS Namespace Plugin does just this and can significantly reduce the amount of intermediate definition modules and other featureless boilerplate code.

The GitHub read me does a good job introducing the plugin so I'm not going to rehash that. I did take my example Backbone + RequireJS project and experimented with using it to manage loading my Backbone elements. There were a few considerations along the way I needed to make to ensure my optimized component worked properly. I decided to branch the project to make it easier to compare and tinker. I'll discuss the changes I made to make it work properly.

First, based on the example on the Namespace read me, I was able to flatten my directory structure and eliminate some files.

I had this layout:

- lib
|- main.js
|- scripts
|- models.js
|- views.js
|- models
| |- model1.js
| |- model2.js
|- views
|- view1.js
|- layout
|- view1.html


And after namespace had this:

- lib
|- main.js
|- models
| |- model1.js
| |- model2.js
|- views
|- view1.js
|- layout
|- view1.html


That removed the intermediate script directory and models.js and views.js files that were simply defining more dependencies and returning object hashes. However, the ramifications of removing those items requires declaring the dependencies with the "namespace!" prefix and defining the mappings in the RequireJS configuration object. It took a try or two to figure out the each nomenclature but it seems easier to define and maintain from the configuration object then creating a bunch of files to define the namespace.

So the main files changes to add the plugin which triggers loading and mapping the dependencies per the configuration.

/component1/lib/main.js:

define(['namespace!./views', 'namespace!./models'], function(views, models) {

return {
Views: views,
Models: models
};

});


Without the configuration, namespace won't know what to include in each object. The left side should correspond to the dependency declared with the namespace prefix and the right side should be dependencies relative to those dependencies so namespace can require them internally before mapping them into an object.

component1/test/lib.js:

(function () {

require.config({

...

config: {
namespace: {
"models": "Model1,Model2",
"views" : "View1"

}
}

...

})();


Since the plugin needs to be part of the optimized build, it will expect the configuration to be available even after being built. I've designed my component to be self-contained using almond, so I don't want downstream users to have to define that namespace mapping. I was hoping to avoid including it at all but couldn't see an easy way to rebuild the dependencies and write them to the optimized file. As such, the best solution was to include the plugin and alter the wrapper files to add the configuration to the built component. Now the namespace is internally managed by the component and nobody needs to have any knowledge of its existence.

component1/build/wrap.end:

...

require.config({
config: {
namespace: {
"models": "Model1,Model2",
"views" : "View1"

}
}
});

...


The end result of all this was to reduce the basically unnecessary files, flatten the directory structure, and build object hashes with all the Backbone object definitions indexed by name. In its current state, the component only returns a final object with all those elements combined. That's probably an over simplification of what would really happen in a real application but provides a reasonable demonstration of the idea.