Search code examples
backbone.jswebpackwebpack-dev-serverwebpack-hmr

Getting marionette.backbone to work with webpack hot module replacement


I used a sample project from here to set up a webpack project with hot module replacement. Then I set up a sample backbone application.

// main.js
import $ from 'jquery';
import Backbone from 'backbone';

import Router from './router';

window.app = window.app || {};

const app = new Backbone.Marionette.Application();
app.addRegions({content: '#content'});

app.on('start', () => {
    if (Backbone.history)
      Backbone.history.start({ pushState: true })
}

);

app.addInitializer(() => {
  return new Router();
});


$( () => { app.start() });

// HMR
if (module.hot) {
    module.hot.accept();
}

I can see that HRM is loading fine based on the [HMR] connected debug output. When a file changes I can see that its rebuilding and pushing updates to the client side based on the following output:

[HMR] Updated modules:
process-update.js?e13e:77 [HMR]  - ./app/backbone/views/template.hbs
process-update.js?e13e:77 [HMR]  - ./app/backbone/views/hello.js
process-update.js?e13e:77 [HMR]  - ./app/backbone/router.js

However the screen doesn't reload. Nothing happens.

Any idea how to get this to work? Or HMR supposed to only work with React?


Solution

  • It's a bit tricker but you can get it working with backbone. A blog post is here that explains it fairly well. (disclaimer, I wrote it)

    In short, you need to explicitly tell your a parent view that you can accept a hot reload, then you re-require that new hot reloaded view, close out your existing child view, and re-render it. The below example uses Ampersand but the same basic principal applies to Marionette or vanilla Backbone

    /* 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;
        }
    });
    

    ```