Chapter 1. The Bigger Picture
The goal of this first chapter is to provide an introduction to the Backbone.js application environment. It focuses mainly on packages of JavaScript, how to fetch these from the command line, and how to bundle many JavaScript files into one single file.
To learn about the ideas behind Backbone.js, you want to manage as few abstractions as possible. This is why we’ll use Node and the command line as our main working environment for the first few chapters. Later in this book, you will meet Yeoman, RequireJS, and Grunt, which automate workflow for JavaScript projects.
If you prefer to skip the command-line ideas for now, and you want to get started with the browser and UX topics directly, you might want to read Chapter 2 first. However, you should return to this chapter afterward so that you can learn more about JavaScript modules and bundling JavaScript for the browser.
In sum, the goal in this chapter is to enter development with JavaScript modules, and we will touch on the following:
-
Getting Backbone.js via
npm
, via a content delivery network (CDN) or from the project site - Basic bundling of JavaScript applications with Browserify and Stitch
- Common use cases for the CommonJS module format
Before You Get Started
Before you can build Backbone.js applications, it is very important that you know some basic abstractions to work with multiple JavaScript files at once.
There are two reasons:
- You will need to fetch a number of JavaScript dependencies to get going with Backbone.js web applications.
- The view and data layer in Backbone.js applications are generally broken up into separate JavaScript modules.
Bundling JavaScript for the browser is an important topic with many options. A related question is this: how can you organize your JavaScript dependencies and share your projects with others? To follow the answers of this book, you will need a working Node.js setup.
If you don’t yet feel comfortable with JavaScript or haven’t set up Node.js, you might want to look at the JavaScript refresher in Appendix A; you will find some instructions to set up Node.js.
Backbonify Your Stack
Like Lego, the philosophy of Backbone.js is centered on combining small building blocks that do one thing well. As an introduction, you’ll see some of the simplest ways to work with Backbone.js in this chapter.
Besides Backbone.js, you need to fetch two additional libraries to get started. Underscore.js is a fixed dependency for Backbone.js and will help you with filtering and sorting data, as well as working with arrays and functions.
Second, you need a library for manipulating the Document Object Model (DOM). One of the most popular libraries for DOM manipulation is jQuery, but there is also Zepto.js for mobile use cases or Cheerio for server-side DOM manipulation.
So, how can we import these libraries into the web application? There are several ways:
-
Fetching local copies by using a package manager, such as
npm
- Working with remote references, or CDN networks
- Fetching local copies by downloading the libraries manually
Using npm
If you want to use Node.js, and we will be using it a lot in this book, you can fetch Backbone.js with Node’s package manager, or npm
.
npm
is one of the most important command-line tools in Node. With npm
, you can quickly access more than 60,000 JavaScript modules. Although npm
has its roots on the server side, you can use it for developing browser web applications, too, as we will see later in this chapter.
Note
JavaScript modules will be mentioned a few times in this book. You can think of modules as small code libraries that bundle some functionality. Modules prevent us from reinventing wheels without falling prey to copy-and-paste code. If you are looking for certain functions, it makes sense to search http://npmjs.org and play with solutions from other programmers.
First, if you start work on a new project, it makes sense to initialize the project directory as follows:
$ npm init
You’ll get asked a number of questions about your project. You can leave most parts empty for new projects, if you are unsure of the answers when you’re first starting out. The important point is that you obtain a package.json file, which should contain the following:
{ "name": "sandbox", "version": "0.0.0", "description": "", "main": "index.js", "author": "Patrick", "dependencies": { } }
Next, we fetch Backbone and its dependencies. You can fetch Backbone with npm
as follows:
$ npm install backbone --save npm http GET https://registry.npmjs.org/backbone npm http 304 https://registry.npmjs.org/backbone npm http GET https://registry.npmjs.org/underscore npm http 304 https://registry.npmjs.org/underscore backbone@1.1.2 node_modules/backbone └── underscore@1.6.0
We use the --save
argument to save Backbone as a fixed depenency for the project. It is also possible to save a dependency only for development with --save-dev
.
After you run the command, you should have a node_modules directory that contains Backbone and its dependency Underscore.js. We also need the jQuery library for DOM manipulation, which we can add as follows:
$ npm install jquery --save npm http GET https://registry.npmjs.org/jquery npm http 304 https://registry.npmjs.org/jquery jquery@2.1.0 node_modules/jquery
We now have the libraries as Node modules that support the so-called CommonJS format. What this is, and how we package these modules for the browser, will be discussed in the following sections.
For now, take away that npm
can create a project manifest and can manage your JavaScript dependencies from the command line. Once Backbone.js is a dependency there, it will allow others to run npm install
on your project and easily get a working environment.
Note
There are a number of solutions to manage JavaScript dependencies. For example, we will meet Bower in Chapter 10, when we look at automated workflows for frontend web development with Grunt. There is also volo, which is preferred by some developers.
Local Backbone.js
If you are rather new to JavaScript and Node.js, you may want to experiment first with Backbone.js without using Node. In this case, you can visit http://backbonejs.org.
There you can fetch a copy of Backbone.js and store it as a local copy on your machine. Local copies might also be handy if you work with server-side web frameworks, such as Ruby on Rails, that have their own JavaScript build process. Last, fetching a local copy might be interesting when you want to play with the newest version of Backbone.js.
To download Backbone.js from the project site, you can scroll down until you see the project download area, as shown in Figure 1-1. In most cases, you want to download the development version. Then you must download the Backbone.js dependencies jQuery and Underscore.js.
It’s a good idea to occasionally visit the home page of the Backbone.js project so that you can stay informed about changes in the project. You should also regularly check the project repository at GitHub: by looking at the latest commits and new issue discussions, you can expand your knowledge of JavaScript and open source development.
Backbone.js via Content Delivery Networks
When you want to share examples online, a good option to load Backbone.js is to fetch the libraries from a content delivery network (CDN).
Note
Loading Backbone.js and its dependencies from a CDN is necessary when working with services such as JSFiddle, JSBin, or Codepen.io. These online sandboxes can help you with sharing problems or publishing work.
There are a number of CDNs that host a version of Backbone.js, but a very good CDN network is provided by Cloudflare. If you want to use Backbone.js with a CDN, you can use the following <script>
tags:
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.js"> </script> <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.5.2/ underscore-min.js"> </script> <script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.1.0/ backbone-min.js"> </script>
To test this, we create a simple HTML file:
<html> <head> <!--- insert CDN scripts here --> <script> $(document).ready(function() { console.log(Backbone); }); </script> </head> <body> </body> </html>
Let’s check this page in the browser. If all goes well, we should see a Backbone object printed in the console of the browser, similar to Figure 1-2. However, you might experience problems without network access or WiFi. We will see in a moment how to fetch local copies of the libraries to work in offline mode, too.
Modules, Packages, and Servers
At this point, you’ve tackled the first hurdle for building web applications with Backbone.js. You can manage some JavaScript dependencies with npm
, and you can manually download a version of Backbone.js.
But how do we bundle multiple JavaScript files so that we only have to worry about a single JavaScript file in the browser? This question becomes especially important when you’re working with 10–20 JavaScript files, because setting each <script>
tag manually in the HTML would be tedious. In this book, we look first at approaches to bundle CommonJS modules, and in later chapters at working with RequireJS.
To understand where we are heading and why there are a number of approaches to bundling assets, let’s take a look at the distributed application design in Figure 1-3.
Your application stack might change, depending on the requirements that evolve from users. If your primary goal is to deliver a mobile web application, we might want to tune every line of JavaScript that we send to the client. An example stack for mobile web applications is given by Walmart’s mobile shopping cart, and we will discuss this stack based on RequireJS and Thorax in later chapters.
If it is important that search engines can crawl your application, rendering of templates should be done on the server to provide links for search engine optimization and a fast first page load. Backbone.js integrates well with so-called isomorphic JavaScript applications, where parts of an application can run on both the client and server. Airbnb’s Rendr.js library shows how client- and server-side rendering can be combined for this use case with Browserify and CommonJS modules.
In other cases, a Backbone.js application is just part of a larger server-side web application. Some server-side approaches, such as Browserify and Express with Stitch, support bundling JavaScript files with the CommonJS module format. Other server-side approaches support RequireJS-based workflows and JavaScript modules in the so-called AMD format.
Don’t worry too much about what is best for you now. The important point here is to experiment with the idea of “modular” JavaScript and observe the influence this has on your use cases and application stack.
CommonJS Modules
When JavaScript was first specified, <script>
tags were the main constructs to run JavaScript. When Node.js arose, there was a new need to reuse JavaScript dependencies as modules across projects. The Node.js community proposed the CommonJS module format. But, should you “require” Backbone as a CommonJS module in the browser too? Well, it depends.
What is the best module format? Opinions vary. Besides the CommonJS module format, there is the RequireJS format. RequireJS has been developed specifically for the browser environment. Yet, as with many software development considerations, the right tool depends on your job.
As the CommonJS format is the default server-side approach, you can have an option to run the same code on the server that runs in the browser, or vice versa. This can be interesting for certain kinds of applications, as we can share the same logic to render views or validate models on the server as in the browser. The Rendr library from Airbnb, for example, follows this approach.
Also, because npm
uses the CommonJS format by default, it can be nice to build quick prototypes and to experiment for learning purposes as we are doing here. We will discuss RequireJS in the second half of this book, when we are looking at static web pages, without backend integration.
The general syntax to “require” Backbone as a CommonJS module looks like this:
var Backbone = require('backbone');
How does this require
work? In a Node environment, the JavaScript runtime would search the local node_modules paths for the Backbone module. If it can’t find the module there, Node would search the global node_modules folder. At the browser, we don’t have these paths, and a browser does not natively know about CommonJS modules. To fix this, we need to wrap JavaScript modules with tools such as Browserify and Stitch to resolve dependencies. We will discuss this in a moment.
First, let’s look closer at the syntax to define a CommonJS module. Say you want to wrap a function to print “Hello, World” in CommonJS. For doing this, we “export” some JavaScript code that we later can “require.”
So, we could define a module in a file greeting.js:
module.exports = function() { console.log("Hello, World!"); };
In the Node console, you can now require this module with the following:
> var greeting = require('./greeting'); > greeting(); Hello, World!
The same module can be executed in the browser. But first we need to package it as a module for the browser.
Beyond index.html
Now that you’ve learned some basics about JavaScript modules, let’s look at ways to “require” these modules in the browser. In a browser, all we have is HTML and <script>
tags.[1]
Loading an index.html in the browser with references to a Backbone.js app with <script>
tags only works for small projects. Putting too much JavaScript in HTML can easily evolve into hard-to-maintain code.
To set up a JavaScript project, we first create two directories: one directory to put the JavaScript files of the application, and another for the bundled JavaScript assets that are delivered to the browser.
Let’s set up these directories. First, we create a directory for the JavaScript sources:
$ mkdir app
Then, we create a directory for “static” files (you can later reference this directory from a web server):
$ mkdir static
Now, back to the main question of this section: what <script>
tags should you use to load Backbone.js and the web application? As is often the case with JavaScript, you have different options to prepare JavaScript modules for the browser.
Browserify
As already mentioned, npm
gives us access to the repository of Node modules.
Modules are important building blocks that let you run code at different places. For example, you could have the following JavaScript module to append DOM nodes to a page:
var appendFooter = function(text) { var footer = document.createElement('p'); footer.innerHTML = text; document.body.appendChild(footer); }; appendFooter('The Pipefishbook');
You could run this code in the browser, when you would include it in an HTML page with <script> tags. But as your projects grow, you might want to organize this code in a special directory, or replace it one day with a solution from npm
.
This is what Browserify allows us to do. If you place the preceding code in a file named appendFooter.js, you can “browserify” the code as follows:
browserify appendFooter.js > bundle.js
The resulting bundle can be loaded from an HTML file:
<html> <head> </head> <body> <script src="bundle.js"></script> </body> </html>
If you open the HTML in a browser, you will see a new <p>
DOM element with some text. To develop browser applications, you will quickly require multiple JavaScript libraries.
With Browserify, you can easily reference all kinds of libraries from http://npmjs.org or from modules in the /node_modules folder.
Let’s try this out in your current project setup.
In Using npm, you used npm
already to install Backbone and its dependencies. In addition to app
and static
, you should have a directory node_modules
at this point, and your project directory tree should look like this:
|-app |-node_modules |---backbone |-----node_modules |-------underscore |---jquery |-static
To bundle a JavaScript application (made up of multiple JavaScript modules) for the browser, let’s first make a module that loads the Backbone module. In app/main.js, we insert the following:
var Backbone = require('backbone'); module.exports = function() { return Backbone };
Node can be used to test that this is a valid module. If you enter the console with node
, you can now import the module as follows:
$ node > require("./app/main") [Function] > require("./app/main")() { VERSION: '1.1.2', ... ]
Note
Being able to run JavaScript on both the server and the browser virtual machine (VM) allows you to better reuse ideas for rendering or validating data, for example. This is less obvious from this small example, but it’s a big deal when you want to build an online shop or social network that needs both fast processing times on a server as well as responsive interfaces on the client.
Now, how can we see the same result from the Node console in the web browser? We can package the app and its Backbone dependency with Browserify:
browserify ./app/main.js > static/bundle.js
You now can load and run the app with some simple HTML as just shown. But to continue examining our app and its dependency in the browser, Browserify gives us another option.
You can bundle ./app/main.js as a module too, which you can later “require” in the browser. Therefore, for the upcoming examples, we continue using the following variation to browserify
an application:
$ browserify -r ./app/main:app > static/bundle.js
What does this command do? A number of things:
-
To bundle the file app/main.js as module, you use the
-r
directive. The colon defines the module name “app” that we can “require” in the browser. Seebrowserify --help
for more options. -
Because
browserify
just provides a plain text file output, you can use a>
to save the command’s output into a file static/bundle.js.
Then, if you look in the resulting file static/bundle.js, you’ll see that the output of Browserify resulted in two things.
First, Browserify output starts with a wrapper function that implicitly defines what and how to “require” code from this file:
require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function" &&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '" +o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&& require;for(var o=0;o<r.length;o++)s(r[o]);return s}) ({1:[function(require,module,exports){ var Backbone = require('backbone'); module.exports = function() { return Backbone };
Second, Browserify bundled up all JavaScript dependencies in this file, such as jQuery, Underscore, and Backbone. Generally, you don’t want to work in this large output file, but you want to use the original JavaScript files. The Browserify command can then be repeated as often as you like to create new static files.
Note
To save you from typing browserify
every time a file changes, you can use the watchify
tool, which automates builds as soon as an input file changes. However, to keep the code examples consistent, the book examples only show the browserify
command.
To run the bundled code in the browser, let’s add a line to load the file static/bundle.js from our index.html. In static/index.html, we now have:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>PAGE TITLE</title> </head> <body> <!-- scripts --> <script src="bundle.js"></script> </body> </html>
If you now load the index.html from the browser, you can run require("app")()
in the browser console, and you should see output similar to Figure 1-4.
Note
If you want to learn more on combining client- and server-side rendering (e.g., in the context of an ecommerce project), you can look at the Rendr library from Airbnb or read up on isomorphic JavaScript web applications.
A slightly more difficult use case is to require modules as local application dependencies, such as custom Backbone views or Backbone collections from a directory (e.g., ./app/views or ./app/collections). Don’t worry about the specifics of views and collections at this point—we will discuss them in more depth in the next chapter.
The important point right now is devising a way to require local modules from an application directory. First, you create directories for views and collections:
$ mkdir app/views $ mkdir app/collections
Browserify follows the Node convention for looking up modules in directories. So, you need to create a node_modules directory inside the app directory, to follow the convention:
$ mkdir app/node_modules $ cd app/node_modules $ ln -sf ../views $ ln -sf ../collections
Based on symbolic links to the ./app/node_modules path, Browserify can find your local modules and you can easily require a module in your application like this:
require('views/movie');
With this setup, you can leave out any relative paths for your require()
statements.
Combining Express.js and Stitch
Browserify is not the only way to run CommonJS modules in the browser. While Browserify is a nice tool to bundle modules from the command line, some developers prefer to maintain a project manifest that explicitly lists a project’s dependencies. How manifest files look depends on your application stack, but the general goal, as with Browserify, is to bundle many files into one file.
For some application stacks (e.g., when you work with a web server similar to Express.js), CommonJS modules, or CommonJS like require
of modules, can be done with some simple configurations. For web servers based on Node.js, there are the package managers Stitch and Mincer, which are somewhat similar to the Sprockets asset manager for web servers in Ruby.
Tip
If you come from Ruby on Rails, you probably have used Sprockets, the asset pipeline in Ruby on Rails. Sprockets is very similar to Stitch but supports its own require
syntax. If you like that syntax, you might want to check out Mincer, a port of Sprockets to Node.js.
To illustrate some ideas behind using a package manager and a manifest file, let’s walk through an example with Express.js and Stitch. The role of the web server is to deliver HTML, CSS, and JavaScript to the client. Stitch helps us to package the frontend JavaScript project.
An Express.js server is very simple to set up. Similar to the approach taken earlier, we can use npm
to fetch the Express.js module:
$ npm install express
Express.js provides a nice language to manage HTTP requests and responses on the server. Let’s create a directory for a server next:
$ mkdir server
To get a basic server going, you can create a server/app.js file that serves a simple index.html page first. For this, we insert the following code in server/app.js:
// First, we require Express.js as dependency var express = require('express'); var logger = require('morgan');
// a helper to resolve relative paths var path = require('path');
// Then we initialize the application... var app = express();
app.use(logger({ immediate: true, format: 'dev' }));
// We add a basic route that serves an index.html // ... let's use the same as above app.get('/', function(req, res) { var html = path.resolve(__dirname + '/../index.html'); res.sendfile(html); });
// Let's listen on port 5000 app.listen(5000); console.log("Server is running.");
And, if we insert the preceding HTML, we can start the server with:
$ node server/app.js
We can check that our new server speaks HTTP from the command line with curl
:
$ curl 0.0.0.0:5000
And this should return the HTML from server/app.js, which we can check in a browser, too.
So far, the server-side Express.js application just transports HTML. Let’s look next at how to wrap JavaScript “modules” with Stitch.
Similar to how we installed Express.js, we can install Stitch with:
$ npm install stitch
Stitch assembles multiples files into one file via configurations of paths. In the previous file server/app.js we now add:
var express = require('express'), path = require('path'), stitch = require('stitch');
// To "stitch" the client-side modules together // we create a package var package = stitch.createPackage({ paths: [__dirname + '/../app'], dependencies: [ __dirname + '/../libs/jquery.js', __dirname + '/../libs/underscore.js', __dirname + '/../libs/backbone.js', ] });
var app = express();
app.use(express.static(__dirname + '/public'));
// Whenever a request goes to the client, we deliver the modules as bundle.js app.get('/static/bundle.js', package.createServer());
app.get('/', function(req, res) { console.log("--> /"); var html = path.resolve(__dirname + '/../index.html'); res.sendfile(html); }); app.listen(5000); console.log("Server is running.");
With this set up, Stitch manages and serves the client-side application whenever we request /static/bundle.js. Stitch resolves the modules in the dependency tree of the client-side application. Let’s check this.
First, we create a directory for the client-side application:
$ mkdir app
and a app/init.js file where we insert:
console.log("hello, world");
Now, we can look at what Stitch does with:
$ curl 0.0.0.0:5000/static/bundle.js
Inspecting the file, we see some code that was added by Stitch, and at the bottom some code from our init.js file:
//.... {"main": function(exports, require, module) {console.log("hello, world"); }
We now can use the main.js file as a CommonJS module (i.e., with require("main")
in the browser console). The following sections show how to work with those to build the Backbone application.
Stitch has less power in resolving dependencies than Browserify, but Stitch will do fine for most examples in this book. Instead of manually configuring and setting up Underscore, Backbone, and Stitch, you can also declare Backbone in the global scope or load Backbone from CDN networks.
When Things Go Wrong
Working with a web browser for development can be unusual for backend developers. However, a browser’s development console is a great playground, as is Node’s read-eval-print-loop (REPL). Many problems with Backbone.js are caused by an incorrect usage of JavaScript syntax or idioms. To understand what went wrong, typing some code into the REPL is a good start.
Problems with rendering and the DOM can often be debugged with breakpoints in the browser (for example, by adding the debugger
statement in your source code). With breakpoints, you can understand why a variable has (not) the expected value, or why a rendering snippet is not reached. The Mozilla documentation on Debugging JavaScript offers good advice on using the debugger in browsers.
On the server-side and on the command line, you might find the following tools helpful:
- JSLint/JSHint
- These tools allow you to debug the syntax of JavaScript. This is especially helpful for finding missing brackets, parentheses, or semicolons. Looking at the output of JSLint, you can also improve your coding style. The rules that get applied in JSLint originate from Douglas Crockford’s JavaScript: The Good Parts.
- Console output
-
Often, it helps to place a line of
console.log("-→ debug");
in your code and see when some output is printed in the browser console. Sometimes code can return unexpectedly and never reaches the functions you expect. - JSON beautifiers
-
Working with JSON, you will often find it helpful to format some data with the
jshon
tool, or a similar browser plug-in. By using thejshon
beautifier, you can inspect data from the command line withcurl
orwget
and compare the data values with what you expect.
Conclusion
This chapter provided a first glance of the development of a web application stack. We used npm
and some Node modules to set up a basic Backbone.js application stack.
At this stage, you want to keep abstractions for an application stack at the bare minimum, such that the application is easy to read and feels nice to play with. As the book progresses, you will be introduced to other options and trade-offs that might be better for deploying an application for your particular use case.
An application with Backbone.js lives partly on the server and partly in the browser, so you should be familiar with the core application libraries and how to set up some basic directories to organize your project files.
You should have played a bit with Browserify or with a JavaScript package manager that bundles multiple JavaScript files into one file. The widely popular RequireJS and JavaScript AMD module format will be discussed later in the book.
For the next chapters, we’ll stay in the web browser. You will learn about the basic abstractions that Backbone.js provides, and we will discuss Munich Cinema, the main example application of the book.
[1] There will be new ways to load JavaScript modules with the upcoming ECMAScript 6 specification, but it will take some time before these are widely used.
Get Full Stack Web Development with Backbone.js now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.