Status: accepted (Phase 1)
TSX must support arbitrary JavaScript expressions, yet must not produce a virtual DOM tree. The compiler has to turn markup into DOM creation code and expressions into targeted updates, without the runtime having to discover at runtime what is static and what is dynamic.
{f(x)} may read a signal three calls deep.jsx() returning descriptor objects). This is a
virtual DOM by another name: one object per element per evaluation.<template>. Every dynamic expression is
emitted as a thunk (() => expr). The runtime evaluates the thunk inside a
tracking scope on first run: if nothing was read, the value is written once
and no effect is retained; if something was read, a minimal effect bound to
that part’s specialised setter is kept.Option 3. The compile-time work is structural (what kind of part, where in the cloned tree, which setter), and the reactivity decision is made by observation, which is always correct.
<div class="row">Hello {name.value}</div>
becomes, in shape:
const _t = template('<div class="row">Hello <!>');
const _r = _t(),
_n = _r.firstChild.lastChild;
insertText(_n, () => name.value);
Node lookup uses compile-time child-index paths, never querySelector and never
runtime marker scanning.
A runtime-only JSX path (jsx-runtime) exists for unconfigured environments and
for tooling; it produces the same parts through the same protocol, is slower,
and warns once in dev. Its output is semantically identical — there is no
“benchmark mode” and no “compiled mode” difference in behaviour.
class={"row"}) retain no effect at all,
because the auto-detection observes zero reads.One <template> per distinct markup shape per module (shared by all instances).
Per instance: the cloned nodes, one closure and one effect per genuinely
reactive part, and nothing for static parts.
Runtime JSX factories (VDOM allocation), full static reactivity analysis (unsound; silent missed updates are the worst possible failure mode).