@fastify/view

repository·main·Indexed 18 days ago

https://github.com/fastify/point-of-view

A template plugin for Fastify (version 12.0.0) that provides template rendering capabilities. It decorates Fastify and Reply objects with methods to render templates using supported engines such as EJS, Pug, Handlebars, Eta, doT, Nunjucks, Liquid, Edge, and Squirrelly. Features include support for layouts, request-global variables via reply.locals, HTML minification via html-minifier-terser, and asynchronous rendering with reply.viewAsync.

Tokens
5.7K
Snippets
29
Records
31
Agent score
14%

What's inside @fastify/view

  1. How layouts work in @fastify/view

    main

    Layouts are supported for EJS, Handlebars, Eta, and doT. When a layout is used, the request template is rendered first, and then the layout template is rendered with the resulting HTML injected into the body variable.

    Important: Global layouts (set via configuration) and providing a layout during a specific render call are mutually exclusive. You cannot mix them.

    Engine-specific body syntax:

    • EJS: <%- body %>
    • Handlebars: {{{ body }}}
    • ETA/doT: <%~ it.body %>
    <!-- layout.ejs: -->
    <!DOCTYPE html>
    <html lang="en">
      <head></head>
      <body>
        <%- body %>
        <br/>
      </body>
    </html>
  2. Migrate from reply.view to reply.viewAsync

    main

    The legacy reply.view method immediately sends the HTML response or a 500 error, which can short-circuit Fastify's error handling hooks.

    Use reply.viewAsync instead. It returns a Promise that resolves to the rendered HTML or rejects on error, allowing for better integration with Fastify's lifecycle and error handling. reply.viewAsync can be used in both synchronous and asynchronous handlers.

    // Async handler migration
    // Previously:
    fastify.get("/", async (req, reply) => {
      const data = await something();
      reply.view("/templates/index.ejs", { data });
      return
    })
    
    // Now:
    fastify.get("/", async (req, reply) => {
      const data = await something();
      return reply.viewAsync("/templates/index.ejs", { data });
    })
  3. Use @fastify/view as a dependency in a fastify-plugin

    main

    If you are developing a fastify-plugin that requires @fastify/view to function, you must declare it in the dependencies array within your plugin's options to ensure it is registered before your plugin.

    fastify.register(myViewRendererPlugin, {
      dependencies: ["@fastify/view"],
    });
  4. Minify HTML on render

    main

    You can integrate html-minifier-terser into the rendering process by passing it through the engine's options.

    1. Pass a reference to html-minifier-terser via useHtmlMinifier.
    2. Provide configuration via htmlMinifierOptions.
    3. (Optional) Use pathsToExcludeHtmlMinifier to provide a list of paths that should not be minified.
    const minifier = require('html-minifier-terser')
    const minifierOpts = {
      removeComments: true,
      collapseWhitespace: true,
      // ... other options
    }
    
    const options = {
      useHtmlMinifier: minifier,
      htmlMinifierOptions: minifierOpts,
      pathsToExcludeHtmlMinifier: ['/test']
    }
    
    fastify.register(require("@fastify/view"), {
      engine: {
        ejs: require('ejs')
      },
      options
    });
  5. Configure Handlebars layouts

    main

    To use layouts in Handlebars, specify a global layout path during @fastify/view registration. This layout will wrap your rendered templates.

    fastify.register(require("@fastify/view"), {
      engine: {
        handlebars: require("handlebars"),
      },
      layout: "./templates/layout.hbs",
    });
    
    fastify.get("/", (req, reply) => {
      reply.view("./templates/index.hbs", { text: "text" });
    });
  6. Configure EJS include files

    main

    To use include files in EJS, you must configure the filename option in your template engine options to resolve the templates folder. This allows you to use relative or absolute paths in your EJS templates (e.g., <%- include('header.ejs') %>).

    const resolve = require('node:path').resolve;
    
    // in template engine options configure how to resolve templates folder
    options: {
      filename: resolve("templates");
    }
  7. Set request-global variables using reply.locals

    main

    To make variables available to all templates within a specific request (e.g., a username), attach a locals object to the reply object using a preHandler hook.

    Precedence order:

    1. Data passed directly to reply.view(template, data) (Highest)
    2. reply.locals
    3. defaultContext (Lowest)
    fastify.addHook("preHandler", function (request, reply, done) {
      reply.locals = {
        text: getTextFromRequest(request),
      };
      done();
    });
  8. Quick start with @fastify/view

    main

    Register @fastify/view using fastify.register. You must provide an engine object containing the template engine you wish to use. By default, the plugin decorates the reply object with a view method for synchronous handlers and a viewAsync method for asynchronous handlers.

    Note: reply.viewAsync is the recommended replacement for reply.view and fastify.view in async contexts.

    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}`);
    })
  9. Configure Liquid engine

    main

    To use Liquid, create a Liquid instance (e.g., from liquidjs) with your desired configuration (like root and extname) and pass it as the engine option.

    const { Liquid } = require("liquidjs");
    const path = require('node:path');
    
    const engine = new Liquid({
      root: path.join(__dirname, "templates"),
      extname: ".liquid",
    });
    
    fastify.register(require("@fastify/view"), {
      engine: {
        liquid: engine,
      },
    });
  10. Configure Edge engine

    main

    To use Edge, instantiate the Edge engine, mount your template directory using engine.mount(), and pass the instance to the engine option.

    const { Edge } = require('edge.js')
    const { join } = require('node:path')
    
    const engine = new Edge()
    engine.mount(join(__dirname, '..', 'templates'))
    
    fastify.register(require('../index'), {
        engine: {
            edge: engine
        }
    })
  11. Configure Squirrelly engine

    main

    To use Squirrelly, pass the Sqrl instance to the engine option and specify the templates directory.

    const Sqrl = require('squirrelly')
    
    fastify.register(require('@fastify/view'), {
        engine: {
            squirrelly: Sqrl
        },
        templates: 'templates'
    })