Handlebars helpers which implement layout blocks similar to Jade, Jinja, Nunjucks, Swig, and Twig.
npx @tessl/cli install tessl/npm-handlebars-layouts@3.1.0Handlebars Layouts provides a comprehensive set of Handlebars helpers that implement layout blocks and templating functionality similar to popular template engines like Jade, Jinja, Nunjucks, Swig, and Twig. The library offers four core helpers for layout inheritance, partial composition, block definition, and content insertion with support for append/prepend/replace modes.
npm install handlebars-layoutsconst layouts = require('handlebars-layouts');For ES modules:
import layouts from 'handlebars-layouts';const handlebars = require('handlebars');
const layouts = require('handlebars-layouts');
// Method 1: Manual registration
const helpers = layouts(handlebars);
handlebars.registerHelper(helpers);
// Method 2: Auto-registration (recommended)
layouts.register(handlebars);
// Register layout partials
handlebars.registerPartial('layout', layoutTemplate);
// Compile and render templates that use layout helpers
const template = handlebars.compile(pageTemplate);
const output = template(data);Handlebars Layouts is built around a stack-based system for managing layout inheritance and content blocks:
extend and embed helpersThe main factory function generates helper objects for registration with Handlebars instances.
/**
* Generates an object of layout helpers
* @param {Object} handlebars - Handlebars instance
* @returns {Object} Object containing extend, embed, block, and content helpers
*/
function layouts(handlebars);
/**
* Both generates and registers layout helpers with Handlebars
* @param {Object} handlebars - Handlebars instance
* @returns {Object} Object containing the registered helpers
*/
layouts.register = function(handlebars);The extend helper enables layout inheritance by loading a partial and defining block content within it.
/**
* Handlebars helper for layout inheritance
* Template usage: {{#extend "layout" customData key="value"}}...{{/extend}}
* @param {String} name - Name of partial to render
* @param {Object} [customContext] - Optional custom context for the partial
* @param {Object} options - Handlebars options object
* @param {Function} options.fn - Template function for the block content
* @param {Object} options.hash - Hash attributes passed to helper
* @returns {String} Rendered partial with block content applied
*/
extend(name, customContext, options);Usage Example:
{{#extend "layout" pageTitle="Home"}}
{{#content "title" mode="prepend"}}Welcome - {{/content}}
{{#content "body"}}
<p>This is the home page content.</p>
{{/content}}
{{/extend}}The embed helper allows loading partials that can extend layouts without conflicts with the main layout's blocks.
/**
* Handlebars helper for partial composition
* Template usage: {{#embed "partial" customData key="value"}}...{{/embed}}
* @param {String} name - Name of partial to render
* @param {Object} [customContext] - Optional custom context for the partial
* @param {Object} options - Handlebars options object
* @param {Function} options.fn - Template function for the block content
* @param {Object} options.hash - Hash attributes passed to helper
* @returns {String} Rendered partial with isolated block content
*/
embed(name, customContext, options);Usage Example:
{{#extend "layout"}}
{{#content "body"}}
{{#embed "gallery"}}
{{#content "images"}}
<img src="1.png" alt="" />
<img src="2.png" alt="" />
{{/content}}
{{/embed}}
{{#embed "modal" title="Gallery"}}
{{#content "body"}}<img src="1.png" alt="" />{{/content}}
{{/embed}}
{{/content}}
{{/extend}}The block helper defines named content blocks with optional default content.
/**
* Handlebars helper for defining content blocks
* Template usage: {{#block "name"}}default content{{/block}}
* @param {String} name - Block identifier
* @param {Object} options - Handlebars options object
* @param {Function} options.fn - Template function for default block content
* @returns {String} Final block content after applying all modifications
*/
block(name, options);Usage Example:
{{#block "header"}}
<h1>Default Title</h1>
{{/block}}
{{#block "main"}}
<p>Default content</p>
{{/block}}
{{#block "footer"}}
<p>© 2023</p>
{{/block}}The content helper sets or checks block content with support for append, prepend, and replace modes.
/**
* Handlebars helper for setting or checking block content
* Template usage as setter: {{#content "name" mode="append"}}content{{/content}}
* Template usage as getter: {{#if (content "name")}}...{{/if}}
* @param {String} name - Identifier of block to modify or check
* @param {Object} options - Handlebars options object
* @param {Function} [options.fn] - Template function for content (setter mode)
* @param {Object} options.hash - Hash attributes
* @param {String} [options.hash.mode="replace"] - Content mode: "append", "prepend", "replace"
* @returns {undefined|Boolean} undefined (setter) or boolean indicating if block has content (getter)
*/
content(name, options);Content Modes:
replace (default): Replaces existing block content entirelyappend: Adds content after existing block contentprepend: Adds content before existing block contentUsage Examples:
{{!-- Setting content with different modes --}}
{{#content "header"}}
<h1>New Title</h1>
{{/content}}
{{#content "main" mode="append"}}
<p>Additional content</p>
{{/content}}
{{#content "sidebar" mode="prepend"}}
<nav>Navigation</nav>
{{/content}}
{{!-- Conditional block rendering --}}
{{#if (content "sidebar")}}
<div class="with-sidebar">
<main class="main-content">{{#block "main"}}{{/block}}</main>
<aside class="sidebar">{{#block "sidebar"}}{{/block}}</aside>
</div>
{{else}}
<div class="full-width">
{{#block "main"}}{{/block}}
</div>
{{/if}}Layout Template (layout.hbs):
<!doctype html>
<html lang="en">
<head>
{{#block "head"}}
<title>{{title}}</title>
<link rel="stylesheet" href="assets/css/screen.css" />
{{/block}}
</head>
<body>
<div class="site">
<header class="site-header">
{{#block "header"}}
<h1>{{title}}</h1>
{{/block}}
</header>
<main class="site-main">
{{#block "body"}}
<h2>Default Content</h2>
{{/block}}
</main>
<footer class="site-footer">
{{#block "footer"}}
<small>© 2023</small>
{{/block}}
</footer>
</div>
{{#block "scripts"}}
<script src="assets/js/main.js"></script>
{{/block}}
</body>
</html>Page Template (page.hbs):
{{#extend "layout"}}
{{#content "head" mode="append"}}
<link rel="stylesheet" href="assets/css/page.css" />
{{/content}}
{{#content "header"}}
<h1>Welcome to Our Site</h1>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
{{/content}}
{{#content "body"}}
<h2>Page Content</h2>
<p>This is the main page content.</p>
<ul>
{{#each items}}
<li>{{this}}</li>
{{/each}}
</ul>
{{/content}}
{{#content "scripts" mode="prepend"}}
<script src="assets/js/analytics.js"></script>
{{/content}}
{{/extend}}JavaScript Setup:
const handlebars = require('handlebars');
const layouts = require('handlebars-layouts');
const fs = require('fs');
// Register helpers
layouts.register(handlebars);
// Register layout partial
handlebars.registerPartial('layout', fs.readFileSync('layout.hbs', 'utf8'));
// Compile page template
const template = handlebars.compile(fs.readFileSync('page.hbs', 'utf8'));
// Render with data
const output = template({
title: 'My Website',
items: ['Apple', 'Orange', 'Banana']
});
console.log(output);The library throws specific errors for common issues:
extend or embed helpers, an Error is thrown with the message 'Missing partial: \'[name]\''// This will throw an error if 'nonexistent' partial is not registered
{{#extend "nonexistent"}}...{{/extend}}
// Error: Missing partial: 'nonexistent'