DPH.AM

I like to draw, code, and build stuff.

Backbone Models With RequireJS in Node

I’ve been working on a very data-intensive, client-facing application at work for a few months now. The application is a WYSIWYG editor which requires outputting some markup and parsing the LESS tree to be inserted onto the page as a user makes changes. Naturally, I used Backbone to help structure the models. At the end of the day, I needed to burn out and persist the markup and LESS files to the server as well.

One of the main reasons I chose Node, and I think this is the same reason for most people, is because I wanted the same consistent code in the client as well as the server. Since I already created Backbone models describing my data, it was a no brainer to use these models on the server. Normally, this is extremely simple, but lets face it, who doesn’t write large client applications these days without a JavaScript module loader? The problem is that node already implements the CommonJS pattern but RequireJS uses the AMD API. However, since require has a node module, it wasn’t too tricky using this to require other client-side modules.

The following is a sample Backbone model wrapped in RequireJS’s define call. If this file is being loaded on the server, the define function will not exist; as you can see, resolving this is as simple as setting define to require('amdefine')(module). I specifically included references to both LESS and jQuery to illustrate how these libraries can be used appropriately inside models and how to resolve conflicts when loading it on the server. You can find comments inside the code.

public/src/models/Template.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// if 'define' is not a function, this file is being requested from the server
if(typeof define !== 'function') {
  define = require('amdefine')(module);
}

define([
  'underscore',
  'backbone',
  'less',
  'jquery',
  'some_collection'
], function(_, Backbone, less, $, SomeCollection) {

  // In the next file, you will notice requirejs's sever
  // configuration points the 'less' module to a null object
  // this allows us to know if we're on the server and
  // reset references accordingly
  if(!less) less = require('requirejs').nodeRequire('less');
  var lessParser = new (less.Parser)();

  // If the model needs to output HTML markup, it's useful to
  // include jQuery to perform node and attribute changes
  // cheerio wraps strings and implements jQuery's API
  if(!$) $ = require('cheerio');

  return Backbone.Model.extend({

    // this model may have dependencies on other models/collections
    // after we resolve the define call, these files will automatically
    // be loaded properly without having to make them CommonJS-compatible
    initialize: function() {
      this.someCollection = new SomeCollection();
    },

    markup: function() {
      var $node = $('<div/>').html(this.get('contents'));
      // do some crazy manipulations here
      return $node.html();
    },

    css: function(fn) {
      lessParser.parse(this.get('bar'), function(e, tree) {
        if(typeof fn === 'function') fn(tree.toCSS());
      });
    },

    data: function() {
      return {
        slug: this.get('id'),
        foo: this.someCollection.toJSON()
      }
    }

  });

});

Now in your server file, you just need to require the requirejs module and configure the paths. Notice less and jquery both point to a file called empty - all this file contains is define(null);. As stated above, this is to let you know that the file is being included on the server and you need to make the right reference adjustments. You’ll also likely want to turn this file into a CommonJS module. To do this, just export the object first; after you’ve successfully used requirejs on the server to load your Backbone model, attach methods/objects to the exported object that has the Backbone model as a dependency.

compile.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
var fs = require('fs');
var requirejs = require('requirejs');

requirejs.config({
  nodeRequire: require,
  bareUrl: 'public/src',
  paths: {
    'less': '../lib/empty',
    'jquery': '../lib/empty',
    'backbone': '../lib/backbone',
    'underscore': '../lib/underscore'
  }
});

function Compiler() {
  this.initialize.apply(this, arguments);
}

// Export this object first
module.exports.Compiler = Compiler;


requirejs(['models/Template'], function(Template) {

  // successfully loaded the Backbone model, now we can
  // set methods to Compiler that has model dependency
  Compiler.prototype = {

    initialize: function(someObject) {
      this.template = new Template(someObject);
    },

    compile: function() {
      var dataString = JSON.stringify(this.theme.data(), undefined, 4);
      fs.writeFile('some_json_file.json', dataString, function(err) {
        if(err) throw err;
      });

      fs.writeFile('some_template_file.dust', this.theme.markup(), function(err) {
        if(err) throw err;
      });
    }

  };

});

Comments