Preact-shaped code.
Direct DOM output.
The lab now covers realistic hooks and context, server rendering without a DOM, compiled islands inside ordinary Preact trees, and hundreds of independently compiled upstream cases.
A much wider credible application surface
These are compiler and runtime paths with focused tests—not a compatibility promise for arbitrary Preact applications.
Components can look like real application code.
Guarded returns, reactive context, template values, renderable arrays, component refs, and package-native hooks all lower into owned direct-DOM work.
useMemoand post-commituseEffectcreateContext, Provider, Consumer, anduseContext- JSX stored in state or Signals as reusable template handles
- Guarded component returns and nested renderable arrays
SSR no longer needs a browser-shaped document.
A plain-object host runs the same generated create path in Workers and Node, then emits one canonical hydration wire format.
Compiled islands fit inside existing Preact trees.
The ABI v3 island wrapper keeps Preact out of the compiled subtree while reactive props update bindings in place—one subtree at a time, with no core cost.
Signals now carry application-level behavior.
Models and actions auto-batch updates, effect ownership disposes deterministically, and dropped computed dependencies stop doing unnecessary work.
The DOM edge cases are part of the contract.
Root swaps, unkeyed reorders, Fragment movement, inner HTML, comments, form defaults, iframe and document hosts, MathML, and Preact v11 SVG attributes now have executable coverage.
- Preact-aligned text and object omission
- Reserved JSX metadata never reaches the host
- Shared conditional edges preserve host state
- Templates retain identity across positions
Less update work. Competitive browser paths.
Correctness-gated results from the checked benchmark fixtures. Lower is better for timings; local browser numbers are directional, machine-sensitive evidence—not CI performance budgets.
Across 100 state updates, each path performs 100 text mutations and 0 child-list mutations. The compiled binding does the same DOM work without re-evaluating the component.
Geometric mean against the fastest compiled reference across nine keyed operations and two consecutive full runs.
Brotli, rebuilt with this page. Budget 9,100 B; 22 B headroom.
Retained row operations
| Operation | Two-run range |
|---|---|
| Create 1,000 | 3.05–3.15 ms |
| Replace 1,000 | 5.30–5.40 ms |
| Append 1,000 | 2.75–2.90 ms |
| Create 10,000 | 26.60–26.85 ms |
Synchronous keyed claim
| Rows | Lab | Preact | Ratio |
|---|---|---|---|
| 1,000 rows | 6.40–6.65 ms | 5.05 ms | 1.27–1.32× |
| 10,000 rows | 69.60–70.35 ms | 162.05–164.25 ms | 0.42–0.43× |
Measured 2026-07-26 on arm64 macOS, Node 22.22.0, Chromium 149.0.7827.55. The hydration fixture covers synchronous keyed hydration only—not streaming, Suspense, or resume.
Keyed rows, direct updates
Authored TSX becomes direct DOM work
Both panes come from this build. The right side is the Rust compiler's emitted JavaScript—not a hand-written approximation.
import { signal, useComputed } from "@preact/signals";
import { useEffect, useMemo, useState } from "preact/hooks";
import { StatCard } from "./StatCard.tsx";
interface DemoRow {
id: number;
label: string;
}
interface DemoProps {
rows: DemoRow[];
compiled: {
excerpt: string;
full: string;
lines: number;
bytes: string;
};
stats: {
upstream: string;
compatibility: string;
bundle: string;
recovery: string;
targets: string;
};
benchmarks: {
measuredAt: string;
environment: {
platform: string;
node: string;
chromium: string;
};
updateWork: {
updates: number;
compiledEvaluations: number;
referenceEvaluations: number;
characterDataMutations: number;
childListMutations: number;
};
keyed: {
frontierMean: string;
operations: Array<{
label: string;
lab: string;
}>;
};
hydration: {
rows: Array<{
label: string;
lab: string;
preact: string;
ratio: string;
}>;
clientSize: string;
htmlSize10k: string;
};
size: {
runtimeBrotli: string;
runtimeBudget: string;
headroom: string;
};
};
source: string;
sourceLines: number;
nextId(): number;
onCommit(rows: number): void;
}
export function App({
rows: initialRows,
compiled,
stats,
benchmarks,
source,
sourceLines,
nextId,
onCommit
}: DemoProps) {
const [count, setCount] = useState(0);
const rows = signal(initialRows);
const selected = signal(initialRows[0].id);
const showFullOutput = signal(false);
const rowCount = useMemo(() => rows.value.length, [rows.value.length]);
const compiledOutput = useComputed(() =>
showFullOutput.value ? compiled.full : compiled.excerpt
);
const outputLabel = useComputed(() =>
showFullOutput.value ? "Show focused excerpts" : "Show full emitted module"
);
useEffect(() => {
onCommit(rowCount);
}, [rowCount]);
return (
<main className="showcase">
<section className="hero">
<div className="eyebrow">July 2026 capability update</div>
<h1>Preact-shaped code.<br />Direct DOM output.</h1>
<p className="lede">
The lab now covers realistic hooks and context, server rendering
without a DOM, compiled islands inside ordinary Preact trees, and
hundreds of independently compiled upstream cases.
</p>
<div className="hero-actions">
<button id="increment" className="primary" onClick={() => setCount(count + 1)}>
Hook updates: {count}
</button>
<span id="hydration-result" className="runtime-status">
Server HTML ready · hydration pending
</span>
</div>
<div className="hero-proof" aria-label="Compiler guarantees">
<span><i></i> opt-in runtime</span>
<span><i></i> zero bytes in default Preact</span>
<span><i></i> one render and hydration shape</span>
</div>
</section>
<section className="stats" aria-label="Verified project statistics">
<StatCard
value={stats.upstream}
label="upstream cases"
detail="independently compiled"
/>
<StatCard
value={stats.compatibility}
label="attributed behaviors"
detail="exact pinned titles"
/>
<StatCard
value={stats.bundle}
label="browser bundle"
detail="Brotli compressed"
/>
<StatCard
value={stats.recovery}
label="recoverable gaps"
detail="from the pinned audit"
/>
</section>
<section id="capabilities" className="release-section">
<div className="release-heading">
<div>
<span className="section-kicker">What landed</span>
<h2>A much wider credible application surface</h2>
</div>
<p>
These are compiler and runtime paths with focused tests—not a
compatibility promise for arbitrary Preact applications.
</p>
</div>
<div className="capability-grid">
<article className="capability-card capability-card-wide">
<div className="capability-index">01 / Authoring</div>
<h3>Components can look like real application code.</h3>
<p>
Guarded returns, reactive context, template values, renderable
arrays, component refs, and package-native hooks all lower into
owned direct-DOM work.
</p>
<ul className="feature-list">
<li><code>useMemo</code> and post-commit <code>useEffect</code></li>
<li><code>createContext</code>, Provider, Consumer, and <code>useContext</code></li>
<li>JSX stored in state or Signals as reusable template handles</li>
<li>Guarded component returns and nested renderable arrays</li>
</ul>
</article>
<article className="capability-card">
<div className="capability-index">02 / Server</div>
<h3>SSR no longer needs a browser-shaped document.</h3>
<p>
A plain-object host runs the same generated create path in Workers
and Node, then emits one canonical hydration wire format.
</p>
<div className="capability-tags">
<span>DOM-independent</span>
<span>boundary options</span>
<span>bounded retries</span>
</div>
</article>
<article className="capability-card">
<div className="capability-index">03 / Adoption</div>
<h3>Compiled islands fit inside existing Preact trees.</h3>
<p>
The ABI v3 island wrapper keeps Preact out of the compiled subtree
while reactive props update bindings in place—one subtree at a
time, with no core cost.
</p>
<div className="capability-tags">
<span>VDOM host</span>
<span>live props</span>
<span>stable DOM</span>
</div>
</article>
<article className="capability-card">
<div className="capability-index">04 / Reactivity</div>
<h3>Signals now carry application-level behavior.</h3>
<p>
Models and actions auto-batch updates, effect ownership disposes
deterministically, and dropped computed dependencies stop doing
unnecessary work.
</p>
<div className="capability-tags">
<span>createModel</span>
<span>action</span>
<span>graph pruning</span>
</div>
</article>
<article className="capability-card capability-card-wide">
<div className="capability-index">05 / Fidelity</div>
<h3>The DOM edge cases are part of the contract.</h3>
<p>
Root swaps, unkeyed reorders, Fragment movement, inner HTML,
comments, form defaults, iframe and document hosts, MathML, and
Preact v11 SVG attributes now have executable coverage.
</p>
<ul className="feature-list feature-list-columns">
<li>Preact-aligned text and object omission</li>
<li>Reserved JSX metadata never reaches the host</li>
<li>Shared conditional edges preserve host state</li>
<li>Templates retain identity across positions</li>
</ul>
</article>
</div>
</section>
<section id="benchmarks" className="benchmark-section">
<div className="release-heading">
<div>
<span className="section-kicker">Measured evidence</span>
<h2>Less update work. Competitive browser paths.</h2>
</div>
<p>
Correctness-gated results from the checked benchmark fixtures.
Lower is better for timings; local browser numbers are directional,
machine-sensitive evidence—not CI performance budgets.
</p>
</div>
<div className="benchmark-grid">
<article className="benchmark-card benchmark-card-wide">
<span className="benchmark-label">Scalar update work</span>
<div className="benchmark-comparison">
<div>
<strong>{benchmarks.updateWork.compiledEvaluations}</strong>
<span>compiled component run</span>
</div>
<i>vs</i>
<div>
<strong>{benchmarks.updateWork.referenceEvaluations}</strong>
<span>reference component runs</span>
</div>
</div>
<p>
Across {benchmarks.updateWork.updates} state updates, each path
performs {benchmarks.updateWork.characterDataMutations} text
mutations and {benchmarks.updateWork.childListMutations} child-list
mutations. The compiled binding does the same DOM work without
re-evaluating the component.
</p>
</article>
<article className="benchmark-card">
<span className="benchmark-label">Keyed compiled frontier</span>
<strong className="benchmark-metric">{benchmarks.keyed.frontierMean}</strong>
<p>
Geometric mean against the fastest compiled reference across nine
keyed operations and two consecutive full runs.
</p>
</article>
<article className="benchmark-card">
<span className="benchmark-label">Standalone runtime</span>
<strong className="benchmark-metric">{benchmarks.size.runtimeBrotli}</strong>
<p>
Brotli, rebuilt with this page. Budget{" "}
{benchmarks.size.runtimeBudget}; {benchmarks.size.headroom} headroom.
</p>
</article>
</div>
<div className="benchmark-detail-grid">
<article className="benchmark-table-card">
<header>
<div>
<span className="benchmark-label">Keyed browser fixture</span>
<h3>Retained row operations</h3>
</div>
<small>Lab time</small>
</header>
<div className="benchmark-table-scroll">
<table>
<thead>
<tr>
<th>Operation</th>
<th>Two-run range</th>
</tr>
</thead>
<tbody>
{benchmarks.keyed.operations.map(operation => (
<tr key={operation.label}>
<td>{operation.label}</td>
<td>{operation.lab}</td>
</tr>
))}
</tbody>
</table>
</div>
</article>
<article className="benchmark-table-card">
<header>
<div>
<span className="benchmark-label">Hydration browser fixture</span>
<h3>Synchronous keyed claim</h3>
</div>
<small>Lab / Preact</small>
</header>
<div className="benchmark-table-scroll">
<table>
<thead>
<tr>
<th>Rows</th>
<th>Lab</th>
<th>Preact</th>
<th>Ratio</th>
</tr>
</thead>
<tbody>
{benchmarks.hydration.rows.map(row => (
<tr key={row.label}>
<td>{row.label}</td>
<td>{row.lab}</td>
<td>{row.preact}</td>
<td>{row.ratio}</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="benchmark-payload">
<span><strong>Client payload</strong>{benchmarks.hydration.clientSize}</span>
<span><strong>10k HTML</strong>{benchmarks.hydration.htmlSize10k}</span>
</div>
</article>
</div>
<div className="benchmark-note">
<p>
Measured {benchmarks.measuredAt} on {benchmarks.environment.platform},
Node {benchmarks.environment.node}, Chromium{" "}
{benchmarks.environment.chromium}. The hydration fixture covers
synchronous keyed hydration only—not streaming, Suspense, or resume.
</p>
<div>
<a href="https://github.com/JoviDeCroock/preact-compiler-lab/blob/main/docs/work-benchmark.md">Update work ↗</a>
<a href="https://github.com/JoviDeCroock/preact-compiler-lab/blob/main/docs/browser-keyed-benchmark.md">Keyed methodology ↗</a>
<a href="https://github.com/JoviDeCroock/preact-compiler-lab/blob/main/docs/browser-hydration-benchmark.md">Hydration methodology ↗</a>
</div>
</div>
</section>
<section id="proof" className="demo-grid">
<article className="interactive-panel">
<div className="section-heading">
<div>
<span className="section-kicker">Live compiled component</span>
<h2>Keyed rows, direct updates</h2>
</div>
<strong className="row-count">{rowCount} rows</strong>
</div>
<div className="toolbar">
<button
id="add-row"
onClick={() =>
rows.value = rows.value.concat({
id: nextId(),
label: `Compiled row ${rows.value.length + 1}`
})
}
>
Add row
</button>
<button
id="rotate-rows"
onClick={() =>
rows.value = rows.value.length > 1
? rows.value.slice(1).concat(rows.value[0])
: rows.value
}
>
Rotate identity
</button>
</div>
<ul id="compiled-rows" className="compiled-rows">
{rows.value.map(row => (
<li
key={row.id}
data-id={row.id}
className={selected.value === row.id ? "selected" : ""}
>
<button
className="row-select"
onClick={() => selected.value = row.id}
>
<span>{row.label}</span>
<small>key {row.id}</small>
</button>
<button
className="row-remove"
aria-label={`Remove ${row.label}`}
onClick={() =>
rows.value = rows.value.filter(item => item.id !== row.id)
}
>
×
</button>
</li>
))}
</ul>
</article>
<aside className="proof-panel">
<span className="section-kicker">What this page proves</span>
<h2>One component, four real paths</h2>
<ol>
<li><strong>Compile</strong><span>Oxc lowers TSX into direct DOM instructions.</span></li>
<li><strong>Render</strong><span>The server host emits the exact HTML you loaded.</span></li>
<li><strong>Hydrate</strong><span>Existing nodes are claimed before exact bindings attach.</span></li>
<li><strong>Update</strong><span>Hooks and Signals mutate only their owned bindings.</span></li>
</ol>
<div className="target-row">
<span>Targets</span>
<code>{stats.targets}</code>
</div>
</aside>
</section>
<section className="architecture-strip" aria-label="Compiler architecture">
<div>
<span>01</span>
<strong>Parse</strong>
<small>Oxc TSX AST</small>
</div>
<i>→</i>
<div>
<span>02</span>
<strong>Prove</strong>
<small>eligible shapes</small>
</div>
<i>→</i>
<div>
<span>03</span>
<strong>Lower</strong>
<small>owned template IR</small>
</div>
<i>→</i>
<div>
<span>04</span>
<strong>Emit</strong>
<small>direct DOM + SSR</small>
</div>
</section>
<section id="compiler" className="compiler-panel">
<div className="compiler-heading">
<div>
<span className="section-kicker">Compiler x-ray</span>
<h2>Authored TSX becomes direct DOM work</h2>
</div>
<p>
Both panes come from this build. The right side is the Rust compiler's
emitted JavaScript—not a hand-written approximation.
</p>
</div>
<div className="compiler-flow">
<article className="code-card">
<header>
<div>
<span>Input</span>
<strong>demo/src/App.tsx</strong>
</div>
<small>{sourceLines} lines</small>
</header>
<pre><code>{source}</code></pre>
</article>
<div className="compile-arrow" aria-hidden="true">→</div>
<article className="code-card emitted-code">
<header>
<div>
<span>Rust / Oxc output</span>
<strong>App.compiled.mjs</strong>
</div>
<small>{compiled.lines} lines · {compiled.bytes}</small>
</header>
<pre id="compiled-output"><code>{compiledOutput.value}</code></pre>
<button
id="toggle-compiled-output"
className="output-toggle"
onClick={() => showFullOutput.value = !showFullOutput.value}
>
{outputLabel.value}
</button>
</article>
</div>
</section>
<section id="boundaries" className="boundary-panel">
<div>
<span className="section-kicker">Honest boundaries</span>
<h2>Useful now. Still a compiler lab.</h2>
</div>
<p>
The remaining boundary is architectural, not hidden test debt:
portals, streaming SSR, <code>preact/compat</code>, arbitrary
VDOM-valued expressions, and runtime Suspense/error boundaries are
not implemented. The compiler stays separate and opt-in until real
application evidence justifies a stronger compatibility claim.
</p>
</section>
<section id="docs" className="docs-section">
<div className="release-heading">
<div>
<span className="section-kicker">Read the evidence</span>
<h2>Design notes, limits, and reproducible proof</h2>
</div>
<p>
The repository documents how each claim is measured and where the
compiler deliberately stops.
</p>
</div>
<div className="docs-grid">
<a href="https://github.com/JoviDeCroock/preact-compiler-lab/blob/main/docs/supported-syntax.md">
<span>Syntax</span>
<strong>Supported authoring surface</strong>
<small>hooks, context, templates, JSX, and diagnostics →</small>
</a>
<a href="https://github.com/JoviDeCroock/preact-compiler-lab/blob/main/docs/external-modules-and-interop.md">
<span>Interop</span>
<strong>Migration and island design</strong>
<small>how compiled and ordinary Preact meet →</small>
</a>
<a href="https://github.com/JoviDeCroock/preact-compiler-lab/blob/main/docs/upstream-suites.md">
<span>Evidence</span>
<strong>Upstream compatibility suites</strong>
<small>exact pins, mappings, and exclusions →</small>
</a>
<a href="https://github.com/JoviDeCroock/preact-compiler-lab/blob/main/docs/architecture.md">
<span>Internals</span>
<strong>Compiler and runtime architecture</strong>
<small>pipeline, ownership, and generated ABI →</small>
</a>
</div>
</section>
</main>
);
}
// Generated by preact_compiler_lab. Do not edit by hand.
import { createRuntime, own as $own } from "preact-compiler-lab:runtime";
import { bindKeyedAttribute as $bindKeyedAttribute } from "preact-compiler-lab:runtime/keyed-attributes";
import { createComponent as $factory_StatCard, componentMetadata as $metadata_StatCard } from "./StatCard.compiled.mjs";
// … source-location table omitted from this focused view …
function $createRoot($ctx, $componentProps, $n = 0) {
return $ctx.renderPhase(() => {
let initialRows, compiled, stats, benchmarks, source, sourceLines, nextId, onCommit;
const $propsVersion0 = $ctx.signal(0);
$ctx.observe(() => { ({
rows: initialRows,
compiled,
stats,
benchmarks,
source,
sourceLines,
nextId,
onCommit
} = $componentProps); $propsVersion0.value = $propsVersion0.peek() + 1; });
const {useState,signal,computed,effect,batch,useComputed,useMemo,useEffect}=$ctx;
const $state_count = useState(0);
let count = $state_count.value;
const setCount = $arg_count => { const $next_count = typeof $arg_count === "function" ? $arg_count($state_count.value) : $arg_count; $ctx.setState($state_count, $next_count, $sync_count=>count=$sync_count); };
const rows = signal(initialRows);
const selected = signal(initialRows[0].id);
const showFullOutput = signal(false);
const $memo_rowCount = useMemo(() => rows.value.length, () => ([rows.value.length]));
let rowCount = $memo_rowCount.value;
const compiledOutput = useComputed(() =>
showFullOutput.value ? compiled.full : compiled.excerpt
);
const outputLabel = useComputed(() =>
showFullOutput.value ? "Show focused excerpts" : "Show full emitted module"
);
useEffect(() => {
onCommit(rowCount);
}, () => ((rowCount = $memo_rowCount.value, [rowCount])));
return () => {
const $el1 = $rt.element("main", $n);
$el1.setAttribute("class", "showcase");
const $el2 = $rt.element("section", $n);
$el2.setAttribute("class", "hero");
const $el3 = $rt.element("div", $n);
$el3.setAttribute("class", "eyebrow");
const $text4 = $rt.text("July 2026 capability update");
$el3.appendChild($text4);
$el2.appendChild($el3);
const $el5 = $rt.element("h1", $n);
// … direct instructions for the remaining static sections …
const $map204 = $rt.map($ctx, () => (($propsVersion0.value, benchmarks.keyed.operations)), (operation, $index, $ctx, $item205) => {
const $el206 = $rt.element("tr", $n);
const $el207 = $rt.element("td", $n);
const $text208 = $rt.text("");
$el207.appendChild($text208);
$el206.appendChild($el207);
const $el209 = $rt.element("td", $n);
const $text210 = $rt.text("");
$el209.appendChild($text210);
$el206.appendChild($el209);
let $value211, $value212;
$item205.refresh = $ctx.observe(() => {
let $error, $next;
if ($value211 !== $ctx) try {
$next = $rt.textValue($item205.value.label);
if ($next !== $value211) { $value211 = $next; if ($text208.data !== $next) $text208.data = $next; }
} catch ($caught) { $value211 = $ctx; $error ??= $caught; }
if ($value212 !== $ctx) try {
$next = $rt.textValue($item205.value.lab);
if ($next !== $value212) { $value212 = $next; if ($text210.data !== $next) $text210.data = $next; }
} catch ($caught) { $value212 = $ctx; $error ??= $caught; }
if ($error) throw $error;
}, true, true).refresh;
return $el206;
}, (operation) => (operation.label), false);
$el203.appendChild($map204);
$el196.appendChild($el203);
$el195.appendChild($el196);
$el186.appendChild($el195);
$el185.appendChild($el186);
const $el213 = $rt.element("article", $n);
$el213.setAttribute("class", "benchmark-table-card");
const $el214 = $rt.element("header", $n);
const $el215 = $rt.element("div", $n);
const $el216 = $rt.element("span", $n);
// … remaining keyed-row bindings omitted …
export function createApp(host = document, { hydratable = true } = {}) {
const $module = $createModule(host);
const $rt = $module.runtime;
return {
render(container, props = {}) {
const $ctx = $rt.createContext(props, true, hydratable);
const $value = $own($ctx, () => $module.create($ctx, props, $rt.ns(container)));
container.textContent = "";
const $root = $rt.mount(container, $value);
$own($ctx, () => $ctx.commit());
return { root: $root, dispose: () => $ctx.dispose() };
},
renderToString(props = {}, { hydratable: $boundaries = true } = {}) {
const $container = host.createElement("div");
const $ctx = $rt.createContext(props, false, $boundaries);
try {
const $value = $module.create($ctx, props);
$rt.mount($container, $value);
// … renderToString() and hydrate() share the generated template shape …Useful now. Still a compiler lab.
The remaining boundary is architectural, not hidden test debt: portals, streaming SSR, preact/compat, arbitrary VDOM-valued expressions, and runtime Suspense/error boundaries are not implemented. The compiler stays separate and opt-in until real application evidence justifies a stronger compatibility claim.
Design notes, limits, and reproducible proof
The repository documents how each claim is measured and where the compiler deliberately stops.