Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,32 @@ If you wish to use handlebars methods like `SafeString` please do so on this pro
hbs.handlebars === require('handlebars');
```

## Compile options ##

Options for [`handlebars.compile()`](https://handlebarsjs.com/api-reference/compilation.html)
(such as `strict`, `noEscape` or `preventIndent`) can be set per instance. They apply to
every view and layout compiled by that instance.

```
var hbs = require('hbs');

var instance = hbs.create(null, {
compileOptions: { strict: true }
});

app.engine('hbs', instance.__express);
```

Note that `noEscape: true` disables HTML escaping for every `{{expression}}` rendered by the
instance, so only use it when the data is trusted.

The options are also exposed as the `compileOptions` property, which can be changed at any
time (compiled templates already in the cache are not affected):

```
hbs.compileOptions = { noEscape: true };
```

## Recipes ##

### more than one instance ###
Expand Down
18 changes: 11 additions & 7 deletions lib/hbs.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ var walk = require('walk').walk;

var async = require('./async');

function Instance(handlebars) {
function Instance(handlebars, options) {
if (!(this instanceof Instance)) {
return new Instance(handlebars);
return new Instance(handlebars, options);
}

// expose handlebars, allows users to use their versions
Expand All @@ -15,6 +15,10 @@ function Instance(handlebars) {

self.handlebars = handlebars || require('handlebars').create();

// options forwarded to `handlebars.compile()` for every view and layout
// see https://handlebarsjs.com/api-reference/compilation.html
self.compileOptions = (options && options.compileOptions) || {};

// cache for templates, express 3.x doesn't do this for us
self.cache = {};

Expand Down Expand Up @@ -84,7 +88,7 @@ function middleware(filename, options, cb) {
return cb(err);
}

var template = handlebars.compile(str);
var template = handlebars.compile(str, self.compileOptions);
if (locals.cache) {
cache[filename] = template;
}
Expand Down Expand Up @@ -175,7 +179,7 @@ function middleware(filename, options, cb) {
}

function cacheAndCompile(filename, str) {
var layout_template = handlebars.compile(str);
var layout_template = handlebars.compile(str, self.compileOptions);
if (options.cache) {
cache[filename] = layout_template;
}
Expand Down Expand Up @@ -213,7 +217,7 @@ Instance.prototype.compile = function (str) {
return str;
}

var template = this.handlebars.compile(str);
var template = this.handlebars.compile(str, this.compileOptions);
return function (locals) {
return template(locals, {
helpers: locals.blockHelpers,
Expand Down Expand Up @@ -326,6 +330,6 @@ Instance.prototype.localsAsTemplateData = function(app) {
};

module.exports = new Instance();
module.exports.create = function(handlebars) {
return new Instance(handlebars);
module.exports.create = function(handlebars, options) {
return new Instance(handlebars, options);
};
124 changes: 124 additions & 0 deletions test/4.x/compile_options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
var path = require('path')
var request = require('supertest')
var utils = require('../support/utils')

// builtin
var assert = require('assert');

// local
var hbs = require('../../')

var strictApp = null
var noEscapeApp = null
var plainApp = null

suite('express 4.x compile options')

function createApp(instance) {
var express = require('express')
var app = express()

app.engine('hbs', instance.__express)
app.set('view engine', 'hbs')
app.set('views', path.join(__dirname, 'views'))

app.get('/', function (req, res) {
res.render('no_escape', {
layout: false,
content: '<b>bold</b>'
})
})

app.get('/strict/present', function (req, res) {
res.render('strict', {
layout: false,
value: 'ok'
})
})

app.get('/strict/missing', function (req, res) {
res.render('strict', {
layout: false
})
})

app.get('/strict/layout', function (req, res) {
// `title` is only referenced by the layout, so a failure here proves
// the layout was compiled with the same options as the view
res.render('strict', {
layout: 'layout',
value: 'ok'
})
})

app.use(function (err, req, res, next) { // eslint-disable-line no-unused-vars
res.status(500).send(err.message)
})

return app
}

before(function () {
if (utils.nodeVersionCompare(0.10) < 0) {
this.skip()
return
}

strictApp = createApp(hbs.create(null, { compileOptions: { strict: true } }))
noEscapeApp = createApp(hbs.create(null, { compileOptions: { noEscape: true } }))
plainApp = createApp(hbs.create())
})

test('compileOptions defaults to an empty object', function () {
assert.deepEqual(hbs.compileOptions, {})
assert.deepEqual(hbs.create().compileOptions, {})
assert.deepEqual(hbs.create(null, {}).compileOptions, {})
})

test('compileOptions is taken from create()', function () {
var instance = hbs.create(null, { compileOptions: { strict: true } })
assert.deepEqual(instance.compileOptions, { strict: true })
})

test('compile() honours compileOptions', function () {
var instance = hbs.create(null, { compileOptions: { noEscape: true } })
assert.strictEqual(instance.compile('{{x}}')({ x: '<b>' }), '<b>')
assert.strictEqual(hbs.compile('{{x}}')({ x: '<b>' }), '&lt;b&gt;')
})

test('compileOptions can be changed after creation', function () {
var instance = hbs.create()
assert.strictEqual(instance.compile('{{x}}')({ x: '<b>' }), '&lt;b&gt;')
instance.compileOptions = { noEscape: true }
assert.strictEqual(instance.compile('{{x}}')({ x: '<b>' }), '<b>')
})

test('strict: renders when the field is present', function (done) {
request(strictApp)
.get('/strict/present')
.expect(200, '<p>ok</p>\n', done)
})

test('strict: errors when the field is missing', function (done) {
request(strictApp)
.get('/strict/missing')
.expect(500, /"value" not defined/, done)
})

test('strict: applies to layouts too', function (done) {
request(strictApp)
.get('/strict/layout')
.expect(500, /"title" not defined/, done)
})

test('noEscape: renders raw html', function (done) {
request(noEscapeApp)
.get('/')
.expect(200, '<p><b>bold</b></p>\n', done)
})

test('default instance still escapes html', function (done) {
request(plainApp)
.get('/')
.expect(200, '<p>&lt;b&gt;bold&lt;/b&gt;</p>\n', done)
})
1 change: 1 addition & 0 deletions test/4.x/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@ require('./async_helpers')
require('./register_partials')
require('./view_engine')
require('./no_layout_app')
require('./compile_options')
1 change: 1 addition & 0 deletions test/4.x/views/no_escape.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p>{{content}}</p>
1 change: 1 addition & 0 deletions test/4.x/views/strict.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p>{{value}}</p>
Loading