Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for layout in handlebars #136

Merged
merged 7 commits into from Oct 31, 2019
Merged
14 changes: 14 additions & 0 deletions README.md
Expand Up @@ -144,6 +144,20 @@ To use partials in handlebars you will need to pass the names and paths in the o
}
```

To use layouts in handlebars you will need to pass the `layout` parameter:
```js
fastify.register(require('point-of-view'), {
engine: {
handlebars: require('handlebars')
},
layout: './templates/layout.hbs'
});

fastify.get('/', (req, reply) => {
reply.view('./templates/index.hbs', { text: 'text' })
})
```

To configure nunjunks environment after initialisation, you can pass callback function to options:
```js
options: {
Expand Down
49 changes: 48 additions & 1 deletion index.js
Expand Up @@ -2,6 +2,7 @@

const fp = require('fastify-plugin')
const readFile = require('fs').readFile
const accessSync = require('fs').accessSync
const resolve = require('path').resolve
const join = require('path').join
const HLRU = require('hashlru')
Expand All @@ -27,10 +28,22 @@ function fastifyView (fastify, opts, next) {
const includeViewExtension = opts.includeViewExtension || false
const prod = typeof opts.production === 'boolean' ? opts.production : process.env.NODE_ENV === 'production'
const defaultCtx = opts.defaultContext || {}
const layoutFileName = opts.layout

if (layoutFileName && type !== 'handlebars') {
next(new Error('"layout" option only available for handlebars engine'))
return
}

if (layoutFileName && !hasAccessToLayoutFile(layoutFileName)) {
next(new Error(`unable to access template "${layoutFileName}"`))
return
}

const renders = {
marko: viewMarko,
'ejs-mate': viewEjsMate,
handlebars: viewHandlebars,
handlebars: withLayout(viewHandlebars),
mustache: viewMustache,
nunjucks: viewNunjucks,
'art-template': viewArtTemplate,
Expand Down Expand Up @@ -398,6 +411,40 @@ function fastifyView (fastify, opts, next) {
} else {
next()
}

function withLayout (render) {
if (layoutFileName) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this option going to affect other engines?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently it would only affect handlebars. This is because some engines already support their own layouts (e.g: pug).

It is also possible to add support to other engines that don't already support layouts by applying them the withLayout decorator (see here)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would you mind throwing if layout is present and the engine is not 'handlebars'?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem. Please see my new commits.

return function (page, data, opts) {
const that = this

render.call({
getHeader: () => {},
header: () => {},
send: (result) => {
if (result instanceof Error) {
throw result
}

data = Object.assign(data, { body: result })

render.call(that, layoutFileName, data, opts)
}
}, page, data, opts)
}
}

return render
}

function hasAccessToLayoutFile (fileName) {
try {
accessSync(join(templatesDir, getPage(fileName, 'hbs')))

return true
} catch (e) {
return false
}
}
}

module.exports = fp(fastifyView, { fastify: '^2.x' })
1 change: 1 addition & 0 deletions templates/index-for-layout.hbs
@@ -0,0 +1 @@
<p>{{text}}</p>
7 changes: 7 additions & 0 deletions templates/layout.hbs
@@ -0,0 +1,7 @@
<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
{{{body}}}
</body>
</html>
58 changes: 58 additions & 0 deletions test/test-handlebars.js
Expand Up @@ -181,6 +181,31 @@ test('fastify.view with handlebars engine with callback and html-minifier', t =>
})
})

test('fastify.view with handlebars engine with layout option', t => {
t.plan(3)

const fastify = Fastify()
const handlebars = require('handlebars')
const data = { text: 'it works!' }

fastify.register(require('../index'), {
engine: {
handlebars
},
layout: './templates/layout.hbs'
})

fastify.ready(err => {
t.error(err)

fastify.view('./templates/index-for-layout.hbs', data, (err, compiled) => {
t.error(err)
t.strictEqual(handlebars.compile(fs.readFileSync('./templates/index.hbs', 'utf8'))(data), compiled)
fastify.close()
})
})
})

test('reply.view with handlebars engine', t => {
t.plan(6)
const fastify = Fastify()
Expand Down Expand Up @@ -519,3 +544,36 @@ test('reply.view with handlebars engine with partials', t => {
})
})
})

test('reply.view with handlebars engine with layout option', t => {
t.plan(6)
const fastify = Fastify()
const handlebars = require('handlebars')
const data = { text: 'text' }

fastify.register(require('../index'), {
engine: {
handlebars: handlebars
},
layout: './templates/layout.hbs'
})

fastify.get('/', (req, reply) => {
reply.view('./templates/index-for-layout.hbs', data)
})

fastify.listen(0, err => {
t.error(err)
sget({
method: 'GET',
url: 'http://localhost:' + fastify.server.address().port
}, (err, response, replyBody) => {
t.error(err)
t.strictEqual(response.statusCode, 200)
t.strictEqual(response.headers['content-length'], '' + replyBody.length)
t.strictEqual(response.headers['content-type'], 'text/html; charset=utf-8')
t.strictEqual(handlebars.compile(fs.readFileSync('./templates/index.hbs', 'utf8'))(data), replyBody.toString())
fastify.close()
})
})
})
30 changes: 30 additions & 0 deletions test/test.js
Expand Up @@ -107,3 +107,33 @@ test('register callback should throw if the engine is not supported', t => {
t.is(err.message, '\'notSupported\' not yet supported, PR? :)')
})
})

test('register callback with handlebars engine should throw if layout file does not exist', t => {
t.plan(2)
const fastify = Fastify()

fastify.register(require('../index'), {
engine: {
handlebars: require('handlebars')
},
layout: './templates/does-not-exist.hbs'
}).ready(err => {
t.ok(err instanceof Error)
t.deepEqual('unable to access template "./templates/does-not-exist.hbs"', err.message)
})
})

test('register callback should throw if layout option is provided and engine is not handlebars', t => {
t.plan(2)
const fastify = Fastify()

fastify.register(require('../index'), {
engine: {
ejs: require('ejs')
},
layout: './templates/layout.hbs'
}).ready(err => {
t.ok(err instanceof Error)
t.deepEqual('"layout" option only available for handlebars engine', err.message)
})
})