RequireJS & Handlebars

requirejs-handlebars

If we are building a web application, code complexity could grow and the app can get bigger. Number of plug-ins can grow bigger, and you will feel the need of adding more modular components. RequireJS can address these issues to increase the speed of your application in an efficient way.

Why RequireJS

When the application grows, we need a way of loading dependencies via a #include/import/require structure.   Also the number of plug-ins can grow bigger, and you will feel the need of adding more modular components. This also could address the ability of loading nested dependencies. Uncontrolled scripts , can lead in to loose control flow and make it hard to understand the code.

RequireJS is a javascript file and a module loader which can be used to organize your code structure and improve brevity. We can lazy load dependencies as we wish, and reuse compoments. Using a modular script loader like RequireJs will improve the speed and the quality of your code.

RequireJS allows modules to be loaded as fast as possible, even if it is out of order, but evaluated in the correct dependency order. With r.js, we could easily minify and combine the app to reduce the amount of script tags.

RequireJS is built on the Module Pattern.

The Module Pattern

A javascript code module is a collection of javascript code located in a registered location. All of the code running inside a function lives in a closure, which provides privacy and state throughout the lifetime of the app.

Technically, it is an anonymous function that executes immediately.

(function(){   // variables and function definition  //  local to this module  // access global variables}());

1. Adding Require.js to your project

Let’s start with downloading the require.js distribution from here.

Once we have the libary, let’s add it to our project. Insert the following snippet to your index.html or equivalent file:

<script data-main="app/js/main" src="static/js/lib/require.js"></script>

The data-main attribute tells require.js which file to load (without .js extension treating it a module). This file contains instructions on how
to start up our application. It can also contain the configuration.

All other <script> tags can be removed from your index.html page.

2. Configuring Require.js

Require.js uses AMD style lazy loading, which means that modules need to declare what they depend on, and what they export.

Let’s create the main.js file now. Insert the project layout description:

requirejs.config({
  'baseUrl': '/public',
  'paths': {
    'app': 'app/js',
    'jquery': 'js/lib/jquery-2.0.0.min',
    'underscore': 'js/lib/underscore-min-1.5.1',
    'backbone': 'js/lib/backbone-min-1.0.0',
    'handlebars': 'js/lib/handlebars',
  }
});

This assumes the following structure:

public/
    app/
        js/
            collections.js            views.js
            models.js
            app.js
            main.js
    js/
        lib/
            jquery-2.0.0.min.js
            underscore-min-1.5.1.js
            backbone-min-1.0.0.js
            handlebars.js

This informs require.js that /public is the root URL for our app and all paths are from now on relative to this location. And describe where our current dependencies are located. Also notice that we don’t specify the .js extension here.  Not all modules can be AMD compliant. Let’s look at those next.

3. Non AMD modules and plug-ins

Distributions like Backbone.js and Underscore.js are currently not AMD compliant.

Shims let require.js load non AMD compliant libraries.  Let’s add them to our config:

requirejs.config({
  'baseUrl': '/public',
  'paths': {
    'app': 'app/js',
    'jquery': 'js/vendor/jquery-2.0.0.min',
    'underscore': 'js/lib/underscore-min-1.5.1',
    'backbone': 'js/lib/backbone-min-1.0.0',
    'bootstrap': 'js/lib/bootstrap.min',
    'handlebars': 'js/lib/handlebars',
  },
  'shim': {
    'underscore': {
      'exports': '_'
    },
    'backbone': {
      'deps': ['jquery', 'underscore'],
      'exports': 'Backbone'
    },
    'handlebars': {
      'exports': 'Handlebars'
    }
  }
});

Notice how jQuery does not need such a shim. This is because jQuery version 1.7 and above is AMD compliant, so it should work fine. So now require.js knows what our app looks like. Now it’s time to convert our own code to be AMD compliant.

4. Converting our code to be AMD compliant

We’ll assume for the purpose of this post that our app is laid out as such:  views.js – containing our Backbone Views, models.js – containing Backbone Models, and app.js – containing our main Application.

First, we examine the file and see what dependencies are being used. for example, the views.js file uses jQuery for DOM manipulation, Handlebars for rendering templates, and Backbone for defining the View subclasses.

Now we can wrap our code with a define() function which require.js provides. This function takes 2 arguments: an array of strings (describing the dependencies) and a function that (returns whatever we want to export from this module). This function gets whatever we decided to import as arguments.

Let’s explain this with an example. Our converted views.js file should now look as follows:

define(['jquery', 'handlebars',

'backbone'], function($, Handlebars, Backbone) {

    var SomeView = Backbone.view.extend({
        render: function() {
            var template = Handlebars.compile( $('#template-someview').html() );
            var rendered = template(this.getContext());
            ...
        }
        // more view code
        ...
    });
    ...
    // export stuff:
    return {
        'SomeView': SomeView
    };
});

Require.js knows how to find our jQuery module using the paths declaration we made before in main.js. So loading something such as jquery seems a bit abstract at first.But the module loading configuration is centralized, and decoupled from our code. And the views module doesn’t need to know where jQuery is located in our project, making it easy to port this module to another project, without changing or adjusting our imports.

Also you don’t have to return all View subclasses. as with any module, you should only export things that could be used by other modules. It’s ok to keep some views or bits of functionality for internal “private” use by the module itself. It’s up to you.

Repeat this process as necessary for other files in your project. This might also be a good time to break things down into smaller files with smaller scopes of functionality. Don’t worry about having to load multiple scripts, we’ll use r.js later to concatenate and minify all our resources into a single file later.

5. Launching our application.

Time to fire up our newly converted app. For that, we’ll revisit our main.js file, and add the following beneath our requirejs.config() declaration:

require(['app/app'], function(Application) {
    var app = new Application();
    app.start(); // or whatever startup logic your app uses.
});

This will load your main application and fire it up, the same way a “main” module would probably do in several other languages. Your app is now AMD compliant and uses require.js to manage and declare modules and dependencies. Way to go!

6. Deploying to production

A great tool that require.js comes with is r.js. It lets you automatically pull all your dependencies and modules into a single file and minify it. It also lets you do other neat stuff such as concatenating CSS files.

The easiest way to build our app for production, is to create a build.js file containing our build configuration. It should look something like this:

({
  ({
  "baseUrl": "./public",
  "name": "app/main",
  "mainConfigFile": "./public/app/js/main.js",
  "out": "./public/js/app.js"
})

I generally put this file one level above my public folder, since I don’t want it to be deployed as a static asset. The most important setting here to note is mainConfigFile – this should be the path to our main module from before.

To build the project we need to first install r.js. The easiest way to do that (provided you have node.jsand npm installed) is to use npm:

$ npm install -g requirejs

Once installed, we can call r.js

pointing it to our build.js file:

$ r.js -o build.js

Depending on the size of your project, it could take a few seconds to resolve all dependencies and minify them. Once done, we can change our script tag to include the built version:

<script src="/static/js/vendor/require.js"></script>
<script src="/static/js/app.js"></script>

Notice how we removed the data-main attribute. We added the compiled version of our code instead, containing everything we need inside it.

Be first to comment

Leave a Reply