Markdown & source details
Supplemental guide. Source snapshot: 679dea933515da69a689a012eeeaaa9f184b493c. Reading source: 679dea933515da69a689a012eeeaaa9f184b493c. Check the guide's compatibility requirements against your installed packages.
On this page
@mnjs/html reference#
A separately versioned browser template renderer and Marionette DOM adapter. Version 1.0.0 is published on npm. It is optional and does not select or change the renderer for an application that has already configured one.
Install and choose an entrypoint#
Install the published package from npm:
npm install --save-exact @mnjs/html@1.0.0
Use Node ^22.22.2 || >=24.15.0 for the package tools. Runtime imports require a
browser DOM, or a DOM-enabled test environment initialized before import. This is
not a server renderer and has no hydration API. The Marionette adapter is tested
with released Marionette 5.0.1 and, in a separate packed integration lane, the
current repository checkout. It has no runtime import or required peer dependency
on Marionette, so standalone rendering does not install the framework.
| Import | Public surface |
|---|---|
@mnjs/html |
html, svg, mathml, render, nothing, noChange; template, part and render-option types. |
@mnjs/html/marionette |
Default and named HtmlDomApi; named setContents, notifyAttach, notifyDetach. |
@mnjs/html/directive |
Directive, directive, PartType, and directive types. |
@mnjs/html/async-directive |
AsyncDirective and its types. |
@mnjs/html/directive-helpers |
Public directive/part utilities for custom directives. |
@mnjs/html/static |
Static-template html, svg, mathml, literal, unsafeStatic, withStatic. |
@mnjs/html/directives/<name>.js |
Individually imported directives; filenames use kebab-case. |
@mnjs/html/is-server.js |
isServer, always false. |
The support entrypoints directive, async-directive, directive-helpers and
static also accept .js aliases. Only package-exported subpaths are public.
Names beginning _$ are internal, including in custom Directive subclasses.
There is no CommonJS entrypoint.
Template values and rendering#
html, svg and mathml are tagged template functions. They return a template
result rather than HTML text or a DOM element. Keep static structure in the tagged
literal and pass data as interpolations; do not construct template-string arrays
manually. Nested results and arrays/iterables compose directly.
import { html, render, nothing } from '@mnjs/html';
const container = document.createElement('section');
const content = (name, disabled) => html`
<p>Hello, ${name}</p><input .value=${name} ?disabled=${disabled}>
`;
render(content('<Sam>', false), container);
const input = container.querySelector('input');
render(content('Ada', true), container);
const reusedInput = container.querySelector('input');
render(nothing, container);
| Position/value | Behavior |
|---|---|
| Child text | Primitives render as text. null, undefined, nothing and the empty string render no child content. Booleans render as text. |
| Nested template / iterable | Renders the result or its items. Plain iterable order is positional; use repeat when identity follows a key. |
| DOM Node | Inserts the supplied node. The caller is responsible for its provenance and ownership. |
name=${value} |
Sets an attribute. nothing removes it; null/undefined contribute empty strings. Multiple expressions in one attribute are supported. |
.name=${value} |
Assigns a property. Validate dangerous sinks such as innerHTML in the application. |
?name=${value} |
Toggles a boolean attribute according to truthiness. |
| Element directive | ${directive(...)} in the element's attribute area receives an element part. |
noChange |
Leaves the existing committed value unchanged. |
render(value, container, options?) commits synchronously and returns the root
part. The first render creates an owned range; later renders update that range.
Do not remove its markers or render into the same range with another engine.
render(nothing, container) clears that range. Direct rendering is distinct from
the Marionette adapter's first-render content replacement.
| Render option / root operation | Contract |
|---|---|
renderBefore |
A boundary node in the container; content is inserted before it. Use a stable boundary. |
creationScope |
A document-like scope used when importing template content. |
host |
Receiver for optional ref callbacks. Does not enable template events. |
Initial isConnected |
Initial directive connection state; defaults to connected for direct rendering. |
root.setConnected(boolean) |
Explicitly updates directive connection state after rendering. Ordinary DOM movement does not invoke it automatically. |
Choosing a DOM integration#
Use @mnjs/html/marionette with this package's template results when Marionette
should own events and lifecycle. Use the existing @mnjs/adapters/dom/lit-html
with upstream Lit template results when the application deliberately uses Lit.
Native HTML-string templates can keep the default DomApi; do not install a
template adapter without matching the values it consumes. This package does not
change an application's configured renderer or adapter automatically.
Marionette integration and ownership#
Configure an isolated runtime before constructing its Views. The default renderer
still calls template(data) without a View receiver; expose View-derived values
through templateContext. The DOM adapter handles the returned template result.
import { createMarionette } from 'marionette';
import { html } from '@mnjs/html';
import HtmlDomApi from '@mnjs/html/marionette';
const mn = createMarionette();
mn.setDomApi(HtmlDomApi);
const Counter = mn.View.extend({
initialize() { this.count = 0; },
templateContext() { return { count: this.count }; },
template: ({ count }) => html`<button type="button" class="increment">${count}</button>`,
events: { 'click .increment'() { this.count++; this.render(); } }
});
const host = document.createElement('main');
const region = new mn.Region({ el: host });
const counter = new Counter();
region.show(counter);
counter.el.querySelector('button').click();
region.destroy();
Use View events, triggers and ui for interactions. Dynamic @event bindings,
ordinary dynamic on* attributes, and browser-known native handler property or
boolean bindings throw MN_HTML_EVENT_BINDING. Ordinary data properties such as
.one, case-sensitive callback properties .onChange/.onClick, and boolean
attributes such as ?online remain valid. Lowercase native .onchange/.onclick
and case-insensitive HTML ?onClick/onClick bindings remain rejected. Static markup is
trusted author code; these restrictions are not a security sandbox.
setContents(element, result) first replaces existing contents, without hydration.
Subsequent calls update one bounded range; children appended after its end marker
remain outside renderer ownership. This supports root-level CollectionView child
Views. Keep Region placeholders under the correct owner, and let Regions own
child View replacement rather than overwriting their DOM externally.
Keep Marionette attachment monitoring enabled. notifyAttach and notifyDetach
forward directive connection changes; direct adapter users must call them.
The adapter does not implicitly pass the View as host. It preserves the
configured event delegator and the root element. Synchronous callback failures
abort the operation; no rollback or failed-construction recovery is added.
Directives and identity#
import { html, render, nothing } from '@mnjs/html';
import { repeat } from '@mnjs/html/directives/repeat.js';
import { live } from '@mnjs/html/directives/live.js';
const list = document.createElement('section');
const rows = items => html`<ul>${repeat(items, item => item.id,
item => html`<li data-id=${item.id}><input .value=${live(item.label)}></li>`)}</ul>`;
render(rows([{ id: 'a', label: 'One' }, { id: 'b', label: 'Two' }]), list);
const firstRow = list.querySelector('[data-id="a"]');
render(rows([{ id: 'b', label: 'Two' }, { id: 'a', label: 'Updated' }]), list);
const movedRow = list.querySelector('[data-id="a"]');
render(nothing, list);
Use unique stable keys for repeat; changing a key means a different item.
Node identity is preserved through supported keyed moves. Native same-parent
moveBefore is used when available; the fallback preserves identity but does not
promise focus, selection, iframe or playback-state preservation.
| Directive group | Exports and purpose |
|---|---|
| Identity and work | repeat for keyed lists; keyed to reset content when its key changes; cache to retain alternative template DOM; guard to skip work while dependencies are unchanged. |
| Values and attributes | classMap, styleMap, ifDefined; live compares a binding with the live DOM value. |
| Composition | when, choose, map, range, join. |
| Async values | until, asyncReplace, asyncAppend. |
| Element access | ref, createRef; refs follow renderer connection changes. |
| Trusted markup | templateContent, unsafeHTML, unsafeSVG, unsafeMathML; opt-in APIs, not sanitizers. |
Async directives can update after the initial synchronous commit. Those updates
do not rerun Marionette rendering hooks, rebind cached UI selectors or recreate
Regions. Do not let them replace owned Region placeholders or cached UI targets.
Custom AsyncDirective implementations must release and reacquire their own
resources on disconnect/reconnect; an AbortSignal is not a substitute for that
reconnectable lifetime. Renderer connection state does not cancel arbitrary
application requests.
Security and build conditions#
Ordinary text and attribute interpolation cannot escape into extra markup.
Supplied Nodes, dangerous URL schemes, property sinks, static template markup and
explicit unsafe APIs still require application-level validation. unsafeHTML,
unsafeSVG, unsafeMathML, unsafeStatic and templateContent accept only trusted
or already-sanitized input. Do not interpolate user data through them.
Imports capture document and register the mnjs-html Trusted Types policy when
supported. A strict CSP must permit that policy name, for example
require-trusted-types-for 'script'; trusted-types mnjs-html. Policy denial fails
closed during import. Deduplicate package copies instead of allowing duplicate
policy names to mask a bundle problem.
The default export condition selects production output; development selects
readable diagnostics. Keep templates, directives and helpers on one package
version and one condition. Mixing engines or build conditions is unsupported.
Production preserves escaping and Trusted Types, but has no custom sanitizer
hooks. Development-only sanitizer hooks can transform or reject values; they are not production protection.
A single-expression property binding such as .footer=${foreignTemplate} may
pass an upstream Lit template object unchanged to a custom-element setter in both
builds. This is opaque application data, not shared renderer interoperability.
Foreign directives and foreign templates in child, attribute or compound property
positions remain unsupported and diagnosed in development.
keyed(newKey, noChange) clears the old key's content and ownership; noChange
does not preserve content across a changed key. With the same key it retains the
existing content. Use a stable key when retaining the existing subtree is intended.
Types and verification#
The root exports TemplateResult, HTML/SVG/MathML result types, RenderOptions,
RootPart and supported Part types. Declarations accompany the ESM exports;
@types/trusted-types is a production type dependency. TypeScript consumers must
provide DOM library declarations. No SSR, hydration or template compiler API is
exported.
The independent package check builds both conditions, exercises public behavior, checks declarations and inspects the packed file list. Its documentation tests execute the JavaScript examples in this reference. Browser tests cover lifecycle, keyed movement and CSP/Trusted Types across Chromium, Firefox and WebKit. Size measurements are not runtime-performance guarantees. License and upstream source notices remain in the package archive.