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.1.1

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, 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 (+200 bytes)

Reusable template fragments.

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>')

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

Syntax: [[> partialName ]]

Dynamic Partials Plugin (+450 bytes)

Select partials dynamically using variables - perfect for component libraries and conditional rendering.

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

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

// Register different layouts
engine.partial('adminLayout', '<div class="admin">[[= content ]]</div>')
engine.partial('userLayout', '<div class="user">[[= content ]]</div>')
engine.partial('guestLayout', '<div class="guest">[[= content ]]</div>')

// Choose layout dynamically based on user role
const html = engine.render('[[> (layoutType) ]]', {
  layoutType: 'adminLayout',  // Variable determines which partial to use
  content: 'Dashboard content'
})

// Works great in loops for rendering different component types
engine.partial('imageCard', '<div class="image">Image</div>')
engine.partial('videoCard', '<div class="video">Video</div>')
engine.partial('textCard', '<div class="text">Text</div>')

const items = engine.render(`
  [[ items.forEach(item => { ]]
    [[> (item.type) ]]
  [[ }) ]]
`, {
  items: [
    { type: 'imageCard' },
    { type: 'videoCard' },
    { type: 'textCard' }
  ]
})

Syntax: [[> (variableName) ]]

Features:

  • Supports dot notation: [[> (user.preferences.theme) ]]
  • Works in loops and conditionals
  • Graceful fallback for undefined variables (renders empty string)

Params Partials Plugin (+400 bytes)

Pass named parameters to partials - create reusable components with custom props.

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

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

// Create a reusable button component
engine.partial('button', `
  <button class="btn btn-[[= variant ]] btn-[[= size ]]" type="[[= type ]]">
    [[= label ]]
  </button>
`)

// Use it with different parameters
const html = engine.render(`
  [[> button variant="primary" size="large" type="submit" label="Save Changes" ]]
  [[> button variant="secondary" size="small" type="button" label="Cancel" ]]
`, {})

// Create alert component
engine.partial('alert', `
  <div class="alert alert-[[= type ]]" role="alert">
    [[= message ]]
  </div>
`)

const alerts = engine.render(`
  [[> alert type="success" message="Operation successful!" ]]
  [[> alert type="warning" message="Please be careful" ]]
  [[> alert type="error" message="Something went wrong" ]]
`, {})

Syntax: [[> partialName key1="value1" key2="value2" ]]

Features:

  • Automatic type conversion: "true"true, "42"42
  • Parameters override context data
  • Mix with global context variables
  • Perfect for component libraries

Combining Dynamic + Params:

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

// You can use both features together!
engine.partial('card', '<div class="[[= theme ]]">[[= title ]]</div>')

// Dynamic partial selection + parameters
engine.render('[[> (cardType) theme="dark" title="Hello" ]]', {
  cardType: 'card'
})

Helpers Plugin (+150 bytes)

Custom functions for formatting and transforming data.

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

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

engine.helper('uppercase', str => str.toUpperCase())
engine.helper('currency', price => `$${price.toFixed(2)}`)

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

Built-in helpers object: helpers.functionName(args)

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: 'John' })

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

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,
  DynamicPartialsPlugin,
  ParamsPartialsPlugin,
  HelpersPlugin,
  StrictModePlugin,
  I18nPlugin
} from '@niuxe/template-engine/plugins'

const engine = new TemplateEngine()
  .use(PartialsPlugin)
  .use(DynamicPartialsPlugin)
  .use(ParamsPartialsPlugin)
  .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.translations = {
  fr: { welcome: 'Bienvenue' }
}

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

Total size with all plugins: ~2.2 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

Component Library with Dynamic Partials

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

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

// 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 Partials

engine.partial('header', `
  <div style="background: #333; color: white; padding: 20px;">
    <h1>[[= companyName ]]</h1>
  </div>
`)

engine.partial('footer', `
  <div style="text-align: center; color: #666;">
    <p>© [[= year ]] [[= companyName ]]. All rights reserved.</p>
  </div>
`)

const email = engine.render(`
  [[> header ]]
  <div style="padding: 20px;">
    <p>Hi [[= userName ]],</p>
    <p>Your order #[[= orderId ]] has been confirmed.</p>
    <ul>
    [[ items.forEach(item => { ]]
      <li>[[= item.name ]] - [[= helpers.currency(item.price) ]]</li>
    [[ }) ]]
    </ul>
    <p><strong>Total: [[= helpers.currency(total) ]]</strong></p>
  </div>
  [[> footer ]]
`, {
  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 | +200 bytes | | + Dynamic Partials Plugin | +450 bytes | | + Params Partials Plugin | +400 bytes | | + Helpers Plugin | +150 bytes | | + Strict Mode Plugin | +290 bytes | | + Async Plugin | +260 bytes | | + I18n Plugin | +230 bytes | | All plugins combined | ~2.9 kio |

Comparison with alternatives

| Library | Size (gzipped) | Partials | Dynamic Partials | Parameters Partials | Helpers | I18n | Async | |---------|---------------|----------|------------------|------------|---------|------|-------| | TemplateEngine (core) | 950 bytes | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | TemplateEngine (full) | 2.9 kio | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Mustache.js | 3.2 kio | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | EJS | 4.3 kio | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | | Handlebars | 26 kio | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |

TemplateEngine is:

  • 3.4× lighter than Mustache.js (core: 950 bytes vs 3.2 kio)
  • 7× lighter than EJS (core: 950 bytes vs 7 kio)
  • 27× lighter than Handlebars (core: 950 bytes vs 26 kio)
  • Same features as Handlebars for 9× less (full: 2.9 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)

What it doesn't do:

  • No layout inheritance (use partials instead)
  • 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
  • Simple server-side rendering
  • Email templates
  • Web components

When NOT to use:

  • User-submitted templates (security risk)
  • Complex CMS with untrusted content
  • Need for advanced template inheritance

Migration from Handlebars

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

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

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

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

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

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

Main differences:

  • {{}}[[ ]]
  • {{var}}[[= var ]]
  • {{{raw}}}[[-raw ]]
  • {{> name}}[[> name ]] (requires PartialsPlugin)
  • {{> (dynamic)}}[[> (dynamic) ]] (requires DynamicPartialsPlugin)
  • {{#each}}[[ forEach ]] (native JavaScript)

Contributing

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

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

License

MIT

Acknowledgments

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


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