npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@fastify/view

v10.0.1

Published

Template plugin for Fastify

Downloads

381,364

Readme

@fastify/view

CI NPM version js-standard-style

Templates rendering plugin support for Fastify.

@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:

In production mode, @fastify/view will heavily cache the templates file and functions, while in development will reload every time the template file and function.

Note: For Fastify v3 support, please use point-of-view 5.x (npm i point-of-view@5).

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.

Note: ejs-mate support has been dropped.

Note: marko support has been dropped. Please use @marko/fastify instead.

Benchmarks

The benchmark were run with the files in the benchmark folder with the ejs engine. The data has been taken with: autocannon -c 100 -d 5 -p 10 localhost:3000

  • Express: 8.8k req/sec
  • Fastify: 15.6k req/sec

Install

npm i @fastify/view

Quick start

fastify.register is used to register @fastify/view. By default, It will decorate the reply object with a view method that takes at least two arguments:

  • the template to be rendered
  • the data that should be available to the template during rendering

This example will render the template using the EJS engine and provide a variable name to be used inside the template:

<!-- index.ejs --->
<!DOCTYPE html>
<html lang="en">
  <head></head>
  <body>
    <p>Hello, <%= name %>!</p>
  </body>
</html>
// index.js:
const fastify = require("fastify")()
const fastifyView = require("@fastify/view")

fastify.register(fastifyView, {
  engine: {
    ejs: require("ejs")
  }
})

// synchronous handler:
fastify.get("/", (req, reply) => {
  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}`);
})

Configuration

Options

| 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. Example: { siteName: "MyAwesomeSite" } | {} | | 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 | "view" | | asyncPropertyName | The property that should be used to decorate reply for async handler 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. Example: "handlebars" | "" | | layout | See Layouts 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 | | | options | See Engine-specific settings | {} |

Example

fastify.register(require("@fastify/view"), {
  engine: {
    handlebars: require("handlebars"),
  },
  root: path.join(__dirname, "views"), // Points to `./views` relative to the current file
  layout: "./templates/template", // Sets the layout to use to `./views/templates/layout.handlebars` relative to the current file.
  viewExt: "handlebars", // Sets the default extension to `.handlebars`
  propertyName: "render", // The template can now be rendered via `reply.render()` and `fastify.render()`
  defaultContext: {
    dev: process.env.NODE_ENV === "development", // Inside your templates, `dev` will be `true` if the expression evaluates to true
  },
  options: {}, // No options passed to handlebars
});

Layouts

@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

<!-- 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>
<!-- template.ejs: -->
<p><%= text %></p>
// 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.

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:

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:

// Promise based, using async/await
const html = await fastify.view("/templates/index.ejs", { text: "text" });

// Callback based
fastify.view("/templates/index.ejs", { text: "text" }, (err, html) => {
  // Handle error
  // Do something with `html`
});

If called within a request hook and you need request-global variables, see Migrating from view to viewAsync.

Registering multiple engines

Registering multiple engines with different configurations is supported. They are distinguished via their propertyName:

fastify.register(require("@fastify/view"), {
  engine: { ejs: ejs },
  layout: "./templates/layout-mobile.ejs",
  propertyName: "mobile",
});

fastify.register(require("@fastify/view"), {
  engine: { ejs: ejs },
  layout: "./templates/layout-desktop.ejs",
  propertyName: "desktop",
});

fastify.get("/mobile", (req, reply) => {
  // Render using the `mobile` render function
  return reply.mobile("/templates/index.ejs", { text: "text" });
});

fastify.get("/desktop", (req, reply) => {
  // Render using the `desktop` render function
  return reply.desktop("/templates/index.ejs", { text: "text" });
});

Minifying HTML on render

To utilize 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:

// get a reference to html-minifier-terser
const minifier = require('html-minifier-terser')
// optionally defined the html-minifier-terser options
const minifierOpts = {
  removeComments: true,
  removeCommentsFromCDATA: true,
  collapseWhitespace: true,
  collapseBooleanAttributes: true,
  removeAttributeQuotes: true,
  removeEmptyAttributes: true
}
// in template engine options configure the use of html-minifier
  options: {
    useHtmlMinifier: minifier,
    htmlMinifierOptions: minifierOpts
  }

To filter some paths from minification, you can add the option pathsToExcludeHtmlMinifier with list of paths

// 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 = {
  useHtmlMinifier: minifier,
  pathsToExcludeHtmlMinifier: ['/test']
}

fastify.register(require("@fastify/view"), {
  engine: {
    ejs: require('ejs')
  },
  options
});

// This path is excluded from minification
fastify.get("/test", (req, reply) => {
  reply.view("./template/index.ejs", { text: "text" });
});

Engine-specific settings

Miscellaneous

Using @fastify/view as a dependency in a fastify-plugin

To require @fastify/view as a dependency to a fastify-plugin, add the name @fastify/view to the dependencies array in the plugin's opts.

fastify.register(myViewRendererPlugin, {
  dependencies: ["@fastify/view"],
});

Forcing a cache-flush

To forcefully clear cache when in production mode, call the view.clearCache() function.

fastify.view.clearCache();

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:

fastify.get('/', (req, reply) => {
  reply.view('index.ejs', { text: 'text' })
})

Now:

fastify.get('/', (req, reply) => {
  return reply.viewAsync('index.ejs', { text: 'text' })
})

Async handler

Previously:

// This is an async function
fastify.get("/", async (req, reply) => {
  const data = await something();
  reply.view("/templates/index.ejs", { data });
  return
})

Now:

// 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:

// Promise based, using async/await
fastify.get("/", async (req, reply) => {
  const html = await fastify.view("/templates/index.ejs", { text: "text" });
  return html
})
// 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:

// Promise based, using async/await
fastify.get("/", (req, reply) => {
  const html = await fastify.viewAsync("/templates/index.ejs", { text: "text" });
  return html
})
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, with the charset specified in options. You can specify a different Content-Type header using reply.type.

Acknowledgements

This project is kindly sponsored by:

License

Licensed under MIT.