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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@niuxe/template-engine

v1.4.0

Published

Ultra-lightweight JavaScript template engine with automatic HTML escaping, intelligent caching, and optional plugins (~950 bytes gzipped)

Readme

TemplateEngine

npm version npm downloads bundle size GitHub stars

Ultra-lightweight JavaScript template engine with automatic HTML escaping, intelligent caching, and optional plugins.

~950 bytes gzipped (core) | Zero dependencies | ES6+ | Modular

Why?

  • Tiny: 27x smaller than Handlebars, 7x smaller than EJS, 3.4x smaller than Mustache.js
  • Fast: Built-in compilation cache with LRU eviction
  • Secure: Auto-escapes HTML by default
  • Simple: Clean syntax, no build step required
  • Modular: Optional plugins for partials, helpers, layouts, strict mode, and async file rendering
  • Pay for what you use: Core is 950 bytes, add only the plugins you need

Installation

npm install @niuxe/template-engine
import { TemplateEngine } from '@niuxe/template-engine'

Quick Start

const engine = new TemplateEngine()

const html = engine.render(`
  <h1>[[= title ]]</h1>
  <ul>
  [[ items.forEach(item => { ]]
    <li>[[= item.name ]] - $[[= item.price ]]</li>
  [[ }) ]]
  </ul>
`, {
  title: 'Products',
  items: [
    { name: 'Coffee', price: 3.50 },
    { name: 'Tea', price: 2.75 }
  ]
})

Syntax

Output (escaped)

[[= variable ]]
[[= user.name ]]
[[= items[0] ]]

