From 2e7a3bd70223c0e532a24290c7d817b951354541 Mon Sep 17 00:00:00 2001 From: Joel Wietelmann Date: Sat, 10 May 2014 14:07:15 -0500 Subject: [PATCH 01/91] Turning CLI precompiler into a wrapper around a module. --- bin/handlebars | 316 ++++++++++------------------------ lib/handlebars/precompiler.js | 140 +++++++++++++++ 2 files changed, 228 insertions(+), 228 deletions(-) create mode 100644 lib/handlebars/precompiler.js diff --git a/bin/handlebars b/bin/handlebars index 8794faa60..86b885370 100755 --- a/bin/handlebars +++ b/bin/handlebars @@ -1,235 +1,95 @@ #!/usr/bin/env node var optimist = require('optimist') - .usage('Precompile handlebar templates.\nUsage: $0 template...', { - 'f': { - 'type': 'string', - 'description': 'Output File', - 'alias': 'output' - }, - 'a': { - 'type': 'boolean', - 'description': 'Exports amd style (require.js)', - 'alias': 'amd' - }, - 'c': { - 'type': 'string', - 'description': 'Exports CommonJS style, path to Handlebars module', - 'alias': 'commonjs', - 'default': null - }, - 'h': { - 'type': 'string', - 'description': 'Path to handlebar.js (only valid for amd-style)', - 'alias': 'handlebarPath', - 'default': '' - }, - 'k': { - 'type': 'string', - 'description': 'Known helpers', - 'alias': 'known' - }, - 'o': { - 'type': 'boolean', - 'description': 'Known helpers only', - 'alias': 'knownOnly' - }, - 'm': { - 'type': 'boolean', - 'description': 'Minimize output', - 'alias': 'min' - }, - 'n': { - 'type': 'string', - 'description': 'Template namespace', - 'alias': 'namespace', - 'default': 'Handlebars.templates' - }, - 's': { - 'type': 'boolean', - 'description': 'Output template function only.', - 'alias': 'simple' - }, - 'r': { - 'type': 'string', - 'description': 'Template root. Base value that will be stripped from template names.', - 'alias': 'root' - }, - 'p' : { - 'type': 'boolean', - 'description': 'Compiling a partial template', - 'alias': 'partial' - }, - 'd' : { - 'type': 'boolean', - 'description': 'Include data when compiling', - 'alias': 'data' - }, - 'e': { - 'type': 'string', - 'description': 'Template extension.', - 'alias': 'extension', - 'default': 'handlebars' - }, - 'b': { - 'type': 'boolean', - 'description': 'Removes the BOM (Byte Order Mark) from the beginning of the templates.', - 'alias': 'bom' - }, - 'v': { - 'type': 'boolean', - 'description': 'Prints the current compiler version', - 'alias': 'version' - } - }) - - .check(function(argv) { - if (argv.version) { - return; - } - - var template = [0]; - if (!argv._.length) { - throw 'Must define at least one template or directory.'; - } - - argv._.forEach(function(template) { - try { - fs.statSync(template); - } catch (err) { - throw 'Unable to open template file "' + template + '"'; - } - }); - }) - .check(function(argv) { - if (argv.simple && argv.min) { - throw 'Unable to minimze simple output'; - } - if (argv.simple && (argv._.length !== 1 || fs.statSync(argv._[0]).isDirectory())) { - throw 'Unable to output multiple templates in simple mode'; - } - }); - -var fs = require('fs'), - handlebars = require('../lib'), - basename = require('path').basename, - uglify = require('uglify-js'); - -var argv = optimist.argv, - template = argv._[0]; - -if (argv.version) { - return console.log(handlebars.VERSION); -} - -// Convert the known list into a hash -var known = {}; -if (argv.known && !Array.isArray(argv.known)) { - argv.known = [argv.known]; -} -if (argv.known) { - for (var i = 0, len = argv.known.length; i < len; i++) { - known[argv.known[i]] = true; - } -} - -// Build file extension pattern -var extension = argv.extension.replace(/[\\^$*+?.():=!|{}\-\[\]]/g, function(arg) { return '\\' + arg; }); -extension = new RegExp('\\.' + extension + '$'); - -var output = []; -if (!argv.simple) { - if (argv.amd) { - output.push('define([\'' + argv.handlebarPath + 'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];'); - } else if (argv.commonjs) { - output.push('var Handlebars = require("' + argv.commonjs + '");'); - } else { - output.push('(function() {\n'); - } - output.push(' var template = Handlebars.template, templates = '); - output.push(argv.namespace); - output.push(' = '); - output.push(argv.namespace); - output.push(' || {};\n'); -} -function processTemplate(template, root, explicit) { - var path = template, - stat = fs.statSync(path); - if (stat.isDirectory()) { - fs.readdirSync(template).map(function(file) { - var path = template + '/' + file; - - if (extension.test(path) || fs.statSync(path).isDirectory()) { - processTemplate(path, root || template); - } - }); - } else if (explicit || extension.test(path)) { - var data = fs.readFileSync(path, 'utf8'); - - if (argv.bom && data.indexOf('\uFEFF') === 0) { - data = data.substring(1); - } - - var options = { - knownHelpers: known, - knownHelpersOnly: argv.o - }; - - if (argv.data) { - options.data = true; - } - - // Clean the template name - if (!root) { - template = basename(template); - } else if (template.indexOf(root) === 0) { - template = template.substring(root.length+1); + .usage('Precompile handlebar templates.\nUsage: $0 template...', { + 'f': { + 'type': 'string', + 'description': 'Output File', + 'alias': 'output' + }, + 'a': { + 'type': 'boolean', + 'description': 'Exports amd style (require.js)', + 'alias': 'amd' + }, + 'c': { + 'type': 'string', + 'description': 'Exports CommonJS style, path to Handlebars module', + 'alias': 'commonjs', + 'default': null + }, + 'h': { + 'type': 'string', + 'description': 'Path to handlebar.js (only valid for amd-style)', + 'alias': 'handlebarPath', + 'default': '' + }, + 'k': { + 'type': 'string', + 'description': 'Known helpers', + 'alias': 'known' + }, + 'o': { + 'type': 'boolean', + 'description': 'Known helpers only', + 'alias': 'knownOnly' + }, + 'm': { + 'type': 'boolean', + 'description': 'Minimize output', + 'alias': 'min' + }, + 'n': { + 'type': 'string', + 'description': 'Template namespace', + 'alias': 'namespace', + 'default': 'Handlebars.templates' + }, + 's': { + 'type': 'boolean', + 'description': 'Output template function only.', + 'alias': 'simple' + }, + 'r': { + 'type': 'string', + 'description': 'Template root. Base value that will be stripped from template names.', + 'alias': 'root' + }, + 'p' : { + 'type': 'boolean', + 'description': 'Compiling a partial template', + 'alias': 'partial' + }, + 'd' : { + 'type': 'boolean', + 'description': 'Include data when compiling', + 'alias': 'data' + }, + 'e': { + 'type': 'string', + 'description': 'Template extension.', + 'alias': 'extension', + 'default': 'handlebars' + }, + 'b': { + 'type': 'boolean', + 'description': 'Removes the BOM (Byte Order Mark) from the beginning of the templates.', + 'alias': 'bom' + }, + 'v': { + 'type': 'boolean', + 'description': 'Prints the current compiler version', + 'alias': 'version' } - template = template.replace(extension, ''); + }) - if (argv.simple) { - output.push(handlebars.precompile(data, options) + '\n'); - } else if (argv.partial) { - if(argv.amd && (argv._.length == 1 && !fs.statSync(argv._[0]).isDirectory())) { - output.push('return '); - } - output.push('Handlebars.partials[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n'); - } else { - if(argv.amd && (argv._.length == 1 && !fs.statSync(argv._[0]).isDirectory())) { - output.push('return '); - } - output.push('templates[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n'); + .check(function(argv) { + if (argv.version) { + return; } - } -} - -argv._.forEach(function(template) { - processTemplate(template, argv.root, true); -}); - -// Output the content -if (!argv.simple) { - if (argv.amd) { - if(argv._.length > 1 || (argv._.length == 1 && fs.statSync(argv._[0]).isDirectory())) { - if(argv.partial){ - output.push('return Handlebars.partials;\n'); - } else { - output.push('return templates;\n'); - } - } - output.push('});'); - } else if (!argv.commonjs) { - output.push('})();'); - } -} -output = output.join(''); - -if (argv.min) { - output = uglify.minify(output, {fromString: true}).code; -} + }); -if (argv.output) { - fs.writeFileSync(argv.output, output, 'utf8'); -} else { - console.log(output); -} +var argv = optimist.argv; +argv.templates = argv._; +delete argv._; +return require('../lib/handlebars/precompiler')(argv); \ No newline at end of file diff --git a/lib/handlebars/precompiler.js b/lib/handlebars/precompiler.js new file mode 100644 index 000000000..5dcd32ac6 --- /dev/null +++ b/lib/handlebars/precompiler.js @@ -0,0 +1,140 @@ + +var fs = require('fs'), + handlebars = require('../../lib'), + basename = require('path').basename, + uglify = require('uglify-js'); + +module.exports = function(opts) { + + var template = [0]; + if (!opts.templates.length) { + throw 'Must define at least one template or directory.'; + } + + opts.templates.forEach(function(template) { + try { + fs.statSync(template); + } catch (err) { + throw 'Unable to open template file "' + template + '"'; + } + }); + + if (opts.simple && opts.min) { + throw 'Unable to minimze simple output'; + } + if (opts.simple && (opts.templates.length !== 1 || fs.statSync(opts.templates[0]).isDirectory())) { + throw 'Unable to output multiple templates in simple mode'; + } + + // Convert the known list into a hash + var known = {}; + if (opts.known && !Array.isArray(opts.known)) { + opts.known = [opts.known]; + } + if (opts.known) { + for (var i = 0, len = opts.known.length; i < len; i++) { + known[opts.known[i]] = true; + } + } + + // Build file extension pattern + var extension = opts.extension.replace(/[\\^$*+?.():=!|{}\-\[\]]/g, function(arg) { return '\\' + arg; }); + extension = new RegExp('\\.' + extension + '$'); + + var output = []; + if (!opts.simple) { + if (opts.amd) { + output.push('define([\'' + opts.handlebarPath + 'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];'); + } else if (opts.commonjs) { + output.push('var Handlebars = require("' + opts.commonjs + '");'); + } else { + output.push('(function() {\n'); + } + output.push(' var template = Handlebars.template, templates = '); + output.push(opts.namespace); + output.push(' = '); + output.push(opts.namespace); + output.push(' || {};\n'); + } + function processTemplate(template, root, explicit) { + var path = template, + stat = fs.statSync(path); + if (stat.isDirectory()) { + fs.readdirSync(template).map(function(file) { + var path = template + '/' + file; + + if (extension.test(path) || fs.statSync(path).isDirectory()) { + processTemplate(path, root || template); + } + }); + } else if (explicit || extension.test(path)) { + var data = fs.readFileSync(path, 'utf8'); + + if (opts.bom && data.indexOf('\uFEFF') === 0) { + data = data.substring(1); + } + + var options = { + knownHelpers: known, + knownHelpersOnly: opts.o + }; + + if (opts.data) { + options.data = true; + } + + // Clean the template name + if (!root) { + template = basename(template); + } else if (template.indexOf(root) === 0) { + template = template.substring(root.length+1); + } + template = template.replace(extension, ''); + + if (opts.simple) { + output.push(handlebars.precompile(data, options) + '\n'); + } else if (opts.partial) { + if(opts.amd && (opts.templates.length == 1 && !fs.statSync(opts.templates[0]).isDirectory())) { + output.push('return '); + } + output.push('Handlebars.partials[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n'); + } else { + if(opts.amd && (opts.templates.length == 1 && !fs.statSync(opts.templates[0]).isDirectory())) { + output.push('return '); + } + output.push('templates[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n'); + } + } + } + + opts.templates.forEach(function(template) { + processTemplate(template, opts.root, true); + }); + + // Output the content + if (!opts.simple) { + if (opts.amd) { + if(opts.templates.length > 1 || (opts.templates.length == 1 && fs.statSync(opts.templates[0]).isDirectory())) { + if(opts.partial){ + output.push('return Handlebars.partials;\n'); + } else { + output.push('return templates;\n'); + } + } + output.push('});'); + } else if (!opts.commonjs) { + output.push('})();'); + } + } + output = output.join(''); + + if (opts.min) { + output = uglify.minify(output, {fromString: true}).code; + } + + if (opts.output) { + fs.writeFileSync(opts.output, output, 'utf8'); + } else { + console.log(output); + } +}; \ No newline at end of file From e143849f8ae745101783eb7801a012914e510213 Mon Sep 17 00:00:00 2001 From: Joel Wietelmann Date: Sat, 10 May 2014 14:10:05 -0500 Subject: [PATCH 02/91] Knocking the indent back to what it was to make for a less scary-looking pull request --- bin/handlebars | 172 ++++++++++++++++++++++++------------------------- 1 file changed, 86 insertions(+), 86 deletions(-) diff --git a/bin/handlebars b/bin/handlebars index 86b885370..b400aaa56 100755 --- a/bin/handlebars +++ b/bin/handlebars @@ -1,93 +1,93 @@ #!/usr/bin/env node var optimist = require('optimist') - .usage('Precompile handlebar templates.\nUsage: $0 template...', { - 'f': { - 'type': 'string', - 'description': 'Output File', - 'alias': 'output' - }, - 'a': { - 'type': 'boolean', - 'description': 'Exports amd style (require.js)', - 'alias': 'amd' - }, - 'c': { - 'type': 'string', - 'description': 'Exports CommonJS style, path to Handlebars module', - 'alias': 'commonjs', - 'default': null - }, - 'h': { - 'type': 'string', - 'description': 'Path to handlebar.js (only valid for amd-style)', - 'alias': 'handlebarPath', - 'default': '' - }, - 'k': { - 'type': 'string', - 'description': 'Known helpers', - 'alias': 'known' - }, - 'o': { - 'type': 'boolean', - 'description': 'Known helpers only', - 'alias': 'knownOnly' - }, - 'm': { - 'type': 'boolean', - 'description': 'Minimize output', - 'alias': 'min' - }, - 'n': { - 'type': 'string', - 'description': 'Template namespace', - 'alias': 'namespace', - 'default': 'Handlebars.templates' - }, - 's': { - 'type': 'boolean', - 'description': 'Output template function only.', - 'alias': 'simple' - }, - 'r': { - 'type': 'string', - 'description': 'Template root. Base value that will be stripped from template names.', - 'alias': 'root' - }, - 'p' : { - 'type': 'boolean', - 'description': 'Compiling a partial template', - 'alias': 'partial' - }, - 'd' : { - 'type': 'boolean', - 'description': 'Include data when compiling', - 'alias': 'data' - }, - 'e': { - 'type': 'string', - 'description': 'Template extension.', - 'alias': 'extension', - 'default': 'handlebars' - }, - 'b': { - 'type': 'boolean', - 'description': 'Removes the BOM (Byte Order Mark) from the beginning of the templates.', - 'alias': 'bom' - }, - 'v': { - 'type': 'boolean', - 'description': 'Prints the current compiler version', - 'alias': 'version' - } - }) + .usage('Precompile handlebar templates.\nUsage: $0 template...', { + 'f': { + 'type': 'string', + 'description': 'Output File', + 'alias': 'output' + }, + 'a': { + 'type': 'boolean', + 'description': 'Exports amd style (require.js)', + 'alias': 'amd' + }, + 'c': { + 'type': 'string', + 'description': 'Exports CommonJS style, path to Handlebars module', + 'alias': 'commonjs', + 'default': null + }, + 'h': { + 'type': 'string', + 'description': 'Path to handlebar.js (only valid for amd-style)', + 'alias': 'handlebarPath', + 'default': '' + }, + 'k': { + 'type': 'string', + 'description': 'Known helpers', + 'alias': 'known' + }, + 'o': { + 'type': 'boolean', + 'description': 'Known helpers only', + 'alias': 'knownOnly' + }, + 'm': { + 'type': 'boolean', + 'description': 'Minimize output', + 'alias': 'min' + }, + 'n': { + 'type': 'string', + 'description': 'Template namespace', + 'alias': 'namespace', + 'default': 'Handlebars.templates' + }, + 's': { + 'type': 'boolean', + 'description': 'Output template function only.', + 'alias': 'simple' + }, + 'r': { + 'type': 'string', + 'description': 'Template root. Base value that will be stripped from template names.', + 'alias': 'root' + }, + 'p' : { + 'type': 'boolean', + 'description': 'Compiling a partial template', + 'alias': 'partial' + }, + 'd' : { + 'type': 'boolean', + 'description': 'Include data when compiling', + 'alias': 'data' + }, + 'e': { + 'type': 'string', + 'description': 'Template extension.', + 'alias': 'extension', + 'default': 'handlebars' + }, + 'b': { + 'type': 'boolean', + 'description': 'Removes the BOM (Byte Order Mark) from the beginning of the templates.', + 'alias': 'bom' + }, + 'v': { + 'type': 'boolean', + 'description': 'Prints the current compiler version', + 'alias': 'version' + } + }) - .check(function(argv) { - if (argv.version) { - return; - } - }); + .check(function(argv) { + if (argv.version) { + return; + } + }); var argv = optimist.argv; argv.templates = argv._; From a14c689fa6adc51faf3dbb33357ef2a5f59a0a53 Mon Sep 17 00:00:00 2001 From: Joel Wietelmann Date: Sat, 10 May 2014 14:11:07 -0500 Subject: [PATCH 03/91] Stupid line ending --- lib/handlebars/precompiler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/handlebars/precompiler.js b/lib/handlebars/precompiler.js index 5dcd32ac6..19a5e4cd5 100644 --- a/lib/handlebars/precompiler.js +++ b/lib/handlebars/precompiler.js @@ -137,4 +137,4 @@ module.exports = function(opts) { } else { console.log(output); } -}; \ No newline at end of file +}; From 3cdf14d2949665a282ea562138b97c8d9f829988 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Tue, 27 May 2014 09:32:29 -0400 Subject: [PATCH 04/91] Add test case for Issue #800 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This isn’t failing in master but this is a useful test to prevent regressions. --- spec/subexpressions.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/spec/subexpressions.js b/spec/subexpressions.js index 5d11473bc..0ecdbb9d2 100644 --- a/spec/subexpressions.js +++ b/spec/subexpressions.js @@ -59,6 +59,24 @@ describe('subexpressions', function() { shouldCompileTo(string, [context, helpers], "val is true"); }); + it('GH-800 : Complex subexpressions', function() { + var context = {a: 'a', b:'b', c:{c:'c'}, d:'d', e: {e: 'e'}}; + var helpers = { + dash: function(a, b) { + return a + "-" + b; + }, + concat: function(a, b) { + return a + b; + } + }; + + shouldCompileTo('{{dash "abc" (concat a b)}}', [context, helpers], 'abc-ab'); + shouldCompileTo('{{dash d (concat a b)}}', [context, helpers], 'd-ab'); + shouldCompileTo('{{dash c.c (concat a b)}}', [context, helpers], 'c-ab'); + shouldCompileTo('{{dash (concat a b) c.c}}', [context, helpers], 'ab-c'); + shouldCompileTo('{{dash (concat a e.e) c.c}}', [context, helpers], 'ae-c'); + }); + it("provides each nested helper invocation its own options hash", function() { var string = '{{equal (equal true true) true}}'; From 7172d167ddf9cbf1e8a0aae48970da42107e9e59 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Tue, 27 May 2014 10:54:22 -0400 Subject: [PATCH 05/91] Provide clear throw on {{#each}} Fixes #773 --- lib/handlebars/base.js | 4 +--- spec/builtins.js | 9 +++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index 3cb432352..9b910c7f1 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -97,10 +97,8 @@ function registerDefaultHelpers(instance) { }); instance.registerHelper('each', function(context, options) { - // Allow for {{#each}} if (!options) { - options = context; - context = this; + throw new Exception('Must pass iterator to #each'); } var fn = options.fn, inverse = options.inverse; diff --git a/spec/builtins.js b/spec/builtins.js index bbe494e08..a28f40003 100644 --- a/spec/builtins.js +++ b/spec/builtins.js @@ -1,4 +1,4 @@ -/*global CompilerContext, shouldCompileTo, compileWithPartials, handlebarsEnv */ +/*global CompilerContext, shouldCompileTo, shouldThrow, compileWithPartials, handlebarsEnv */ describe('builtin helpers', function() { describe('#if', function() { it("if", function() { @@ -182,9 +182,10 @@ describe('builtin helpers', function() { }); it("each on implicit context", function() { - var string = "{{#each}}{{text}}! {{/each}}cruel world!"; - var hash = [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}]; - shouldCompileTo(string, [hash], "goodbye! Goodbye! GOODBYE! cruel world!"); + shouldThrow(function() { + var template = CompilerContext.compile("{{#each}}{{text}}! {{/each}}cruel world!"); + template({}); + }, handlebarsEnv.Exception, 'Must pass iterator to #each'); }); }); From b79e31e5a52b73b445ee27c5272a0ad213f26ae8 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 28 Jun 2014 18:44:11 -0700 Subject: [PATCH 06/91] Remove disassemble log statement Fixes #772 --- lib/handlebars/compiler/javascript-compiler.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index 8bb5da699..4b9db9293 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -1,4 +1,4 @@ -import { COMPILER_REVISION, REVISION_CHANGES, log } from "../base"; +import { COMPILER_REVISION, REVISION_CHANGES } from "../base"; import Exception from "../exception"; function Literal(value) { @@ -62,8 +62,6 @@ JavaScriptCompiler.prototype = { this.trackIds = this.options.trackIds; this.precompile = !asObject; - log('debug', this.environment.disassemble() + "\n\n"); - this.name = this.environment.name; this.isChild = !!context; this.context = context || { From 7890c7dc891b264bb28b477ffd3b2e6053f4b1d4 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 5 Jul 2014 12:21:39 -0500 Subject: [PATCH 07/91] Add basic FAQ --- FAQ.md | 22 ++++++++++++++++++++++ README.markdown | 4 ++-- 2 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 FAQ.md diff --git a/FAQ.md b/FAQ.md new file mode 100644 index 000000000..35cbee95f --- /dev/null +++ b/FAQ.md @@ -0,0 +1,22 @@ +# Frequently Asked Questions + +1. How can I file a bug report: + Please don't hesitate to let us know if you find something wrong! In general we are going to ask for an example of the problem failing, which can be as simple as a jsfiddle/jsbin/etc. We've put together a jsfiddle [template](http://jsfiddle.net/9D88g/11/) to ease this. (We will keep this link up to date as new releases occur, so feel free to check back here) + +1. Why is it slower when compiling? + The Handlebars compiler must parse the template and construct a JavaScript program which can then be run. Under some environments such as older mobile devices this can have a performance impact which can be avoided by precompiling. Generally it's recommended that precompilation and the runtime library be used on all clients. + +1. Why doesn't this work with Content Security Policy restrictions? + Handlebars generates a dynamic function for each template which can cause issues with pages that have enabled Content Policy. It's recommended that templates are precompiled or the `unsafe-eval` policy is enabled for sites that must generate dynamic templates at runtime. + +1. How can I include script tags in my template? + If loading the template via an inlined ` +``` + + It's generally recommended that templates are served through external, precompiled, files, which do not suffer from this issue. diff --git a/README.markdown b/README.markdown index a675c0062..87b17aa7d 100644 --- a/README.markdown +++ b/README.markdown @@ -455,7 +455,7 @@ gem build handlebars-source.gemspec gem push handlebars-source-*.gem ``` -After this point the handlebars site needs to be updated to point to the new version numbers. The jsfiddle bitly link should be updated to point to the most recent distribution. +After this point the handlebars site needs to be updated to point to the new version numbers. The jsfiddle link should be updated to point to the most recent distribution for all instances in our documentation. License ------- @@ -466,4 +466,4 @@ Handlebars.js is released under the MIT license. [generator-release]: https://github.com/walmartlabs/generator-release [pull-request]: https://github.com/wycats/handlebars.js/pull/new/master [issue]: https://github.com/wycats/handlebars.js/issues/new -[jsfiddle]: http://l.kde.cc/hbs-bug +[jsfiddle]: http://jsfiddle.net/9D88g/11/ From efb17b80a634e5fdfb06edaee2c9d94da6d0e0b7 Mon Sep 17 00:00:00 2001 From: Kevin Decker Date: Sat, 5 Jul 2014 12:22:28 -0500 Subject: [PATCH 08/91] Update FAQ styling --- FAQ.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FAQ.md b/FAQ.md index 35cbee95f..ac520426e 100644 --- a/FAQ.md +++ b/FAQ.md @@ -12,11 +12,11 @@ 1. How can I include script tags in my template? If loading the template via an inlined ` -``` + ``` It's generally recommended that templates are served through external, precompiled, files, which do not suffer from this issue. From 059532295d022c775f6c3e6636a9d6ffce8da728 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 5 Jul 2014 14:17:11 -0500 Subject: [PATCH 09/91] Move precompiler to safer location Attempts to avoid some of the Node vs. ES6 confusion between the different styles within the application. Also adds some simple tests in addition to the integration test. --- bin/handlebars | 2 +- lib/{handlebars => }/precompiler.js | 22 +++++++++------- spec/precompiler.js | 40 +++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 10 deletions(-) rename lib/{handlebars => }/precompiler.js (84%) create mode 100644 spec/precompiler.js diff --git a/bin/handlebars b/bin/handlebars index b400aaa56..4ea329661 100755 --- a/bin/handlebars +++ b/bin/handlebars @@ -92,4 +92,4 @@ var optimist = require('optimist') var argv = optimist.argv; argv.templates = argv._; delete argv._; -return require('../lib/handlebars/precompiler')(argv); \ No newline at end of file +return require('../lib/precompiler').cli(argv); diff --git a/lib/handlebars/precompiler.js b/lib/precompiler.js similarity index 84% rename from lib/handlebars/precompiler.js rename to lib/precompiler.js index 19a5e4cd5..bae923c60 100644 --- a/lib/handlebars/precompiler.js +++ b/lib/precompiler.js @@ -1,29 +1,33 @@ var fs = require('fs'), - handlebars = require('../../lib'), + Handlebars = require('./index'), basename = require('path').basename, uglify = require('uglify-js'); -module.exports = function(opts) { +module.exports.cli = function(opts) { + if (opts.version) { + console.log(Handlebars.VERSION); + return; + } var template = [0]; if (!opts.templates.length) { - throw 'Must define at least one template or directory.'; + throw new Handlebars.Exception('Must define at least one template or directory.'); } opts.templates.forEach(function(template) { try { fs.statSync(template); } catch (err) { - throw 'Unable to open template file "' + template + '"'; + throw new Handlebars.Exception('Unable to open template file "' + template + '"'); } }); if (opts.simple && opts.min) { - throw 'Unable to minimze simple output'; + throw new Handlebars.Exception('Unable to minimze simple output'); } if (opts.simple && (opts.templates.length !== 1 || fs.statSync(opts.templates[0]).isDirectory())) { - throw 'Unable to output multiple templates in simple mode'; + throw new Handlebars.Exception('Unable to output multiple templates in simple mode'); } // Convert the known list into a hash @@ -92,17 +96,17 @@ module.exports = function(opts) { template = template.replace(extension, ''); if (opts.simple) { - output.push(handlebars.precompile(data, options) + '\n'); + output.push(Handlebars.precompile(data, options) + '\n'); } else if (opts.partial) { if(opts.amd && (opts.templates.length == 1 && !fs.statSync(opts.templates[0]).isDirectory())) { output.push('return '); } - output.push('Handlebars.partials[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n'); + output.push('Handlebars.partials[\'' + template + '\'] = template(' + Handlebars.precompile(data, options) + ');\n'); } else { if(opts.amd && (opts.templates.length == 1 && !fs.statSync(opts.templates[0]).isDirectory())) { output.push('return '); } - output.push('templates[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n'); + output.push('templates[\'' + template + '\'] = template(' + Handlebars.precompile(data, options) + ');\n'); } } } diff --git a/spec/precompiler.js b/spec/precompiler.js new file mode 100644 index 000000000..10e2a2ee0 --- /dev/null +++ b/spec/precompiler.js @@ -0,0 +1,40 @@ +/*global shouldThrow */ + +// NOP Under non-node environments +if (typeof process === 'undefined') { + return; +} + +var Handlebars = require('../lib'), + Precompiler = require('../lib/precompiler'); + +describe('precompiler', function() { + var log, + logFunction; + + beforeEach(function() { + logFunction = console.log; + log = ''; + console.log = function() { + log += Array.prototype.join.call(arguments, ''); + }; + }); + afterEach(function() { + console.log = logFunction; + }); + + it('should output version', function() { + Precompiler.cli({templates: [], version: true}); + equals(log, Handlebars.VERSION); + }); + it('should throw if lacking templates', function() { + shouldThrow(function() { + Precompiler.cli({templates: []}); + }, Handlebars.Exception, 'Must define at least one template or directory.'); + }); + it('should throw on missing template', function() { + shouldThrow(function() { + Precompiler.cli({templates: ['foo']}); + }, Handlebars.Exception, 'Unable to open template file "foo"'); + }); +}); From 058e7495f9ae22738919292ae0486531cd5b8b3e Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 5 Jul 2014 14:45:13 -0500 Subject: [PATCH 10/91] Add precompiler version FAQ entry. Resolves #789 --- FAQ.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/FAQ.md b/FAQ.md index ac520426e..bd4f6aa38 100644 --- a/FAQ.md +++ b/FAQ.md @@ -12,7 +12,7 @@ 1. How can I include script tags in my template? If loading the template via an inlined ` + + + + + + + + + + + +
+ + diff --git a/spec/umd-runtime.html b/spec/umd-runtime.html new file mode 100644 index 000000000..90b91448a --- /dev/null +++ b/spec/umd-runtime.html @@ -0,0 +1,89 @@ + + + Mocha + + + + + + + + + + + + + + + + + + +
+ + diff --git a/spec/umd.html b/spec/umd.html new file mode 100644 index 000000000..5f3863fc8 --- /dev/null +++ b/spec/umd.html @@ -0,0 +1,109 @@ + + + Mocha + + + + + + + + + + + + + + + + + + +
+ + From dfca676d63d8e99985bf787e5f8998def19fd0e3 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 25 Aug 2014 22:39:01 -0500 Subject: [PATCH 85/91] Fix test path --- Gruntfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index 09e478768..b73974059 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -149,7 +149,7 @@ module.exports = function(grunt) { sanity: { options: { build: process.env.TRAVIS_JOB_ID, - urls: ['http://localhost:9999/spec/umd.html?headless=true', 'http://localhost:9999/spec/amd-runtime?headless=true', 'http://localhost:9999/spec/umd-runtime.html?headless=true'], + urls: ['http://localhost:9999/spec/umd.html?headless=true', 'http://localhost:9999/spec/amd-runtime.html?headless=true', 'http://localhost:9999/spec/umd-runtime.html?headless=true'], detailedError: true, concurrency: 2, browsers: [ From ca1486b960af514fc2816351c6a4599a8c1ae79e Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 25 Aug 2014 22:39:17 -0500 Subject: [PATCH 86/91] Prune unused code --- lib/handlebars/base.js | 2 +- lib/handlebars/compiler/ast.js | 2 -- lib/handlebars/compiler/compiler.js | 2 +- lib/handlebars/compiler/javascript-compiler.js | 2 +- lib/handlebars/runtime.js | 4 +--- 5 files changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index 2dfca0235..0bc2948e4 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -194,7 +194,7 @@ function registerDefaultHelpers(instance) { instance.log(level, context); }); - instance.registerHelper('lookup', function(obj, field, options) { + instance.registerHelper('lookup', function(obj, field) { return obj && obj[field]; }); } diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js index 66533a5bf..49bdc3337 100644 --- a/lib/handlebars/compiler/ast.js +++ b/lib/handlebars/compiler/ast.js @@ -37,8 +37,6 @@ var AST = { this.sexpr = new AST.SexprNode(rawParams, hash); } - this.sexpr.isRoot = true; - // Support old AST API that stored this info in MustacheNode this.id = this.sexpr.id; this.params = this.sexpr.params; diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index eafee7490..1aba34b4e 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -244,7 +244,7 @@ Compiler.prototype = { id.falsy = true; this.ID(id); - this.opcode('invokeHelper', params.length, id.original, id.isSimple, sexpr.isRoot); + this.opcode('invokeHelper', params.length, id.original, id.isSimple); } }, diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index c59fd7005..19066d34f 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -515,7 +515,7 @@ JavaScriptCompiler.prototype = { // and pushes the helper's return value onto the stack. // // If the helper is not found, `helperMissing` is called. - invokeHelper: function(paramSize, name, isSimple, isRoot) { + invokeHelper: function(paramSize, name, isSimple) { this.aliases.helperMissing = 'helpers.helperMissing'; var nonHelper = this.popStack(); diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js index ca85d8478..59701757c 100644 --- a/lib/handlebars/runtime.js +++ b/lib/handlebars/runtime.js @@ -120,9 +120,7 @@ export function template(templateSpec, env) { var ret = function(context, options) { options = options || {}; - var helpers, - partials, - data = options.data; + var data = options.data; ret._setup(options); if (!options.partial && templateSpec.useData) { From 3c869866c8b1c78e646b6f3ecb11dc108499cb6d Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 25 Aug 2014 22:55:05 -0500 Subject: [PATCH 87/91] Update FAQ with comment on UMD vs. AMD build Fixes #796 --- FAQ.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/FAQ.md b/FAQ.md index 748f68a68..3b1cbd80d 100644 --- a/FAQ.md +++ b/FAQ.md @@ -42,4 +42,11 @@ Should these match, please file an issue with us, per our [issue filing guidelines](https://github.com/wycats/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues). 1. Why doesn't IE like the `default` name in the AMD module? - Some browsers such as particular versions of IE treat `default` as a reserved word in JavaScript source files. To safely use this you need to reference this via the `Handlebars['default']` lookup method. This is an unfortunate side effect of the shims necessary to backport the handlebars ES6 code to all current browsers. + Some browsers such as particular versions of IE treat `default` as a reserved word in JavaScript source files. To safely use this you need to reference this via the `Handlebars['default']` lookup method. This is an unfortunate side effect of the shims necessary to backport the Handlebars ES6 code to all current browsers. + +1. How do I load the runtime library when using AMD? + There are two options for loading under AMD environments. The first is to use the `handlebars.runtime.amd.js` file. This may require a [path mapping](https://github.com/wycats/handlebars.js/blob/master/spec/amd-runtime.html#L31) as well as access via the `default` field. + + The other option is to load the `handlebars.runtime.js` UMD build, which might not require path configuration and exposes the library as both the module root and the `default` field for compatibility. + + If not using ES6 transpilers or accessing submodules in the build the former option should be sufficent for most use cases. From 4f01f650dc2458343fcb40147803b30ba9a724de Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 25 Aug 2014 23:35:43 -0500 Subject: [PATCH 88/91] =?UTF-8?q?Render=20false=20literal=20as=20=E2=80=9C?= =?UTF-8?q?false=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #827 --- lib/handlebars/compiler/javascript-compiler.js | 2 +- lib/handlebars/utils.js | 4 +++- spec/basic.js | 8 ++++++++ spec/utils.js | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index 19066d34f..d41cacd73 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -293,7 +293,7 @@ JavaScriptCompiler.prototype = { // when we examine local this.flushInline(); var local = this.popStack(); - this.pushSource("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }"); + this.pushSource('if (' + local + ' != null) { ' + this.appendToBuffer(local) + ' }'); if (this.environment.isSimple) { this.pushSource("else { " + this.appendToBuffer("''") + " }"); } diff --git a/lib/handlebars/utils.js b/lib/handlebars/utils.js index 087183e54..f38b7afb2 100644 --- a/lib/handlebars/utils.js +++ b/lib/handlebars/utils.js @@ -55,8 +55,10 @@ export function escapeExpression(string) { // don't escape SafeStrings, since they're already safe if (string instanceof SafeString) { return string.toString(); - } else if (!string && string !== 0) { + } else if (string == null) { return ""; + } else if (!string) { + return string + ''; } // Force a string conversion as this will be done by the append regardless and diff --git a/spec/basic.js b/spec/basic.js index 8aa54b98c..8a9c116cf 100644 --- a/spec/basic.js +++ b/spec/basic.js @@ -50,6 +50,14 @@ describe("basic context", function() { shouldCompileTo("num: {{.}}", 0, "num: 0"); shouldCompileTo("num: {{num1/num2}}", {num1: {num2: 0}}, "num: 0"); }); + it('false', function() { + shouldCompileTo('val1: {{val1}}, val2: {{val2}}', {val1: false, val2: new Boolean(false)}, 'val1: false, val2: false'); + shouldCompileTo('val: {{.}}', false, 'val: false'); + shouldCompileTo('val: {{val1/val2}}', {val1: {val2: false}}, 'val: false'); + + shouldCompileTo('val1: {{{val1}}}, val2: {{{val2}}}', {val1: false, val2: new Boolean(false)}, 'val1: false, val2: false'); + shouldCompileTo('val: {{{val1/val2}}}', {val1: {val2: false}}, 'val: false'); + }); it("newlines", function() { shouldCompileTo("Alan's\nTest", {}, "Alan's\nTest"); diff --git a/spec/utils.js b/spec/utils.js index ea7d78256..0216c8de8 100644 --- a/spec/utils.js +++ b/spec/utils.js @@ -30,8 +30,8 @@ describe('utils', function() { equals(Handlebars.Utils.escapeExpression(''), ''); equals(Handlebars.Utils.escapeExpression(undefined), ''); equals(Handlebars.Utils.escapeExpression(null), ''); - equals(Handlebars.Utils.escapeExpression(false), ''); + equals(Handlebars.Utils.escapeExpression(false), 'false'); equals(Handlebars.Utils.escapeExpression(0), '0'); }); it('should handle empty objects', function() { From c5acea75aabb9f8238a35b53ac23f1bdb0c51f07 Mon Sep 17 00:00:00 2001 From: Kevin Decker Date: Mon, 25 Aug 2014 23:37:40 -0500 Subject: [PATCH 89/91] Update FAQ.md --- FAQ.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/FAQ.md b/FAQ.md index 3b1cbd80d..0bd0997d6 100644 --- a/FAQ.md +++ b/FAQ.md @@ -1,18 +1,23 @@ # Frequently Asked Questions 1. How can I file a bug report: + See our guidelines on [reporting issues](https://github.com/wycats/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues). 1. Why isn't my Mustache template working? + Handlebars deviates from Mustache slightly on a few behaviors. These variations are documented in our [readme](https://github.com/wycats/handlebars.js#differences-between-handlebarsjs-and-mustache). 1. Why is it slower when compiling? + The Handlebars compiler must parse the template and construct a JavaScript program which can then be run. Under some environments such as older mobile devices this can have a performance impact which can be avoided by precompiling. Generally it's recommended that precompilation and the runtime library be used on all clients. 1. Why doesn't this work with Content Security Policy restrictions? + When not using the precompiler, Handlebars generates a dynamic function for each template which can cause issues with pages that have enabled Content Policy. It's recommended that templates are precompiled or the `unsafe-eval` policy is enabled for sites that must generate dynamic templates at runtime. 1. How can I include script tags in my template? + If loading the template via an inlined `