A lightweight HTML template engine with update capabilities. Creates templates that can be re-rendered with new data without recreating DOM nodes.
import { html } from '@relax.js/core/html';
// Create a template
const userCard = html`
<div class="user-card">
<h2>{{name}}</h2>
<p>{{email}}</p>
</div>
`;
// Render with data
const result = userCard({ name: 'John', email: 'john@example.com' });
container.appendChild(result.fragment);
// Update with new data (no DOM recreation)
result.update({ name: 'Jane', email: 'jane@example.com' });Use {{property}} syntax to bind context properties to text content or attributes:
const template = html`
<a href="{{url}}" class="{{linkClass}}">{{linkText}}</a>
`;Use ${} for static values or functions:
const staticClass = 'container';
const template = html`<div class="${staticClass}">{{content}}</div>`;Bind event handlers with context:
const row = html`
<tr>
<td>{{name}}</td>
<td>
<button onclick=${function() { this.onEdit(this.id); }}>Edit</button>
</td>
</tr>
`;
const result = row({
id: 42,
name: 'Item',
onEdit(id) { console.log('Edit:', id); }
});Call methods from the context object:
const template = html`<div>{{formatPrice}}</div>`;
const result = template({
price: 99.99,
formatPrice() {
return `$${this.price.toFixed(2)}`;
}
});Pass arguments to context functions using the pipe syntax:
const template = html`<div>{{greet|name}}</div>`;
const result = template({
name: 'John',
greet(name) { return `Hello, ${name}!`; }
});
// Result: <div>Hello, John!</div>The value after | is resolved from the context and passed to the function. Multiple arguments use comma separation:
const template = html`<div>{{format|name,title}}</div>`;
const result = template({
name: 'John',
title: 'Developer',
format(name, title) { return `${name} - ${title}`; }
});For value transformations using pipes (like uppercase, currency), use compileTemplate instead.
npx @relax.js/core check verifies every {{name}} and {{fn|args}} against the object the
template is bound with, and reports a misspelled name with file and line. No annotation needed;
the type comes from the bind call. See Checking templates.
function html(
templateStrings: TemplateStringsArray,
...substitutions: any[]
): (context: any) => RenderTemplateCreates a template function that accepts a context object and returns a RenderTemplate.
interface RenderTemplate {
fragment: DocumentFragment;
update(context: any): void;
}fragment: The rendered DOM fragment. Add to the DOM once.update(context): Re-renders with new data without recreating DOM nodes.
This template engine is designed for single-use updateable templates:
- Create the template once
- Render and add
fragmentto the DOM - Call
update()to push changes to the existing nodes
Step 2 happens once. fragment is a DocumentFragment, so adding it to the DOM moves its
nodes out and leaves it empty. The bindings keep pointing at those nodes, which is what
makes update() work.
Calling the template function again does not give you a second instance. It returns the same, now empty, fragment and rebinds the nodes already on screen, so the first instance changes to the new data and nothing is added. Nothing reports this:
const card = html`<p>{{name}}</p>`;
host.appendChild(card({ name: 'Alice' }).fragment);
host.appendChild(card({ name: 'Bob' }).fragment); // adds nothing, Alice now reads BobEvaluate the tagged literal again for a second instance:
const card = (name: string) => html`<p>{{name}}</p>`({ name });
host.appendChild(card('Alice').fragment);
host.appendChild(card('Bob').fragment);compileTemplate returns an element rather than a fragment, so its content survives
being added to the DOM. It renders a collection through loop= rather than through
multiple instances.
The engine automatically detects when to set element properties vs attributes:
// Sets input.value property (not attribute)
const input = html`<input value="{{val}}">`;
// Sets data-id attribute
const div = html`<div data-id="{{id}}"></div>`;Custom elements are automatically upgraded when encountered in templates.
The html template is optimized for simple, updateable templates. For advanced features, use compileTemplate:
| Feature | html |
compileTemplate |
|---|---|---|
| Mustache bindings | Yes | Yes |
| Event handlers | Yes (onclick=${fn}) |
Yes (r-click="handler(args)") |
| In-place updates | Yes | Yes |
Nested paths (user.name) |
No | Yes |
Loops (loop="item in items") |
No | Yes |
Conditionals (if, unless) |
No | Yes |
| Pipe transformations | No | Yes |
| Function calls with args | Yes (via |) |
Yes |