Auto-escapes HTML entities (<, >, &, ", ')

Output (raw)

[[-htmlContent ]]

Renders unescaped HTML (use with caution)

JavaScript Code

[[ if (user.admin) { ]]
  <button>Admin Panel</button>
[[ } else { ]]
  <button>Dashboard</button>
[[ } ]]

[[ items.forEach(item => { ]]
  <div>[[= item ]]</div>
[[ }) ]]

[[ for (let i = 0; i < 10; i++) { ]]
  <span>[[= i ]]</span>
[[ } ]]

Plugins

TemplateEngine uses a modular plugin system. Import only what you need to keep your bundle small.

Partials Plugin (+800 bytes)

Reusable template fragments with three modes: simple, dynamic, and parameterized.

import { TemplateEngine } from '@niuxe/template-engine'
import { PartialsPlugin } from '@niuxe/template-engine/plugins/partials'

const engine = new TemplateEngine().use(PartialsPlugin)

engine.partial('header', '<header><h1>[[= title ]]</h1></header>')
engine.partial('footer', '<footer>© 2025</footer>')

// 1. Simple partials
const html = engine.render(`
  [[> header ]]
  <main>[[= content ]]</main>
  [[> footer ]]
`, { title: 'My Site', content: 'Welcome!' })

// 2. Dynamic partials - choose partial from variable
engine.partial('adminLayout', '<div class="admin">[[= content ]]</div>')
engine.partial('userLayout', '<div class="user">[[= content ]]</div>')

const layout = engine.render('[[> (layoutType) ]]', {
  layoutType: 'adminLayout',
  content: 'Dashboard'
})

// 3. Parameterized partials - pass custom props
engine.partial('button', `
  <button class="btn-[[= variant ]]" type="[[= type ]]">
    [[= label ]]
  </button>
`)

const button = engine.render(`
  [[> button variant="primary" type="submit" label="Save" ]]
`, {})

Syntax:

  • Simple: [[> partialName ]]
  • Dynamic: [[> (variableName) ]]
  • With params: [[> partialName key="value" ]]

Layout Plugin (+650 bytes)

Template inheritance with blocks, perfect for building multi-page applications with shared layouts.

import { TemplateEngine } from '@niuxe/template-engine'
import { LayoutPlugin } from '@niuxe/template-engine/plugins/layout'

const engine = new TemplateEngine().use(LayoutPlugin)

// Define a base layout
engine.layout('base', `
  <html>
    <head>
      <title>[[= title ]]</title>
      [[block:styles]]
        <link rel="stylesheet" href="default.css">
      [[/block]]
    </head>
    <body>
      [[block:header]]
        <h1>Default Header</h1>
      [[/block]]
      [[block:content]][[/block]]
      [[block:footer]]
        <footer>© 2025</footer>
      [[/block]]
    </body>
  </html>
`)

// Extend the layout in a page template
const html = engine.render(`
  [[extends:base]]
  [[block:styles]]
    [[parent]]
    <link rel="stylesheet" href="custom.css">
  [[/block]]
  [[block:content]]
    <main>
      <h2>Welcome to my page</h2>
      <p>[[= message ]]</p>
    </main>
  [[/block]]
`, {
  title: 'Home Page',
  message: 'Hello World!'
})

Features:

  • Template inheritance: Extend layouts with [[extends:layoutName]]
  • Block system: Define and override blocks with [[block:name]]...[[/block]]
  • Parent content: Include parent block content with [[parent]]
  • Multi-level inheritance: Layouts can extend other layouts (up to 10 levels)
  • Nested blocks: Blocks can contain other blocks for complex layouts

Syntax:

  • Extend layout: [[extends:layoutName]]
  • Define block: [[block:name]]content[[/block]]
  • Include parent: [[parent]]

Advanced example - Multi-level inheritance:

// Base layout
engine.layout('base', `
  <html>
    <head>[[block:styles]]<link rel="base.css">[[/block]]</head>
    <body>[[block:content]][[/block]]</body>
  </html>
`)

// Admin layout extends base
engine.layout('admin', `
  [[extends:base]]
  [[block:styles]]
    [[parent]]
    <link rel="admin.css">
  [[/block]]
  [[block:content]]
    <nav>Admin Navigation</nav>
    [[block:main]][[/block]]
  [[/block]]
`)

// Dashboard page extends admin
const page = engine.render(`
  [[extends:admin]]
  [[block:styles]]
    [[parent]]
    <link rel="dashboard.css">
  [[/block]]
  [[block:main]]
    <h1>Dashboard</h1>
    <p>Welcome, admin!</p>
  [[/block]]
`, {})

Result:

<html>
  <head>
    <link rel="base.css">
    <link rel="admin.css">
    <link rel="dashboard.css">
  </head>
  <body>
    <nav>Admin Navigation</nav>
    <h1>Dashboard</h1>
    <p>Welcome, admin!</p>
  </body>
</html>

Perfect for:

  • Multi-page websites with shared layouts
  • Admin panels with nested layouts
  • Email templates with consistent structure
  • Blog themes with customizable sections

Helpers Plugin (+200 bytes)

Custom functions for formatting and transforming data with chainable API.

import { TemplateEngine } from '@niuxe/template-engine'
import { HelpersPlugin } from '@niuxe/template-engine/plugins/helpers'

const engine = new TemplateEngine().use(HelpersPlugin)

engine.helper('upper', str => str.toUpperCase())
engine.helper('lower', str => str.toLowerCase())
engine.helper('truncate', (str, len) => str.slice(0, len) + '...')
engine.helper('wrap', (str, tag) => `<${tag}>${str}</${tag}>`)
engine.helper('currency', price => `$${price.toFixed(2)}`)

// Standard usage
const html1 = engine.render(`
  <h1>[[= helpers.upper(title) ]]</h1>
  <p>Price: [[= helpers.currency(price) ]]</p>
`, { title: 'hello', price: 19.99 })
// Output: <h1>HELLO</h1><p>Price: $19.99</p>

// Chainable usage - compose transformations Unix-style
const html2 = engine.render(`
  <h1>[[-helpers(title).upper().wrap('strong') ]]</h1>
  <p>[[-helpers(description).truncate(50).lower().wrap('em') ]]</p>
`, {
  title: 'welcome',
  description: 'THIS IS A VERY LONG DESCRIPTION THAT NEEDS TO BE SHORTENED'
})
// Output: <h1><strong>WELCOME</strong></h1>
//         <p><em>this is a very long description that needs to be sho...</em></p>

Features:

  • Standard mode: helpers.functionName(args) - call helpers directly
  • Chain mode: helpers(value).fn1().fn2() - compose transformations
  • Unix philosophy: Each helper does one thing, chain them together
  • Zero overhead: Chainable proxy only created when needed

Strict Mode Plugin (+290 bytes)

Throws errors when accessing undefined variables, helping catch typos and missing data.

import { TemplateEngine } from '@niuxe/template-engine'
import { StrictModePlugin } from '@niuxe/template-engine/plugins/strict'

const engine = new TemplateEngine().use(StrictModePlugin)

engine.strict = true

// ❌ Throws: Variable "userName" is not defined
engine.render('[[= userName ]]', { userNaem: 'Denis' })

// ✅ Works fine
engine.render('[[= userName ]]', { userName: 'Denis' })

Perfect for catching refactoring errors and validating API responses.

I18n Plugin (+230 bytes)

Multi-language support with variable interpolation.

import { TemplateEngine } from '@niuxe/template-engine'
import { I18nPlugin } from '@niuxe/template-engine/plugins/i18n'

const engine = new TemplateEngine().use(I18nPlugin)

engine.translations = {
  en: {
    greeting: 'Hello {name}!',
    items_count: 'You have {count} items'
  },
  fr: {
    greeting: 'Bonjour {name} !',
    items_count: 'Vous avez {count} articles'
  }
}

// Switch language
engine.locale = 'fr'

const html = engine.render(`
  <h1>[[= t("greeting", {name: userName}) ]]</h1>
  <p>[[= t("items_count", {count: items.length}) ]]</p>
`, { userName: 'Alice', items: [1, 2, 3] })
// Output: <h1>Bonjour Alice !</h1><p>Vous avez 3 articles</p>

Features:

  • Variable interpolation with {varName} syntax
  • Dynamic locale switching
  • Fallback to key if translation missing
  • Works with all template features (loops, conditionals)

Note: For complex i18n needs (plurals, dates, currencies), consider using i18next with the HelpersPlugin.

Async Plugin (+260 bytes)

Read and render templates from files (Node.js only).

import { TemplateEngine } from '@niuxe/template-engine'
import { AsyncPlugin } from '@niuxe/template-engine/plugins/async'

const engine = new TemplateEngine().use(AsyncPlugin)

// Read template from file system
const html = await engine.renderFile('./templates/email.html', {
  name: 'Alice',
  orderId: 12345
})

Node.js only. Throws error in browser environments.

Combining Plugins

Plugins can be chained together:

import { TemplateEngine } from '@niuxe/template-engine'
import {
  PartialsPlugin,
  LayoutPlugin,
  HelpersPlugin,
  StrictModePlugin,
  I18nPlugin
} from '@niuxe/template-engine/plugins'

const engine = new TemplateEngine()
  .use(PartialsPlugin)
  .use(LayoutPlugin)
  .use(HelpersPlugin)
  .use(StrictModePlugin)
  .use(I18nPlugin)

engine.strict = true
engine.locale = 'fr'
engine.partial('badge', '<span class="badge">[[= text ]]</span>')
engine.helper('upper', s => s.toUpperCase())
engine.layout('base', '<html><body>[[block:content]][[/block]]</body></html>')
engine.translations = {
  fr: { welcome: 'Bienvenue' }
}

const html = engine.render(`
  [[extends:base]]
  [[block:content]]
    [[> badge text="New" ]]
    <p>[[= t("welcome") ]] [[= helpers.upper(name) ]]</p>
  [[/block]]
`, { name: 'alice' })

Total size with all plugins: ~3.3 kio gzipped

Core API

render(template, data)

Compiles and renders a template with given data.

engine.render('<h1>[[= title ]]</h1>', { title: 'Hello' })
// Returns: '<h1>Hello</h1>'

Parameters:

  • template (string): Template string
  • data (object): Data object for interpolation

Returns: Rendered HTML string

Throws: Error if template is invalid or compilation fails

use(plugin)

Adds a plugin to the engine.

engine.use(PartialsPlugin)

Returns: this (for chaining)

clear()

Clears the compilation cache.

engine.clear()

Returns: this (for chaining)

Useful when:

  • Updating partials or helpers
  • Managing memory in long-running processes
  • Testing

Advanced Examples

Multi-Page Website with Layouts

import { TemplateEngine } from '@niuxe/template-engine'
import { LayoutPlugin, HelpersPlugin } from '@niuxe/template-engine/plugins'

const engine = new TemplateEngine()
  .use(LayoutPlugin)
  .use(HelpersPlugin)

engine.helper('formatDate', date => new Date(date).toLocaleDateString())

// Base layout
engine.layout('base', `
  <!DOCTYPE html>
  <html>
    <head>
      <meta charset="UTF-8">
      <title>[[= pageTitle ]] - My Site</title>
      [[block:styles]][[/block]]
    </head>
    <body>
      <header>
        <nav>
          <a href="/">Home</a>
          <a href="/about">About</a>
          <a href="/blog">Blog</a>
        </nav>
      </header>
      <main>
        [[block:content]][[/block]]
      </main>
      <footer>
        <p>© [[= new Date().getFullYear() ]] My Site</p>
      </footer>
    </body>
  </html>
`)

// Blog layout extends base
engine.layout('blog', `
  [[extends:base]]
  [[block:styles]]
    <link rel="stylesheet" href="/css/blog.css">
  [[/block]]
  [[block:content]]
    <article>
      [[block:article]][[/block]]
    </article>
    <aside>
      [[block:sidebar]]
        <h3>Recent Posts</h3>
      [[/block]]
    </aside>
  [[/block]]
`)

// Blog post page
const blogPost = engine.render(`
  [[extends:blog]]
  [[block:article]]
    <h1>[[= post.title ]]</h1>
    <time>[[= helpers.formatDate(post.date) ]]</time>
    <div>[[-post.content ]]</div>
  [[/block]]
  [[block:sidebar]]
    [[parent]]
    <ul>
    [[ recent.forEach(p => { ]]
      <li><a href="/blog/[[= p.slug ]]">[[= p.title ]]</a></li>
    [[ }) ]]
    </ul>
  [[/block]]
`, {
  pageTitle: 'My First Post',
  post: {
    title: 'Hello World',
    date: '2025-01-15',
    content: '<p>Welcome to my blog!</p>'
  },
  recent: [
    { slug: 'second-post', title: 'Second Post' },
    { slug: 'third-post', title: 'Third Post' }
  ]
})

Data Transformation with Chainable Helpers

import { TemplateEngine } from '@niuxe/template-engine'
import { HelpersPlugin } from '@niuxe/template-engine/plugins/helpers'

const engine = new TemplateEngine().use(HelpersPlugin)

// Register simple, focused helpers (Unix philosophy)
engine
  .helper('upper', s => s.toUpperCase())
  .helper('lower', s => s.toLowerCase())
  .helper('truncate', (s, len) => s.length > len ? s.slice(0, len) + '...' : s)
  .helper('trim', s => s.trim())
  .helper('slugify', s => s.toLowerCase().replace(/\s+/g, '-'))
  .helper('wrap', (s, tag) => `<${tag}>${s}</${tag}>`)
  .helper('prefix', (s, pre) => pre + s)

// Compose transformations by chaining
const userCard = engine.render(`
  <div class="user-card">
    <!-- Standard helper usage -->
    <h2>[[= helpers.upper(user.name) ]]</h2>

    <!-- Chainable composition -->
    <p class="bio">[[-helpers(user.bio).trim().truncate(100).wrap('em') ]]</p>
    <p class="slug">[[= helpers(user.name).lower().slugify() ]]</p>

    <!-- Complex chains -->
    <div class="tags">
    [[ user.tags.forEach(tag => { ]]
      <span>[[-helpers(tag).upper().prefix('#').wrap('strong') ]]</span>
    [[ }) ]]
    </div>
  </div>
`, {
  user: {
    name: 'Alice Michu',
    bio: '  Software engineer passionate about clean code and minimal design. Loves open source and teaching.  ',
    tags: ['javascript', 'react', 'node']
  }
})

// Output:
// <div class="user-card">
//   <h2>ALICE MICHU</h2>
//   <p class="bio"><em>Software engineer passionate about clean code and minimal design. Loves open source and teaching.</em></p>
//   <p class="slug">alice-michu</p>
//   <div class="tags">
//     <span><strong>#JAVASCRIPT</strong></span>
//     <span><strong>#REACT</strong></span>
//     <span><strong>#NODE</strong></span>
//   </div>
// </div>

Component Library with Dynamic Partials

import { TemplateEngine } from '@niuxe/template-engine'
import { PartialsPlugin } from '@niuxe/template-engine/plugins'

const engine = new TemplateEngine().use(PartialsPlugin)

// Define components
engine.partial('button', '<button class="btn-[[= variant ]]">[[= label ]]</button>')
engine.partial('input', '<input type="[[= type ]]" placeholder="[[= placeholder ]]">')
engine.partial('card', '<div class="card-[[= theme ]]">[[= content ]]</div>')

// Render different components dynamically
const form = engine.render(`
  [[ components.forEach(comp => { ]]
    [[> (comp.type) variant="primary" label="Submit" ]]
  [[ }) ]]
`, {
  components: [
    { type: 'button' },
    { type: 'input' }
  ]
})

Multi-state Component

engine.partial('loading', '<div class="spinner">Loading...</div>')
engine.partial('error', '<div class="error">[[= message ]]</div>')
engine.partial('success', '<div class="success">[[= data ]]</div>')

// Render based on application state
const widget = engine.render('[[> (state) message="Error occurred" data="Success!" ]]', {
  state: 'loading' // Can be 'loading', 'error', or 'success'
})

Email Template with Layouts and Partials

import { TemplateEngine } from '@niuxe/template-engine'
import { LayoutPlugin, PartialsPlugin, HelpersPlugin } from '@niuxe/template-engine/plugins'

const engine = new TemplateEngine()
  .use(LayoutPlugin)
  .use(PartialsPlugin)
  .use(HelpersPlugin)

engine.helper('currency', price => `$${price.toFixed(2)}`)
engine.helper('upper', str => str.toUpperCase())
engine.helper('truncate', (str, len) => str.length > len ? str.slice(0, len) + '...' : str)
// Email base layout
engine.layout('email', `
  <!DOCTYPE html>
  <html>
    <body style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
      [[block:header]]
        <div style="background: #333; color: white; padding: 20px;">
          <h1>[[= companyName ]]</h1>
        </div>
      [[/block]]
      [[block:content]][[/block]]
      [[block:footer]]
        <div style="text-align: center; color: #666; padding: 20px;">
          <p>© [[= year ]] [[= companyName ]]. All rights reserved.</p>
        </div>
      [[/block]]
    </body>
  </html>
`)

// Partials for reusable components
engine.partial('button', `
  <a href="[[= url ]]" style="background: #007bff; color: white; padding: 10px 20px; text-decoration: none; display: inline-block; border-radius: 5px;">
    [[= text ]]
  </a>
`)

// Order confirmation email
const email = engine.render(`
  [[extends:email]]
  [[block:content]]
    <div style="padding: 20px;">
      <h2>Order Confirmation</h2>
      <p>Hi [[= helpers(userName).upper() ]],</p>
      <p>Your order #[[= orderId ]] has been confirmed.</p>

      <table style="width: 100%; border-collapse: collapse;">
        <thead>
          <tr style="border-bottom: 2px solid #ddd;">
            <th style="text-align: left; padding: 10px;">Item</th>
            <th style="text-align: right; padding: 10px;">Price</th>
          </tr>
        </thead>
        <tbody>
        [[ items.forEach(item => { ]]
          <tr style="border-bottom: 1px solid #eee;">
            <td style="padding: 10px;">[[= helpers(item.name).truncate(30) ]]</td>
            <td style="text-align: right; padding: 10px;">[[= helpers.currency(item.price) ]]</td>
          </tr>
        [[ }) ]]
        </tbody>
        <tfoot>
          <tr>
            <td style="padding: 10px;"><strong>Total</strong></td>
            <td style="text-align: right; padding: 10px;"><strong>[[= helpers.currency(total) ]]</strong></td>
          </tr>
        </tfoot>
      </table>

      <div style="margin-top: 30px; text-align: center;">
        [[> button url="https://example.com/orders/12345" text="View Order" ]]
      </div>
    </div>
  [[/block]]
`, {
  companyName: 'ACME Inc',
  year: 2025,
  userName: 'Alice',
  orderId: 12345,
  items: [
    { name: 'Product A', price: 29.99 },
    { name: 'Product B', price: 49.99 }
  ],
  total: 79.98
})

Performance

  • Compilation cache: Templates are compiled once, cached for reuse
  • Cache limit: 100 templates max (LRU eviction)
  • Benchmarks (10,000 renders):
    • First render: ~2ms (compilation + render)
    • Cached renders: ~0.3ms (cache hit)

Security

HTML Escaping

By default, [[= ... ]] escapes HTML to prevent XSS:

engine.render('[[= html ]]', { html: '<script>alert("xss")</script>' })
// Returns: '&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;'

Raw Output

Use [[-... ]] for trusted HTML only:

engine.render('[[-trustedHTML ]]', { trustedHTML: '<b>Safe</b>' })
// Returns: '<b>Safe</b>'

⚠️ Never use raw output with user-generated content.

Template Injection

Templates use JavaScript's with() statement and execute arbitrary code. Only use templates from trusted sources. Never allow users to submit their own template strings.

Use Strict Mode to catch undefined variables and prevent typos from becoming security issues.

Size Breakdown

| Component | Minified + Gzipped | |-----------|-------------------| | Core Engine | 950 bytes | | + Partials Plugin (all 3 modes) | +800 bytes | | + Layout Plugin | +650 bytes | | + Strict Mode Plugin | +290 bytes | | + I18n Plugin | +230 bytes | | + Async Plugin | +260 bytes | | + Helpers Plugin (with chaining) | +150 bytes | | All plugins combined | ~3.3 kio |

Comparison with alternatives

| Library | Size (gzipped) | Layouts | Partials | Dynamic Partials | Params Partials | Helpers | Helpers Chainable | Strict Mode | I18n | Async | |---------|---------------|----------|---------|------------------|-----------------|---------|-----------|-------------|------|-------| | TemplateEngine (core) | 950 bytes | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | TemplateEngine (full) | 3.3 kio | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Mustache.js | 3.3 kio | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | | EJS | 7 kio | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | | Handlebars | 26 kio | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | | Nunjucks | 32 kio | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |

TemplateEngine is:

  • Same size as Mustache.js (core: 950 bytes vs 3.3 kio) but with more features when using plugins
  • 2.1× lighter than EJS (full: 3.3 kio vs 7 kio)
  • 7.9× lighter than Handlebars (full: 3.3 kio vs 26 kio)
  • 9.7× lighter than Nunjucks (full: 3.3 kio vs 32 kio)
  • More features than Handlebars for ~8× less (layouts + chainable helpers + all features: 3.3 kio vs 26 kio)

Browser Support

Works in all modern browsers and Node.js 14+.

Requires:

  • ES6 classes
  • Private fields (#)
  • Template literals
  • Map
  • Proxy (for Strict Mode plugin only)

Limitations

What it does well:

  • Small bundle size
  • Fast rendering
  • Simple syntax
  • Plugin extensibility
  • Component-based development (with Dynamic + Params plugins)
  • Layout inheritance (with Layout plugin)

What it doesn't do:

  • No precompilation to static files
  • No advanced i18n (plurals, date/currency formatting - use i18next instead)
  • No sandboxing (templates can execute any JavaScript)

When to use:

  • SPAs where bundle size matters
  • Component libraries and design systems
  • Multi-page websites with shared layouts
  • Simple server-side rendering
  • Email templates
  • Web components

When NOT to use:

  • User-submitted templates (security risk)
  • Complex CMS with untrusted content

Migration from Handlebars

// Handlebars
{{> header}}
<h1>{{title}}</h1>
{{#each items}}
  <li>{{name}}</li>
{{/each}}

// TemplateEngine (equivalent features, 8× smaller!)
[[> header ]]
<h1>[[= title ]]</h1>
[[ items.forEach(item => { ]]
  <li>[[= item.name ]]</li>
[[ }) ]]

// Handlebars dynamic partials
{{> (whichPartial) }}

// TemplateEngine (with PartialsPlugin)
[[> (whichPartial) ]]

// Handlebars with parameters
{{> card title="Hello" theme="dark" }}

// TemplateEngine (with PartialsPlugin)
[[> card title="Hello" theme="dark" ]]

Main differences:

  • {{}}[[ ]]
  • {{var}}[[= var ]]
  • {{{raw}}}[[-raw ]]
  • {{> name}}[[> name ]] (requires PartialsPlugin)
  • {{> (dynamic)}}[[> (dynamic) ]] (PartialsPlugin supports this)
  • {{> name param="value"}}[[> name param="value" ]] (PartialsPlugin supports this)
  • {{#each}}[[ forEach ]] (native JavaScript)

Migration from Nunjucks

// Nunjucks
{% extends "base.html" %}
{% block content %}
  <h1>{{ title }}</h1>
{% endblock %}

// TemplateEngine (with LayoutPlugin - 10× smaller!)
[[extends:base]]
[[block:content]]
  <h1>[[= title ]]</h1>
[[/block]]

// Nunjucks parent block
{% block styles %}
  {{ super() }}
  <link rel="custom.css">
{% endblock %}

// TemplateEngine
[[block:styles]]
  [[parent]]
  <link rel="custom.css">
[[/block]]

Main differences:

  • {% %}[[ ]]
  • {{ var }}[[= var ]]
  • {% extends "base" %}[[extends:base]]
  • {% block name %}[[block:name]]
  • {{ super() }}[[parent]]
  • {% include %}[[> name ]] (requires PartialsPlugin)

Contributing

Found a bug? Open an issue with a minimal reproduction.

Want to add a plugin? PRs welcome! Keep it small and focused.

Support the project

If you find this engine useful, please consider giving it a ⭐ on GitHub. It helps more developers discover the project!

License

MIT

Acknowledgments

Inspired by Handlebars, EJS, Nunjucks, Underscore templates, and the pursuit of minimalism.


Built with ❤️ for developers who care about bundle size.