@t09tanaka/typed-hbs
v0.1.1
Published
Minimal typed Handlebars-style template engine for TypeScript
Maintainers
Readme
typed-hbs
Minimal typed Handlebars-style template engine for TypeScript.
{{key}} placeholders are replaced with values from a typed context object. Zero dependencies.
Install
npm install @t09tanaka/typed-hbsUsage
import { render } from "@t09tanaka/typed-hbs";
type ContactMailProps = {
name: string;
email: string;
meta: { ip: string };
};
const props: ContactMailProps = {
name: "Alice",
email: "[email protected]",
meta: { ip: "127.0.0.1" },
};
render<ContactMailProps>("Hello {{name}}", props);
// => "Hello Alice"
render<ContactMailProps>("ip={{ meta.ip }}", props);
// => "ip=127.0.0.1"Dot paths
Nested objects are accessed with . separated paths.
render("{{a.b.c}}", { a: { b: { c: "deep" } } });
// => "deep"Array indices
Array elements are accessed by numeric index.
render("{{items.0.title}}", { items: [{ title: "First" }] });
// => "First"Loops
Iterate over arrays with {{#each}}. Parent props are accessible inside the loop, and item properties take precedence.
const template = "{{#each items}}- {{title}} by {{author}}\n{{/each}}";
const props = {
author: "Alice",
items: [{ title: "Post 1" }, { title: "Post 2" }],
};
render(template, props);
// => "- Post 1 by Alice\n- Post 2 by Alice\n"
//
// - Post 1 by Alice
// - Post 2 by AliceFor primitive arrays, use {{.}} to reference the current value:
const tagList = "{{#each tags}}#{{.}} {{/each}}";
render(tagList, { tags: ["ts", "js", "node"] });
// => "#ts #js #node "Conditionals
Show or hide content based on truthy/falsy values:
const tpl = `{{#if premium}}
Premium member
{{else}}
Free member
{{/if}}`;
render(tpl, { premium: true });
// =>
// Premium member
//
render(tpl, { premium: false });
// =>
// Free member
//Chained conditions with {{else if}}:
const tpl = `{{#if role}}Admin{{else if guest}}Guest{{else}}User{{/if}}`;
render(tpl, { role: false, guest: true });
// => "Guest"Use {{#unless}} for negated conditions:
const tpl = "{{#unless paid}}Please pay{{/unless}}";
render(tpl, { paid: false });
// => "Please pay"Behavior
| Value | Result |
| ------------- | ------------------------------------------- |
| string, etc | String(value) |
| null | "" (empty string) |
| undefined | Throws Error |
| Missing key | Throws Error |
- Prototype chain values are not resolved (
hasOwnPropertycheck). - HTML escaping is not performed. Escape on the caller side if needed.
- Empty arrays produce an empty string (the
{{#each}}block is removed). - Non-array values in
{{#each}}throw anError. {{#if}}uses JavaScript truthy/falsy rules (false,0,"",null,undefinedare falsy).- Blocks can be nested (
{{#if}}inside{{#each}}, etc.).
API
render<T>(template: string, props: T): string
Replaces all {{key}} placeholders in template with values from props.
Throws an Error if any key is missing or resolves to undefined.
Scripts
npm test # Run tests
npm run build # Build ESM + CJS
npm run typecheck # Type checkLicense
MIT
