debugging underscore.js templates is difficult without line numbers

In Backbone Eye (Firebug extension), you can debug underscore templates - just as if they were regular JavaScript files. The template id (if specified) comes up in the script window (of Firefox) and you can select it (just like a regular script file), put breakpoints and watch the template being incrementally built. More details on how to do this is at http://dhruvaray.github.io/spa-eye/#views. This should help you narrow down the source of your error easily.

[Disclaimer : I am the author of Backbone Eye]


Errata as of Apr 2012: Underscore 1.3.2 (April 9, 2012) have introduced changes to _.template(), check the changelog and the source since complications to what have been described here might have shown up.

Yes and no - the template is first translated to a string of (hard to read) Javascript code and the executed as one block of code, so if you're looking for a syntax error you must remove the offending code from the template you're trying to execute.

But if it's something else, embedding a <% return __p.join(''); %> will break execution and return the result of the template up to that point (read the source to see why, but essentially, the results of template blocks are put into an array named __p one after another).

You can also do logging while your template evaluates (i.e. put <% console.log(<..>) %> in your template to see diagnostics. For more advanced troubleshooting, you could also put a <% debugger; %> in your template code to drop into your favourite debugger. Although the code you'll see will be unfriendly to read you will have access to the evaluating templates scope.

If I were doing extensive work and needed more extensive debugging facilities, I'd probably take a copy of the underscore.js script and add some diagnostic support code to the _.template() function itself. For example:

_.template = function(str, data) {
  var c  = _.templateSettings;
  var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' +
    'with(obj||{}){__p.push(\'' +
    str.replace(/\\/g, '\\\\')
       .replace(/'/g, "\\'")
       .replace(c.interpolate, function(match, code) {
         return "'," + code.replace(/\\'/g, "'") + ",'";
       })
       .replace(c.evaluate || null, function(match, code) {
         return "');" + code.replace(/\\'/g, "'")
                            .replace(/[\r\n\t]/g, ' ') + "__p.push('";
       })
       .replace(/\r/g, '\\r')
       .replace(/\n/g, '\\n')
       .replace(/\t/g, '\\t')
       + "');}return __p.join('');";
  console.log(tmpl.replace(/;/g, '\n')); // <- dump compiled code to console before evaluating
  var func = new Function('obj', tmpl);
  return data ? func(data) : func;
};