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/ 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 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/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/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'} + ] +})); ``` 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/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/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": [ 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 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/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'}); 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; }