Vision for hapi.js

repository·master·Indexed 41 days ago

https://github.com/hapijs/vision

A template rendering plugin for the hapi.js ecosystem that allows developers to render dynamic content using various template engines. Version 7.0.3 provides support for configuring view engines via server.views(), rendering templates using h.view(), server.render(), or request.render(), and managing layouts, partials, and helpers.

Tokens
6K
Snippets
22
Records
27
Agent score
50%

What's inside Vision

  1. What is @hapi/vision?

    master
    @hapi/vision is a template rendering plugin designed for the hapi.js web framework. It provides support for rendering templates and is part of the hapi ecosystem, though it can be used independently or with other frameworks.
  2. Map custom engines to the Vision API

    master

    If a templating engine does not follow the standard API pattern, you can map it to the Vision API by providing a compile function (and optionally a prepare function) within the engines configuration object.

    The compile function

    The compile function receives the source (src) and options. It must return a function that accepts a context object and returns the rendered string.

    The prepare function

    The prepare function is used to perform setup tasks (like configuring an environment) before compilation. It receives options and a next callback.

    // Example: Mapping Mustache to the Vision API
    server.views({
        engines: {
            html: {
                compile: (template) => {
                    Mustache.parse(template);
                    return (context) => {
                        return Mustache.render(template, context);
                    };
                }
            }
        }
    });
  3. Register and use template engines

    master

    To use a template engine, include it in the engines object within the server.views() configuration.

    If you are providing a custom engine object instead of just a module, the module must contain a compile() function. The signature of compile() depends on the compileMode setting:

    1. Sync Mode (compileMode: 'sync', default):

      • compile(template, options) returns a function with signature function(context, options).
    2. Async Mode (compileMode: 'async'):

      • compile(template, options, next) where next is function(err, compiled).
      • The compiled function has the signature function(context, options, callback) where callback is function(err, rendered).
    server.views({
        engines: {
            html: {
                module: require('handlebars'),
                compileMode: 'sync'
            }
        },
        path: __dirname + '/templates'
    });
  4. Configure TypeScript typings for Vision

    master

    To enable autocompletion and type safety for templates and layouts, you can augment the @hapi/vision module.

    Typing the Response Handler

    Define ViewTypes to restrict template and layout to your specific filenames.

    Typing Decorated Handlers

    To provide strict type checking for server.render, request.render, and h.view, augment the following interfaces:

    • RenderMethod (for server.render)
    • RequestRenderMethod (for request.render)
    • ToolkitRenderMethod (for h.view)

    You can even define specific context shapes for specific templates to ensure strict type safety across your views.

    declare module '@hapi/vision' {
        // User defined template names
        type CustomTemplates = 'test' | 'hello' | 'temp1';
    
        // User defined layout names
        type CustomLayout = 'auth' | 'unauth' | 'admin';
    
        interface ViewTypes {
            template: CustomTemplates
            layout: CustomLayout
        }
    
        // Typing the toolkit h.view()
        interface ToolkitRenderMethod {
            (template: 'test', context: { apples: number }): ResponseObject;
            (template: 'hello', context: { oranges: boolean }): ResponseObject;
        }
    }
  5. Register @hapi/vision with a Hapi server

    master

    To use templated responses, you must register the @hapi/vision plugin with your Hapi server. Once registered, the server and the h response toolkit are decorated with methods to manage view engines and render templates.

    const Hapi = require('@hapi/hapi');
    const Vision = require('@hapi/vision');
    
    const server = Hapi.Server({ port: 3000 });
    
    const provision = async () => {
        await server.register(Vision);
        await server.start();
    
        console.log('Server running at:', server.info.uri);
    };
    
    provision();
  6. Configure the Views Manager options

    master

    When initializing the view manager via server.views(options), you can provide a configuration object to control how templates are resolved, rendered, and cached.

    Key configuration options include:

    • engines: A required object mapping file extensions (e.g., 'html', 'hbr') to npm modules. You can also map an extension to an object containing a module and optional methods like prepare(), registerPartial(), or registerHelper().
    • path: The root directory for templates. Defaults to the current working directory.
    • partialsPath: The directory for reusable template segments.
    • helpersPath: The directory for helper functions. Files in this directory must export a single function with the signature function(context) that returns a string.
    • layout: Enables layout support. If set to a filename, that file acts as the parent template.
    • layoutPath: The directory for layout templates. Defaults to path.
    • layoutKeyword: The key used by the engine to denote where primary content is injected. Defaults to 'content'.
    • isCached: Determines if templates are cached. Defaults to true.
    • context: A global context object or function (can be async) applied to all templates. When using the view handler, the function receives the request object.
  7. How the Vision Manager works

    master

    The Vision Manager follows a lifecycle of Prepare → Compile → Render:

    1. Prepare: The manager identifies the correct engine based on the file extension (or defaultExtension). If the engine has a prepare method, it is called to initialize the engine state.
    2. Compile: The template file is read from disk and passed to the engine's compile function. If isCached is enabled, the resulting compiled function is stored in an internal cache.
    3. Render: The compiled function is executed with the provided context. If a layout is configured, the rendered content is injected into the layout template using the layoutKeyword (defaulting to content) and the layout is rendered as well.

    Context Merging: If a global context was provided during Manager initialization, it is shallow-cloned and merged with the local context passed to the render call.

  8. Configure the Vision Manager

    master

    The Manager class is responsible for registering template engines, managing partials, helpers, and rendering templates. When instantiating the Manager, you must provide an engines object where keys are file extensions and values are engine configurations.

    Default Options

    • compileOptions: Object passed to the engine's compile function.
    • runtimeOptions: Object passed to the engine's render function.
    • layout: Boolean or string. If true, uses a default layout template. If a string, uses that specific template as the layout.
    • layoutKeyword: The key in the context used to inject the rendered content into the layout (defaults to 'content').
    • encoding: File encoding (defaults to 'utf8').
    • isCached: Boolean. If true, compiled templates are cached (defaults to true).
    • allowAbsolutePaths: Boolean. If false, absolute paths are rejected (defaults to false).
    • allowInsecureAccess: Boolean. If false, paths containing ../ are rejected (defaults to false).
    • contentType: The Content-Type header for the response (defaults to 'text/html').
    • compileMode: 'sync' or 'async' (defaults to 'sync').
    • context: An object or a function (request) => context that provides base data for all renders.
    const Manager = require('./lib/manager');
    
    const manager = new Manager(server, {
        engines: {
            pug: {
                module: require('pug'),
                compile: (str, opt) => require('pug').compile(str, opt)
            }
        },
        defaultExtension: 'pug',
        path: '/views',
        isCached: true
    });
  9. Register @hapi/vision as a hapi plugin

    master

    To use @hapi/vision in your hapi server, register it as a plugin. You can pass an options object to configure the views manager. If options.relativeTo is not provided, it will default to the server's settings.files.relativeTo if available.

    Note: The plugin can be registered multiple times, but a views manager can only be set once per realm.

    await server.register({
        plugin: require('@hapi/vision'),
        options: {
            // configuration options for the views manager
        }
    });
  10. Configure Pug with basedir

    master

    When using Pug, you may need to set compileOptions.basedir to ensure that absolute includes (e.g., include /root.pug) resolve correctly relative to your template directory.

    server.views({
        engines: { pug: Pug },
        relativeTo: __dirname,
        path: 'templates/pug',
        compileOptions: {
            basedir: Path.join(__dirname, 'templates/pug')
        }
    });
  11. Configure Nunjucks with prepare and compile

    master

    Nunjucks can be integrated by using the prepare method to configure the Nunjucks environment and the compile method to handle the template compilation.

    server.views({
        engines: {
            html: {
                compile: (src, options) => {
                    const template = Nunjucks.compile(src, options.environment);
                    return (context) => {
                        return template.render(context);
                    };
                },
                prepare: (options, next) => {
                    options.compileOptions.environment = Nunjucks.configure(options.path, { watch : false });
                    return next();
                }
            }
        },
        relativeTo: __dirname,
        path: 'templates/nunjucks'
    });
  12. Render templates using request.render()

    master

    Inside a request handler, use request.render(template, context, [options], [callback]) to render a template. This method is preferred over request.server.render() when working with plugins, as request.render() correctly identifies the view manager associated with the route's realm.

    Note: This method will not work in onRequest extensions because the route is not yet bound to the request. In those cases, use the response toolkit (h) instead.

    server.route({
        method: 'GET',
        path: '/view',
        handler: function (request, h) {
            return request.render('test', { message: 'hello' });
        }
    });