Friday, December 2, 2011

Backbone.js + Require.js Odds and Ends

‹prev | My Chain | next›

I had myself a very satisfying breakthrough yesterday working the combination of Backbone.js and requirejs. And by breakthrough, I mean that I actually got it to work. Today, I would like to use that working solution to answer a few burning questions.

First off, is the question of code organization. From last night, I am just dumping everything into the scripts directory, including the my-calendar, paginator, and router Javascript files that comprise my solution:
➜  backbone-requirejs-test  tree -I node_modules 
.
├── app.js
├── index.html
├── scripts
│   ├── backbone.js
│   ├── jquery-1.7.1.js
│   ├── main.js
│   ├── my-calendar.js
│   ├── paginator.js
│   ├── require.js
│   ├── router.js
│   └── underscore.js
└── styles
The matter of mixing libraries and applications aside, I am also a bit concerned with in-code organization. In Recipes with Backbone, we suggested a namespacing solution in which models in a calendaring application would go in Calendar.Models, views would go in Calendar.Views, etc.

I rather like that approach. I have no idea how to do something similar with Backbone.js and Require.js. At least not without introducing unwarranted complexity.

Currently, the top-level my-calendar file is relatively simple:
define(['jquery', 'backbone', 'paginator', 'router'],
  function($, Backbone, Paginator, Router) {
    return {
      initialize: function(){
        var paginator = new Paginator({el: $('#paginator')}).render();

        new Router({paginator: paginator});
        Backbone.history.start();
      }
    };
  }
);
I require four libraries in order to define() my-calendar. The second argument to define(), an anonymous function, describes how I will refer to those libraries when I use them. It maps jQuery to the familiar $, the backbone library to the Backbone variable, and my two library classes to similarly appropriate Paginator and Router class names.

Because these variables are assigned as the arguments of a function, I cannot assign them as Views.Paginator:
/* this won't work */
define(['jquery', 'backbone', 'paginator', 'router'],
  function($, Backbone, Views.Paginator, Router) {
  }
);
The period is a Javascript operator and is not allowed in a variable name.

So how do I namespace my views and collections and models (and even routers) from each other? How can I achieve the organization that I so desire?

Perhaps the answer is that I do not need to. Perhaps namespacing and requirejs solve the same problem in two different (and incompatible) ways.

For instance, I re-organize the code on my filesystem thusly:
.
├── index.html
├── scripts
│   ├── backbone.js
│   ├── jquery-1.7.1.js
│   ├── main.js
│   ├── my-calendar
│   │   ├── routers
│   │   │   └── paginator.js
│   │   └── views
│   │       └── paginator.js
│   ├── my-calendar.js
│   ├── require.js
│   └── underscore.js
└── styles
Back in the definition of my-calendar, I only need to change the dependencies:
define(['jquery',
        'backbone',
        'my-calendar/views/paginator',
        'my-calendar/routers/paginator'],
  function($, Backbone, Paginator, Router) {
    return {
      initialize: function(){
        var paginator = new Paginator({el: $('#paginator')}).render();

        new Router({paginator: paginator});
        Backbone.history.start();
      }
    };
  }
);
I have gained a little clarity through the self-documenting file system organization. If there is any doubt what Paginator is, I can just look up at the dependencies.

The only way that I can see this getting out of hand is if I have a large number of dependencies. But... if I have a large number of dependencies, then I think I have other problems. Specifically, if a view depends on several other views, and several models, and a collection, then it is safe to say that I am violating single responsibility. Or the precipitation pattern (from Recipes with Backbone). Or both.

In reality, each Backbone class in my application should be small, self-contained, with a minimum of dependencies. In that case, all of my classes should be as readily defined as I have done my-calendar. And, if there really are only a few dependencies, then namespacing is overkill.

I think I can live with that.

Tomorrow, I will try to convert my largish Funky Calendar Backbone application to use this approach. If I am correct, it should be relatively painless. That is sure to be an interesting experiment.

But first, I have another question that I would like answered: what does James Burke's AMD-enabled fork of Backbone buy me? What would happen if I swapped it out for the normal version?

That is answered easily enough. I download the latest Backbone from the main Backbone.js site and fire up my previously working Backbone app to find...

That it is indeed broken:


I did not expect that to work, but thought it worth checking. Under the covers, James' patch is making a requirejs compatible define() call:
define('backbone', ['underscore', 'jquery', 'exports'], function(_, $, exports) {
      factory(root, exports, _, $);
    });
Without that, things fall apart.

That will suffice for a stopping point tonight. Up tomorrow, I will try to convert my calendar application to requirejs. And maybe fiddle with the requirejs optimizer.



Day #223

2 comments:

  1. Hi Chris,
    Have a look at this gist to see how we are laying out our directory structure for Require and Backbone.
    https://gist.github.com/1426901

    The html files in this dir structure are actually templates. We are using jQuery tempting at the moment, but I am thinking of swapping over to Underscore tempting after reading the tempting recipes in your book.

    Basically we are using subdirectories under /src as packages, and we will probably use the optimiser in Require to build per package minified js files.

    Note that we do not require in backbone or query for each 'class' definition. We just rely on them being globally available.

    As another tip, if you are not already, do yourself a favour and use Less for your css.
    Each of those package directories contains little .less bits of CSS. Makes it WAY easy to keep your css alongside your code/templates, and makes it very maintainable with the variables and mixing that Less has.

    cheers.

    ReplyDelete
  2. hmmm, in the above post 'tempting' should be 'templating'. The spell corrector munged it.

    ReplyDelete