Bundling Front-end Resources with RequireJS

why

  • faster load times because of fewer http calls and lesser data over the wire.
  • effectively invalidate browser cache when the newer version of a resource is available.
  • lends itself to manage these resources via CDN

what

  • combines and minifies javascript
  • combines and minifies css
  • revises file names of js/css files to support heavy browser caching
  • updates the html to reference these new hyper-optimized js/css files

how

Lets start with combining javascript. This could be as trivial as writing a shell script to concatenate all of your script files into one single file. Works great for monolithic applications but for large-scale javascript application where significant amount of data manipulation and display is done at the browser level, having a modular architecture greatly helps.

Modules are an integral piece of any robust application’s architecture and typically help in keeping the units of code for a project both cleanly separated and organized. They need to be highly decoupled and represent distinct pieces of functionality thus facilatiting easier maintainibility and easily replaceable without affecting the entire system.

“The secret to building large apps is never build large apps. Break your applications into small pieces. Then, assemble those testable, bite-sized pieces into your big application” -Justin Meyer, author JavaScriptMVC

However, there are two main problems in writing modular javascript:

  • No means to import modules of code in a clean and organized manner.
    • Developers at present use variations of the module or object literal patterns to write modules. The modules are exported as namespaces of a single global object where it is possible to incur name collisions as the application and team grows.
  • No cleaner way to handle dependency management.
    • Developers use the exported namespace to include the modules. And then load the corresponding script files in the order they are included. This becomes really difficult as the application grows and particularly when scripts start to have overlapping and nesting dependecies.

There are various script loaders that help out but again there are no native means that solve the two problems in a holistic manner. And this where Asynchronous Module Definition (AMD) greatly shines. It has greatly helped out in architecting our application, the benefits of which will be a different post, but the following pretext from Addy Osmani should summarize well its use.

The AMD module format itself is a proposal for defining modules where both the module and dependencies can be asynchronously loaded. It has a number of distinct advantages including being both asynchronous and highly flexible by nature which removes the tight coupling one might commonly find between code and module identity. Many developers enjoy using it and one could consider it a reliable stepping stone towards the module system proposed for ES Harmony. – Addy Osmani

The following example demonstrates how we address the first problem with AMD:

define(
 	module_id /*optional*/,
 	['dog', 'actions'] /*other module dependencies if any*/,
 	function( dog, actions){ /*function for instantiating the module or object*/ 
 		//create your module here
 		var myModule = { 
 			dogActions: function(){
 				console.log('dog actions');
 			}
 		} 
 	//return a value that defines the module export
 	return myModule; 
 });

We have solved the second problem by using RequireJS, a popular script loader and we felt it is the natural fit for AMD. It uses the AMD module specification for defining and requiring modules, and it loads these modules via a built in script loader. It also provides for loading non-script dependencies such as text files and we have used that ability to load our handlebar templates.

Lets extend the above example to demonstrate how well AMD and requireJS compliment each other to keep your code modular, yet bought together without one worrying about module dependencies and name space conflicts.

<script src="scripts/require.js" data-main="scripts/main"></script>
// In scripts/main.js: your application bootstrap
require( 
	['dog', 'actions', 'hbs!templates/dogActions'],
	function( Dog, Actions, DogActionsTemplate ){
 		document.body.innerHTML = DogActionsTemplate({
 			dog: Dog,
 		 	actions: Actions
 		});
 	}
);
// In scripts/dog.js
define([], function( ){
	var dog = {
 		name: 'lula'
 	};
	return dog;
});
// In scripts/actions.js
define([], function( ){
	var actions = {
		name: 'fetchBall'
	};
 	return actions;
});
// In scripts/templates/dogActions
<h1>{{dog.name}}</h1>
<ul>
<ul>{{#each actions}}
	<li>{{this}}</li>
</ul>
</ul>
{{/each}}

In addition, RequireJS also provides an optimizer tool that we could use to build our entire javascript into a single file or two for production. And thus, how we address combining javascript.

Combining css was easy. We use sass to write our style sheets. As the name itself suggests they are awesome and were introduced to me by awesome fellow developer, Justin. We use scout or similar tools to output a single css file from our sass files that acts as the stylesheet for the entire application.

Now that we have successfully combined javascript and css files, lets compress them. We have earlier mentioned script loaders and how require’s optimizer tool helps out. Currently require provides two options: uglifyjs (default) and closure compiler to minify. we use uglifyjs and have it configured to “beautify” in our dev environment so that we could still effectively debug. Source maps seems to be an interesting concept in this arena that would help you debug “production” scripts.

Now that we have combined, minified our resources, our next step is to add a build fingerprint to the resource file names so that the browser treats them as new resources and invalidates its cached version of the resource.

We have used bunch of maven-ant scripts to achieve this. More on this in my next post.

challenges

One of the major challenges with requireJS is using non-AMD compatible scripts, mainly the third-party libraries. When we initially started with requireJS, we simply got the AMD versions of the third party libraries we needed or modified the libraries to be of AMD format. But we quickly realized that is not a feasible approach and was a major pain point when we had to add a new library. Tim Branyen had a good blog post on how to overcome this problem which was later incorporated into requireJS 2.0. With the introduction of shim, this problem was easily addressed in the config file as follows:

require.config({
 shim: {
 'backbone': {
 deps: ['underscore', 'jquery']
 , exports: 'Backbone'
 }
 , 'underscore': {
 exports: '_'
 }
 , 'raphael': {
 exports: 'Raphael'
 }
 }
 , hbs: {
 templateExtension: 'html'
 , disableI18n: true
 , helperPathCallback: function(name){
 return 'templates/helpers/' + name;
 }
 }
 , paths: {
 jquery: 'libs/vendor/jquery-1.7.2'
 , underscore: 'libs/vendor/underscore'
 , Handlebars: 'libs/vendor/handlebars_hbs'
 , raphael: 'libs/vendor/raphael'
 , backbone: 'libs/vendor/backbone'
 , hbs: 'libs/vendor/hbs'
 }
});

 

reads

http://addyosmani.github.com/backbone-fundamentals
https://github.com/h5bp/
http://www.nczonline.net/blog/2010/07/06/data-uris-make-css-sprites-obsolete/
http://addyosmani.com/blog/yeoman-at-your-service/
http://www.youtube.com/watch?v=Mk-tFn2Ix6g
http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/

 

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>