JavaScriptNode.jsTemplate EngineSSROpen Source

HTML6 Template Engine

A lightweight, component-based server-side template engine for building fast, dynamic HTML from plain JavaScript. It's the engine powering this site's own rendering.

Timeline

Stable

Team

2 developers

Role

Co-Developer & Maintainer

HTML6 Template Engine

Overview

HTML6 is a server-side template engine that compiles plain HTML into fast, reusable render functions. The motivation was for someone who only knows HTML and CSS to be able to write conditionals, loops, and components without ever touching JavaScript or a framework like React.

The Challenge

The goal was for HTML6 to be the fastest template engine out there, not just a workable one. The catch: tags like if/elsif/else need to see their siblings to resolve correctly, and the obvious way to handle that means walking the parsed tree more than once, exactly the kind of overhead a fastest-in-class claim can't afford.

The Solution

HTML6 compiles by walking the parsed tree exactly once, bottom-up, dispatching each node only after its children are done. The technique that makes that possible is masking and unmasking.

Tags that need sibling context, like an if/elsif/else chain or a map loop, get masked instead of resolved inline: swapped for a placeholder key in a lookup table so the walk never has to backtrack. Once that single pass finishes, unmasking swaps every placeholder back to its real content, and the result is wrapped into a native JavaScript render function.

Key Features

  • ✓ Real .html files, no build step required
  • ✓ Conditions and expressions are real JavaScript
  • ✓ Components with slots and isolated props
  • ✓ Chainable pipes: {{price |> formatCurrency}}
  • ✓ Compiles once, renders every request from that function
  • ✓ Missing a variable fails loud, never silent

It's just .html

Every HTML6 template is a real .html file. Not .pug, not .hbs, not a .jsx file pretending to be markup. It's HTML with a few extra attributes and {{ }} sprinkled in, so your editor, Prettier, and every linter you already have already understand it.

That's the actual differentiator, more than any single feature below. No plugin to install, no new extension for your tooling to learn, no build step standing between the file you write and the file the browser would recognize as HTML. A UI/UX designer with zero JavaScript experience can open the file and follow exactly what it's doing.

Real JavaScript, not a mini-language

Interpolations and conditionals run real JavaScript expressions against the render scope, not a simplified template language that only looks similar:

<h1>{{title}}</h1>
<div if="user.loggedIn">Welcome back</div>
<div elsif="user.pending">Pending approval</div>
<div else>Please log in</div>

If it's valid JavaScript, it's valid inside {{ }} or an if. There's no mini-language to look up.

Loops with map

map loops over arrays directly on the tag, with an optional index and its own if:

<ul>
  <li map="p, i of projects" if="p.title.length > 0">{{i}}: {{p.title}}</li>
</ul>

Nested loops work the same way, one map attribute per level:

<div map="group of groups">
  <h2>{{group.name}}</h2>
  <ul>
    <li map="item of group.items">{{item}}</li>
  </ul>
</div>

Pipes for safe transforms

Function calls inside {{ }} are disabled for security. |> chains transforms onto a value instead:

{{title |> upper |> truncate 20}}
{{price |> formatCurrency}}
{{date |> formatDate 'YYYY-MM-DD'}}

Pipes are just functions registered by name, so adding a new one is a few lines of JavaScript, not a change to the template language itself:

var pipes = {
  upper: (x) => String(x).toUpperCase(),
  truncate: (x, len) => String(x).slice(0, len)
}

html6.compile(template, { pipes })

Components, slots, and isolated props

<template is="..."> defines a component. <slot> marks where its children get inserted. Props stay isolated to that component, while everything else in the outer scope flows down automatically, without being re-declared at every level:

<template is="card" title="string">
  <div class="card">
    <h2>{{props.title}}</h2>
    <slot></slot>
  </div>
</template>
<card title="Hi {{user.name}}">
  <p>Rendered from the slot.</p>
</card>

Compiles once, runs on every request

compile() takes the template and returns a render function. That's the expensive part, so it only runs once, at startup, and the result gets cached. Every request after that just calls that cached render function with fresh data.

one compile, many renders Template compiles once, at startup
Compiling depends only on the template, so it happens once and gets cached. Rendering depends only on the data, so every request just calls that cached function again. An already fast engine, made even faster: compiling never runs twice.

Where it actually runs

HTML6 first shipped in production at Nobo, the workplace this all started at. It also powers Ultimate Learning, a language-learning app I actively maintain. Two real production sites, not a demo that got built once and abandoned.

Interested in this project?

I'd love to discuss the technical details, challenges faced, and lessons learned from building this project.

Get in Touch