From 899727b956ad50535f8c9eabf80b6a5648328001 Mon Sep 17 00:00:00 2001 From: Francis Brito Date: Mon, 7 Oct 2019 13:56:44 -0400 Subject: [PATCH 1/6] feat(handlebars): add support for layouts --- index.js | 37 +++++++++++++++++++++- templates/index-for-layout.hbs | 1 + templates/layout.hbs | 7 ++++ test/test-handlebars.js | 58 ++++++++++++++++++++++++++++++++++ test/test.js | 15 +++++++++ types.test.ts | 2 +- 6 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 templates/index-for-layout.hbs create mode 100644 templates/layout.hbs diff --git a/index.js b/index.js index 2aa2407a..de4b8ac7 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,7 @@ const fp = require('fastify-plugin') const readFile = require('fs').readFile +const readFileSync = require('fs').readFileSync const resolve = require('path').resolve const join = require('path').join const HLRU = require('hashlru') @@ -27,10 +28,20 @@ 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) { + try { + readFileSync(join(templatesDir, getPage(layoutFileName, 'hbs'))) + } catch (e) { + next(e) + } + } + const renders = { marko: viewMarko, 'ejs-mate': viewEjsMate, - handlebars: viewHandlebars, + handlebars: withLayout(viewHandlebars), mustache: viewMustache, nunjucks: viewNunjucks, 'art-template': viewArtTemplate, @@ -398,6 +409,30 @@ function fastifyView (fastify, opts, next) { } else { next() } + + function withLayout (render) { + if (layoutFileName) { + 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 + } } module.exports = fp(fastifyView, { fastify: '^2.x' }) diff --git a/templates/index-for-layout.hbs b/templates/index-for-layout.hbs new file mode 100644 index 00000000..4de36880 --- /dev/null +++ b/templates/index-for-layout.hbs @@ -0,0 +1 @@ +

{{text}}

\ No newline at end of file diff --git a/templates/layout.hbs b/templates/layout.hbs new file mode 100644 index 00000000..d52cf0dd --- /dev/null +++ b/templates/layout.hbs @@ -0,0 +1,7 @@ + + + + + {{{body}}} + + \ No newline at end of file diff --git a/test/test-handlebars.js b/test/test-handlebars.js index 3c999c27..edee1137 100644 --- a/test/test-handlebars.js +++ b/test/test-handlebars.js @@ -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() @@ -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() + }) + }) +}) diff --git a/test/test.js b/test/test.js index 1dec464b..a9751996 100644 --- a/test/test.js +++ b/test/test.js @@ -107,3 +107,18 @@ 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.ok(/no such file or directory/gi.test(err.message)) + }) +}) diff --git a/types.test.ts b/types.test.ts index f2e6bd4b..0ee37b24 100644 --- a/types.test.ts +++ b/types.test.ts @@ -3,7 +3,7 @@ import pointOfView = require("./"); const app = fastify(); -app.register(pointOfView, { +app.register(pointOfView, { engine: { ejs: require("ejs"), }, From f94ede15f45b250bcfad42c360b8ffcac3982058 Mon Sep 17 00:00:00 2001 From: Francis Brito Date: Mon, 7 Oct 2019 21:00:47 -0400 Subject: [PATCH 2/6] docs(readme): add documentation on how to use layout in handlebars --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 3a644af9..da10e71e 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,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: { From 1e3d7b6e44832cbe25121fca335c6885c9a97fda Mon Sep 17 00:00:00 2001 From: Francis Brito Date: Sun, 13 Oct 2019 23:28:33 -0400 Subject: [PATCH 3/6] fix(plugin): fix plugin not returning if unable to read layout --- index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/index.js b/index.js index de4b8ac7..70677465 100644 --- a/index.js +++ b/index.js @@ -35,6 +35,7 @@ function fastifyView (fastify, opts, next) { readFileSync(join(templatesDir, getPage(layoutFileName, 'hbs'))) } catch (e) { next(e) + return } } From 791596b8daa140f6b633052e3490ce3021e895cc Mon Sep 17 00:00:00 2001 From: Francis Brito Date: Wed, 23 Oct 2019 05:02:33 -0400 Subject: [PATCH 4/6] refactor(plugin): refactor layout file pressence check --- index.js | 22 ++++++++++++++-------- test/test.js | 2 +- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index 70677465..4583ead8 100644 --- a/index.js +++ b/index.js @@ -2,7 +2,7 @@ const fp = require('fastify-plugin') const readFile = require('fs').readFile -const readFileSync = require('fs').readFileSync +const accessSync = require('fs').accessSync const resolve = require('path').resolve const join = require('path').join const HLRU = require('hashlru') @@ -30,13 +30,9 @@ function fastifyView (fastify, opts, next) { const defaultCtx = opts.defaultContext || {} const layoutFileName = opts.layout - if (layoutFileName) { - try { - readFileSync(join(templatesDir, getPage(layoutFileName, 'hbs'))) - } catch (e) { - next(e) - return - } + if (layoutFileName && !hasAccessToLayoutFile(layoutFileName)) { + next(new Error(`unable to access template "${layoutFileName}"`)) + return } const renders = { @@ -434,6 +430,16 @@ function fastifyView (fastify, opts, next) { 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' }) diff --git a/test/test.js b/test/test.js index a9751996..9dd1820b 100644 --- a/test/test.js +++ b/test/test.js @@ -119,6 +119,6 @@ test('register callback with handlebars engine should throw if layout file does layout: './templates/does-not-exist.hbs' }).ready(err => { t.ok(err instanceof Error) - t.ok(/no such file or directory/gi.test(err.message)) + t.deepEqual('unable to access template "./templates/does-not-exist.hbs"', err.message) }) }) From 735d5ee8da973a08475c618725cb431d5b0fc177 Mon Sep 17 00:00:00 2001 From: Francis Brito Date: Wed, 23 Oct 2019 05:12:58 -0400 Subject: [PATCH 5/6] fix(plugin): update plugin to throw if layout option provided to an engine other than handlebars --- index.js | 4 ++++ test/test.js | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/index.js b/index.js index 4583ead8..9a32a9e2 100644 --- a/index.js +++ b/index.js @@ -30,6 +30,10 @@ function fastifyView (fastify, opts, next) { const defaultCtx = opts.defaultContext || {} const layoutFileName = opts.layout + if (layoutFileName && type !== 'handlebars') { + next(new Error('"layout" option only available for handlebars engine')) + } + if (layoutFileName && !hasAccessToLayoutFile(layoutFileName)) { next(new Error(`unable to access template "${layoutFileName}"`)) return diff --git a/test/test.js b/test/test.js index 9dd1820b..b3994352 100644 --- a/test/test.js +++ b/test/test.js @@ -122,3 +122,18 @@ test('register callback with handlebars engine should throw if layout file does 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) + }) +}) From 96225a01c99e7d1f51a49aefdb7229521f172fe7 Mon Sep 17 00:00:00 2001 From: Francis Brito Date: Wed, 23 Oct 2019 22:46:41 -0400 Subject: [PATCH 6/6] fix(plugin): fix register callback not exiting after error --- index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/index.js b/index.js index 9a32a9e2..9cef3bd1 100644 --- a/index.js +++ b/index.js @@ -32,6 +32,7 @@ function fastifyView (fastify, opts, next) { if (layoutFileName && type !== 'handlebars') { next(new Error('"layout" option only available for handlebars engine')) + return } if (layoutFileName && !hasAccessToLayoutFile(layoutFileName)) {