Vision for hapi.js
repository·master·Indexed 41 days ago
https://github.com/hapijs/visionA 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.
What's inside Vision
- @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.
Map custom engines to the Vision API
masterIf a templating engine does not follow the standard API pattern, you can map it to the Vision API by providing a
compilefunction (and optionally apreparefunction) within theenginesconfiguration object.The
compilefunctionThe
compilefunction receives the source (src) andoptions. It must return a function that accepts acontextobject and returns the rendered string.The
preparefunctionThe
preparefunction is used to perform setup tasks (like configuring an environment) before compilation. It receivesoptionsand anextcallback.// Example: Mapping Mustache to the Vision API server.views({ engines: { html: { compile: (template) => { Mustache.parse(template); return (context) => { return Mustache.render(template, context); }; } } } });Register and use template engines
masterTo use a template engine, include it in the
enginesobject within theserver.views()configuration.If you are providing a custom engine object instead of just a module, the
modulemust contain acompile()function. The signature ofcompile()depends on thecompileModesetting:Sync Mode (
compileMode: 'sync', default):compile(template, options)returns a function with signaturefunction(context, options).
Async Mode (
compileMode: 'async'):compile(template, options, next)wherenextisfunction(err, compiled).- The
compiledfunction has the signaturefunction(context, options, callback)wherecallbackisfunction(err, rendered).
server.views({ engines: { html: { module: require('handlebars'), compileMode: 'sync' } }, path: __dirname + '/templates' });Configure TypeScript typings for Vision
masterTo enable autocompletion and type safety for templates and layouts, you can augment the
@hapi/visionmodule.Typing the Response Handler
Define
ViewTypesto restricttemplateandlayoutto your specific filenames.Typing Decorated Handlers
To provide strict type checking for
server.render,request.render, andh.view, augment the following interfaces:RenderMethod(forserver.render)RequestRenderMethod(forrequest.render)ToolkitRenderMethod(forh.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; } }Register @hapi/vision with a Hapi server
masterTo use templated responses, you must register the
@hapi/visionplugin with your Hapi server. Once registered, theserverand thehresponse 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();Configure the Views Manager options
masterWhen 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 amoduleand optional methods likeprepare(),registerPartial(), orregisterHelper().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 signaturefunction(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 topath.layoutKeyword: The key used by the engine to denote where primary content is injected. Defaults to'content'.isCached: Determines if templates are cached. Defaults totrue.context: A global context object or function (can be async) applied to all templates. When using the view handler, the function receives therequestobject.
How the Vision Manager works
masterThe Vision Manager follows a lifecycle of Prepare → Compile → Render:
- Prepare: The manager identifies the correct engine based on the file extension (or
defaultExtension). If the engine has apreparemethod, it is called to initialize the engine state. - Compile: The template file is read from disk and passed to the engine's
compilefunction. IfisCachedis enabled, the resulting compiled function is stored in an internal cache. - Render: The compiled function is executed with the provided
context. If alayoutis configured, the rendered content is injected into the layout template using thelayoutKeyword(defaulting tocontent) and the layout is rendered as well.
Context Merging: If a global
contextwas provided during Manager initialization, it is shallow-cloned and merged with the localcontextpassed to therendercall.- Prepare: The manager identifies the correct engine based on the file extension (or
Configure the Vision Manager
masterThe
Managerclass is responsible for registering template engines, managing partials, helpers, and rendering templates. When instantiating theManager, you must provide anenginesobject 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. Iftrue, uses a defaultlayouttemplate. 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. Iftrue, compiled templates are cached (defaults totrue).allowAbsolutePaths: Boolean. Iffalse, absolute paths are rejected (defaults tofalse).allowInsecureAccess: Boolean. Iffalse, paths containing../are rejected (defaults tofalse).contentType: TheContent-Typeheader for the response (defaults to'text/html').compileMode:'sync'or'async'(defaults to'sync').context: An object or a function(request) => contextthat 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 });Register @hapi/vision as a hapi plugin
masterTo use
@hapi/visionin your hapi server, register it as a plugin. You can pass an options object to configure the views manager. Ifoptions.relativeTois not provided, it will default to the server'ssettings.files.relativeToif 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 } });Configure Pug with basedir
masterWhen using Pug, you may need to set
compileOptions.basedirto 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') } });Configure Nunjucks with prepare and compile
masterNunjucks can be integrated by using the
preparemethod to configure the Nunjucks environment and thecompilemethod 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' });Render templates using request.render()
masterInside a request handler, use
request.render(template, context, [options], [callback])to render a template. This method is preferred overrequest.server.render()when working with plugins, asrequest.render()correctly identifies the view manager associated with the route's realm.Note: This method will not work in
onRequestextensions 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' }); } });