Introduction

First off, why an article on Backbone / Marionette / Ampersand? These libraries no longer capture the imagination of developers nowadays. However, a significant number of applications were developed with these libraries and many are still around! Our company has at least three projects still using this stack. During their normal development, we’ve upgraded the bundling mechanism from require.js to Webpack and have found enormous benefits, especially with hot reloading.

Many people assume that hot reloading can only be done using React. However, any project using Webpack can use hot reloading to super-charge your dev environment. I’ll show how you can enable this feature, and how you can use it in your Backbone/Marionette/Ampersand project. I’ll also explain what can be hot reloaded and what cannot.

What is Hot Reloading

Webpack is a module bundler that goes beyond building a single javascript file for your application. It is amazing in its own right, but we’re going to focus on Hot Reloading in this post. Hot Reloading in this post is the ability to accept changes from the Webpack dev server and apply them to your application without doing a browser refresh. You may already be familiar with one form of hot reloading if you’ve used ‘LiveReload’ from Grunt or BrowserSync. These processes allow you to update your CSS and see the changes in realtime on your application without having to do a browser refresh.

Webpack Hot Reloading

Webpack takes this process a giant step forward by allowing you to not only live reload your css changes, but also any asset you’d like! Update your javascript file? We can hot reload it. Your HTML template? Yep!

How?

My Javascript is dependent on all sorts of things, how can Webpack do this. It must be magic or a joke - You.

It’s not too magical: Webpack builds a dependency tree of your modules as it builds them. If something changes, it can let you know if something you’re interested in has changed. It’s true, Webpack can’t magically know how your app is put together and what state it’s in, but if you give it some help or direction, you can do great things.

Webpack gives you an API that lets you accept any changes to an asset, re-require it, and then update your application accordingly. It might be best to start with an example.

/* child.view.js */
var ChildView = AmpersandView.extend({
    template: require('path/to/child/template.hbs'),
    render: function(){
        return this.renderWithTemplate(this);
    }
});
module.exports = ChildView;
/* parent.view.js */
var ChildView = require('./child.view.js');
var ParentView = AmpersandView.extend({
    template : require('path/to/template.hbs')
    render: function(){
        this.renderWithTemplate(this);
        var child = new ChildView({
            model: this.model
        });
        this.childView = this.renderSubview(child, this.query('.container'));
        return this;
    }
});
module.exports = ParentView;

So some things to note here:

  • If you’re not familiar with Ampersand, it’s not too different from Marionette or Backbone. The renderWithTemplate is basically equivalent to this.$el.html(template()) if template is a function. It also works with string only templates.
  • We’ve got template properties that are requireing ‘.hbs’ files. We’re using handlebars templates which get compiled to functions. I’ve got a Webpack loader setup so that anytime you require a '*.hbs' file, Webpack knows to call handlebars and returns to you the output of that, which is a javascript function that takes a model and returns a template. If instead we were requireing just a simple string, we’d configure a Webpack loader (the raw-loader) to load up the file as a string.

Let’s hot reload

Let’s say that you’re working heavily on the ChildView. You’ve got your tests running but you also would like to see how new HTML would look as you change it as well as the behavior of the view. Let’s set it up for hot reloading.

/* parent.view.js */
var ChildView = require('./child.view.js');
var ParentView = AmpersandView.extend({
    template : require('path/to/template.hbs')

    initialize: function(){
        var self = this;
        if(module.hot){
            module.hot.accept('./child.view.js', function(){
                // re-require your child view, this will give you the new hot-reloaded child view
                var NewChildView = require('./child.view.js');
                // Remove the old view.  In ampersand you just call 'remove'
                self.child.remove();
                // create a new view and render it
                var childView = new NewChildView({
                    model : self.model
                });
                self.childView = self.renderSubview(childView, this.query('.container'));
            });
        }
    }

    render: function(){
        this.renderWithTemplate(this);
        var childView = new ChildView({
            model: this.model
        });
        this.child = this.renderSubview(childView, this.query('.container'));
        return this;
    }
});
module.exports = ParentView;

All the hot reloading code is done in the initialize of the view, but it could be done anywhere. The interesting thing about this is that Webpack keeps track of anything that ChildView depends on and will call our function when any of those things change. For example, if you update your .hbs file and add a new <div>, our child view will be reloaded. If ChildView depends on and uses another different view and that view changes, then child view will get reloaded.

This can be an incredible productivity booster!

####Yeah but.. It sure seems like that’s a lot of code! Is all that going to be in my code base? First let’s try to refactor a bit:

/* parent.view.js */
var ChildView = require('./child.view.js');
var ParentView = AmpersandView.extend({
    template : require('path/to/template.hbs')

    initialize: function(){
        var self = this;
        if(module.hot){
            module.hot.accept('./child.view.js', function(){
                // re-require your child view, this will give you the new hot-reloaded child view
                var NewChildView = require('./child.view.js');
                // Remove the old view.  In ampersand you just call 'remove'
                self.renderChildView(NewChildView);
            });
        }
    },

    renderChildView(View){
        if(this.child){
            this.child.remove();
        }
        // use passed in view
        var childView = new View({
            model: this.model
        });
        this.child = this.renderSubview(childView, this.query('.container'));
    }

    render: function(){
        this.renderWithTemplate(this);
        renderChildView(ChildView);
        return this;
    }
});
module.exports = ParentView;

Even so, we still have a if(module.hot) statement.

Optimization to the rescue:

At this point, as far as I know, you’re stuck with this piece of code in your project. But don’t worry, Webpack has a trick up its sleeve. Webpack can be aware of your development environment and your production environment. If you disable hot module reloading in your Webpack config and then optimize, that whole block of code just gets removed. How does it do this? Let’s look at how Webpack builds this bundle. If hot module reloading is disabled, Webpack will convert the if(module.hot) to if(false) on one pass, and then since that’s always false, Webpack then removes that whole block on optimization. Bottom line, no extra code in your production system, but all the benefits of hot reloading in development!

Tips for happy hot reloading

  • In my experience it is best to add this code to a strategic section of your app you want reloaded. Don’t go overboard and make every view hot reloaded. Even with optimization removing extra code, you’ll still see it in your files, increasing cognitive load.

  • Along those lines, if a view has completely stabilized and is not being worked on, consider removing the hot reloading code for the same reasons (cognitive load). It’s easy to add it back later should you want it.

  • You may be tempted to try to abstract out the hot reloading to a separate mixin. I’ve tried this unsuccessfully and spent a lot of time doing so. If you do succeed, please share!

  • You will have the most success if you try to keep your views as stateless as possible. Your model should be holding most of your state. If you’re storing that state in the DOM or in your view, then your hot reloading block will need to be ‘smarter’ and try to transfer that state over when it rebuilds the hot reloaded view. For example, if you want to keep a checkbox checked on hot reload, you need to either have the checkbox state be derived ultimately from the model or stored somewhere on the view. Then when hot reloading, you need to grab that state from the view and re-apply it to the new view. Needless to say, this can be cumbersome and a good reason to go for stateless views.

  • This works best for views and their subviews. But as I said in the intro, you can hot reload almost anything, including controllers. The difficult part is building the logic to hot replace it in your app without making things blow up. But since Webpack gives you a place to do your own logic, it is definitely possible!

Conclusion

Using Webpack as a bundler brings a whole slew of benefits. React users have been enjoying hot reloading for a while, but if you’ve got a Backbone/Marionette/Ampersand application, you too can enjoy these benefits with just a little extra effort. The gains for your development cycle time are enormous! Enjoy!