Template Engine#

Built-in Handlebars-like template engine with zero dependencies. Perfect for email templates, HTML generation, and more.

Quick Start#

typescript
import { render } from "@buntok/core/template";

const html = render("Hello {{ name }}!", { name: "World" });
// → "Hello World!"

Variables#

Use {{ variable }} to interpolate values. Supports dot notation for nested objects.

typescript
render("Hello {{ name }}!", { name: "Budi" });
// → "Hello Budi!"

render("{{ user.email }}", { user: { email: "budi@example.com" } });
// → "budi@example.com"
Info
Variables are HTML-escaped by default. Use {{{ rawHtml }}} for unescaped output.

Raw HTML (Triple Braces)#

typescript
const html = "<b>Bold text</b>";
render("{{{ html }}}", { html });
// → "<b>Bold text</b>" (not escaped)

Conditionals#

typescript
// Basic if
render("{{#if active}}Active{{/if}}", { active: true });
// → "Active"

// If/else
render("{{#if active}}Yes{{else}}No{{/if}}", { active: false });
// → "No"

// Unless (negative if)
render("{{#unless verified}}Please verify{{/unless}}", { verified: false });
// → "Please verify"

Loops#

typescript
const items = [{ name: "A" }, { name: "B" }, { name: "C" }];

render("{{#each items}}{{name}} {{/each}}", { items });
// → "A B C "
Info
Special variables in loops: @index (0-based), @first (boolean), @last (boolean)
typescript
render("{{#each items}}{{@index}}: {{name}}{{/each}}", {
  items: [{ name: "X" }, { name: "Y" }],
});
// → "0: X1: Y"

render("{{#each items}}{{#if @first}}FIRST{{/if}} {{/each}}", {
  items: ["a", "b", "c"],
});
// → "FIRST   "

Partials#

Reusable template fragments. Register once, use anywhere.

typescript
import { TemplateEngine } from "@buntok/core/template";

const engine = new TemplateEngine();

// Register partials
engine.registerPartial("header", "<h1>{{ title }}</h1>");
engine.registerPartial("footer", "<p>© 2024 Company</p>");

// Use in templates
const html = engine.render(`
  {{> header }}
  <p>Main content</p>
  {{> footer }}
`, { title: "Welcome" });

Helpers#

Custom functions for data transformation.

typescript
const engine = new TemplateEngine();

engine.registerHelper("formatDate", (date: unknown) => {
  return new Date(date as string).toLocaleDateString("id-ID");
});

engine.registerHelper("uppercase", (str: unknown) => {
  return String(str).toUpperCase();
});

const html = engine.render(
  "{{ uppercase name }} - {{ formatDate date }}",
  { name: "budi", date: "2024-01-15" }
);
// → "BUDI - 15/1/2024"

Comments#

typescript
render("Hello {{! this is a comment }}World", {});
// → "Hello World"

Strict Mode & Error Detection#

By default, missing variables throw an error with helpful suggestions.

typescript
render("Hello {{ usre.name }}", { user: { name: "Budi" } });
// → Error: Variable 'usre.name' not found in context.
//   Available keys: user
//   Did you mean: 'user.name'?
typescript
// Lenient mode - missing variables become empty string
const engine = new TemplateEngine({ strict: false });
engine.render("Hello {{ missing }}", {});
// → "Hello "

Compile & Reuse#

Pre-compile templates for better performance when rendering multiple times.

typescript
const engine = new TemplateEngine();
const compiled = engine.compile("Hello {{ name }}!");

// Reuse without re-parsing
compiled({ name: "A" }); // → "Hello A!"
compiled({ name: "B" }); // → "Hello B!"

Full Email Template Example#

typescript
import { render, TemplateEngine } from "@buntok/core/template";

const engine = new TemplateEngine();
engine.registerPartial("header", '<div class="header"><h1>{{ company }}</h1></div>');
engine.registerHelper("formatCurrency", (val: unknown) => `Rp ${Number(val).toLocaleString()}`);

const template = `
  {{> header }}
  
  <h1>Invoice #{{ invoice.number }}</h1>
  
  {{#if invoice.paid}}
    <p style="color: green;">✓ Paid</p>
  {{else}}
    <p style="color: red;">Please pay by {{ invoice.dueDate }}</p>
  {{/if}}
  
  {{#each invoice.items}}
    <div>{{ name }} - {{ formatCurrency price }}</div>
  {{/each}}
  
  <p>Total: {{ formatCurrency invoice.total }}</p>
`;

const html = engine.render(template, {
  company: "My Company",
  invoice: {
    number: "INV-001",
    paid: false,
    dueDate: "2024-02-01",
    items: [
      { name: "Product A", price: 150000 },
      { name: "Product B", price: 250000 },
    ],
    total: 400000,
  },
});

API Reference#

FunctionDescription
render(template, context)Render a template with context data
registerPartial(name, template)Register a reusable partial
registerHelper(name, fn)Register a helper function
new TemplateEngine(options)Create engine instance with options
engine.compile(template)Pre-compile template for reuse