From 1ed163f4ae24d7f3bedb9d8c26becd0846843721 Mon Sep 17 00:00:00 2001 From: Nils Knappmeier Date: Sun, 21 May 2017 14:15:39 +0200 Subject: [PATCH 1/7] Update jsfiddle-link to 4.0.10 --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1ea246649..42cdc56d6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -96,4 +96,4 @@ After this point the handlebars site needs to be updated to point to the new ver [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]: https://jsfiddle.net/9D88g/47/ +[jsfiddle]: https://jsfiddle.net/9D88g/113/ From 5b76f041b37c0f42c99633b62e6555014da28fc5 Mon Sep 17 00:00:00 2001 From: Nils Knappmeier Date: Sun, 14 May 2017 23:11:43 +0200 Subject: [PATCH 2/7] Fix build on Windows Closes #1233 - Handle path-separators properly. Use "path.sep" instead of "/". Or use "require.resolve()" if possible - Use "execFile" instead of "exec" to run the Handlebars executable. This prevents problems due to (missing) shell escaping. - Use explicit call to "node" in order to run the executable on Windows. - Add "appveyor"-CI in order to run regular tests on Windows. --- README.markdown | 1 + appveyor.yml | 38 ++++++++++++++++++++++++++++++++++++++ spec/env/browser.js | 3 ++- spec/env/runner.js | 2 +- tasks/test.js | 10 ++++++++-- 5 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 appveyor.yml diff --git a/README.markdown b/README.markdown index 89e3fc59a..434a0e3a6 100644 --- a/README.markdown +++ b/README.markdown @@ -1,4 +1,5 @@ [![Travis Build Status](https://img.shields.io/travis/wycats/handlebars.js/master.svg)](https://travis-ci.org/wycats/handlebars.js) +[![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/github/wycats/handlebars.js?branch=master&svg=true)](https://ci.appveyor.com/project/wycats/handlebars-js) [![Selenium Test Status](https://saucelabs.com/buildstatus/handlebars)](https://saucelabs.com/u/handlebars) Handlebars.js diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 000000000..b67fb4ca5 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,38 @@ +# Test against these versions of Node.js +environment: + matrix: + - nodejs_version: "4" + - nodejs_version: "5" + +platform: + - x64 + +# Install scripts (runs after repo cloning) +install: + # Get the latest stable version of Node.js + - ps: Install-Product node $env:nodejs_version $env:platform + # Clone submodules (mustache spec) + - cmd: git submodule update --init --recursive + # Install modules + - cmd: npm install + - cmd: npm install -g grunt-cli + + +# Post-install test scripts +test_script: + # Output useful info for debugging + - cmd: node --version + - cmd: npm --version + # Run tests + - cmd: grunt --stack travis + +# Don't actually build +build: off + +on_failure: + - cmd: 7z a coverage.zip coverage + - cmd: appveyor PushArtifact coverage.zip + + +# Set build version format here instead of in the admin panel +version: "{build}" \ No newline at end of file diff --git a/spec/env/browser.js b/spec/env/browser.js index 60a5d3547..8b89207e9 100644 --- a/spec/env/browser.js +++ b/spec/env/browser.js @@ -9,7 +9,8 @@ var filename = 'dist/handlebars.js'; if (global.minimizedTest) { filename = 'dist/handlebars.min.js'; } -vm.runInThisContext(fs.readFileSync(__dirname + '/../../' + filename), filename); +var distHandlebars = fs.readFileSync(require.resolve(`../../${filename}`), 'utf-8'); +vm.runInThisContext(distHandlebars, filename); global.CompilerContext = { browser: true, diff --git a/spec/env/runner.js b/spec/env/runner.js index f4b23d825..a069c2d71 100644 --- a/spec/env/runner.js +++ b/spec/env/runner.js @@ -15,7 +15,7 @@ if (grep === '--min') { var files = fs.readdirSync(testDir) .filter(function(name) { return (/.*\.js$/).test(name); }) - .map(function(name) { return testDir + '/' + name; }); + .map(function(name) { return testDir + path.sep + name; }); if (global.minimizedTest) { run('./runtime', function() { diff --git a/tasks/test.js b/tasks/test.js index 342a28362..6bee3920a 100644 --- a/tasks/test.js +++ b/tasks/test.js @@ -6,9 +6,15 @@ module.exports = function(grunt) { grunt.registerTask('test:bin', function() { var done = this.async(); + var cmd = './bin/handlebars'; + var args = [ '-a', 'spec/artifacts/empty.handlebars' ]; + // On Windows, the executable handlebars.js file cannot be run directly - var prefix = os.type().match(/^Windows/) ? process.argv[0] : ''; - childProcess.exec(prefix + ' ./bin/handlebars -a spec/artifacts/empty.handlebars', function(err, stdout) { + if (os.platform() === 'win32') { + args.unshift(cmd); + cmd = process.argv[0]; + } + childProcess.execFile(cmd, args, function(err, stdout) { if (err) { throw err; } From 79309659e1496fead1d0a4743286398f1512b666 Mon Sep 17 00:00:00 2001 From: Nils Knappmeier Date: Fri, 13 Oct 2017 23:54:53 +0200 Subject: [PATCH 3/7] Gracefully handle missing uglify-js dependency closes #1391 uglify-js is an optional dependency and should be treated as such. This commit gracefully handles MODULE_NOT_FOUND errors while loading uglify. - Check for existing uglify-js (and load uglify-js) only if minification was activated - Use "require.resolve" to check if uglify exists. Otherwise, a missing dependency of uglify-js would cause the same behavior as missing uglify-js. (Only a warning, no error) - The code to load and run uglify is put into a single for readability purposes - Tests use a mockup Module._resolveFilename to simulate the missing module. This function is used by both "require" and "require.resolve", so both are mocked equally. (cherry picked from commit d5caa56) --- lib/precompiler.js | 40 +++++++++++++++++++++++------ spec/precompiler.js | 61 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 8 deletions(-) diff --git a/lib/precompiler.js b/lib/precompiler.js index 6ba3800cb..64608f1e9 100644 --- a/lib/precompiler.js +++ b/lib/precompiler.js @@ -4,7 +4,7 @@ import fs from 'fs'; import * as Handlebars from './handlebars'; import {basename} from 'path'; import {SourceMapConsumer, SourceNode} from 'source-map'; -import uglify from 'uglify-js'; + module.exports.loadTemplates = function(opts, callback) { loadStrings(opts, function(err, strings) { @@ -235,7 +235,6 @@ module.exports.cli = function(opts) { } } - if (opts.map) { output.add('\n//# sourceMappingURL=' + opts.map + '\n'); } @@ -244,12 +243,7 @@ module.exports.cli = function(opts) { output.map = output.map + ''; if (opts.min) { - output = uglify.minify(output.code, { - fromString: true, - - outSourceMap: opts.map, - inSourceMap: JSON.parse(output.map) - }); + output = minify(output, opts.map); } if (opts.map) { @@ -271,3 +265,33 @@ function arrayCast(value) { } return value; } + +/** + * Run uglify to minify the compiled template, if uglify exists in the dependencies. + * + * We are using `require` instead of `import` here, because es6-modules do not allow + * dynamic imports and uglify-js is an optional dependency. Since we are inside NodeJS here, this + * should not be a problem. + * + * @param {string} output the compiled template + * @param {string} sourceMapFile the file to write the source map to. + */ +function minify(output, sourceMapFile) { + try { + // Try to resolve uglify-js in order to see if it does exist + require.resolve('uglify-js'); + } catch (e) { + if (e.code !== 'MODULE_NOT_FOUND') { + // Something else seems to be wrong + throw e; + } + // it does not exist! + console.error('Code minimization is disabled due to missing uglify-js dependency'); + return output; + } + return require('uglify-js').minify(output.code, { + fromString: true, + outSourceMap: sourceMapFile, + inSourceMap: JSON.parse(output.map) + }); +} diff --git a/spec/precompiler.js b/spec/precompiler.js index 006a37e39..9f2a6442b 100644 --- a/spec/precompiler.js +++ b/spec/precompiler.js @@ -12,6 +12,8 @@ describe('precompiler', function() { var log, logFunction, + errorLog, + errorLogFunction, precompile, minify, @@ -26,16 +28,51 @@ describe('precompiler', function() { content, writeFileSync; + /** + * Mock the Module.prototype.require-function such that an error is thrown, when "uglify-js" is loaded. + * + * The function cleans up its mess when "callback" is finished + * + * @param {Error} loadError the error that should be thrown if uglify is loaded + * @param {function} callback a callback-function to run when the mock is active. + */ + function mockRequireUglify(loadError, callback) { + var Module = require('module'); + var _resolveFilename = Module._resolveFilename; + delete require.cache[require.resolve('uglify-js')]; + delete require.cache[require.resolve('../dist/cjs/precompiler')]; + Module._resolveFilename = function(request, mod) { + if (request === 'uglify-js') { + throw loadError; + } + return _resolveFilename.call(this, request, mod); + }; + try { + callback(); + } finally { + Module._resolveFilename = _resolveFilename; + delete require.cache[require.resolve('uglify-js')]; + delete require.cache[require.resolve('../dist/cjs/precompiler')]; + } + } + beforeEach(function() { precompile = Handlebars.precompile; minify = uglify.minify; writeFileSync = fs.writeFileSync; + // Mock stdout and stderr logFunction = console.log; log = ''; console.log = function() { log += Array.prototype.join.call(arguments, ''); }; + errorLogFunction = console.error; + errorLog = ''; + console.error = function() { + errorLog += Array.prototype.join.call(arguments, ''); + }; + fs.writeFileSync = function(_file, _content) { file = _file; content = _content; @@ -46,6 +83,7 @@ describe('precompiler', function() { uglify.minify = minify; fs.writeFileSync = writeFileSync; console.log = logFunction; + console.error = errorLogFunction; }); it('should output version', function() { @@ -148,6 +186,29 @@ describe('precompiler', function() { equal(log, 'min'); }); + it('should omit minimization gracefully, if uglify-js is missing', function() { + var error = new Error("Cannot find module 'uglify-js'"); + error.code = 'MODULE_NOT_FOUND'; + mockRequireUglify(error, function() { + var Precompiler = require('../dist/cjs/precompiler'); + Handlebars.precompile = function() { return 'amd'; }; + Precompiler.cli({templates: [emptyTemplate], min: true}); + equal(/template\(amd\)/.test(log), true); + equal(/\n/.test(log), true); + equal(/Code minimization is disabled/.test(errorLog), true); + }); + }); + + it('should fail on errors (other than missing module) while loading uglify-js', function() { + mockRequireUglify(new Error('Mock Error'), function() { + shouldThrow(function() { + var Precompiler = require('../dist/cjs/precompiler'); + Handlebars.precompile = function() { return 'amd'; }; + Precompiler.cli({templates: [emptyTemplate], min: true}); + }, Error, 'Mock Error'); + }); + }); + it('should output map', function() { Precompiler.cli({templates: [emptyTemplate], map: 'foo.js.map'}); From 21386b6474b6dbd731bcd92295ddf8ae7b48a3bf Mon Sep 17 00:00:00 2001 From: Marcos Marado Date: Thu, 5 Oct 2017 18:44:35 +0200 Subject: [PATCH 4/7] Update (C) year in the LICENSE file Welcome to 2017! (cherry picked from commit 33773c2) --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 307ebc1c2..b802d14e7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (C) 2011-2016 by Yehuda Katz +Copyright (C) 2011-2017 by Yehuda Katz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 59548b4bdca44f139347697e52e9bcbb511ae6dd Mon Sep 17 00:00:00 2001 From: Nils Knappmeier Date: Thu, 24 Aug 2017 22:16:21 +0200 Subject: [PATCH 5/7] Extend compiler-api example by replacing child-compiler closes #1376 (cherry picked from commit ce3cd8a) --- docs/compiler-api.md | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/docs/compiler-api.md b/docs/compiler-api.md index 29382191e..f419fbcd2 100644 --- a/docs/compiler-api.md +++ b/docs/compiler-api.md @@ -66,7 +66,7 @@ interface MustacheStatement <: Statement { interface BlockStatement <: Statement { type: "BlockStatement"; - path: PathExpression; + path: PathExpression | Literal; params: [ Expression ]; hash: Hash; @@ -296,21 +296,43 @@ The `Handlebars.JavaScriptCompiler` object has a number of methods that may be c - `initializeBuffer()` Allows for buffers other than the default string buffer to be used. Generally needs to be paired with a custom `appendToBuffer` implementation. +### Example for the compiler api. + +This example changes all lookups of properties are performed by a helper (`lookupLowerCase`) which looks for `test` if `{{Test}}` occurs in the template. This is just to illustrate how compiler behavior can be change. + +There is also [a jsfiddle with this code](https://jsfiddle.net/9D88g/162/) if you want to play around with it. + + ```javascript function MyCompiler() { Handlebars.JavaScriptCompiler.apply(this, arguments); } -MyCompiler.prototype = Object.create(Handlebars.JavaScriptCompiler); +MyCompiler.prototype = new Handlebars.JavaScriptCompiler(); -MyCompiler.nameLookup = function(parent, name, type) { - if (type === 'partial') { - return 'MyPartialList[' + JSON.stringify(name) ']'; +// Use this compile to compile BlockStatment-Blocks +MyCompiler.prototype.compiler = MyCompiler + +MyCompiler.prototype.nameLookup = function(parent, name, type) { + if (type === 'context') { + return this.source.functionCall('helpers.lookupLowerCase', '', [parent, JSON.stringify(name)]) } else { return Handlebars.JavaScriptCompiler.prototype.nameLookup.call(this, parent, name, type); } -}; +} var env = Handlebars.create(); +env.registerHelper('lookupLowerCase', function(parent, name) { + return parent[name.toLowerCase()] +}) + env.JavaScriptCompiler = MyCompiler; -env.compile('my template'); + +var template = env.compile('{{#each Test}} ({{Value}}) {{/each}}'); +console.log(template({ + test: [ + {value: 'a'}, + {value: 'b'}, + {value: 'c'} + ] +})); ``` From 1ac131e652edd5d26b4d8ac34f90acf6e812f86f Mon Sep 17 00:00:00 2001 From: Nils Knappmeier Date: Tue, 17 Oct 2017 22:51:42 +0200 Subject: [PATCH 6/7] Update release notes --- release-notes.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/release-notes.md b/release-notes.md index 4db22231d..fae710549 100644 --- a/release-notes.md +++ b/release-notes.md @@ -2,7 +2,17 @@ ## Development -[Commits](https://github.com/nknapp/handlebars.js/compare/v4.0.10...master) +[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.11...master) + +## v4.0.11 - October 17th, 2017 +- [#1391](https://github.com/wycats/handlebars.js/issues/1391) - `uglify-js` is unconditionally imported, but only listed as optional dependency ([@Turbo87](https://api.github.com/users/Turbo87)) +- [#1233](https://github.com/wycats/handlebars.js/issues/1233) - Unable to build under windows - error at test:bin task ([@blikblum](https://api.github.com/users/blikblum)) +- Update (C) year in the LICENSE file - 21386b6 + +Compatibility notes: +- This is a bugfix release. There are no breaking change and no new features. + +[Commits](https://github.com/nknapp/handlebars.js/compare/v4.0.10...v4.0.11) ## v4.0.10 - May 21st, 2017 - Fix regression in 4.0.9: Replace "Object.assign" (not support in IE) by "util/extend" - 0e953d1 From 1e954ddf3c3ec6d2318e1fadc5e03aaf065b2fbd Mon Sep 17 00:00:00 2001 From: Nils Knappmeier Date: Tue, 17 Oct 2017 22:52:25 +0200 Subject: [PATCH 7/7] v4.0.11 --- components/bower.json | 2 +- components/handlebars.js.nuspec | 2 +- lib/handlebars/base.js | 2 +- package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/bower.json b/components/bower.json index 0bf954a4a..7a28ba7b9 100644 --- a/components/bower.json +++ b/components/bower.json @@ -1,6 +1,6 @@ { "name": "handlebars", - "version": "4.0.10", + "version": "4.0.11", "main": "handlebars.js", "license": "MIT", "dependencies": {} diff --git a/components/handlebars.js.nuspec b/components/handlebars.js.nuspec index 9b735a972..f84690c2a 100644 --- a/components/handlebars.js.nuspec +++ b/components/handlebars.js.nuspec @@ -2,7 +2,7 @@ handlebars.js - 4.0.10 + 4.0.11 handlebars.js Authors https://github.com/wycats/handlebars.js/blob/master/LICENSE https://github.com/wycats/handlebars.js/ diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index d0ad13310..f1a395756 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -4,7 +4,7 @@ import {registerDefaultHelpers} from './helpers'; import {registerDefaultDecorators} from './decorators'; import logger from './logger'; -export const VERSION = '4.0.10'; +export const VERSION = '4.0.11'; export const COMPILER_REVISION = 7; export const REVISION_CHANGES = { diff --git a/package.json b/package.json index fcbabc2c9..b458985ea 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "handlebars", "barename": "handlebars", - "version": "4.0.10", + "version": "4.0.11", "description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration", "homepage": "http://www.handlebarsjs.com/", "keywords": [