Backbone.js

BackboneJS with Webpack: A lesson in optimization

Developing a large BackboneJS application presents a unique design problem. As developers, we like to organize our code so it is understandable, logical, and predictable. However, doing so can cause performance issues on the client side.

In this blog I will discuss a handy tool I like to use for this purpose: Webpack. I’ll show it in action, how to use it, and what it is good for. But first, let’s talk about how I came across Webpack.

An Example

On a previous project, I was building an audio and video streaming player. The frontend was developed using BackboneJS. I used libraries such as jQuery and SocketIO. Using Require shim configuration, I ended up with my dependencies / exports organized as follows.

require.config({
    baseUrl: "js/",
    paths: {
        jquery: 'libs/jquery.js',
        underscore: '/libs/underscore.js/1.6.0/underscore-min',
        socketio: '/libs/socket.io/0.9.16/socket.io.min',
        backbone: '/libs/backbone.js/1.1.0/backbone-min',
        templates: 'templates'
    },
    shim: {
        'jquery': {
            exports: '$'
        },
        'backbone': {
            deps: ['underscore', 'jquery'],
            exports: 'Backbone'
        },
        'underscore': {
            exports: '_'
        },
        'socketio': {
            exports: 'io'
        }
    }
});

This worked great for my loading all my libraries. For each of my files, I defined the libraries I wanted to use. Breaking down each of my files into modules is a great way to handle organizing the large code base. Then I used the RequireJS AMD text resource loader plugin to load all template files.

