Tuesday, December 13, 2011

Naming Backbone.js Code with Require.js

‹prev | My Chain | next›

One last bit of bookkeeping remains for my in my exploration of require.js with Backbone.js: file naming.

As I have worked with the code in my calendar application, it has become apparent that I need a better way of naming files. The entire list of scripts is currently:
javascripts
├── backbone.js
├── calendar
│   ├── collections
│   │   └── appointments.js
│   ├── helpers
│   │   ├── dayAfter.js
│   │   ├── firstOfTheMonth.js
│   │   ├── from_iso8601.js
│   │   ├── template.js
│   │   ├── to_iso8601.js
│   │   └── weekAfter.js
│   ├── models
│   │   └── appointment.js
│   ├── router.js
│   └── views
│       ├── Application.js
│       ├── AppointmentAdd.js
│       ├── AppointmentEdit.js
│       ├── Appointment.html
│       ├── Appointment.js
│       ├── CalendarMonthBody.js
│       ├── CalendarMonthDay.js
│       ├── CalendarMonthHeader.js
│       ├── CalendarMonth.js
│       ├── CalendarMonthWeek.js
│       ├── CalendarNavigation.js
│       └── TitleView.js
├── calendar.js
├── css
├── jquery.min.js
├── jquery-ui.min.js
├── main.js
├── require.js
├── text.js
└── underscore.js
At a high level, that is not too bad. I have all of my collections in the collections directory. I have all of my views in the views sub-directory. And all of my models, views, and collections are in my application directory: calendar. So what's the problem?

I have been struggling with editing these files. In particular, I wind up with multiple Emacs buffers named "Apppointment.js". The only way for me to edit the Appointment.js model is to open each buffer in turn until I find the right one (sometimes I even get confused with the Appointments.js collection). More often than not, by the time that I find the right one, I have forgotten why I wanted to open it in the first place.

I do not believe that the current structure is a recipe for long term success.

But first, I think I will rename the top-level public/javascripts to simply public/scripts. I do not recall where I read that recently, but the name javascripts is long without conveying any useful information. So just scripts should suffice.

Mercifully, and thanks in no small part to require.js, making this change is trivial. I do a simple git mv public/javascripts public/scripts and then update a single line of HTML:
<script data-main="scripts/main" src="scripts/require.js"></script>
Now that both the src and data-main attributes point to the new directory, everything else will be loaded relative to them. Admittedly, I will not be renaming my top-level directory often, but cool, nonetheless.

Next up, I would like to namespace the filenames. That is, instead of scripts/calendar/collections/Appointments.js, I think scripts/Calendar/Collections.Appointments.js will work better. I am not normally a fan of Hungarian notation, but maybe Hungarian notation in filenames is not such a bad thing.

The capitalization in the file path and name (scripts/Calendar/Collections.Appointments.js) just makes sense. The Calendar is a top-level application object, so I am always going to capitalize the class name in code:
require(['Calendar'], function(Calendar){
  var calendar = new Calendar($('#calendar'));
});
Since I am doing it in code, I might as well do so on the filesystem. It makes it more obvious that this is an object.

Naming the file Collections.Appointments.js also follows the capitalize-a-class convention. Instead of grouping my collections in a directory, I will merely group them alphabetically when listing directories. Applying that same idea to the entirety of my Backbone application leaves me with:
scripts
├── backbone.js
├── Calendar
│   ├── Collections.Appointments.js
│   ├── Helpers.dayAfter.js
│   ├── Helpers.firstOfTheMonth.js
│   ├── Helpers.from_iso8601.js
│   ├── Helpers.template.js
│   ├── Helpers.to_iso8601.js
│   ├── Helpers.weekAfter.js
│   ├── Models.Appointment.js
│   ├── Router.js
│   ├── Views.Application.js
│   ├── Views.AppointmentAdd.js
│   ├── Views.AppointmentEdit.js
│   ├── Views.Appointment.js
│   ├── Views.CalendarMonthBody.js
│   ├── Views.CalendarMonthDay.js
│   ├── Views.CalendarMonthHeader.js
│   ├── Views.CalendarMonth.js
│   ├── Views.CalendarMonthWeek.js
│   ├── Views.CalendarNavigation.js
│   └── Views.TitleView.js
├── Calendar.js
├── css
├── jquery.min.js
├── jquery-ui.min.js
├── main.js
├── require.js
└── underscore.js
Again, I do not believe that I have lost anything, but I have definitely made it easier to immediately know which buffer I am editing.

Unfortunately, require.js is no help here. For each of these changes, I have to update the requiring classes accordingly:
define(function(require) {
  var $ = require('jquery')
    , _ = require('underscore')
    , Backbone = require('backbone')
    , Router = require('Calendar/Router')
    , Appointments = require('Calendar/Collections.Appointments')
    , Application = require('Calendar/Views.Application')
    , to_iso8601 = require('Calendar/Helpers.to_iso8601');
...
Then again, require.js is not getting in my way either. If I made similar changes to a server-side scripted language, I would no doubt have to make the same number of changes.

With that, I think I am in much better shape for long-term maintainability of my Backbone application.

Day #234

3 comments:

  1. Reading this series of posts and poking through your Git repo have been invaluable to me in getting RequireJS integrated into my Backbone app. Thank you for so thoroughly documenting your effort. You've spared me a lot of frustration.

    ReplyDelete
  2. @schwartzie Glad it is proving helpful :)

    I dunno what it is with require.js... It's an immensely powerful thing and simple enough in concept and execution. But, for some reason, it really took me a while to wrap my brain around it. Documenting it like this really helped me figure it out -- great to hear it helped you as well!

    ReplyDelete
  3. I'm not sure if this will help out. But I have aliased my module in require.config paths.
    example:
    require.config({
    "paths": {
    "jquery": "libs/jquery-1.7.1"
    ,"underscore": "libs/underscore"
    ,"myModule": "someModule"
    }
    });

    Modules with defined names (jquery, underscore) can not aliased, but your custom modules can be. Now you only need to reference the module path once.

    Additional Resources

    RequireJS – Configuration Options
    http://requirejs.org/docs/api.html#config

    Optionally call AMD define() to register module https://github.com/documentcloud/underscore/pull/338#issuecomment-3253751

    AMD modules with named defines. So much pain for what gain? http://dvdotsenko.blogspot.com/2011/12/amd-modules-with-named-defines-so-much.html

    In this post James Burk recommends not using name module. https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon

    ReplyDelete