So, as part of my To-Learn list for 2013 I’m currently messing around with Javascript tools and libraries.
It has been a long way since we used Javascript mostly to validate forms and show alert boxes.
Today we have so many good tools that allows us to write better front-end applications, that’s hard to keep up.
Recently I’ve been playing with Backbone.js, an awesome library that helps you separate content/logic in your front-end application. And while I’m no expert on it, one of the first things I noticed is that Backbone doesn’t explain how you should lay out your code.
This can be good, but it can also lead to architecture design disasters if not used correctly. Specially if you are developing multi-pages website.
So, in this tutorial I’m going to show you how to create a good foundation for your application using AMD design pattern with Backbone.js and RequireJs.
Notice: This is not a tutorial for beginners on BackboneJs or RequireJs. It's expected that you have some experiencie with these tools.
TL;DR
I show you how to create a basic boilerplate multi-page using Backbone.js and RequireJs.
I’ve placed a improved version of this tutorial on github.
Intro
I’m not going to explain in full detail what each of these libraries do, I assume most of you already know it, if not, just click on the likes above.
We’re going to create a sort of boilerplate, that can be reused in future projects, so in order to do this we will need a few extra things to keep everything tidy.
Bower Setup
The first thing you want to do, is to install Bower. This is a very light and solid package manager for the web. You can install/update/remove external vendor packages from your command line.
From your command line, create a folder somewhere in your web root folder.
$ mkdir myapp && cd myapp/
At the root of myapp/ create a new file called “.bowerrc” and add this
{
"directory": "src/scripts/vendors"
}
This will make bower install it’s dependencies under the “directory” param.
Save the file and go back to the command line.
Create another file called “component.json” and past this.
{
"name": "MyApp",
"version" : "1.0.0",
"dependencies": {
"jquery": null,
"backbone-amd": null,
"underscore-amd": null,
"requirejs": null,
}
}
This file will allow Bower to install all of this dependencies for us.
Let the magic begin. From the command line type:
$ bower install
You should start see Bower fetching all those libraries from git and copying them into “src/scripts/vendors”.
By default, Bower copies the entire repository of each library. So it’s up to you if you want to delete some extra files you don’t want. You can also just specify a single file, but read the Bower documentation for more info about this.
Defining a directory structure
Now, let’s create our bundle root folder.
$ mkdir src/scripts/bundles
This folder will be the home of our bundles.
$ mkdir build/
This folder will contain our building scripts, that will merge and minify our dirs into an output dir.
Loading…
Now, let’s get our hands dirty.
Open up your favourite code editor, and create a file “src/index.html”
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name='viewport' content='width=device-width'/>
<title>Backbone Boilerplate</title>
<script data-main="scripts/main"
data-root="/myapp/src/"
src="scripts/vendors/requirejs/require.js"></script>
</head>
<body>
<h1>My title</title>
</body>
</html>
Notice that data-root is a custom field we add. This field defines the default web url root path. If your root path is “/” you can remove this.
data-main is the main entry point for RequireJs, this will load the file “src/scripts/main.js”, notice that we didn’t need to add “.js” extension. RequireJs does this for us.
So let’s create this file.
(function () {
'use strict';
requirejs.config({
baseUrl: "scripts/",
paths: {
"jquery": "vendors/jquery/jquery",
"underscore": "vendors/underscore-amd/underscore",
"backbone": "vendors/backbone-amd/backbone",
}
});
require(['app'], function (app) {
app.initialize();
});
}());
This file will be responsible for creating a config structure that will be used by RequireJs.
On line 17, RequireJs will need to include a “app.js” file, so let’s create it.
define([
'jquery',
'underscore',
'backbone',
'router'
], function ($, _, Backbone, router) {
'use strict';
// Add your modules routing here
router.route("/*", "home", function () {
this.loadModule("bundles/todos/main");
});
var root = $("[data-main][data-root]").data("root");
root = root ? root : '/';
return {
initialize: function () {
Backbone.history.start({
pushState: true,
root: root
});
}
};
});
This file will allows to create our routing patterns for our bundles.
Inside the define() function, you’ll notice that it will include our dependencies and a new file, called “router”, you haven’t created this yet.
It will also start the Backbone History. Also, notice that it’s using our custom data-field “data-root” around line 15.
To complete our basic dynamic loading, we just need to create one last file. The router.js.
define(["underscore", "backbone"], function (_, Backbone) {
'use strict';
// Router
var Router = Backbone.Router.extend({
loadModule: function (module) {
require([module], function (module) {
module();
});
}
});
return new Router();
});
This file will create a new Object from Backbone.Router and add a new function called loadModule(). This function will load our Bundles dynamically. If you look at the file “app.js” you will see the following code.
router.route("/*", "home", function () {
this.loadModule("bundles/demo/main");
});
This must be added by you. What this means is that everytime a url matches “/*” this will trigger an event that will load the “bundles/todos/main.js” file. We haven’t created this yet.
And we are done with the basic foundation for our multi-page app.
To wrap it up, here’s the file tree.
.
├── .bowerrc
├── component.json
└── src
├── index.html
└── scripts
├── app.js
├── bundles
├── main.js
├── router.js
└── vendors
Creating our bundle
The idea behind a bundle is to isolate specific sections of our websites in it.
For example.
http://www.myhost.com/ —> /src/scripts/bundles/home
http://www.myhost.com/users —> /src/scripts/bundles/users
http://www.myhost.com/invoices —> /src/scripts/bundles/invoices
And since we are using requireJs, we can easily create dependencies and share resources between bundles.
So let’s create a basic bundle.
Inside “myapp/src/scripts/bundles” create a directory named “demo” with the following structure
. ├── collections --> Backbone Collections ├── models --> Backbone Models ├── templates --> Mustache Templates ├── tests --> To store unit tests ├── views --> Backbone Views └── main.js --> Bundle entry point
The main.js required by all bundlers. It will be used as the entry point when a bundle is loaded.
Here’s an example
define([ "./views/main"], function (MainView) {
'use strict';
return function () {
var mainView = new MainView();
};
});
This file will load a Backbone view object from “views/main.js” file.
Building
“Building” means that we will merge and minify/uglify our source code inside “myapp/src/” and output into a new folder “myapp/dist”.
We will endup with a single merged file for each bundle, and a single common file for our global dependencies like Backbone.js, RequireJs, jQuery and so on.
For eg. All of our files inside “myapp/src/scripts/bundles/demo” will be merged into one “main.js” file in the output directory.
As you know, this should be used on production only.
The major difference here, is that since we are using this for multi-page websites, we really don’t want to merge every thing into one big file, or having all bundles include the common libraries.
So by using the scripts below, we will organize our output code into single files that are loaded on demand.
When we created our directory structure, we created a dir at “myapp/build”.
Inside that directory create two new files
({
appDir: '../src/',
baseUrl: './scripts/',
dir: '../dist',
mainConfigFile: '../src/scripts/main.js',
optimizeCss: 'none',
paths: {
requireLib: '../scripts/vendors/requirejs/require'
},
modules: [
// WARNING: Do not remove this entry, it will be required
// by all your bundles.
{
name: 'main',
include: ['requireLib', 'main', 'app'],
},
//
// Add your bundles here.
// Make sure you always exclude 'main',
// unless you want to have
// one single big file with every bundle
// and dependency. This is
// not recommended.
//
{
name: 'bundles/demo/main',
exclude: ['main']
}
]
})
In a nutshell, what we are doing here is specifying which bundles should be included, and make sure none of the bundles are merged with the “main” common scripts. Resulting in lighter files for the bundles.
r.js -o app.build.js skipDirOptimize=true rm -rf ../dist/scripts/vendors rm ../dist/scripts/router.js rm ../dist/scripts/app.js rm ../dist/build.txt
This is the build bash script. It will run r.js and output the result into “myapp/dist” and remove some unused folders and files.
You need to install r.js first.
$ cd build/ $ chmod +x build.sh $ ./build.sh
You should start seeing a bunch of files being merged, and minified.
After it’s completed, you should see a new folder “myapp/dist”. It should contain all of your code, but without the vendors directory and other files like “router.js”, “app.js”.
Instead you should see only a “main.js” file. If you open this file it should include all the common libraries, including require.js
So in your new “myapp/dist/index.html” file you can change the script to
<script data-main="scripts/main"
data-root="/backbone-boilerplate/src/"
src="scripts/main.js"></script>
because “scripts/vendors/require/require.js” no longer exists.
Conclusion
And there you have it.
A basic boilerplate form multi-page websites using Backbone and Requirejs.
I’ve created a more completed boilerpate called Backbone-Boilerplate (creative name right?) that includes the concepts we are described here and goes a little further by using Unit testing, Template engine and including a Todo demo bundle for your to check it out.
I highly recommend your check this repository out, as it includes a more detail information on how to create bundles.
I also hope you’ve learned something, and if I missed or you have other suggestions please let me know in the comments below.
Ron Reiter
January 28, 2013 at 1:47pmNot a good idea to link module loading and routing. The whole app should be loaded, and the routing should only switch between states of the application. The router should depend on the application view, and should not initialize before the application is loaded.
Henrique B.
January 28, 2013 at 2:08pmYou are right if you were to use it in a single-page app. This however is to make it work for multi-pages apps. I have done this way in order to avoid having this big compiled JS file with all the merged modules in it.
By using this, you only load the code needed for that particular page.
zsitro
March 29, 2013 at 9:45amAgree! I was looking for this “on demand loading” solution for days..
I think this is the definite way to load subpages in your Backbone application without polluting the memory with unnecessary assets.
Philip Thrasher
February 22, 2013 at 1:19pmGreat article. Ron, you’re mostly correct. However, what if I’m building a larger web application wherein I have different “areas” that are completely different functionality. For instance, what if I’m developing google docs. I’d need code minified for spreadsheet, but separate code minified for the presentation designer app. They may have some shared code, but there would also be a ton of code you wouldn’t want to load in both instances.
Phil
March 14, 2013 at 1:45amSo the whole idea here is to use data-root to do 2 things: (1) load the module in that path, (2) change pushState to that url.
So what prevent me to use tbranyen’s version of Backbone Boilerplate and modify this file to load module based on the route then? https://github.com/tbranyen/backbone-boilerplate/blob/master/app/router.js
It’s basically the same thing, isn’t it?
Charlie
April 15, 2013 at 9:19pmGreat article and very enlightening, Henrique.
I’m trying to understand what you meant to say with “module()” in the “loadModule” function of “router” module. did you mean: return module. I recently started to work with AMD so I apologize if this seems too obvious but I just can’t see it. thanks.
Walter Macambira
April 17, 2013 at 11:51pmLook this: require([module], function (module) { module(); });
The first module is the string “bundles/home/main”, the second is a parameter of the callback function, it will be what is returned by “bundles/home/main” (because RequireJS is designed to behave this way). In this case, it is a function(), because the main.js file returns a function (look at it). So, the third module, is not the string “bundles/home/main”, it is actually the function() returned. As it is “module()”, it is actually just executing the function returned.
Hope I helped.
Walter Macambira
April 17, 2013 at 9:20pmI am not an expert on this subject, but I will suggest a way to avoid memory leaks due to views event bindings and other bindings to DOM elements. To achieve this, you need to add in your loadModule function a view transition mechanism that would be responsible for calling a “clear” method from your current view. For this, your main.js (for each bundle), instead of returning a function with the MainView instantiation, would return the view itself. So your router would be aware of which MainView-bundle is loaded and be responsible for calling its “clearing” method to avoid memory leaks. This way, loadModule would also be responsible for adding the view’s el to your app container element. Hope I helped!
By the way, greate article, helped me a lot understanding a better approach for a multi-page app with Backbone.
Charlie
April 21, 2013 at 8:04pmDefinitely helpful, Walter!
I took another look at RequireJS docs, too. I’m used to returning object from modules.
Jon West
April 25, 2013 at 5:06pm@ Henrique Great article! I’m currently struggling with Backbone’s lack of organization in a large single page app which really has multiple large views/pages/sections. What you’ve written makes me feel like I’m not crazy for feeling like the app isn’t quite the same as the single view todo apps I’ve seen every where. Do you have any examples where you switch between pages? I noticed that you only have 1 bundle defined so the app really only has 1 page. Managing the transitions between various pages is tricky at best and messy and awkward at worst.
Henrique B.
April 26, 2013 at 8:44amHi Jon, what I meant was that each page would be a bundle. So say /(index) would be a “home” bundle, /users would be a “users” bundle and so on.
Eg:
router.route(“/users”, “users”, function () {
this.loadModule(“bundles/users/main”);
});
Hope this helps
Josh Habdas
May 2, 2013 at 4:02amThanks for the boilerplate, Henri. Using Brunch you can get it all for free, as shown in my article Developing modern web applications on Windows with Vagrant. Hopefully some of your readers may find it helpful.
alessio
May 16, 2013 at 12:47amArticolo interessante e colgo l’occasione per complimentarmi per questo sito! veramente ben fatto e con tanti articoli utili!