Make the Handlebars environment into an object

The idea is that the environment wraps up the mutable stuff in
Handlebars (like the helpers) and that you could theoretically create a
new one at any time and pass it in to Handlebars.template.

Every test makes a new environment and uses it in template compilation.
This commit is contained in:
Yehuda Katz
2013-07-26 16:50:37 +00:00
parent f5c8484ea0
commit 5f664dd78b
8 changed files with 70 additions and 61 deletions
+9 -2
View File
@@ -1,4 +1,4 @@
import { base as handlebars } from "./handlebars/base"; import { HandlebarsEnvironment } from "./handlebars/base";
// Each of these augment the Handlebars object. No need to setup here. // Each of these augment the Handlebars object. No need to setup here.
// (This is done to easily share code between commonjs and browse envs) // (This is done to easily share code between commonjs and browse envs)
@@ -8,7 +8,14 @@ import { template } from "./handlebars/runtime";
// For compatibility and usage outside of module systems, make the Handlebars object a namespace // For compatibility and usage outside of module systems, make the Handlebars object a namespace
var create = function() { var create = function() {
var hb = handlebars(); var hb = {},
env = new HandlebarsEnvironment();
// support new environments in global namespace mode
hb.HandlebarsEnvironment = HandlebarsEnvironment;
hb.registerHelper = env.registerHelper.bind(env);
hb.registerPartial = env.registerPartial.bind(env);
hb.SafeString = SafeString; hb.SafeString = SafeString;
hb.Exception = Exception; hb.Exception = Exception;
+32 -33
View File
@@ -1,6 +1,6 @@
/*jshint eqnull: true */ /*jshint eqnull: true */
import { Exception, extend } from "./utils"; import { Exception, extend, isEmpty } from "./utils";
var K = function() { return this; }; var K = function() { return this; };
@@ -14,40 +14,41 @@ export var REVISION_CHANGES = {
4: '>= 1.0.0' 4: '>= 1.0.0'
}; };
// TODO: Make this a class var toString = Object.prototype.toString,
export function base(helpers, partials) { functionType = '[object Function]',
objectType = '[object Object]';
var exports = {}; export function HandlebarsEnvironment(helpers, partials) {
this.helpers = helpers || {};
this.partials = partials || {};
var helpers = helpers || {}; registerDefaultHelpers(this);
var partials = partials || {}; }
exports.helpers = helpers; HandlebarsEnvironment.prototype = {
exports.partials = partials; constructor: HandlebarsEnvironment,
var toString = Object.prototype.toString, registerHelper: function(name, fn, inverse) {
functionType = '[object Function]',
objectType = '[object Object]';
exports.registerHelper = function(name, fn, inverse) {
if (toString.call(name) === objectType) { if (toString.call(name) === objectType) {
if (inverse || fn) { throw new Exception('Arg not supported with multiple helpers'); } if (inverse || fn) { throw new Exception('Arg not supported with multiple helpers'); }
extend(helpers, name); extend(this.helpers, name);
} else { } else {
if (inverse) { fn.not = inverse; } if (inverse) { fn.not = inverse; }
helpers[name] = fn; this.helpers[name] = fn;
} }
}; },
exports.registerPartial = function(name, str) { registerPartial: function(name, str) {
if (toString.call(name) === objectType) { if (toString.call(name) === objectType) {
extend(partials, name); extend(this.partials, name);
} else { } else {
partials[name] = str; this.partials[name] = str;
} }
}; }
};
exports.registerHelper('helperMissing', function(arg) { function registerDefaultHelpers(instance) {
instance.registerHelper('helperMissing', function(arg) {
if(arguments.length === 2) { if(arguments.length === 2) {
return undefined; return undefined;
} else { } else {
@@ -55,7 +56,7 @@ export function base(helpers, partials) {
} }
}); });
exports.registerHelper('blockHelperMissing', function(context, options) { instance.registerHelper('blockHelperMissing', function(context, options) {
var inverse = options.inverse || function() {}, fn = options.fn; var inverse = options.inverse || function() {}, fn = options.fn;
var type = toString.call(context); var type = toString.call(context);
@@ -68,7 +69,7 @@ export function base(helpers, partials) {
return inverse(this); return inverse(this);
} else if(type === "[object Array]") { } else if(type === "[object Array]") {
if(context.length > 0) { if(context.length > 0) {
return Handlebars.helpers.each(context, options); return instance.helpers.each(context, options);
} else { } else {
return inverse(this); return inverse(this);
} }
@@ -77,7 +78,7 @@ export function base(helpers, partials) {
} }
}); });
exports.registerHelper('each', function(context, options) { instance.registerHelper('each', function(context, options) {
var fn = options.fn, inverse = options.inverse; var fn = options.fn, inverse = options.inverse;
var i = 0, ret = "", data; var i = 0, ret = "", data;
@@ -112,34 +113,32 @@ export function base(helpers, partials) {
return ret; return ret;
}); });
exports.registerHelper('if', function(conditional, options) { instance.registerHelper('if', function(conditional, options) {
var type = toString.call(conditional); var type = toString.call(conditional);
if(type === functionType) { conditional = conditional.call(this); } if(type === functionType) { conditional = conditional.call(this); }
if(!conditional || Handlebars.Utils.isEmpty(conditional)) { if(!conditional || isEmpty(conditional)) {
return options.inverse(this); return options.inverse(this);
} else { } else {
return options.fn(this); return options.fn(this);
} }
}); });
exports.registerHelper('unless', function(conditional, options) { instance.registerHelper('unless', function(conditional, options) {
return Handlebars.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn}); return instance.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn});
}); });
exports.registerHelper('with', function(context, options) { instance.registerHelper('with', function(context, options) {
var type = toString.call(context); var type = toString.call(context);
if(type === functionType) { context = context.call(this); } if(type === functionType) { context = context.call(this); }
if (!Handlebars.Utils.isEmpty(context)) return options.fn(context); if (!isEmpty(context)) return options.fn(context);
}); });
exports.registerHelper('log', function(context, options) { instance.registerHelper('log', function(context, options) {
var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1; var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
Handlebars.log(level, context); Handlebars.log(level, context);
}); });
return exports;
} }
var levels = { var levels = {
+3 -3
View File
@@ -443,17 +443,17 @@ export function compile(input, options) {
var compiled; var compiled;
function compile() { function compileInput() {
var ast = parse(input); var ast = parse(input);
var environment = new Compiler().compile(ast, options); var environment = new Compiler().compile(ast, options);
var templateSpec = new JavaScriptCompiler().compile(environment, options, undefined, true); var templateSpec = new JavaScriptCompiler().compile(environment, options, undefined, true);
return template(templateSpec); return template(templateSpec, options.env || Handlebars, compile);
} }
// Template is only compiled on first use and cached after that point. // Template is only compiled on first use and cached after that point.
return function(context, options) { return function(context, options) {
if (!compiled) { if (!compiled) {
compiled = compile(); compiled = compileInput();
} }
return compiled.call(this, context, options); return compiled.call(this, context, options);
}; };
+7 -9
View File
@@ -3,17 +3,14 @@ import { COMPILER_REVISION, REVISION_CHANGES } from "./base";
// TODO: Remove this line and break up compilePartial // TODO: Remove this line and break up compilePartial
export function template(templateSpec, Hbars) { export function template(templateSpec, Hbars, compile) {
// TODO: Make this less global if (compile) {
Hbars = Hbars || Handlebars;
if (Hbars.compile) {
var invokePartialWrapper = function(partial, name, context, helpers, partials, data) { var invokePartialWrapper = function(partial, name, context, helpers, partials, data) {
var result = invokePartial.apply(this, arguments); var result = invokePartial.apply(this, arguments);
if (result) { return result; } if (result) { return result; }
var options = { helpers: helpers, partials: partials, data: data }; var options = { helpers: helpers, partials: partials, data: data };
partials[name] = Hbars.compile(partial, { data: data !== undefined }); partials[name] = compile(partial, { data: data !== undefined });
return partials[name](context, options); return partials[name](context, options);
}; };
} else { } else {
@@ -24,6 +21,10 @@ export function template(templateSpec, Hbars) {
}; };
} }
if (!Hbars) {
throw new Error("YUNO HANDLEBARS");
}
// Just add water // Just add water
var container = { var container = {
escapeExpression: escapeExpression, escapeExpression: escapeExpression,
@@ -56,9 +57,6 @@ export function template(templateSpec, Hbars) {
return function(context, options) { return function(context, options) {
options = options || {}; options = options || {};
Hbars = Hbars || require("handlebars");
// TODO: Why does templateSpec require a reference to the global Handlebars?
var result = templateSpec.call(container, Hbars, context, options.helpers, options.partials, options.data); var result = templateSpec.call(container, Hbars, context, options.helpers, options.partials, options.data);
var compilerInfo = container.compilerInfo || [], var compilerInfo = container.compilerInfo || [],
+6
View File
@@ -1,3 +1,9 @@
global.handlebarsEnv = null;
beforeEach(function() {
global.handlebarsEnv = new Handlebars.HandlebarsEnvironment();
});
describe("basic context", function() { describe("basic context", function() {
it("most basic", function() { it("most basic", function() {
shouldCompileTo("{{foo}}", { foo: "foo" }, "foo"); shouldCompileTo("{{foo}}", { foo: "foo" }, "foo");
+5 -3
View File
@@ -3,12 +3,14 @@ require('./common');
global.Handlebars = require('../../zomg/lib/handlebars'); global.Handlebars = require('../../zomg/lib/handlebars');
global.CompilerContext = { global.CompilerContext = {
compile: function(template, options) { compile: function(template, options, env) {
env = env || handlebarsEnv;
var templateSpec = Handlebars.precompile(template, options); var templateSpec = Handlebars.precompile(template, options);
console.log(templateSpec); return Handlebars.template(eval('(' + templateSpec + ')'), env);
return Handlebars.template(eval('(' + templateSpec + ')'));
}, },
compileWithPartial: function(template, options) { compileWithPartial: function(template, options) {
options = options || {};
options.env = handlebarsEnv;
return Handlebars.compile(template, options); return Handlebars.compile(template, options);
} }
}; };
+3 -3
View File
@@ -5,11 +5,11 @@ global.Handlebars = require('../../dist/handlebars.runtime');
var compiler = require('../../lib/handlebars'); var compiler = require('../../lib/handlebars');
global.CompilerContext = { global.CompilerContext = {
compile: function(template, options) { compile: function(template, options, env) {
var templateSpec = compiler.precompile(template, options); var templateSpec = compiler.precompile(template, options);
return Handlebars.template(eval('(' + templateSpec + ')')); return Handlebars.template(eval('(' + templateSpec + ')'), env);
}, },
compileWithPartial: function(template, options) { compileWithPartial: function(template, options, env) {
return compiler.compile(template, options); return compiler.compile(template, options);
} }
}; };
+5 -8
View File
@@ -176,15 +176,12 @@ describe('helpers', function() {
var helpers = Handlebars.helpers; var helpers = Handlebars.helpers;
try { try {
Handlebars.helpers = {}; Handlebars.helpers = {};
Handlebars.registerHelper('if', helpers['if']);
Handlebars.registerHelper('world', function() { return "world!"; });
Handlebars.registerHelper('test_helper', function() { return 'found it!'; });
//Handlebars.registerHelper({ Handlebars.registerHelper({
//'if': helpers['if'], 'if': helpers['if'],
//world: function() { return "world!"; }, world: function() { return "world!"; },
//test_helper: function() { return 'found it!'; } test_helper: function() { return 'found it!'; }
//}); });
shouldCompileTo( shouldCompileTo(
"{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}", "{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}",