define([
    'jquery',
    'underscore',
    'backbone',
    'text!templates/recordings/recordingTemplate.html'
], function($, _, Backbone, recordingTemplate) {
   // Do some stuff
}

At the time this was a decent solution. My code base was organized, easy to understand, and predictable. However, the larger my application became, a performance problem began to develop. For each template that was added, a new call to the server was made. This began to balloon the initial loading time of the application.

Wouldn’t it be great if all necessary resources were compacted into a single file, optimizing the loading time, and still be able to keep our code organized?

Developing a BackboneJS app all in a single file would be frustrating experience to manage. That’s where Webpack comes to the rescue.

What is Webpack?

Webpack is a module bundler that takes your files, compacts them, and generates a static file. Think of it as your own personal secretary there to help keep your life organized. You provide the configuration, it supplies the optimization.

Webpack’s primary goal is to keep initial loading time down. It does this by code splitting, and loaders.

Code Splitting

Code splitting is ideal for large applications where it is not efficient to put all code into a single file. Some blocks of code may only be useful for certain pages of the site. Using this opt-in feature allows you to define the split points in your code base, and Webpack will optimize the dependencies required to generate the optimal bundle.

var a = require("a");
require.ensure(["b"], function(require) {
    require("a").dosomething();
    var c = require("c");
});

This example uses CommonJS require.ensure to load resources on demand. The final output would contain two chunked files:

  • output.js – the primary entry point chunk containing
    • chunk loading logic
    • module A
  • 1.output.js – the additional chunk to be loaded on demand containing
    • module B
    • module C
[sixthpoint@sixthpoint webpack-code-splitting]$ webpack
Hash: e863fe1f9db99737fcd2
Version: webpack 1.12.2
Time: 564ms
      Asset       Size  Chunks             Chunk Names
  output.js     302 kB       0  [emitted]  main
1.output.js  172 bytes       1  [emitted]  
   [0] ./main.js 338 bytes {0} [built]
   [6] ./a.js 18 bytes {0} [built]
   [7] ./b.js 19 bytes {1} [built]
   [8] ./c.js 17 bytes {1} [built]

Loaders

Loaders preprocess files as you use the require() method. Using a loader you can easy require() resources such as CSS, images, or compile-to JS languages (CoffeeScript or JSX).

By default, Webpack knows how to process your JavaScript files, minify, and combine them. But it doesn’t really know how to do much else. Loaders are the solution to processing different types of files and turn them into usable resources for your application.

Load Bootstrap CSS file:

require('./styles.css');

Create a image element, and set the src to the image resource file:

var img = document.createElement('img');
img.src = require('./bootstrap-logo.png');

Using the CLI, all loaders can be defined in the webpack.config.js file.

module.exports = {
  entry: './main.js',
  output: {
    filename: 'output.js'       
  },
  module: {
    loaders: [
      { test: /\.css$/, loader: "style!css" },
      { test: /\.png$/, loader: "url-loader?mimetype=image/png" }
    ]
  }
};

This example configuration file sets the application entry point, desired output file name for webpack to build, and a list of two loaders (CSS and PNG).

If you want to test this out for yourself, check out this github repo: https://github.com/sixthpoint/webpack-async-code-splitting

How do I use BackboneJS with Webpack?

Above I showed my starter application which had an initial loading performance issue. Remember all those template calls? Webpack async loading and code splitting is going to significantly decrease load times. Let’s assume my application needs only a two entry points:

  • #/nowplaying – will be responsible for loading data from socket.io
  • #/schedules – will display all scheduling information

To start I have modified my Webpack config file and using the providePlugin added jQuery, Backbone, and Underscore to the global scope of my application. I will no longer have to require these libraries through my app.  This is similar to the shim config above.

var webpack = require('webpack');

module.exports = {
  entry: './main.js',
  output: {
    filename: 'output.js'       
  },
  module: {
    loaders: [
      { test: /\.css$/, loader: "style-loader!css-loader" },
      { test: /\.png$/, loader: "url-loader?mimetype=image/png" },
    ]
  },
  plugins : [ new webpack.ProvidePlugin({
			$ : "jquery",
			Backbone : "backbone",
			_ : "underscore"
		}) ]
};

The most important file of this app is the Backbone router. The router defines the code splitting points.

Notice that by using require.ensure, I will load only the socket.io API resource when navigating to the now playing page. This way, if somebody never goes to the now playing page, the resources for that page will never have to be loaded. If the user navigates to the now playing page, it will then be cached for if they return, for performance reasons.

var AppRouter = Backbone.Router.extend({
	routes : {
		'nowplaying' : 'nowplaying',
		'schedule' : 'schedule',
		'*actions' : 'home'
	}
});

var initialize = function() {
	var appRouter = new AppRouter;

	appRouter.on('route:home', function() {
		$('#content').text("Home Screen");
	});

	appRouter.on('route:nowplaying', function() {
		require.ensure([], function() {
                    // nowPlayingView contains socketIO resource
		    require('./nowplayingView');
		  });
	});

	appRouter.on('route:schedule', function() {
		require.ensure([], function() {
		    require('./scheduleView');
		  });
	});

	Backbone.history.start();
};

module.exports = initialize;

So how does Webpack organize this? Simple, both the now playing view (1.output.js) and the schedule view (2.output.js) get their respective files since they are async loaded.

Here is the output of the terminal, as expected:

[sixthpoint@sixthpoint webpack-backboneJS-socketIO-client]$ webpack
Hash: b29b2a6017bad0dd0577
Version: webpack 1.12.2
Time: 808ms
      Asset       Size  Chunks             Chunk Names
  output.js     388 kB       0  [emitted]  main
1.output.js     180 kB       1  [emitted]  
2.output.js  248 bytes       2  [emitted]  
   [0] ./main.js 54 bytes {0} [built]
   [1] ./router.js 604 bytes {0} [built]
   [5] ./nowplayingView.js 132 bytes {1} [built]
  [56] ./scheduleView.js 37 bytes {2} [built]
    + 53 hidden modules

Final Thoughts

What kind of project is Webpack good for? Webpack is great for any scale of project. It is simple to use and configure. Anyone who is developing a JavaScript application should consider using Webpack for its performance improvements and excellent set of plugins.

The complete source code of the optimized project can be found on github: https://github.com/sixthpoint/webpack-backbonejs-socketIO-client

Reference: BackboneJS with Webpack: A lesson in optimization from our WCG partner Keyhole Software at the Keyhole Software blog.

Keyhole Software

Keyhole is a midwest-based consulting firm with a tight-knit technical team. We work primarily with Java, JavaScript and .NET technologies, specializing in application development. We love the challenge that comes in consulting and blog often regarding some of the technical situations and technologies we face.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
krzepa
9 years ago

Have you used requirejs optimizer before using webpack? as a command line tool r.js or grunt plugin? Can you compare it with webpack?

Brandon Klimek
9 years ago
Reply to  krzepa

I did not use requirejs optimizer prior to using webpack. I researched which built tool would best fit my requirements. I came across this page: http://webpack.github.io/docs/comparison.html

I preferred commonJS require, as well as how simple it was to synchronously require dependencies using require.ensure.

Thanks for reading.

Back to top button