xml is a tagged template that parses HTML-like markup at runtime and
produces a JSX-equivalent component — a compiler-less alternative to
JSX. Inspired and influenced by
@trusktr.
Templates are parsed as text/xml, so markup must be well-formed:
void elements need a trailing slash (<br/>, <img src=""/>), every
open tag must be closed, and attribute values must be quoted.
Ill-formed input renders a visible parsererror element rather than
throwing. Unlike the browser HTML parser, attributes, tags, and
components are case sensitive, matching JSX.
xml is the global, shared instance. For a sandboxed instance with
its own component registry, see XML().
xml is a tagged template, so it is called as xml`markup` with
${...} interpolations rather than with positional arguments.
| Member | Type | Description |
|---|---|---|
${...} |
unknown |
An interpolated value — anything render accepts. |
xml.define |
(components: Record<string, JSX.ElementType>) => void |
Registers components addressable by tag name (case sensitive). |
xml.components |
Record<string, JSX.ElementType> |
The registry, seeded with the built-in components. |
Returns: a function (a component) — or an array of them when the
template has several root nodes — so xml composes with
render, <Show/>,
<Suspense/> and friends like any other
component.
Every built-in component from pota/components is registered out of
the box — For, Show, Switch, Match, Suspense, Dynamic, and
the rest — so they can be referenced by tag name without calling
xml.define. The lowercase/exception helpers load, customElement,
and CustomElement are intentionally not registered.
children attribute applies only while the element has no
childNodes. If the element has any children, the children
attribute is ignored.xml.define is case sensitive.xml.define must run before the first xml`...` invocation
that uses the new tag — templates compile once and cache their
component-vs-element decisions, so a later define won't rebind an
already-compiled template. An unregistered capitalized tag logs a
warning; lowercase and hyphenated tags are assumed to be real
elements and stay quiet.xml.define can override a name like div, making all div tags
behave differently. This is a warning, not a recommendation.xml makes it global —
visible wherever xml is imported. To avoid polluting the global
registry, create a local instance with XML().A reactive template: pass the signal's reader {count.read} as a
child, and wire native events with on:click.
import { signal, render } from 'pota'
import { xml } from 'pota/xml'
const count = signal(0)
const App = xml`
<div>
<p>count: ${count.read}</p>
<button on:click="${() => count.update(n => n + 1)}">+</button>
<button on:click="${() => count.write(0)}">reset</button>
</div>
`
render(App)
A ${...} accepts anything render accepts — strings, numbers,
arrays, other xml components, even JSX elements. Interpolated text
is never parsed as markup, so a value like <JSX> stays literal text
rather than becoming a tag.
import { render } from 'pota'
import { xml } from 'pota/xml'
const Bold = text => xml`<b>${text}</b>`
const App = xml`
<div>
${'Hola!'}
${[4, 5, 6, Bold(' really')]}
${Bold('whoa')}
<u>${'Look mom no <JSX>'}</u>
${(<div>:)</div>)}
</div>
`
render(App)
Registering a component on the exported xml makes it addressable by
tag name everywhere xml is imported.
import { render } from 'pota'
import { xml } from 'pota/xml'
xml.define({ test: () => 'hello world!' })
const App = xml`<div><test/> div contents</div>`
render(App)
Built-in components like <For/> work by tag name
without any registration.
import { render } from 'pota'
import { xml } from 'pota/xml'
const App = xml`<For each="${[1, 2, 3]}">${val => val * 2}</For>`
render(App)
context propagates through xml templates exactly as it
does through JSX. Register the Provider (and any consumers) with
xml.define, then nest them — the value resolves by tree position.
import { render, context } from 'pota'
import { XML } from 'pota/xml'
const xml = XML()
const Context = context({ myValue: 1 })
function Value() {
return Context().myValue
}
xml.define({ Provider: Context.Provider, Value })
// renders "12321" (whitespace-only text between tags is dropped,
// following JSX whitespace rules)
const App = xml`
<Value />
<Provider value="${{ myValue: 2 }}">
<Value />
<Provider value="${{ myValue: 3 }}">
<Value />
</Provider>
<Value />
</Provider>
<Value />
`
render(App)