From 400769989ba1604d922336663e47b2bf35a81113 Mon Sep 17 00:00:00 2001 From: Matt Weber <1062734+mweberxyz@users.noreply.github.com> Date: Fri, 22 Mar 2024 11:39:30 -0400 Subject: [PATCH 1/3] feat: viewAsync reply decorator Closes fastify/point-of-view#394 Closes fastify/point-of-view#412 --- README.md | 267 ++++++++++++++++++++++++--------- benchmark/fastify-viewAsync.js | 6 + index.js | 49 +++--- test/test-ejs-async.js | 68 +++++++++ test/test-ejs.js | 267 +++++++++++++++++++++++++++++++++ test/test-handlebars.js | 38 +++++ types/index.d.ts | 3 + 7 files changed, 610 insertions(+), 88 deletions(-) create mode 100644 benchmark/fastify-viewAsync.js diff --git a/README.md b/README.md index fb01e97..30f73b0 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Templates rendering plugin support for Fastify. -`@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. Currently supports the following templates engines: @@ -27,6 +27,10 @@ _Note: For **Fastify v3 support**, please use point-of-view `5.x` (npm i point-o _Note that at least Fastify `v2.0.0` is needed._ +## 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)._ _Note: [`marko`](https://markojs.com/) support has been dropped. Please use [`@marko/fastify`](https://github.com/marko-js/fastify) instead._ @@ -54,56 +58,66 @@ npm i @fastify/view - the template to be rendered - the data that should be available to the template during rendering -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 + + + +
+ +Hello, <%= name %>!
+ + +``` ```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(<%= text %>
+``` -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: +```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 @@ -136,6 +219,9 @@ fastify.view("/templates/index.ejs", { text: "text" }, (err, html) => { }); ``` +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 Registering multiple engines with different configurations is supported. They are distinguished via their `propertyName`: @@ -164,36 +250,6 @@ fastify.get("/desktop", (req, reply) => { }); ``` -## 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-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`, @@ -729,7 +785,6 @@ fastify.get('/', (req, reply) => { }) }) ``` -