@fastify/view
Advanced tools
Comparing version 8.2.0 to 9.0.0
'use strict' | ||
process.env.NODE_ENV = 'production' | ||
const fastify = require('fastify')() | ||
fastify.register(require('../index'), { | ||
engine: { | ||
ejs: require('ejs') | ||
} | ||
require('./setup.js')({ | ||
engine: { ejs: require('ejs') }, | ||
route: (req, reply) => { reply.view('index.ejs', { text: 'text' }) } | ||
}) | ||
fastify.get('/', (req, reply) => { | ||
reply.view('../templates/index.ejs', { text: 'text' }) | ||
}) | ||
fastify.listen({ port: 3000 }, err => { | ||
if (err) throw err | ||
console.log(`server listening on ${fastify.server.address().port}`) | ||
}) |
621
index.js
'use strict' | ||
const { readFile } = require('node:fs/promises') | ||
const fp = require('fastify-plugin') | ||
const { accessSync, existsSync, mkdirSync, readdirSync, readFile } = require('node:fs') | ||
const { accessSync, existsSync, mkdirSync, readdirSync } = require('node:fs') | ||
const { basename, dirname, extname, join, resolve } = require('node:path') | ||
@@ -9,11 +9,9 @@ const HLRU = require('hashlru') | ||
function fastifyView (fastify, opts, next) { | ||
async function fastifyView (fastify, opts) { | ||
if (!opts.engine) { | ||
next(new Error('Missing engine')) | ||
return | ||
throw new Error('Missing engine') | ||
} | ||
const type = Object.keys(opts.engine)[0] | ||
if (supportedEngines.indexOf(type) === -1) { | ||
next(new Error(`'${type}' not yet supported, PR? :)`)) | ||
return | ||
throw new Error(`'${type}' not yet supported, PR? :)`) | ||
} | ||
@@ -32,2 +30,16 @@ const charset = opts.charset || 'utf-8' | ||
/** | ||
* @type {Map<string, Promise>} | ||
*/ | ||
const readFileMap = new Map() | ||
function readFileSemaphore (filePath) { | ||
if (readFileMap.has(filePath) === false) { | ||
const promise = readFile(filePath, 'utf-8') | ||
readFileMap.set(filePath, promise) | ||
return promise.finally(() => readFileMap.delete(filePath)) | ||
} | ||
return readFileMap.get(filePath) | ||
} | ||
function templatesDirIsValid (_templatesDir) { | ||
@@ -60,11 +72,6 @@ if (Array.isArray(_templatesDir) && type !== 'nunjucks') { | ||
try { | ||
templatesDirIsValid(templatesDir) | ||
templatesDirIsValid(templatesDir) | ||
if (globalLayoutFileName) { | ||
layoutIsValid(globalLayoutFileName) | ||
} | ||
} catch (error) { | ||
next(error) | ||
return | ||
if (globalLayoutFileName) { | ||
layoutIsValid(globalLayoutFileName) | ||
} | ||
@@ -90,3 +97,3 @@ | ||
function viewDecorator () { | ||
function viewDecorator (page) { | ||
const args = Array.from(arguments) | ||
@@ -99,18 +106,9 @@ | ||
const promise = new Promise((resolve, reject) => { | ||
renderer.apply({ | ||
getHeader: () => { }, | ||
header: () => { }, | ||
send: result => { | ||
if (result instanceof Error) { | ||
reject(result) | ||
return | ||
} | ||
let promise = !page ? Promise.reject(new Error('Missing page')) : renderer.apply(this, args) | ||
resolve(result) | ||
} | ||
}, args) | ||
}) | ||
if (minify) { | ||
promise = promise.then((result) => minify(result, globalOptions.htmlMinifierOptions)) | ||
} | ||
if (done && typeof done === 'function') { | ||
if (typeof done === 'function') { | ||
promise.then(done.bind(null, null), done) | ||
@@ -129,4 +127,20 @@ return | ||
fastify.decorateReply(propertyName, function () { | ||
renderer.apply(this, arguments) | ||
fastify.decorateReply(propertyName, async function (page) { | ||
if (!page) { | ||
this.send(new Error('Missing page')) | ||
} | ||
try { | ||
const result = await renderer.apply(this, arguments) | ||
if (!this.getHeader('Content-Type')) { | ||
this.header('Content-Type', 'text/html; charset=' + charset) | ||
} | ||
if (minify && !isPathExcludedMinification(this)) { | ||
this.send(minify(result, globalOptions.htmlMinifierOptions)) | ||
} else { | ||
this.send(result) | ||
} | ||
} catch (err) { | ||
this.send(err) | ||
} | ||
return this | ||
@@ -179,19 +193,17 @@ }) | ||
function isPathExcludedMinification (currentPath, pathsToExclude) { | ||
return (pathsToExclude && Array.isArray(pathsToExclude)) ? pathsToExclude.includes(currentPath) : false | ||
} | ||
const minify = typeof globalOptions.useHtmlMinifier?.minify === 'function' | ||
? globalOptions.useHtmlMinifier.minify | ||
: null | ||
function useHtmlMinification (globalOpts, requestedPath) { | ||
return globalOptions.useHtmlMinifier && | ||
(typeof globalOptions.useHtmlMinifier.minify === 'function') && | ||
!isPathExcludedMinification(requestedPath, globalOptions.pathsToExcludeHtmlMinifier) | ||
} | ||
const minifyExcludedPaths = Array.isArray(globalOptions.pathsToExcludeHtmlMinifier) | ||
? new Set(globalOptions.pathsToExcludeHtmlMinifier) | ||
: null | ||
function getRequestedPath (fastify) { | ||
return (fastify && fastify.request) ? fastify.request.routeOptions.url : null | ||
return fastify?.request?.routeOptions.url ?? null | ||
} | ||
function onTemplatesLoaded (file, data, callback, requestedPath) { | ||
if (useHtmlMinification(globalOptions, requestedPath)) { | ||
data = globalOptions.useHtmlMinifier.minify(data, globalOptions.htmlMinifierOptions || {}) | ||
} | ||
function isPathExcludedMinification (that) { | ||
return minifyExcludedPaths?.has(getRequestedPath(that)) | ||
} | ||
function onTemplatesLoaded (file, data) { | ||
if (type === 'handlebars') { | ||
@@ -201,10 +213,10 @@ data = engine.compile(data, globalOptions.compileOptions) | ||
lru.set(file, data) | ||
callback(null, data) | ||
return data | ||
} | ||
// Gets template as string (or precompiled for Handlebars) | ||
// from LRU cache or filesystem. | ||
const getTemplate = function (file, callback, requestedPath) { | ||
const getTemplate = async function (file) { | ||
if (typeof file === 'function') { | ||
callback(null, file) | ||
return | ||
return file | ||
} | ||
@@ -218,48 +230,28 @@ let isRaw = false | ||
if (data && prod) { | ||
callback(null, data) | ||
return | ||
return data | ||
} | ||
if (isRaw) { | ||
onTemplatesLoaded(file, file, callback, requestedPath) | ||
return | ||
return onTemplatesLoaded(file, file) | ||
} | ||
readFile(join(templatesDir, file), 'utf-8', (err, data) => { | ||
if (err) { | ||
callback(err, null) | ||
return | ||
} | ||
onTemplatesLoaded(file, data, callback, requestedPath) | ||
}) | ||
const fileData = await readFileSemaphore(join(templatesDir, file)) | ||
return onTemplatesLoaded(file, fileData) | ||
} | ||
// Gets partials as collection of strings from LRU cache or filesystem. | ||
const getPartials = function (page, { partials, requestedPath }, callback) { | ||
const getPartials = async function (page, { partials, requestedPath }) { | ||
const cacheKey = getPartialsCacheKey(page, partials, requestedPath) | ||
const partialsObj = lru.get(cacheKey) | ||
if (partialsObj && prod) { | ||
callback(null, partialsObj) | ||
return partialsObj | ||
} else { | ||
let filesToLoad = Object.keys(partials).length | ||
if (filesToLoad === 0) { | ||
callback(null, {}) | ||
return | ||
const partialKeys = Object.keys(partials) | ||
if (partialKeys.length === 0) { | ||
return {} | ||
} | ||
let error = null | ||
const partialsHtml = {} | ||
Object.keys(partials).forEach((key, index) => { | ||
readFile(join(templatesDir, partials[key]), 'utf-8', (err, data) => { | ||
if (err) { | ||
error = err | ||
} | ||
if (useHtmlMinification(globalOptions, requestedPath)) { | ||
data = globalOptions.useHtmlMinifier.minify(data, globalOptions.htmlMinifierOptions || {}) | ||
} | ||
partialsHtml[key] = data | ||
if (--filesToLoad === 0) { | ||
lru.set(cacheKey, partialsHtml) | ||
callback(error, partialsHtml) | ||
} | ||
}) | ||
}) | ||
await Promise.all(partialKeys.map(async (key) => { | ||
partialsHtml[key] = await readFileSemaphore(join(templatesDir, partials[key])) | ||
})) | ||
lru.set(cacheKey, partialsHtml) | ||
return partialsHtml | ||
} | ||
@@ -280,51 +272,18 @@ } | ||
function readCallbackEnd (that, compile, data, localOptions) { | ||
let cachedPage | ||
try { | ||
cachedPage = compile(data) | ||
} catch (error) { | ||
cachedPage = error | ||
} | ||
if (!that.getHeader('content-type')) { | ||
that.header('Content-Type', 'text/html; charset=' + charset) | ||
} | ||
const requestedPath = getRequestedPath(that) | ||
if (!localOptions) { | ||
localOptions = globalOptions | ||
} | ||
if (type === 'ejs' && ((localOptions.async ?? globalOptions.async) || cachedPage instanceof Promise)) { | ||
cachedPage.then(html => { | ||
if (useHtmlMinification(globalOptions, requestedPath)) { | ||
html = globalOptions.useHtmlMinifier.minify(html, globalOptions.htmlMinifierOptions || {}) | ||
function readCallbackParser (page, html, localOptions) { | ||
if ((type === 'ejs') && viewExt && !globalOptions.includer) { | ||
globalOptions.includer = (originalPath, parsedPath) => { | ||
return { | ||
filename: parsedPath || join(templatesDir, originalPath + '.' + viewExt) | ||
} | ||
that.send(html) | ||
}).catch(err => that.send(err)) | ||
return | ||
} | ||
} | ||
if (useHtmlMinification(globalOptions, requestedPath)) { | ||
cachedPage = globalOptions.useHtmlMinifier.minify(cachedPage, globalOptions.htmlMinifierOptions || {}) | ||
} | ||
that.send(cachedPage) | ||
} | ||
if (localOptions) { | ||
for (const key in globalOptions) { | ||
if (!Object.prototype.hasOwnProperty.call(localOptions, key)) localOptions[key] = globalOptions[key] | ||
} | ||
} else localOptions = globalOptions | ||
function readCallbackParser (that, page, html, localOptions) { | ||
let compiledPage | ||
try { | ||
if ((type === 'ejs') && viewExt && !globalOptions.includer) { | ||
globalOptions.includer = (originalPath, parsedPath) => { | ||
return { | ||
filename: parsedPath || join(templatesDir, originalPath + '.' + viewExt) | ||
} | ||
} | ||
} | ||
if (localOptions) { | ||
for (const key in globalOptions) { | ||
if (!Object.prototype.hasOwnProperty.call(localOptions, key)) localOptions[key] = globalOptions[key] | ||
} | ||
} else localOptions = globalOptions | ||
compiledPage = engine.compile(html, localOptions) | ||
} catch (error) { | ||
that.send(error) | ||
return | ||
} | ||
const compiledPage = engine.compile(html, localOptions) | ||
lru.set(page, compiledPage) | ||
@@ -334,13 +293,5 @@ return compiledPage | ||
function readCallback (that, page, data, localOptions) { | ||
return function _readCallback (err, html) { | ||
if (err) { | ||
that.send(err) | ||
return | ||
} | ||
globalOptions.filename = join(templatesDir, page) | ||
const compiledPage = readCallbackParser(that, page, html, localOptions) | ||
readCallbackEnd(that, compiledPage, data, localOptions) | ||
} | ||
function readCallback (page, data, localOptions, html) { | ||
globalOptions.filename = join(templatesDir, page) | ||
return readCallbackParser(page, html, localOptions) | ||
} | ||
@@ -376,12 +327,6 @@ | ||
function view (page, data, opts) { | ||
if (!page) { | ||
this.send(new Error('Missing page')) | ||
return | ||
} | ||
async function view (page, data, opts) { | ||
data = Object.assign({}, defaultCtx, this.locals, data) | ||
if (typeof page === 'function') { | ||
readCallbackEnd(this, page, data, opts) | ||
return | ||
return page(data) | ||
} | ||
@@ -396,37 +341,24 @@ let isRaw = false | ||
} | ||
const toHtml = lru.get(page) | ||
if (toHtml && prod) { | ||
readCallbackEnd(this, toHtml, data, opts) | ||
return | ||
return toHtml(data) | ||
} else if (isRaw) { | ||
const compiledPage = readCallbackParser(page, page, opts) | ||
return compiledPage(data) | ||
} | ||
if (isRaw) { | ||
const compiledPage = readCallbackParser(this, page, page, opts) | ||
readCallbackEnd(this, compiledPage, data, opts) | ||
return | ||
} | ||
readFile(join(templatesDir, page), 'utf8', readCallback(this, page, data, opts)) | ||
const file = await readFileSemaphore(join(templatesDir, page)) | ||
const render = readCallback(page, data, opts, file) | ||
return render(data) | ||
} | ||
function viewEjs (page, data, opts) { | ||
if (opts && opts.layout) { | ||
try { | ||
layoutIsValid(opts.layout) | ||
const that = this | ||
return withLayout(viewEjs, opts.layout).call(that, page, data) | ||
} catch (error) { | ||
this.send(error) | ||
return | ||
} | ||
async function viewEjs (page, data, opts) { | ||
if (opts?.layout) { | ||
layoutIsValid(opts.layout) | ||
return withLayout(viewEjs, opts.layout).call(this, page, data) | ||
} | ||
if (!page) { | ||
this.send(new Error('Missing page')) | ||
return | ||
} | ||
data = Object.assign({}, defaultCtx, this.locals, data) | ||
if (typeof page === 'function') { | ||
readCallbackEnd(this, page, data, opts) | ||
return | ||
return page(data) | ||
} | ||
@@ -444,18 +376,14 @@ let isRaw = false | ||
if (toHtml && prod) { | ||
readCallbackEnd(this, toHtml, data, opts) | ||
return | ||
return toHtml(data) | ||
} else if (isRaw) { | ||
const compiledPage = readCallbackParser(page, page, opts) | ||
return compiledPage(data) | ||
} | ||
if (isRaw) { | ||
const compiledPage = readCallbackParser(this, page, page, opts) | ||
readCallbackEnd(this, compiledPage, data, opts) | ||
return | ||
} | ||
readFile(join(templatesDir, page), 'utf8', readCallback(this, page, data, opts)) | ||
const file = await readFileSemaphore(join(templatesDir, page)) | ||
const render = readCallback(page, data, opts, file) | ||
return render(data) | ||
} | ||
function viewArtTemplate (page, data) { | ||
if (!page) { | ||
this.send(new Error('Missing page')) | ||
return | ||
} | ||
async function viewArtTemplate (page, data) { | ||
data = Object.assign({}, defaultCtx, this.locals, data) | ||
@@ -492,18 +420,6 @@ | ||
try { | ||
const html = render(page, data) | ||
if (!this.getHeader('content-type')) { | ||
this.header('Content-Type', 'text/html; charset=' + charset) | ||
} | ||
this.send(html) | ||
} catch (error) { | ||
this.send(error) | ||
} | ||
return render(page, data) | ||
} | ||
function viewNunjucks (page, data) { | ||
if (!page) { | ||
this.send(new Error('Missing page')) | ||
return | ||
} | ||
async function viewNunjucks (page, data) { | ||
data = Object.assign({}, defaultCtx, this.locals, data) | ||
@@ -514,3 +430,2 @@ let render | ||
page = getPage(page, 'njk') | ||
// template = nunjucksEnv.getTemplate(page) | ||
render = nunjucksEnv.render.bind(nunjucksEnv, page) | ||
@@ -524,32 +439,20 @@ } else if (typeof page === 'object' && typeof page.render === 'function') { | ||
} | ||
render(data, (err, html) => { | ||
const requestedPath = getRequestedPath(this) | ||
if (err) return this.send(err) | ||
if (useHtmlMinification(globalOptions, requestedPath)) { | ||
html = globalOptions.useHtmlMinifier.minify(html, globalOptions.htmlMinifierOptions || {}) | ||
} | ||
if (!this.getHeader('content-type')) { | ||
this.header('Content-Type', 'text/html; charset=' + charset) | ||
} | ||
this.send(html) | ||
return new Promise((resolve, reject) => { | ||
render(data, (err, html) => { | ||
if (err) { | ||
reject(err) | ||
return | ||
} | ||
resolve(html) | ||
}) | ||
}) | ||
} | ||
function viewHandlebars (page, data, opts) { | ||
if (opts && opts.layout) { | ||
try { | ||
layoutIsValid(opts.layout) | ||
const that = this | ||
return withLayout(viewHandlebars, opts.layout).call(that, page, data) | ||
} catch (error) { | ||
this.send(error) | ||
return | ||
} | ||
async function viewHandlebars (page, data, opts) { | ||
if (opts?.layout) { | ||
layoutIsValid(opts.layout) | ||
return withLayout(viewHandlebars, opts.layout).call(this, page, data) | ||
} | ||
if (!page) { | ||
this.send(new Error('Missing page')) | ||
return | ||
} | ||
let options | ||
@@ -570,50 +473,19 @@ | ||
const requestedPath = getRequestedPath(this) | ||
getTemplate(page, (err, template) => { | ||
if (err) { | ||
this.send(err) | ||
return | ||
} | ||
const template = await getTemplate(page) | ||
if (prod) { | ||
try { | ||
const html = template(data, options) | ||
if (!this.getHeader('content-type')) { | ||
this.header('Content-Type', 'text/html; charset=' + charset) | ||
} | ||
this.send(html) | ||
} catch (e) { | ||
this.send(e) | ||
} | ||
} else { | ||
getPartials(type, { partials: globalOptions.partials || {}, requestedPath }, (err, partialsObject) => { | ||
if (err) { | ||
this.send(err) | ||
return | ||
} | ||
if (prod) { | ||
const html = template(data, options) | ||
return html | ||
} else { | ||
const partialsObject = await getPartials(type, { partials: globalOptions.partials || {}, requestedPath }) | ||
try { | ||
Object.keys(partialsObject).forEach((name) => { | ||
engine.registerPartial(name, engine.compile(partialsObject[name], globalOptions.compileOptions)) | ||
}) | ||
Object.keys(partialsObject).forEach((name) => { | ||
engine.registerPartial(name, engine.compile(partialsObject[name], globalOptions.compileOptions)) | ||
}) | ||
const html = template(data, options) | ||
if (!this.getHeader('content-type')) { | ||
this.header('Content-Type', 'text/html; charset=' + charset) | ||
} | ||
this.send(html) | ||
} catch (e) { | ||
this.send(e) | ||
} | ||
}) | ||
} | ||
}, requestedPath) | ||
return template(data, options) | ||
} | ||
} | ||
function viewMustache (page, data, opts) { | ||
if (!page) { | ||
this.send(new Error('Missing page')) | ||
return | ||
} | ||
async function viewMustache (page, data, opts) { | ||
const options = Object.assign({}, opts) | ||
@@ -626,33 +498,16 @@ data = Object.assign({}, defaultCtx, this.locals, data) | ||
const requestedPath = getRequestedPath(this) | ||
getTemplate(page, (err, templateString) => { | ||
if (err) { | ||
this.send(err) | ||
return | ||
} | ||
getPartials(page, { partials: options.partials || {}, requestedPath }, (err, partialsObject) => { | ||
if (err) { | ||
this.send(err) | ||
return | ||
} | ||
let html | ||
if (typeof templateString === 'function') { | ||
html = templateString(data, partialsObject) | ||
} else { | ||
html = engine.render(templateString, data, partialsObject) | ||
} | ||
const templateString = await getTemplate(page) | ||
const partialsObject = await getPartials(page, { partials: options.partials || {}, requestedPath }) | ||
if (!this.getHeader('content-type')) { | ||
this.header('Content-Type', 'text/html; charset=' + charset) | ||
} | ||
this.send(html) | ||
}) | ||
}, requestedPath) | ||
let html | ||
if (typeof templateString === 'function') { | ||
html = templateString(data, partialsObject) | ||
} else { | ||
html = engine.render(templateString, data, partialsObject) | ||
} | ||
return html | ||
} | ||
function viewTwig (page, data, opts) { | ||
if (!page) { | ||
this.send(new Error('Missing page')) | ||
return | ||
} | ||
async function viewTwig (page, data, opts) { | ||
data = Object.assign({}, defaultCtx, globalOptions, this.locals, data) | ||
@@ -671,23 +526,15 @@ let render | ||
} | ||
render(data, (err, html) => { | ||
const requestedPath = getRequestedPath(this) | ||
if (err) { | ||
return this.send(err) | ||
} | ||
if (useHtmlMinification(globalOptions, requestedPath)) { | ||
html = globalOptions.useHtmlMinifier.minify(html, globalOptions.htmlMinifierOptions || {}) | ||
} | ||
if (!this.getHeader('content-type')) { | ||
this.header('Content-Type', 'text/html; charset=' + charset) | ||
} | ||
this.send(html) | ||
return new Promise((resolve, reject) => { | ||
render(data, (err, html) => { | ||
if (err) { | ||
reject(err) | ||
return | ||
} | ||
resolve(html) | ||
}) | ||
}) | ||
} | ||
function viewLiquid (page, data, opts) { | ||
if (!page) { | ||
this.send(new Error('Missing page')) | ||
return | ||
} | ||
async function viewLiquid (page, data, opts) { | ||
data = Object.assign({}, defaultCtx, this.locals, data) | ||
@@ -706,34 +553,11 @@ let render | ||
render(data, opts) | ||
.then((html) => { | ||
const requestedPath = getRequestedPath(this) | ||
if (useHtmlMinification(globalOptions, requestedPath)) { | ||
html = globalOptions.useHtmlMinifier.minify(html, globalOptions.htmlMinifierOptions || {}) | ||
} | ||
if (!this.getHeader('content-type')) { | ||
this.header('Content-Type', 'text/html; charset=' + charset) | ||
} | ||
this.send(html) | ||
}) | ||
.catch((err) => { | ||
this.send(err) | ||
}) | ||
return render(data, opts) | ||
} | ||
function viewDot (renderModule) { | ||
return function _viewDot (page, data, opts) { | ||
if (opts && opts.layout) { | ||
try { | ||
layoutIsValid(opts.layout) | ||
const that = this | ||
return withLayout(dotRender, opts.layout).call(that, page, data) | ||
} catch (error) { | ||
this.send(error) | ||
return | ||
} | ||
return async function _viewDot (page, data, opts) { | ||
if (opts?.layout) { | ||
layoutIsValid(opts.layout) | ||
return withLayout(dotRender, opts.layout).call(this, page, data) | ||
} | ||
if (!page) { | ||
this.send(new Error('Missing page')) | ||
return | ||
} | ||
data = Object.assign({}, defaultCtx, this.locals, data) | ||
@@ -748,31 +572,12 @@ let render | ||
} | ||
let html = render(data) | ||
const requestedPath = getRequestedPath(this) | ||
if (useHtmlMinification(globalOptions, requestedPath)) { | ||
html = globalOptions.useHtmlMinifier.minify(html, globalOptions.htmlMinifierOptions || {}) | ||
} | ||
if (!this.getHeader('content-type')) { | ||
this.header('Content-Type', 'text/html; charset=' + charset) | ||
} | ||
this.send(html) | ||
return render(data) | ||
} | ||
} | ||
function viewEta (page, data, opts) { | ||
if (opts && opts.layout) { | ||
try { | ||
layoutIsValid(opts.layout) | ||
const that = this | ||
return withLayout(viewEta, opts.layout).call(that, page, data) | ||
} catch (error) { | ||
this.send(error) | ||
return | ||
} | ||
async function viewEta (page, data, opts) { | ||
if (opts?.layout) { | ||
layoutIsValid(opts.layout) | ||
return withLayout(viewEta, opts.layout).call(this, page, data) | ||
} | ||
if (!page) { | ||
this.send(new Error('Missing page')) | ||
return | ||
} | ||
if (globalOptions.templatesSync) { | ||
@@ -794,33 +599,7 @@ engine.templatesSync = globalOptions.templatesSync | ||
const sendContent = html => { | ||
if ( | ||
config.useHtmlMinifier && | ||
typeof config.useHtmlMinifier.minify === 'function' && | ||
!isPathExcludedMinification(getRequestedPath(this), config.pathsToExcludeHtmlMinifier) | ||
) { | ||
html = config.useHtmlMinifier.minify( | ||
html, | ||
config.htmlMinifierOptions || {} | ||
) | ||
} | ||
this.header('Content-Type', 'text/html; charset=' + charset) | ||
this.send(html) | ||
} | ||
data = Object.assign({}, defaultCtx, this.locals, data) | ||
if (typeof page === 'function') { | ||
try { | ||
const ret = page.call(engine, data, config) | ||
if (ret instanceof Promise) { | ||
ret.then(sendContent).catch(err => { | ||
this.send(err) | ||
}) | ||
} else { | ||
sendContent(ret) | ||
} | ||
} catch (err) { | ||
this.send(err) | ||
} | ||
return | ||
const ret = await page.call(engine, data, config) | ||
return ret | ||
} | ||
@@ -842,16 +621,5 @@ | ||
if (opts?.async ?? globalOptions.async) { | ||
renderAsync(page, data, config) | ||
.then((res) => { | ||
sendContent(res) | ||
}) | ||
.catch(err => { | ||
this.send(err) | ||
}) | ||
return renderAsync(page, data, config) | ||
} else { | ||
try { | ||
const html = render(page, data, config) | ||
sendContent(html) | ||
} catch (err) { | ||
this.send(err) | ||
} | ||
return render(page, data, config) | ||
} | ||
@@ -861,14 +629,6 @@ } | ||
if (prod && type === 'handlebars' && globalOptions.partials) { | ||
getPartials(type, { partials: globalOptions.partials, requestedPath: getRequestedPath(this) }, (err, partialsObject) => { | ||
if (err) { | ||
next(err) | ||
return | ||
} | ||
Object.keys(partialsObject).forEach((name) => { | ||
engine.registerPartial(name, engine.compile(partialsObject[name], globalOptions.compileOptions)) | ||
}) | ||
next() | ||
const partialsObject = await getPartials(type, { partials: globalOptions.partials, requestedPath: getRequestedPath(this) }) | ||
Object.keys(partialsObject).forEach((name) => { | ||
engine.registerPartial(name, engine.compile(partialsObject[name], globalOptions.compileOptions)) | ||
}) | ||
} else { | ||
next() | ||
} | ||
@@ -878,19 +638,8 @@ | ||
if (layout) { | ||
return function (page, data, opts) { | ||
if (opts && opts.layout) throw new Error('A layout can either be set globally or on render, not both.') | ||
const that = this | ||
return async function (page, data, opts) { | ||
if (opts?.layout) throw new Error('A layout can either be set globally or on render, not both.') | ||
data = Object.assign({}, defaultCtx, this.locals, data) | ||
render.call({ | ||
getHeader: () => { }, | ||
header: () => { }, | ||
send: (result) => { | ||
if (result instanceof Error) { | ||
that.send(result) | ||
return | ||
} | ||
data = Object.assign(data, { body: result }) | ||
render.call(that, layout, data, opts) | ||
} | ||
}, page, data, opts) | ||
const result = await render.call(this, page, data, opts) | ||
data = Object.assign(data, { body: result }) | ||
return render.call(this, layout, data, opts) | ||
} | ||
@@ -897,0 +646,0 @@ } |
{ | ||
"name": "@fastify/view", | ||
"version": "8.2.0", | ||
"version": "9.0.0", | ||
"description": "Template plugin for Fastify", | ||
"main": "index.js", | ||
"type": "commonjs", | ||
"types": "types/index.d.ts", | ||
"scripts": { | ||
"benchmark": "node benchmark.js", | ||
"example": "node examples/example.js", | ||
@@ -48,2 +50,3 @@ "example-with-options": "node examples/example-ejs-with-some-options.js", | ||
"art-template": "^4.13.2", | ||
"autocannon": "^7.15.0", | ||
"cross-env": "^7.0.2", | ||
@@ -67,3 +70,3 @@ "dot": "^1.1.3", | ||
"tap": "^16.0.0", | ||
"tsd": "^0.29.0", | ||
"tsd": "^0.30.0", | ||
"twig": "^1.13.3" | ||
@@ -70,0 +73,0 @@ }, |
@@ -1203,1 +1203,52 @@ 'use strict' | ||
}) | ||
test('reply.view with ejs engine and failed call to render when onError hook defined', t => { | ||
t.plan(6) | ||
const fastify = Fastify() | ||
const ejs = require('ejs') | ||
fastify.register(require('../index'), { | ||
engine: { | ||
ejs | ||
} | ||
}) | ||
fastify.get('/invalid', (req, reply) => { | ||
// Note the mistake in the ternary statement -- the second `?` should be a `:` | ||
reply.view({ | ||
raw: '<p><%= true ? "text" ? "text2" %></p>' | ||
}) | ||
}) | ||
fastify.get('/valid', (req, reply) => { | ||
reply.view({ | ||
raw: '<%= true ? "text" : "text2" %>' | ||
}) | ||
}) | ||
// when onError hook is defined, certain errors (such as calls to reply.send inside the `onError` hook) are uncaught | ||
fastify.addHook('onError', async (request, reply, err) => {}) | ||
fastify.listen({ port: 0 }, err => { | ||
t.error(err) | ||
// request route with invalid template, followed by route with valid template | ||
// in order to ensure server does not crash after first request | ||
sget({ | ||
method: 'GET', | ||
url: `http://localhost:${fastify.server.address().port}/invalid` | ||
}, (err, response) => { | ||
t.error(err) | ||
t.equal(response.statusCode, 500) | ||
sget({ | ||
method: 'GET', | ||
url: `http://localhost:${fastify.server.address().port}/valid` | ||
}, (err, response, body) => { | ||
t.error(err) | ||
t.equal('text', body.toString()) | ||
t.equal(response.statusCode, 200) | ||
fastify.close() | ||
}) | ||
}) | ||
}) | ||
}) |
@@ -847,3 +847,3 @@ 'use strict' | ||
get (k) { | ||
if (typeof this.cache[k] !== 'undefined') { | ||
if (this.cache[k] !== undefined) { | ||
t.pass() | ||
@@ -850,0 +850,0 @@ } |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
116
327437
24
9341
6
1