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 🙏

© 2025 – Pkg Stats / Ryan Hefner

handlebars-jle

v1.0.12

Published

Faster Handlebars templating with JSON Logic Engine

Readme

Handlebars (JSON Logic Edition)

Hey there! The documentation for this module is still being rewritten.

This is an implementation of Handlebars that processes templates into JSON Logic, which is then used by JSON Logic Engine to optimize and evaluate the templates.

This implementation of Handlebars appears to be ~25x faster than the original implementation, and allows for more variety in execution strategy.

I will admit upfront that there are some differences in this implementation compared to the original Handlebars. I will try to document these differences as I go along.

Some of the obvious differences include:

  • Asynchronous Execution is fully supported; this means you can add async helpers natively.
  • ~~Iteration is strictly done via block expressions like #each, implicit iteration is not supported. (Use #each kids instead of #kids)~~
  • There are significantly more built-in helpers, which I may remove or document as I publish this module.
  • ~~The whitespace control is currently not supported in the grammar. (I may add it later) Ex. {{~foo}} is not supported.~~
  • Whitespace control is supported, but is handled by a preprocessor and not the grammar. This nuance probably won't affect you, but it's worth mentioning. (Essentially, the whitespace is stripped before the template is parsed)
  • Inline Partials are supported, but slightly different currently.
  • ~~To avoid additional syntax, as is not supported in block expressions, I chose to use hash arguments in with instead.~~
  • as is supported in block expressions, however it will incur a performance cost / disable inline optimizations as it will perform a recursive lookup. In spite of the de-optimization, it should still perform quite reasonably.
  • with supports hash arguments, which allows for more flexibility in how the block is executed.
  • Supports an (optimized) Interpreted Mode for both synchronous and asynchronous execution; this means you execute templates in browser contexts that disallow new Function or eval.
  • If you need to access the index / private context of an above iterator, ../@index is used instead of @../index. I will likely change this to match the original handlebars. This is kind of a niche edge case, but it's worth mentioning.

I believe these differences are relatively minor and do not impact most use cases, and should be easy to work aorund, but I might iterate on this in the future.

Install

To install:

bun install handlebars-jle

Conditionals

Conditionals are done via the if block helper.

{{#if age}}
    {{name}} is {{age}} years old.
{{else}}
    {{name}}'s age is unknown.
{{/if}}

There is also support for if/else if chains

{{#if age}}
    {{name}} is {{age}} years old.
{{else if dob}}
    {{name}} was born on {{dob}}.
{{else}}
    {{name}}'s age is unknown.
{{/if}}

Iteration

Iteration is done via block expressions like #each, and the block helper is used to define the iteration context.

{{#each kids}}
    {{name}} is {{age}} years old.
{{/each}}

Data:

[
    { "name": "John", "age": 5 },
    { "name": "Jane", "age": 7 }
]

And over objects,

{{#each kids}}
    {{@key}} is {{this}} years old.
{{/each}}

Data:

{
    "John": 5,
    "Jane": 7
}

Variable Traversal Syntax is supported with ../ and @key and @index are supported.

With

The with block helper has been adjusted a bit from the original Handlebars. It now supports hash arguments, which allows for more flexibility in how the block is executed.

{{#with name='John Doe' age=27}}
    {{name}} is {{age}} years old.
{{/with}}

Adding Helpers

You can add helpers by adding methods to the JSON Logic Engine.

import { Handlebars } from 'handlebars-jle';

const hbs = new Handlebars();
hbs.engine.addMethod('addOne', ([a]) => a + 1, { sync: true, deterministic: true });

const template = hbs.compile('{{addOne age}}');

template({ age: 5 }); // 6
template({ age: 10 }); // 11

If your method is synchronous and deterministic (same input always produces the same output, and it will never return a promise), you should specify that in the options. This will allow the engine to optimize the method; and if synchronous, allow it to be used by compile

Here is a more interesting example, using async support:

import { AsyncHandlebars } from 'handlebars-jle';

const hbs = new AsyncHandlebars();

hbs.engine.addMethod('fetch', async ([url]) => {
    const response = await fetch(url)
    return response.json()
})

const template = hbs.compile(`{{#each (fetch 'https://jsonplaceholder.typicode.com/users')}}
@{{username}} - {{name}}
{{/each}}`)

template().then(console.log)

Would produce:

@Bret - Leanne Graham
@Antonette - Ervin Howell
@Samantha - Clementine Bauch
@Karianne - Patricia Lebsack
@Kamren - Chelsey Dietrich
@Leopoldo_Corkery - Mrs. Dennis Schulist
@Elwyn.Skiles - Kurtis Weissnat
@Maxime_Nienow - Nicholas Runolfsdottir V
@Delphine - Glenna Reichert
@Moriah.Stanton - Clementina DuBuque

Executing Templates

This module supports both a compiled mode and an "interpreted" mode.

The compiled mode is faster, but the interpreted mode is more flexible and can be used in environments that disallow eval or new Function.

For example:


const { Handlebars, AsyncHandlebars } = require('handlebars-jle');

const hbs = new Handlebars();
const hbsAsync = new AsyncHandlebars();
const hbsInterpreted = new Handlebars({ interpreted: true });
const hbsAsyncInterpreted = new AsyncHandlebars({ interpreted: true });

const hello = hbs.compile('Hello, {{name}}!')
const helloAsync = hbsAsync.compile('Hello, {{name}}!')
const helloInterpreted = hbsInterpreted.compile('Hello, {{name}}!')
const helloAsyncInterpreted = hbsAsyncInterpreted.compile('Hello, {{name}}!) 

Any of these can now be run with:

hello('Jesse') // Hello, Jesse!
helloAsync('Bob') // Promise<'Hello, Bob!'>
helloInterpreted('Steve') // Hello, Steve!
helloAsyncInterpreted('Tara') // Promise<Hello, Tara!>

If you have no async helpers to add to your templates, it's strongly recommended you use the synchronous class.

While the compiler / optimizer will make the execution fully synchronous if everything in the template is not async, there is still some overhead in JavaScript engines that slow it down a bit when packing it into a promise, so it should only be used if you've added async helpers to your engine you'd like to use.

Adding Partials

You can add partials by using register,

import { Handlebars } from 'handlebars-jle';

const hbs = new Handlebars();
hbs.register('greeting', 'Hello, {{name}}!')

const template = hbs.compile('{{>greeting name="Jesse"}}');

template(); // Hello, Jesse!

Of some note, partials that can be fully evaluated and inlined will be! In the above case, since greeting receives all of the information it needs as constants, it will fully evaluate the partial and inline it into the template.

import { Handlebars } from 'handlebars-jle';

const hbs = new Handlebars({ interpreted: true });
hbs.register('greeting', 'Hello, {{name}}!')

const template = hbs.compile('{{>greeting name="Jesse"}}');

template(); // Hello, Jesse!

Works in the exact same way, but it will not use eval or new Function to compile the partial, which is useful in environments that disallow it. While this is not "compiled", this too will inline the partial if it can be fully evaluated.

Inline Partials (Experimental)

Inline Partials are supported but have a slightly different syntax as they do not use the deprecated decorators syntax.

{{#inline "greeting"}}Hello, {{name}}!{{/inline}}
{{>greeting name="Jesse"}}

This will produce the same output as the previous example.

The reason they are flagged as experimental is because they currently do not scope themselves exclusively to the template that registered them. This means that if you register an inline partial in one template, it will be available in all templates. This is not ideal, and I will be working on a solution to this in the future.

Adding Block Helpers

This needs fleshed out documentation. This needs some explanation because you're able to deeply optimize the block helper in some powerful ways.