Skip to content

Instantly share code, notes, and snippets.

@SlexAxton
Created September 2, 2011 05:23
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save SlexAxton/1187974 to your computer and use it in GitHub Desktop.
Save SlexAxton/1187974 to your computer and use it in GitHub Desktop.
Compiling Sproutcore Handlebars Templates on the Server
{ "description" : "Sproutcore Handlebars Precompiler"
, "version" : "0.1.0"
, "author" : "Alex Sexton <Alex.Sexton@bazaarvoice.com>"
, "engines" : ["node >=0.4.7"]
, "main" : "./lib/main"
, "dependencies": {
"handlebars": "1.0.x"
}
}
var sys = require("sys");
var fs = require("fs");
var Handlebars = require("handlebars");
var file = require("file");
var SC = { Handlebars : {} };
SC.Handlebars.Compiler = function() {};
SC.Handlebars.Compiler.prototype = Object.create( Handlebars.Compiler.prototype );
SC.Handlebars.Compiler.prototype.compiler = SC.Handlebars.Compiler;
SC.Handlebars.JavaScriptCompiler = function() {};
SC.Handlebars.JavaScriptCompiler.prototype = Object.create(Handlebars.JavaScriptCompiler.prototype);
SC.Handlebars.JavaScriptCompiler.prototype.compiler = SC.Handlebars.JavaScriptCompiler;
/**
Override the default property lookup semantics of Handlebars.
By default, Handlebars uses object[property] to look up properties. SproutCore's Handlebars
uses SC.get().
@private
*/
SC.Handlebars.JavaScriptCompiler.prototype.nameLookup = function(parent, name, type) {
if (type === 'context') {
return "SC.get(" + parent + ", " + this.quotedString(name) + ");";
} else {
return Handlebars.JavaScriptCompiler.prototype.nameLookup.call(this, parent, name, type);
}
};
SC.Handlebars.JavaScriptCompiler.prototype.initializeBuffer = function() {
return "''";
};
/**
Override the default buffer for SproutCore Handlebars. By default, Handlebars creates
an empty String at the beginning of each invocation and appends to it. SproutCore's
Handlebars overrides this to append to a single shared buffer.
@private
*/
SC.Handlebars.JavaScriptCompiler.prototype.appendToBuffer = function(string) {
return "data.buffer.push("+string+");";
};
/**
Rewrite simple mustaches from {{foo}} to {{bind "foo"}}. This means that all simple
mustaches in SproutCore's Handlebars will also set up an observer to keep the DOM
up to date when the underlying property changes.
@private
*/
SC.Handlebars.Compiler.prototype.mustache = function(mustache) {
if (mustache.params.length || mustache.hash) {
return Handlebars.Compiler.prototype.mustache.call(this, mustache);
} else {
var id = new Handlebars.AST.IdNode(['bind']);
// Update the mustache node to include a hash value indicating whether the original node
// was escaped. This will allow us to properly escape values when the underlying value
// changes and we need to re-render the value.
if(mustache.escaped) {
mustache.hash = mustache.hash || new Handlebars.AST.HashNode([]);
mustache.hash.pairs.push(["escaped", new Handlebars.AST.StringNode("true")]);
}
mustache = new Handlebars.AST.MustacheNode([id].concat([mustache.id]), mustache.hash, !mustache.escaped);
return Handlebars.Compiler.prototype.mustache.call(this, mustache);
}
};
SC.Handlebars.precompile = function(string) {
var options = { data: true, stringParams: true };
var ast = Handlebars.parse(string);
var environment = new SC.Handlebars.Compiler().compile(ast, options);
return new SC.Handlebars.JavaScriptCompiler().compile(environment, options);
};
exports.precompile = SC.Handlebars.precompile;
var sys = require('sys');
// The build runs in node (ours uses requirejs & r.js - but you could use anything
(function () {
// DO BUILDY STUFF HERE
var SCHB = require.nodeRequire('./SCHandlebars');
// LOOP THROUGH EACH .handlebars FILE -- fileName is the templateName, and content is the actual template
for( var tmpl in templates ) {
var scName = templates[tmpl].fileName; // Template name on SC.TEMPLATES
var content = templates[tmpl].content; // Actual content
// assume that some sort of output function exists for you
sys.puts("SC.TEMPLATES['" + scName + "'] = Handlebars.template(" + SCHB.precompile( content.replace( /\s+/g, " " ) ) + ");\n");
}
// you should end up minifying anyways, but I think I run a `output.replace(/\n/g, " ")` to get rid of newlines.
})();
//
// if the filename was `myTemplate`
// and the content was `<div>{{ lol }}</div>`
//
// This would be our output
SC.TEMPLATES['myTemplate'] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) { helpers = helpers || Handlebars.helpers; var buffer = '', stack1, stack2, stack3, stack4, tmp1, self=this, functionType="function", helperMissing=helpers.helperMissing, undef=void 0, escapeExpression=this.escapeExpression; data.buffer.push("<div>"); stack1 = depth0; stack2 = "lol"; stack3 = {}; stack4 = "true"; stack3['escaped'] = stack4; stack4 = helpers.bind || SC.get(depth0, "bind"); tmp1 = {}; tmp1.hash = stack3; tmp1.contexts = []; tmp1.contexts.push(stack1); tmp1.data = data; if(typeof stack4 === functionType) { stack1 = stack4.call(depth0, stack2, tmp1); } else if(stack4=== undef) { stack1 = helperMissing.call(depth0, "bind", stack2, tmp1); } else { stack1 = stack4; } data.buffer.push(escapeExpression(stack1) + "</div>"); return buffer;});
// After this runs on the client side, your in-page views and templates can reference it as it's name, and all will be nice and linked, etc.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment