fastify / point-of-view

Template rendering plugin for Fastify
MIT License
341 stars 86 forks source link

feat: viewAsync reply decorator #420

Closed mweberxyz closed 5 months ago

mweberxyz commented 6 months ago

Closes #394 and closes #412

The purpose of this PR is to implement a new reply decorator, called viewAsync by default.

viewAsync is similar to view, except it does not call reply.send on template render or error, and instead returns a promise (to be returned from the route handler and allow fastify hook chain to take over).

See https://github.com/fastify/point-of-view/issues/394 and https://github.com/fastify/point-of-view/issues/412 for context. 412 will be closed by this PR, though maybe it should be revisited in the future, if the legacy view should be replaced with viewAsync as the default - would be a semver-major change and would probably make most sense to coincide with a major fastify version bump.

Approach

  1. Add new asyncRender async function
  2. Add new fastify.viewAsync decorator
  3. Call asyncRender for existing fastify.view decorator
  4. Call asyncRender for existing reply.view decorator

With this PR, all exposed decorators utilize the same underlying implementation of the rendering function, so each only contains logic specific to the needs of that decorator.

Tests

Sufficient tests added to be thoroughly confident the changes work as expected. Of particular interest:

  1. EJS: Tested both sync and async route handlers
  2. EJS: Ensured a call to fastify's setErrorHandler works as expected
  3. EJS: Tested asyncPropertyName and propertyName options work as expected
  4. Handlebars: Added a test specifically to make sure #394 is resolved

Benchmarks

All within run-to-run variance (my laptop, M1 mac @ Node v20):

Benchmark PR Master
express.js 20354.67 req/s 20104 req/s
fastify.js 110634.67 req/s 112906.67 req/s
fastify-art.js 23810.67 req/s 23501.34 req/s
fastify-dot.js 128826.67 req/s 128192 req/s
fastify-eta.js 80298.67 req/s 81258.67 req/s
fastify-pug.js 118080 req/s 120672 req/s
fastify-twig.js 96906.67 req/s 98037.34 req/s
fastify-liquid.js 24936 req/s 25080 req/s
fastify-mustache.js 89386.67 req/s 88512 req/s
fastify-nunjucks.js 113600 req/s 113162.67 req/s
fastify-ejs-async.js 108789.34 req/s 111978.67 req/s
fastify-viewAsync.js 112384 req/s -
fastify-ejs-minify.js 35445.34 req/s 35360 req/s
fastify-handlebars.js 84170.67 req/s 83808 req/s
fastify-ejs-local-layout.js 96746.67 req/s 97888 req/s
fastify-ejs-global-layout.js 99914.67 req/s 99754.67 req/s

Docs

Cleanup, re-organize for readability, and add documentation for new decorator:

Checklist

mweberxyz commented 6 months ago

The README after changes: https://github.com/mweberxyz/point-of-view/blob/feat/view-async/README.md

mweberxyz commented 6 months ago

The only difference between fastify.view and reply.viewAsync when called to render the html into a string inside a route handler is that the Content-Type: text/html.. header will be set by the latter, if Content-Type has not already been set.

mweberxyz commented 5 months ago

This has been sitting for awhile - anything needed to get it merged? It's semver-minor due to purely additive functionality.