@fastify/view
Advanced tools
Comparing version 9.0.0 to 9.1.0
@@ -7,3 +7,3 @@ 'use strict' | ||
options: { | ||
useHtmlMinifier: require('html-minifier'), | ||
useHtmlMinifier: require('html-minifier-terser'), | ||
htmlMinifierOptions: { | ||
@@ -10,0 +10,0 @@ removeComments: true, |
105
index.js
@@ -6,6 +6,22 @@ 'use strict' | ||
const { basename, dirname, extname, join, resolve } = require('node:path') | ||
const HLRU = require('hashlru') | ||
const { LruMap } = require('toad-cache') | ||
const supportedEngines = ['ejs', 'nunjucks', 'pug', 'handlebars', 'mustache', 'art-template', 'twig', 'liquid', 'dot', 'eta'] | ||
const viewCache = Symbol('@fastify/view/cache') | ||
const fastifyViewCache = fp( | ||
async function cachePlugin (fastify, opts) { | ||
const lru = new LruMap(opts.maxCache || 100) | ||
fastify.decorate(viewCache, lru) | ||
}, | ||
{ | ||
fastify: '4.x', | ||
name: '@fastify/view/cache' | ||
} | ||
) | ||
async function fastifyView (fastify, opts) { | ||
if (fastify[viewCache] === undefined) { | ||
await fastify.register(fastifyViewCache, opts) | ||
} | ||
if (!opts.engine) { | ||
@@ -20,6 +36,6 @@ throw new Error('Missing engine') | ||
const propertyName = opts.propertyName || 'view' | ||
const engine = opts.engine[type] | ||
const asyncPropertyName = opts.asyncPropertyName || `${propertyName}Async` | ||
const engine = await opts.engine[type] | ||
const globalOptions = opts.options || {} | ||
const templatesDir = resolveTemplateDir(opts) | ||
const lru = HLRU(opts.maxCache || 100) | ||
const includeViewExtension = opts.includeViewExtension || false | ||
@@ -96,2 +112,20 @@ const viewExt = opts.viewExt || '' | ||
async function asyncRender (page) { | ||
if (!page) { | ||
throw new Error('Missing page') | ||
} | ||
let result = await renderer.apply(this, arguments) | ||
if (minify && !isPathExcludedMinification(this)) { | ||
result = await minify(result, globalOptions.htmlMinifierOptions) | ||
} | ||
if (this.getHeader && !this.getHeader('Content-Type')) { | ||
this.header('Content-Type', 'text/html; charset=' + charset) | ||
} | ||
return result | ||
} | ||
function viewDecorator (page) { | ||
@@ -105,8 +139,4 @@ const args = Array.from(arguments) | ||
let promise = !page ? Promise.reject(new Error('Missing page')) : renderer.apply(this, args) | ||
const promise = asyncRender.apply({}, args) | ||
if (minify) { | ||
promise = promise.then((result) => minify(result, globalOptions.htmlMinifierOptions)) | ||
} | ||
if (typeof done === 'function') { | ||
@@ -121,3 +151,3 @@ promise.then(done.bind(null, null), done) | ||
viewDecorator.clearCache = function () { | ||
lru.clear() | ||
fastify[viewCache].clear() | ||
} | ||
@@ -127,23 +157,15 @@ | ||
fastify.decorateReply(propertyName, async function (page) { | ||
if (!page) { | ||
this.send(new Error('Missing page')) | ||
} | ||
fastify.decorateReply(propertyName, async function (page, data, opts) { | ||
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) | ||
} | ||
const html = await asyncRender.call(this, page, data, opts) | ||
this.send(html) | ||
} catch (err) { | ||
this.send(err) | ||
} | ||
return this | ||
}) | ||
fastify.decorateReply(asyncPropertyName, asyncRender) | ||
if (!fastify.hasReplyDecorator('locals')) { | ||
@@ -160,3 +182,3 @@ fastify.decorateReply('locals', null) | ||
const pageLRU = `getPage-${page}-${extension}` | ||
let result = lru.get(pageLRU) | ||
let result = fastify[viewCache].get(pageLRU) | ||
@@ -170,3 +192,3 @@ if (typeof result === 'string') { | ||
lru.set(pageLRU, result) | ||
fastify[viewCache].set(pageLRU, result) | ||
@@ -213,3 +235,3 @@ return result | ||
} | ||
lru.set(file, data) | ||
fastify[viewCache].set(file, data) | ||
return data | ||
@@ -229,3 +251,3 @@ } | ||
} | ||
const data = lru.get(file) | ||
const data = fastify[viewCache].get(file) | ||
if (data && prod) { | ||
@@ -244,3 +266,3 @@ return data | ||
const cacheKey = getPartialsCacheKey(page, partials, requestedPath) | ||
const partialsObj = lru.get(cacheKey) | ||
const partialsObj = fastify[viewCache].get(cacheKey) | ||
if (partialsObj && prod) { | ||
@@ -257,3 +279,3 @@ return partialsObj | ||
})) | ||
lru.set(cacheKey, partialsHtml) | ||
fastify[viewCache].set(cacheKey, partialsHtml) | ||
return partialsHtml | ||
@@ -291,3 +313,3 @@ } | ||
lru.set(page, compiledPage) | ||
fastify[viewCache].set(page, compiledPage) | ||
return compiledPage | ||
@@ -342,3 +364,3 @@ } | ||
} | ||
const toHtml = lru.get(page) | ||
const toHtml = fastify[viewCache].get(page) | ||
@@ -374,3 +396,3 @@ if (toHtml && prod) { | ||
} | ||
const toHtml = lru.get(page) | ||
const toHtml = fastify[viewCache].get(page) | ||
@@ -580,4 +602,2 @@ if (toHtml && prod) { | ||
lru.define = lru.set | ||
engine.configure({ | ||
@@ -651,9 +671,19 @@ views: templatesDir, | ||
function hasAccessToLayoutFile (fileName, ext) { | ||
const layoutKey = `layout-${fileName}-${ext}` | ||
let result = fastify[viewCache].get(layoutKey) | ||
if (typeof result === 'boolean') { | ||
return result | ||
} | ||
try { | ||
accessSync(join(templatesDir, getPage(fileName, ext))) | ||
return true | ||
result = true | ||
} catch (e) { | ||
return false | ||
result = false | ||
} | ||
fastify[viewCache].set(layoutKey, result) | ||
return result | ||
} | ||
@@ -668,1 +698,2 @@ } | ||
module.exports.fastifyView = fastifyView | ||
module.exports.fastifyViewCache = viewCache |
{ | ||
"name": "@fastify/view", | ||
"version": "9.0.0", | ||
"version": "9.1.0", | ||
"description": "Template plugin for Fastify", | ||
@@ -44,3 +44,3 @@ "main": "index.js", | ||
"fastify-plugin": "^4.0.0", | ||
"hashlru": "^2.3.0" | ||
"toad-cache": "^3.7.0" | ||
}, | ||
@@ -59,3 +59,3 @@ "devDependencies": { | ||
"handlebars": "^4.7.6", | ||
"html-minifier": "^4.0.0", | ||
"html-minifier-terser": "^7.2.0", | ||
"liquidjs": "^10.0.0", | ||
@@ -65,3 +65,2 @@ "mustache": "^4.0.1", | ||
"pino": "^8.0.0", | ||
"proxyquire": "^2.1.3", | ||
"pug": "^3.0.0", | ||
@@ -72,3 +71,3 @@ "simple-get": "^4.0.0", | ||
"tap": "^16.0.0", | ||
"tsd": "^0.30.0", | ||
"tsd": "^0.31.0", | ||
"twig": "^1.13.3" | ||
@@ -75,0 +74,0 @@ }, |
283
README.md
@@ -9,3 +9,3 @@ # @fastify/view | ||
`@fastify/view` decorates the reply interface with the `view` method for managing view engines, which can be used to render templates responses. | ||
`@fastify/view` decorates the reply interface with the `view` and `viewAsync` methods for managing view engines, which can be used to render templates responses. | ||
@@ -31,2 +31,6 @@ Currently supports the following templates engines: | ||
## Recent Changes | ||
_Note: `reply.viewAsync` added as a replacement for `reply.view` and `fastify.view`. See [Migrating from view to viewAsync](#migrating-from-view-to-viewAsync)._ | ||
_Note: [`ejs-mate`](https://github.com/JacksonTian/ejs-mate) support [has been dropped](https://github.com/fastify/point-of-view/pull/157)._ | ||
@@ -59,52 +63,62 @@ | ||
This example will render the template and provide a variable `text` to be used inside the template: | ||
This example will render the template using the EJS engine and provide a variable `name` to be used inside the template: | ||
```html | ||
<!-- index.ejs ---> | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head></head> | ||
<body> | ||
<p>Hello, <%= name %>!</p> | ||
</body> | ||
</html> | ||
``` | ||
```js | ||
const fastify = require("fastify")(); | ||
// index.js: | ||
const fastify = require("fastify")() | ||
const fastifyView = require("@fastify/view") | ||
fastify.register(require("@fastify/view"), { | ||
fastify.register(fastifyView, { | ||
engine: { | ||
ejs: require("ejs"), | ||
}, | ||
}); | ||
ejs: require("ejs") | ||
} | ||
}) | ||
// synchronous handler: | ||
fastify.get("/", (req, reply) => { | ||
reply.view("/templates/index.ejs", { text: "text" }); | ||
}); | ||
reply.view("index.ejs", { name: "User" }); | ||
}) | ||
// asynchronous handler: | ||
fastify.get("/", async (req, reply) => { | ||
return reply.viewAsync("index.ejs", { name: "User" }); | ||
}) | ||
fastify.listen({ port: 3000 }, (err) => { | ||
if (err) throw err; | ||
console.log(`server listening on ${fastify.server.address().port}`); | ||
}); | ||
}) | ||
``` | ||
If your handler function is asynchronous, make sure to return the result - otherwise this will result in an `FST_ERR_PROMISE_NOT_FULFILLED` error: | ||
```js | ||
// This is an async function | ||
fastify.get("/", async (req, reply) => { | ||
// We are awaiting a function result | ||
const t = await something(); | ||
// Note the return statement | ||
return reply.view("/templates/index.ejs", { text: "text" }); | ||
}); | ||
``` | ||
## Configuration | ||
`fastify.register(<engine>, <options>)` accepts an options object. | ||
### Options | ||
- `engine`: The template engine object - pass in the return value of `require('<engine>')`. This option is mandatory. | ||
- `layout`: @fastify/view supports layouts for **EJS**, **Handlebars**, **Eta** and **doT**. This option lets you specify a global layout file to be used when rendering your templates. Settings like `root` or `viewExt` apply as for any other template file. Example: `./templates/layouts/main.hbs` | ||
- `propertyName`: The property that should be used to decorate `reply` and `fastify` - E.g. `reply.view()` and `fastify.view()` where `"view"` is the property name. Default: `"view"`. | ||
- `root`: The root path of your templates folder. The template name or path passed to the render function will be resolved relative to this path. Default: `"./"`. | ||
- `includeViewExtension`: Setting this to `true` will automatically append the default extension for the used template engine **if omitted from the template name** . So instead of `template.hbs`, just `template` can be used. Default: `false`. | ||
- `viewExt`: Let's you override the default extension for a given template engine. This has precedence over `includeViewExtension` and will lead to the same behavior, just with a custom extension. Default `""`. Example: `"handlebars"`. | ||
- `defaultContext`: The template variables defined here will be available to all views. Variables provided on render have precedence and will **override** this if they have the same name. Default: `{}`. Example: `{ siteName: "MyAwesomeSite" }`. | ||
- `maxCache`: In `production` mode, maximum number of templates file and functions caches. Default: `100`. Example: `{ maxCache: 100 }`. | ||
| Option | Description | Default | | ||
| ---------------------- | ----------- | ------- | | ||
| `engine` | **Required**. The template engine object - pass in the return value of `require('<engine>')` | | | ||
| `production` | Enables caching of template files and render functions | `NODE_ENV === "production"` | | ||
| `maxCache` | In `production` mode, maximum number of cached template files and render functions | `100` | | ||
| `defaultContext` | Template variables available to all views. Variables provided on render have precedence and will **override** this if they have the same name. <br><br>Example: `{ siteName: "MyAwesomeSite" }` | `{}` | | ||
| `propertyName` | The property that should be used to decorate `reply` and `fastify` <br><br>E.g. `reply.view()` and `fastify.view()` where `"view"` is the property name | `"view"` | | ||
| `asyncPropertyName` | The property that should be used to decorate `reply` for async handler <br><br>Defaults to `${propertyName}Async` if `propertyName` is defined | `"viewAsync"` | | ||
| `root` | The root path of your templates folder. The template name or path passed to the render function will be resolved relative to this path | `"./"` | | ||
| `charset` | Default charset used when setting `Content-Type` header | `"utf-8"` | | ||
| `includeViewExtension` | Automatically append the default extension for the used template engine **if omitted from the template name** . So instead of `template.hbs`, just `template` can be used | `false` | | ||
| `viewExt` | Override the default extension for a given template engine. This has precedence over `includeViewExtension` and will lead to the same behavior, just with a custom extension. <br><br>Example: `"handlebars"` | `""` | | ||
| `layout` | See [Layouts](#layouts) <br><br>This option lets you specify a global layout file to be used when rendering your templates. Settings like `root` or `viewExt` apply as for any other template file. <br><br>Example: `./templates/layouts/main.hbs` | | | ||
| `options` | See [Engine-specific settings](#engine-specific-settings) | `{}` | | ||
Example: | ||
### Example | ||
@@ -127,7 +141,76 @@ ```js | ||
## Rendering the template into a variable | ||
## Layouts | ||
The `fastify` object is decorated the same way as `reply` and allows you to just render a view into a variable instead of sending the result back to the browser: | ||
@fastify/view supports layouts for **EJS**, **Handlebars**, **Eta** and **doT**. When a layout is specified, the request template is first rendered, then the layout template is rendered with the request-rendered html set on `body`. | ||
### Example | ||
```html | ||
<!-- layout.ejs: --> | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head></head> | ||
<body> | ||
<!-- | ||
Ensure body is not escaped: | ||
EJS: <%- body %> | ||
Handlebars: {{{ body }}} | ||
ETA/doT: <%~ it.body %> | ||
--> | ||
<%- body %> | ||
<br/> | ||
</body> | ||
</html> | ||
``` | ||
```html | ||
<!-- template.ejs: --> | ||
<p><%= text %></p> | ||
``` | ||
```js | ||
// index.js: | ||
fastify.register(fastifyView, { | ||
engine: { ejs }, | ||
layout: "layout.ejs" | ||
}) | ||
fastify.get('/', (req, reply) => { | ||
const data = { text: "Hello!"} | ||
reply.view('template.ejs', data) | ||
}) | ||
``` | ||
### Providing a layout on render | ||
**Please note:** Global layouts and providing layouts on render are mutually exclusive. They can not be mixed. | ||
```js | ||
fastify.get('/', (req, reply) => { | ||
const data = { text: "Hello!"} | ||
reply.view('template.ejs', data, { layout: 'layout.ejs' }) | ||
}) | ||
``` | ||
## Setting request-global variables | ||
Sometimes, several templates should have access to the same request-specific variables. E.g. when setting the current username. | ||
If you want to provide data, which will be depended on by a request and available in all views, you have to add property `locals` to `reply` object, like in the example below: | ||
```js | ||
fastify.addHook("preHandler", function (request, reply, done) { | ||
reply.locals = { | ||
text: getTextFromRequest(request), // it will be available in all views | ||
}; | ||
done(); | ||
}); | ||
``` | ||
Properties from `reply.locals` will override those from `defaultContext`, but not from `data` parameter provided to `reply.view(template, data)` function. | ||
## Rendering the template into a variable | ||
The `fastify` object is decorated the same way as `reply` and allows you to just render a view into a variable (without request-global variables) instead of sending the result back to the browser: | ||
```js | ||
// Promise based, using async/await | ||
@@ -143,2 +226,5 @@ const html = await fastify.view("/templates/index.ejs", { text: "text" }); | ||
If called within a request hook and you need request-global variables, see [Migrating from view to viewAsync](#migrating-from-view-to-viewAsync). | ||
## Registering multiple engines | ||
@@ -172,41 +258,11 @@ | ||
## Providing a layout on render | ||
@fastify/view supports layouts for **EJS**, **Handlebars**, **Eta** and **doT**. | ||
These engines also support providing a layout on render. | ||
**Please note:** Global layouts and providing layouts on render are mutually exclusive. They can not be mixed. | ||
```js | ||
fastify.get('/', (req, reply) => { | ||
reply.view('index-for-layout.ejs', data, { layout: 'layout.html' }) | ||
}) | ||
``` | ||
## Setting request-global variables | ||
Sometimes, several templates should have access to the same request-specific variables. E.g. when setting the current username. | ||
If you want to provide data, which will be depended on by a request and available in all views, you have to add property `locals` to `reply` object, like in the example below: | ||
```js | ||
fastify.addHook("preHandler", function (request, reply, done) { | ||
reply.locals = { | ||
text: getTextFromRequest(request), // it will be available in all views | ||
}; | ||
done(); | ||
}); | ||
``` | ||
Properties from `reply.locals` will override those from `defaultContext`, but not from `data` parameter provided to `reply.view(template, data)` function. | ||
## Minifying HTML on render | ||
To utilize [`html-minifier`](https://www.npmjs.com/package/html-minifier) in the rendering process, you can add the option `useHtmlMinifier` with a reference to `html-minifier`, | ||
and the optional `htmlMinifierOptions` option is used to specify the `html-minifier` options: | ||
To utilize [`html-minifier-terser`](https://www.npmjs.com/package/html-minifier-terser) in the rendering process, you can add the option `useHtmlMinifier` with a reference to `html-minifier-terser`, | ||
and the optional `htmlMinifierOptions` option is used to specify the `html-minifier-terser` options: | ||
```js | ||
// get a reference to html-minifier | ||
const minifier = require('html-minifier') | ||
// optionally defined the html-minifier options | ||
// get a reference to html-minifier-terser | ||
const minifier = require('html-minifier-terser') | ||
// optionally defined the html-minifier-terser options | ||
const minifierOpts = { | ||
@@ -229,5 +285,5 @@ removeComments: true, | ||
```js | ||
// get a reference to html-minifier | ||
const minifier = require('html-minifier') | ||
// in options configure the use of html-minifier and set paths to exclude from minification | ||
// get a reference to html-minifier-terser | ||
const minifier = require('html-minifier-terser') | ||
// in options configure the use of html-minifier-terser and set paths to exclude from minification | ||
const options = { | ||
@@ -739,3 +795,2 @@ useHtmlMinifier: minifier, | ||
``` | ||
<!--- | ||
@@ -809,6 +864,80 @@ // This seems a bit random given that there was no mention of typescript before. | ||
### Migrating from `view` to `viewAsync` | ||
The behavior of `reply.view` is to immediately send the HTML response as soon as rendering is completed, or immediately send a 500 response with error if encountered, short-circuiting fastify's error handling hooks, whereas `reply.viewAsync` returns a promise that either resolves to the rendered HTML, or rejects on any errors. `fastify.view` has no mechanism for providing request-global variables, if needed. `reply.viewAsync` can be used in both sync and async handlers. | ||
#### Sync handler | ||
Previously: | ||
```js | ||
fastify.get('/', (req, reply) => { | ||
reply.view('index.ejs', { text: 'text' }) | ||
}) | ||
``` | ||
Now: | ||
```js | ||
fastify.get('/', (req, reply) => { | ||
return reply.viewAsync('index.ejs', { text: 'text' }) | ||
}) | ||
``` | ||
#### Async handler | ||
Previously: | ||
```js | ||
// This is an async function | ||
fastify.get("/", async (req, reply) => { | ||
const data = await something(); | ||
reply.view("/templates/index.ejs", { data }); | ||
return | ||
}) | ||
``` | ||
Now: | ||
```js | ||
// This is an async function | ||
fastify.get("/", async (req, reply) => { | ||
const data = await something(); | ||
return reply.viewAsync("/templates/index.ejs", { data }); | ||
}) | ||
``` | ||
#### fastify.view (when called inside a route hook) | ||
Previously: | ||
```js | ||
// Promise based, using async/await | ||
fastify.get("/", async (req, reply) => { | ||
const html = await fastify.view("/templates/index.ejs", { text: "text" }); | ||
return html | ||
}) | ||
``` | ||
```js | ||
// Callback based | ||
fastify.get("/", (req, reply) => { | ||
fastify.view("/templates/index.ejs", { text: "text" }, (err, html) => { | ||
if(err) { | ||
reply.send(err) | ||
} | ||
else { | ||
reply.type("application/html").send(html) | ||
} | ||
}); | ||
}) | ||
``` | ||
Now: | ||
```js | ||
// Promise based, using async/await | ||
fastify.get("/", (req, reply) => { | ||
const html = await fastify.viewAsync("/templates/index.ejs", { text: "text" }); | ||
return html | ||
}) | ||
``` | ||
```js | ||
fastify.get("/", (req, reply) => { | ||
fastify.viewAsync("/templates/index.ejs", { text: "text" }) | ||
.then((html) => reply.type("application/html").send(html)) | ||
.catch((err) => reply.send(err)) | ||
}); | ||
}) | ||
``` | ||
## Note | ||
By default views are served with the mime type 'text/html; charset=utf-8', | ||
but you can specify a different value using the type function of reply, or by specifying the desired charset in the property 'charset' in the options object given to the plugin. | ||
By default views are served with the mime type `text/html`, with the charset specified in options. You can specify a different `Content-Type` header using `reply.type`. | ||
@@ -815,0 +944,0 @@ ## Acknowledgements |
@@ -5,3 +5,3 @@ 'use strict' | ||
const Fastify = require('fastify') | ||
const minifier = require('html-minifier') | ||
const minifier = require('html-minifier-terser') | ||
const fs = require('node:fs') | ||
@@ -30,3 +30,3 @@ const dot = require('dot') | ||
test('reply.view with dot engine and html-minifier', t => { | ||
test('reply.view with dot engine and html-minifier-terser', t => { | ||
t.plan(6) | ||
@@ -57,3 +57,3 @@ const fastify = Fastify() | ||
url: 'http://localhost:' + fastify.server.address().port | ||
}, (err, response, body) => { | ||
}, async (err, response, body) => { | ||
t.error(err) | ||
@@ -63,3 +63,3 @@ t.equal(response.statusCode, 200) | ||
t.equal(response.headers['content-type'], 'text/html; charset=utf-8') | ||
t.equal(minifier.minify(dot.process(compileOptions).testdot(data), options), body.toString()) | ||
t.equal(await minifier.minify(dot.process(compileOptions).testdot(data), options), body.toString()) | ||
fastify.close() | ||
@@ -69,3 +69,3 @@ }) | ||
}) | ||
test('reply.view with dot engine and paths excluded from html-minifier', t => { | ||
test('reply.view with dot engine and paths excluded from html-minifier-terser', t => { | ||
t.plan(6) | ||
@@ -116,3 +116,3 @@ const fastify = Fastify() | ||
test('reply.view with eta engine and html-minifier', t => { | ||
test('reply.view with eta engine and html-minifier-terser', t => { | ||
t.plan(6) | ||
@@ -141,3 +141,3 @@ const fastify = Fastify() | ||
url: 'http://localhost:' + fastify.server.address().port | ||
}, (err, response, body) => { | ||
}, async (err, response, body) => { | ||
t.error(err) | ||
@@ -147,3 +147,3 @@ t.equal(response.statusCode, 200) | ||
t.equal(response.headers['content-type'], 'text/html; charset=utf-8') | ||
t.equal(minifier.minify(eta.renderString(fs.readFileSync('./templates/index.eta', 'utf8'), data), options), body.toString()) | ||
t.equal(await minifier.minify(eta.renderString(fs.readFileSync('./templates/index.eta', 'utf8'), data), options), body.toString()) | ||
fastify.close() | ||
@@ -154,3 +154,3 @@ }) | ||
test('reply.view with eta engine and async and html-minifier', t => { | ||
test('reply.view with eta engine and async and html-minifier-terser', t => { | ||
t.plan(6) | ||
@@ -180,3 +180,3 @@ const fastify = Fastify() | ||
url: 'http://localhost:' + fastify.server.address().port | ||
}, (err, response, body) => { | ||
}, async (err, response, body) => { | ||
t.error(err) | ||
@@ -186,3 +186,3 @@ t.equal(response.statusCode, 200) | ||
t.equal(response.headers['content-type'], 'text/html; charset=utf-8') | ||
t.equal(minifier.minify(eta.renderString(fs.readFileSync('./templates/index.eta', 'utf8'), data), options), body.toString()) | ||
t.equal(await minifier.minify(eta.renderString(fs.readFileSync('./templates/index.eta', 'utf8'), data), options), body.toString()) | ||
fastify.close() | ||
@@ -192,3 +192,3 @@ }) | ||
}) | ||
test('reply.view with eta engine and paths excluded from html-minifier', t => { | ||
test('reply.view with eta engine and paths excluded from html-minifier-terser', t => { | ||
t.plan(6) | ||
@@ -234,3 +234,3 @@ const fastify = Fastify() | ||
test('fastify.view with handlebars engine and html-minifier', t => { | ||
test('fastify.view with handlebars engine and html-minifier-terser', t => { | ||
t.plan(2) | ||
@@ -253,4 +253,4 @@ const fastify = Fastify() | ||
fastify.view('./templates/index.html', data).then(compiled => { | ||
t.equal(minifier.minify(handlebars.compile(fs.readFileSync('./templates/index.html', 'utf8'))(data), options), compiled) | ||
fastify.view('./templates/index.html', data).then(async compiled => { | ||
t.equal(await minifier.minify(handlebars.compile(fs.readFileSync('./templates/index.html', 'utf8'))(data), options), compiled) | ||
fastify.close() | ||
@@ -266,3 +266,3 @@ }) | ||
test('reply.view with liquid engine and html-minifier', t => { | ||
test('reply.view with liquid engine and html-minifier-terser', t => { | ||
t.plan(7) | ||
@@ -298,5 +298,5 @@ const fastify = Fastify() | ||
engine.renderFile('./templates/index.liquid', data) | ||
.then((html) => { | ||
.then(async (html) => { | ||
t.error(err) | ||
t.equal(minifier.minify(html, options), body.toString()) | ||
t.equal(await minifier.minify(html, options), body.toString()) | ||
}) | ||
@@ -307,3 +307,3 @@ fastify.close() | ||
}) | ||
test('reply.view with liquid engine and paths excluded from html-minifier', t => { | ||
test('reply.view with liquid engine and paths excluded from html-minifier-terser', t => { | ||
t.plan(7) | ||
@@ -354,3 +354,3 @@ const fastify = Fastify() | ||
test('reply.view with nunjucks engine, full path templates folder, and html-minifier', t => { | ||
test('reply.view with nunjucks engine, full path templates folder, and html-minifier-terser', t => { | ||
t.plan(6) | ||
@@ -380,3 +380,3 @@ const fastify = Fastify() | ||
url: 'http://localhost:' + fastify.server.address().port | ||
}, (err, response, body) => { | ||
}, async (err, response, body) => { | ||
t.error(err) | ||
@@ -387,3 +387,3 @@ t.equal(response.statusCode, 200) | ||
// Global Nunjucks templates dir changed here. | ||
t.equal(minifier.minify(nunjucks.render('./index.njk', data), options), body.toString()) | ||
t.equal(await minifier.minify(nunjucks.render('./index.njk', data), options), body.toString()) | ||
fastify.close() | ||
@@ -393,3 +393,3 @@ }) | ||
}) | ||
test('reply.view with nunjucks engine, full path templates folder, and paths excluded from html-minifier', t => { | ||
test('reply.view with nunjucks engine, full path templates folder, and paths excluded from html-minifier-terser', t => { | ||
t.plan(6) | ||
@@ -437,3 +437,3 @@ const fastify = Fastify() | ||
test('reply.view with pug engine and html-minifier', t => { | ||
test('reply.view with pug engine and html-minifier-terser', t => { | ||
t.plan(6) | ||
@@ -462,3 +462,3 @@ const fastify = Fastify() | ||
url: 'http://localhost:' + fastify.server.address().port | ||
}, (err, response, body) => { | ||
}, async (err, response, body) => { | ||
t.error(err) | ||
@@ -468,3 +468,3 @@ t.equal(response.statusCode, 200) | ||
t.equal(response.headers['content-type'], 'text/html; charset=utf-8') | ||
t.equal(minifier.minify(pug.render(fs.readFileSync('./templates/index.pug', 'utf8'), data), options), body.toString()) | ||
t.equal(await minifier.minify(pug.render(fs.readFileSync('./templates/index.pug', 'utf8'), data), options), body.toString()) | ||
fastify.close() | ||
@@ -474,3 +474,3 @@ }) | ||
}) | ||
test('reply.view with pug engine and paths excluded from html-minifier', t => { | ||
test('reply.view with pug engine and paths excluded from html-minifier-terser', t => { | ||
t.plan(6) | ||
@@ -516,3 +516,3 @@ const fastify = Fastify() | ||
test('reply.view with twig engine and html-minifier', t => { | ||
test('reply.view with twig engine and html-minifier-terser', t => { | ||
t.plan(7) | ||
@@ -546,5 +546,5 @@ const fastify = Fastify() | ||
t.equal(response.headers['content-type'], 'text/html; charset=utf-8') | ||
Twig.renderFile('./templates/index.twig', data, (err, html) => { | ||
Twig.renderFile('./templates/index.twig', data, async (err, html) => { | ||
t.error(err) | ||
t.equal(minifier.minify(html, options), body.toString()) | ||
t.equal(await minifier.minify(html, options), body.toString()) | ||
}) | ||
@@ -555,3 +555,3 @@ fastify.close() | ||
}) | ||
test('reply.view with twig engine and paths excluded from html-minifier', t => { | ||
test('reply.view with twig engine and paths excluded from html-minifier-terser', t => { | ||
t.plan(7) | ||
@@ -558,0 +558,0 @@ const fastify = Fastify() |
@@ -8,2 +8,3 @@ 'use strict' | ||
const fs = require('node:fs') | ||
const minifier = require('html-minifier-terser') | ||
@@ -81,5 +82,4 @@ test('reply.view with ejs engine and async: true (global option)', t => { | ||
const minifier = require('html-minifier') | ||
const minifierOpts = { collapseWhitespace: true } | ||
test('reply.view with ejs engine, async: true (global option), and html-minifier', t => { | ||
test('reply.view with ejs engine, async: true (global option), and html-minifier-terser', t => { | ||
t.plan(6) | ||
@@ -114,3 +114,3 @@ const fastify = Fastify() | ||
t.equal(response.headers['content-type'], 'text/html; charset=utf-8') | ||
t.equal(minifier.minify(await ejs.render(fs.readFileSync('./templates/ejs-async.ejs', 'utf8'), {}, { async: true }), minifierOpts), body.toString()) | ||
t.equal(await minifier.minify(await ejs.render(fs.readFileSync('./templates/ejs-async.ejs', 'utf8'), {}, { async: true }), minifierOpts), body.toString()) | ||
fastify.close() | ||
@@ -150,3 +150,3 @@ }) | ||
t.equal(response.headers['content-type'], 'text/html; charset=utf-8') | ||
t.equal(minifier.minify(await ejs.render(fs.readFileSync('./templates/ejs-async.ejs', 'utf8'), {}, { async: true })), body.toString()) | ||
t.equal(await minifier.minify(await ejs.render(fs.readFileSync('./templates/ejs-async.ejs', 'utf8'), {}, { async: true })), body.toString()) | ||
fastify.close() | ||
@@ -191,3 +191,3 @@ }) | ||
t.equal(response.headers['content-type'], 'text/html; charset=utf-8') | ||
t.equal(minifier.minify(await ejs.render(fs.readFileSync('./templates/ejs-async.ejs', 'utf8'), {}, { async: true }), minifierOpts), body.toString()) | ||
t.equal(await minifier.minify(await ejs.render(fs.readFileSync('./templates/ejs-async.ejs', 'utf8'), {}, { async: true }), minifierOpts), body.toString()) | ||
if (i === numTests - 1) fastify.close() | ||
@@ -270,3 +270,3 @@ resolve() | ||
test('reply.view with ejs engine, async: true (local override), and html-minifier', t => { | ||
test('reply.view with ejs engine, async: true (local override), and html-minifier-terser', t => { | ||
t.plan(6) | ||
@@ -301,3 +301,3 @@ const fastify = Fastify() | ||
t.equal(response.headers['content-type'], 'text/html; charset=utf-8') | ||
t.equal(minifier.minify(await ejs.render(fs.readFileSync('./templates/ejs-async.ejs', 'utf8'), { }, { async: true }), minifierOpts), body.toString()) | ||
t.equal(await minifier.minify(await ejs.render(fs.readFileSync('./templates/ejs-async.ejs', 'utf8'), { }, { async: true }), minifierOpts), body.toString()) | ||
fastify.close() | ||
@@ -308,3 +308,3 @@ }) | ||
test('reply.view with ejs engine, async: false (local override), and html-minifier', t => { | ||
test('reply.view with ejs engine, async: false (local override), and html-minifier-terser', t => { | ||
t.plan(6) | ||
@@ -339,3 +339,3 @@ const fastify = Fastify() | ||
t.equal(response.headers['content-type'], 'text/html; charset=utf-8') | ||
t.equal(minifier.minify(await ejs.render(fs.readFileSync('./templates/index.ejs', 'utf8'), { text: 'text' }, { async: false }), minifierOpts), body.toString()) | ||
t.equal(await minifier.minify(await ejs.render(fs.readFileSync('./templates/index.ejs', 'utf8'), { text: 'text' }, { async: false }), minifierOpts), body.toString()) | ||
fastify.close() | ||
@@ -346,3 +346,3 @@ }) | ||
test('reply.view with ejs engine, async: true (local override), and html-minifier in production mode', t => { | ||
test('reply.view with ejs engine, async: true (local override), and html-minifier-terser in production mode', t => { | ||
const numTests = 3 | ||
@@ -381,3 +381,3 @@ t.plan(numTests * 5 + 1) | ||
t.equal(response.headers['content-type'], 'text/html; charset=utf-8') | ||
t.equal(minifier.minify(await ejs.render(fs.readFileSync('./templates/ejs-async.ejs', 'utf8'), {}, { async: true }), minifierOpts), body.toString()) | ||
t.equal(await minifier.minify(await ejs.render(fs.readFileSync('./templates/ejs-async.ejs', 'utf8'), {}, { async: true }), minifierOpts), body.toString()) | ||
if (i === numTests - 1) fastify.close() | ||
@@ -391,3 +391,3 @@ resolve() | ||
test('reply.view with ejs engine, async: false (local override), and html-minifier in production mode', t => { | ||
test('reply.view with ejs engine, async: false (local override), and html-minifier-terser in production mode', t => { | ||
const numTests = 2 | ||
@@ -426,3 +426,3 @@ t.plan(numTests * 5 + 1) | ||
t.equal(response.headers['content-type'], 'text/html; charset=utf-8') | ||
t.equal(minifier.minify(await ejs.render(fs.readFileSync('./templates/index.ejs', 'utf8'), { text: 'text' }, { async: false }), minifierOpts), body.toString()) | ||
t.equal(await minifier.minify(await ejs.render(fs.readFileSync('./templates/index.ejs', 'utf8'), { text: 'text' }, { async: false }), minifierOpts), body.toString()) | ||
if (i === numTests - 1) fastify.close() | ||
@@ -522,1 +522,69 @@ resolve() | ||
}) | ||
test('reply.viewAsync with ejs engine and async: true (global option)', t => { | ||
t.plan(6) | ||
const fastify = Fastify() | ||
const ejs = require('ejs') | ||
fastify.register(require('../index'), { | ||
engine: { ejs }, | ||
options: { async: true }, | ||
templates: 'templates' | ||
}) | ||
fastify.get('/', async (req, reply) => { | ||
return reply.viewAsync('ejs-async.ejs') | ||
}) | ||
fastify.listen({ port: 0 }, err => { | ||
t.error(err) | ||
sget({ | ||
method: 'GET', | ||
url: 'http://localhost:' + fastify.server.address().port | ||
}, async (err, response, body) => { | ||
t.error(err) | ||
t.equal(response.statusCode, 200) | ||
t.equal(response.headers['content-length'], '' + body.length) | ||
t.equal(response.headers['content-type'], 'text/html; charset=utf-8') | ||
t.equal(await ejs.render(fs.readFileSync('./templates/ejs-async.ejs', 'utf8'), {}, { async: true }), body.toString()) | ||
fastify.close() | ||
}) | ||
}) | ||
}) | ||
test('reply.viewAsync with ejs engine, async: true (global option), and html-minifier-terser', t => { | ||
t.plan(6) | ||
const fastify = Fastify() | ||
const ejs = require('ejs') | ||
fastify.register(require('../index'), { | ||
engine: { ejs }, | ||
options: { | ||
async: true, | ||
useHtmlMinifier: minifier, | ||
htmlMinifierOptions: minifierOpts | ||
}, | ||
templates: 'templates' | ||
}) | ||
fastify.get('/', (req, reply) => { | ||
return reply.viewAsync('ejs-async.ejs') | ||
}) | ||
fastify.listen({ port: 0 }, err => { | ||
t.error(err) | ||
sget({ | ||
method: 'GET', | ||
url: 'http://localhost:' + fastify.server.address().port | ||
}, async (err, response, body) => { | ||
t.error(err) | ||
t.equal(response.statusCode, 200) | ||
t.equal(response.headers['content-length'], '' + body.length) | ||
t.equal(response.headers['content-type'], 'text/html; charset=utf-8') | ||
t.equal(await minifier.minify(await ejs.render(fs.readFileSync('./templates/ejs-async.ejs', 'utf8'), {}, { async: true }), minifierOpts), body.toString()) | ||
fastify.close() | ||
}) | ||
}) | ||
}) |
@@ -9,3 +9,3 @@ 'use strict' | ||
const path = require('node:path') | ||
const minifier = require('html-minifier') | ||
const minifier = require('html-minifier-terser') | ||
const minifierOpts = { | ||
@@ -549,3 +549,3 @@ removeComments: true, | ||
test('reply.view with ejs engine and html-minifier', t => { | ||
test('reply.view with ejs engine and html-minifier-terser', t => { | ||
t.plan(6) | ||
@@ -576,3 +576,3 @@ const fastify = Fastify() | ||
url: 'http://localhost:' + fastify.server.address().port | ||
}, (err, response, body) => { | ||
}, async (err, response, body) => { | ||
t.error(err) | ||
@@ -582,3 +582,3 @@ t.equal(response.statusCode, 200) | ||
t.equal(response.headers['content-type'], 'text/html; charset=utf-8') | ||
t.equal(minifier.minify(ejs.render(fs.readFileSync('./templates/index.ejs', 'utf8'), data), minifierOpts), body.toString()) | ||
t.equal(await minifier.minify(ejs.render(fs.readFileSync('./templates/index.ejs', 'utf8'), data), minifierOpts), body.toString()) | ||
fastify.close() | ||
@@ -588,3 +588,3 @@ }) | ||
}) | ||
test('reply.view with ejs engine and paths excluded from html-minifier', t => { | ||
test('reply.view with ejs engine and paths excluded from html-minifier-terser', t => { | ||
t.plan(6) | ||
@@ -626,3 +626,3 @@ const fastify = Fastify() | ||
}) | ||
test('reply.view with ejs engine and html-minifier in production mode', t => { | ||
test('reply.view with ejs engine and html-minifier-terser in production mode', t => { | ||
const numTests = 5 | ||
@@ -660,3 +660,3 @@ t.plan(numTests * 5 + 1) | ||
t.equal(response.headers['content-type'], 'text/html; charset=utf-8') | ||
t.equal(minifier.minify(ejs.render(fs.readFileSync('./templates/index.ejs', 'utf8'), data), minifierOpts), body.toString()) | ||
t.equal(await minifier.minify(ejs.render(fs.readFileSync('./templates/index.ejs', 'utf8'), data), minifierOpts), body.toString()) | ||
if (i === numTests - 1) fastify.close() | ||
@@ -1261,1 +1261,268 @@ resolve() | ||
}) | ||
test('reply.viewAsync with ejs engine - sync handler', t => { | ||
t.plan(6) | ||
const fastify = Fastify() | ||
const ejs = require('ejs') | ||
const data = { text: 'text' } | ||
fastify.register(require('../index'), { | ||
engine: { | ||
ejs | ||
} | ||
}) | ||
fastify.get('/', async (req, reply) => { | ||
return reply.viewAsync('templates/index.ejs', data) | ||
}) | ||
fastify.listen({ port: 0 }, err => { | ||
t.error(err) | ||
sget({ | ||
method: 'GET', | ||
url: 'http://localhost:' + fastify.server.address().port | ||
}, (err, response, body) => { | ||
t.error(err) | ||
t.equal(response.statusCode, 200) | ||
t.equal(response.headers['content-length'], '' + body.length) | ||
t.equal(response.headers['content-type'], 'text/html; charset=utf-8') | ||
t.equal(ejs.render(fs.readFileSync('./templates/index.ejs', 'utf8'), data), body.toString()) | ||
fastify.close() | ||
}) | ||
}) | ||
}) | ||
test('reply.viewAsync with ejs engine - async handler', t => { | ||
t.plan(6) | ||
const fastify = Fastify() | ||
const ejs = require('ejs') | ||
const data = { text: 'text' } | ||
fastify.register(require('../index'), { | ||
engine: { | ||
ejs | ||
} | ||
}) | ||
fastify.get('/', (req, reply) => { | ||
return reply.viewAsync('templates/index.ejs', data) | ||
}) | ||
fastify.listen({ port: 0 }, err => { | ||
t.error(err) | ||
sget({ | ||
method: 'GET', | ||
url: 'http://localhost:' + fastify.server.address().port | ||
}, (err, response, body) => { | ||
t.error(err) | ||
t.equal(response.statusCode, 200) | ||
t.equal(response.headers['content-length'], '' + body.length) | ||
t.equal(response.headers['content-type'], 'text/html; charset=utf-8') | ||
t.equal(ejs.render(fs.readFileSync('./templates/index.ejs', 'utf8'), data), body.toString()) | ||
fastify.close() | ||
}) | ||
}) | ||
}) | ||
test('reply.viewAsync should return 500 if layout is missing on render', t => { | ||
t.plan(3) | ||
const fastify = Fastify() | ||
const ejs = require('ejs') | ||
const data = { text: 'text' } | ||
fastify.register(require('../index'), { | ||
engine: { | ||
ejs | ||
}, | ||
root: path.join(__dirname, '../templates') | ||
}) | ||
fastify.get('/', (req, reply) => { | ||
return reply.viewAsync('index-for-layout.ejs', data, { layout: 'non-existing-layout.html' }) | ||
}) | ||
fastify.listen({ port: 0 }, err => { | ||
t.error(err) | ||
sget({ | ||
method: 'GET', | ||
url: 'http://localhost:' + fastify.server.address().port | ||
}, (err, response, body) => { | ||
t.error(err) | ||
t.equal(response.statusCode, 500) | ||
fastify.close() | ||
}) | ||
}) | ||
}) | ||
test('reply.viewAsync should allow errors to be handled by custom error handler', t => { | ||
t.plan(7) | ||
const fastify = Fastify() | ||
const ejs = require('ejs') | ||
const data = { text: 'text' } | ||
fastify.register(require('../index'), { | ||
engine: { | ||
ejs | ||
}, | ||
root: path.join(__dirname, '../templates') | ||
}) | ||
fastify.get('/', (req, reply) => { | ||
return reply.viewAsync('index-for-layout.ejs', data, { layout: 'non-existing-layout.html' }) | ||
}) | ||
fastify.setErrorHandler((err, request, reply) => { | ||
t.ok(err instanceof Error) | ||
t.same(reply.getHeader('Content-Type'), null) | ||
return 'something went wrong' | ||
}) | ||
fastify.listen({ port: 0 }, err => { | ||
t.error(err) | ||
sget({ | ||
method: 'GET', | ||
url: 'http://localhost:' + fastify.server.address().port | ||
}, (err, response, body) => { | ||
t.error(err) | ||
t.equal(response.headers['content-type'], 'text/plain; charset=utf-8') | ||
t.equal(response.statusCode, 200) | ||
t.equal('something went wrong', body.toString()) | ||
fastify.close() | ||
}) | ||
}) | ||
}) | ||
test('reply.viewAsync with ejs engine and custom propertyName', t => { | ||
t.plan(6) | ||
const fastify = Fastify() | ||
const ejs = require('ejs') | ||
const data = { text: 'text' } | ||
fastify.register(require('../index'), { | ||
engine: { | ||
ejs | ||
}, | ||
propertyName: 'render' | ||
}) | ||
fastify.get('/', async (req, reply) => { | ||
return reply.renderAsync('templates/index.ejs', data) | ||
}) | ||
fastify.listen({ port: 0 }, err => { | ||
t.error(err) | ||
sget({ | ||
method: 'GET', | ||
url: 'http://localhost:' + fastify.server.address().port | ||
}, (err, response, body) => { | ||
t.error(err) | ||
t.equal(response.statusCode, 200) | ||
t.equal(response.headers['content-length'], '' + body.length) | ||
t.equal(response.headers['content-type'], 'text/html; charset=utf-8') | ||
t.equal(ejs.render(fs.readFileSync('./templates/index.ejs', 'utf8'), data), body.toString()) | ||
fastify.close() | ||
}) | ||
}) | ||
}) | ||
test('reply.viewAsync with ejs engine and custom asyncPropertyName', t => { | ||
t.plan(6) | ||
const fastify = Fastify() | ||
const ejs = require('ejs') | ||
const data = { text: 'text' } | ||
fastify.register(require('../index'), { | ||
engine: { | ||
ejs | ||
}, | ||
asyncPropertyName: 'viewAsPromise' | ||
}) | ||
fastify.get('/', async (req, reply) => { | ||
return reply.viewAsPromise('templates/index.ejs', data) | ||
}) | ||
fastify.listen({ port: 0 }, err => { | ||
t.error(err) | ||
sget({ | ||
method: 'GET', | ||
url: 'http://localhost:' + fastify.server.address().port | ||
}, (err, response, body) => { | ||
t.error(err) | ||
t.equal(response.statusCode, 200) | ||
t.equal(response.headers['content-length'], '' + body.length) | ||
t.equal(response.headers['content-type'], 'text/html; charset=utf-8') | ||
t.equal(ejs.render(fs.readFileSync('./templates/index.ejs', 'utf8'), data), body.toString()) | ||
fastify.close() | ||
}) | ||
}) | ||
}) | ||
test('reply.viewAsync with ejs engine and custom asyncPropertyName and custom propertyName', t => { | ||
t.plan(11) | ||
const fastify = Fastify() | ||
const ejs = require('ejs') | ||
const data = { text: 'text' } | ||
fastify.register(require('../index'), { | ||
engine: { | ||
ejs | ||
}, | ||
asyncPropertyName: 'renderPromise', | ||
propertyName: 'oldRenderSend' | ||
}) | ||
fastify.get('/asyncPropertyName', async (req, reply) => { | ||
return reply.renderPromise('templates/index.ejs', data) | ||
}) | ||
fastify.get('/propertyName', (req, reply) => { | ||
reply.oldRenderSend('templates/index.ejs', data) | ||
}) | ||
fastify.listen({ port: 0 }, err => { | ||
t.error(err) | ||
sget({ | ||
method: 'GET', | ||
url: 'http://localhost:' + fastify.server.address().port + '/asyncPropertyName' | ||
}, (err, response, body) => { | ||
t.error(err) | ||
t.equal(response.statusCode, 200) | ||
t.equal(response.headers['content-length'], '' + body.length) | ||
t.equal(response.headers['content-type'], 'text/html; charset=utf-8') | ||
t.equal(ejs.render(fs.readFileSync('./templates/index.ejs', 'utf8'), data), body.toString()) | ||
sget({ | ||
method: 'GET', | ||
url: 'http://localhost:' + fastify.server.address().port + '/propertyName' | ||
}, (err2, response2, body2) => { | ||
t.error(err2) | ||
t.equal(response2.statusCode, 200) | ||
t.equal(response2.headers['content-length'], '' + body.length) | ||
t.equal(response2.headers['content-type'], 'text/html; charset=utf-8') | ||
t.equal(ejs.render(fs.readFileSync('./templates/index.ejs', 'utf8'), data), body2.toString()) | ||
fastify.close() | ||
}) | ||
}) | ||
}) | ||
}) | ||
test('reply.viewAsync with ejs engine and conflicting propertyName/asyncPropertyName', t => { | ||
t.plan(1) | ||
const fastify = Fastify() | ||
const ejs = require('ejs') | ||
fastify.register(require('../index'), { | ||
engine: { | ||
ejs | ||
}, | ||
propertyName: 'render', | ||
asyncPropertyName: 'render' | ||
}) | ||
fastify.listen({ port: 0 }, err => { | ||
t.ok(err instanceof Error) | ||
}) | ||
}) |
@@ -9,3 +9,2 @@ 'use strict' | ||
const { join } = require('node:path') | ||
const proxyquire = require('proxyquire') | ||
@@ -336,3 +335,3 @@ require('./helper').handleBarsHtmlMinifierTests(t, true) | ||
test('fastify.view with handlebars engine with invalid layout option on render should throw', t => { | ||
t.plan(3) | ||
t.plan(5) | ||
@@ -355,2 +354,7 @@ const fastify = Fastify() | ||
}) | ||
// repeated for test coverage of layout access check lru | ||
fastify.view('./templates/index-for-layout.hbs', data, { layout: './templates/invalid-layout.hbs' }, (err, compiled) => { | ||
t.ok(err instanceof Error) | ||
t.equal(err.message, 'unable to access template "./templates/invalid-layout.hbs"') | ||
}) | ||
}) | ||
@@ -481,135 +485,2 @@ }) | ||
test('reply.view with ejs engine and includeViewExtension property as true', t => { | ||
t.plan(6) | ||
const fastify = Fastify() | ||
const ejs = require('ejs') | ||
const data = { text: 'text' } | ||
fastify.register(require('../index'), { | ||
engine: { | ||
ejs | ||
}, | ||
includeViewExtension: true | ||
}) | ||
fastify.get('/', (req, reply) => { | ||
reply.view('./templates/index', data) | ||
}) | ||
fastify.listen({ port: 0 }, err => { | ||
t.error(err) | ||
sget({ | ||
method: 'GET', | ||
url: 'http://localhost:' + fastify.server.address().port | ||
}, (err, response, body) => { | ||
t.error(err) | ||
t.equal(response.statusCode, 200) | ||
t.equal(response.headers['content-length'], '' + body.length) | ||
t.equal(response.headers['content-type'], 'text/html; charset=utf-8') | ||
t.equal(ejs.render(fs.readFileSync('./templates/index.ejs', 'utf8'), data), body.toString()) | ||
fastify.close() | ||
}) | ||
}) | ||
}) | ||
test('reply.view with ejs engine, template folder specified, include files (ejs and html) used in template, includeViewExtension property as true', t => { | ||
t.plan(7) | ||
const fastify = Fastify() | ||
const ejs = require('ejs') | ||
const resolve = require('node:path').resolve | ||
const templatesFolder = 'templates' | ||
const options = { | ||
filename: resolve(templatesFolder), // needed for include files to be resolved in include directive ... | ||
views: [__dirname] // must be put to make tests (with include files) working ... | ||
} | ||
const data = { text: 'text' } | ||
fastify.register(require('../index'), { | ||
engine: { | ||
ejs | ||
}, | ||
includeViewExtension: true, | ||
templates: templatesFolder, | ||
options | ||
}) | ||
fastify.get('/', (req, reply) => { | ||
reply.type('text/html; charset=utf-8').view('index-linking-other-pages', data) // sample for specifying with type | ||
// reply.view('index-with-includes', data) | ||
}) | ||
fastify.listen({ port: 0 }, err => { | ||
t.error(err) | ||
sget({ | ||
method: 'GET', | ||
url: 'http://localhost:' + fastify.server.address().port | ||
}, (err, response, body) => { | ||
t.error(err) | ||
t.equal(response.statusCode, 200) | ||
t.equal(response.headers['content-type'], 'text/html; charset=utf-8') | ||
t.equal(response.headers['content-length'], '' + body.length) | ||
let content = null | ||
ejs.renderFile(templatesFolder + '/index-linking-other-pages.ejs', data, options, function (err, str) { | ||
content = str | ||
t.error(err) | ||
t.equal(content.length, body.length) | ||
}) | ||
fastify.close() | ||
}) | ||
}) | ||
}) | ||
test('reply.view with ejs engine, templates with folder specified, include files and attributes; home', t => { | ||
t.plan(7) | ||
const fastify = Fastify() | ||
const ejs = require('ejs') | ||
const resolve = require('node:path').resolve | ||
const templatesFolder = 'templates' | ||
const options = { | ||
filename: resolve(templatesFolder), | ||
views: [__dirname] | ||
} | ||
const data = { text: 'Hello from EJS Templates' } | ||
fastify.register(require('../index'), { | ||
engine: { | ||
ejs | ||
}, | ||
includeViewExtension: true, | ||
templates: templatesFolder, | ||
options | ||
}) | ||
fastify.get('/', (req, reply) => { | ||
reply.type('text/html; charset=utf-8').view('index', data) | ||
}) | ||
fastify.listen({ port: 0 }, err => { | ||
t.error(err) | ||
sget({ | ||
method: 'GET', | ||
url: 'http://localhost:' + fastify.server.address().port | ||
}, (err, response, body) => { | ||
t.error(err) | ||
t.equal(response.statusCode, 200) | ||
t.equal(response.headers['content-type'], 'text/html; charset=utf-8') | ||
t.equal(response.headers['content-length'], '' + body.length) | ||
let content = null | ||
ejs.renderFile(templatesFolder + '/index.ejs', data, options, function (err, str) { | ||
content = str | ||
t.error(err) | ||
t.equal(content.length, body.length) | ||
}) | ||
fastify.close() | ||
}) | ||
}) | ||
}) | ||
test('reply.view with handlebars engine and includeViewExtension property as true', t => { | ||
@@ -756,13 +627,10 @@ t.plan(6) | ||
const handlebars = require('handlebars') | ||
const POV = proxyquire('..', { | ||
hashlru: function () { | ||
return { | ||
get: (key) => { | ||
t.equal(key, 'handlebars|body:./templates/body.hbs|null-Partials') | ||
}, | ||
set: (key, value) => { | ||
t.equal(key, 'handlebars|body:./templates/body.hbs|null-Partials') | ||
t.strictSame(value, { body: fs.readFileSync('./templates/body.hbs', 'utf8') }) | ||
} | ||
} | ||
const POV = require('..') | ||
fastify.decorate(POV.fastifyViewCache, { | ||
get: (key) => { | ||
t.equal(key, 'handlebars|body:./templates/body.hbs|null-Partials') | ||
}, | ||
set: (key, value) => { | ||
t.equal(key, 'handlebars|body:./templates/body.hbs|null-Partials') | ||
t.strictSame(value, { body: fs.readFileSync('./templates/body.hbs', 'utf8') }) | ||
} | ||
@@ -1062,11 +930,8 @@ }) | ||
const handlebars = require('handlebars') | ||
const POV = proxyquire('..', { | ||
hashlru: function () { | ||
return { | ||
get: () => { | ||
return () => { throw Error('Template Error') } | ||
}, | ||
set: () => { } | ||
} | ||
} | ||
const POV = require('..') | ||
fastify.decorate(POV.fastifyViewCache, { | ||
get: () => { | ||
return () => { throw Error('Template Error') } | ||
}, | ||
set: () => { } | ||
}) | ||
@@ -1263,1 +1128,39 @@ | ||
}) | ||
test('reply.viewAsync for handlebars engine without defaultContext but with reply.locals and data-parameter, with async fastify hooks', t => { | ||
t.plan(6) | ||
const fastify = Fastify() | ||
const handlebars = require('handlebars') | ||
const localsData = { text: 'text from locals' } | ||
const data = { text: 'text' } | ||
fastify.register(require('../index'), { | ||
engine: { | ||
handlebars | ||
} | ||
}) | ||
fastify.addHook('preHandler', async function (request, reply) { | ||
reply.locals = localsData | ||
}) | ||
fastify.get('/', async (req, reply) => { | ||
return reply.viewAsync('./templates/index.html', data) | ||
}) | ||
fastify.listen({ port: 0 }, err => { | ||
t.error(err) | ||
sget({ | ||
method: 'GET', | ||
url: 'http://localhost:' + fastify.server.address().port | ||
}, (err, response, body) => { | ||
t.error(err) | ||
t.equal(response.statusCode, 200) | ||
t.equal(response.headers['content-length'], '' + body.length) | ||
t.equal(response.headers['content-type'], 'text/html; charset=utf-8') | ||
t.equal(handlebars.compile(fs.readFileSync('./templates/index.html', 'utf8'))(data), body.toString()) | ||
fastify.close() | ||
}) | ||
}) | ||
}) |
@@ -8,4 +8,3 @@ 'use strict' | ||
const fs = require('node:fs') | ||
const minifier = require('html-minifier') | ||
const proxyquire = require('proxyquire') | ||
const minifier = require('html-minifier-terser') | ||
const minifierOpts = { | ||
@@ -349,11 +348,9 @@ removeComments: true, | ||
const data = { text: 'text' } | ||
const POV = proxyquire('..', { | ||
hashlru: function () { | ||
return { | ||
get: () => { | ||
return '<div>Cached Response</div>' | ||
}, | ||
set: () => { } | ||
} | ||
} | ||
const POV = require('..') | ||
fastify.decorate(POV.fastifyViewCache, { | ||
get: () => { | ||
return '<div>Cached Response</div>' | ||
}, | ||
set: () => { } | ||
}) | ||
@@ -438,3 +435,3 @@ | ||
test('reply.view with mustache engine with partials and html-minifier', t => { | ||
test('reply.view with mustache engine with partials and html-minifier-terser', t => { | ||
t.plan(6) | ||
@@ -464,3 +461,3 @@ const fastify = Fastify() | ||
url: 'http://localhost:' + fastify.server.address().port | ||
}, (err, response, replyBody) => { | ||
}, async (err, response, replyBody) => { | ||
t.error(err) | ||
@@ -470,3 +467,3 @@ t.equal(response.statusCode, 200) | ||
t.equal(response.headers['content-type'], 'text/html; charset=utf-8') | ||
t.equal(minifier.minify(mustache.render(fs.readFileSync('./templates/index.mustache', 'utf8'), data, { body: '<p>{{ text }}</p>' }), minifierOpts), replyBody.toString()) | ||
t.equal(await minifier.minify(mustache.render(fs.readFileSync('./templates/index.mustache', 'utf8'), data, { body: '<p>{{ text }}</p>' }), minifierOpts), replyBody.toString()) | ||
fastify.close() | ||
@@ -477,3 +474,3 @@ }) | ||
test('reply.view with mustache engine with partials and paths excluded from html-minifier', t => { | ||
test('reply.view with mustache engine with partials and paths excluded from html-minifier-terser', t => { | ||
t.plan(6) | ||
@@ -480,0 +477,0 @@ const fastify = Fastify() |
@@ -8,3 +8,2 @@ 'use strict' | ||
const fs = require('node:fs') | ||
const proxyquire = require('proxyquire') | ||
@@ -51,11 +50,9 @@ require('./helper').pugHtmlMinifierTests(t, true) | ||
const pug = require('pug') | ||
const POV = proxyquire('..', { | ||
hashlru: function () { | ||
return { | ||
get: () => { | ||
return () => '<div>Cached Response</div>' | ||
}, | ||
set: () => { } | ||
} | ||
} | ||
const POV = require('..') | ||
fastify.decorate(POV.fastifyViewCache, { | ||
get: () => { | ||
return () => '<div>Cached Response</div>' | ||
}, | ||
set: () => { } | ||
}) | ||
@@ -62,0 +59,0 @@ |
@@ -1,2 +0,2 @@ | ||
import { FastifyPluginCallback } from 'fastify'; | ||
import { FastifyPluginAsync } from 'fastify'; | ||
@@ -12,2 +12,4 @@ declare module "fastify" { | ||
view(page: string, data?: object, opts?: RouteSpecificOptions): FastifyReply; | ||
viewAsync<T extends { [key: string]: any; }>(page: string, data: T, opts?: RouteSpecificOptions): Promise<string>; | ||
viewAsync(page: string, data?: object, opts?: RouteSpecificOptions): Promise<string>; | ||
} | ||
@@ -21,3 +23,3 @@ | ||
type FastifyView = FastifyPluginCallback<fastifyView.FastifyViewOptions> | ||
type FastifyView = FastifyPluginAsync<fastifyView.FastifyViewOptions> | ||
@@ -49,2 +51,3 @@ declare namespace fastifyView { | ||
propertyName?: string; | ||
asyncPropertyName?: string; | ||
} | ||
@@ -59,2 +62,3 @@ | ||
export { fastifyView as default } | ||
export const fastifyViewCache: Symbol | ||
} | ||
@@ -61,0 +65,0 @@ |
import fastify from "fastify"; | ||
import fastifyView, { PointOfViewOptions, FastifyViewOptions } from ".."; | ||
import { expectAssignable, expectDeprecated, expectType } from "tsd"; | ||
import { expectAssignable, expectNotAssignable, expectDeprecated, expectType } from "tsd"; | ||
import * as path from "path"; | ||
@@ -44,2 +44,4 @@ | ||
// reply.locals.appVersion = 1 // not a valid type | ||
expectNotAssignable<NonNullable<(typeof reply)['locals']>['appVersion']>(1) | ||
reply.locals.appVersion = '4.14.0' | ||
@@ -55,2 +57,4 @@ reply.view("/index", { text: "Sample data" }); | ||
// reply.locals.appVersion = 1 // not a valid type | ||
expectNotAssignable<NonNullable<(typeof reply)['locals']>['appVersion']>(1) | ||
reply.locals.appVersion = '4.14.0' | ||
@@ -64,2 +68,25 @@ reply.view<{ text: string; }>("/index", { text: "Sample data" }); | ||
app.get("/view-async", async (request, reply) => { | ||
expectAssignable<NonNullable<(typeof reply)['locals']>['appVersion']>('4.14.0') | ||
expectNotAssignable<NonNullable<(typeof reply)['locals']>['appVersion']>(1) | ||
type ViewAsyncDataParamType = Parameters<typeof reply.viewAsync>[1] | ||
expectAssignable<ViewAsyncDataParamType>({ text: "Sample data" }) | ||
expectAssignable<ViewAsyncDataParamType>({ notText: "Sample data "}) | ||
const html = await reply.viewAsync("/index", { text: "Sample data" }); | ||
expectType<string>(html) | ||
return html | ||
}); | ||
app.get("/view-async-generic-provided", async (request, reply) => { | ||
type ViewAsyncDataParamType = Parameters<typeof reply.viewAsync<{ text: string; }>>[1] | ||
expectAssignable<ViewAsyncDataParamType>({ text: "Sample data" }) | ||
expectNotAssignable<ViewAsyncDataParamType>({ notText: "Sample data "}) | ||
const html = reply.viewAsync<{ text: string; }>("/index", { text: "Sample data" }); | ||
expectType<Promise<string>>(html) | ||
return html | ||
}); | ||
app.listen({port: 3000}, (err, address) => { | ||
@@ -66,0 +93,0 @@ if (err) throw err |
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
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
341305
23
118
9616
945
+ Addedtoad-cache@^3.7.0
+ Addedtoad-cache@3.7.0(transitive)
- Removedhashlru@^2.3.0
- Removedhashlru@2.3.0(transitive)