Benchmark
Performance comparison between Ultima and five close-by projects — Litedom, Alpine.js, Lit, Minze and VanJS — across four common reactive UI scenarios. Runs entirely in your browser; no number here was pre-computed.
Methodology. Each framework runs isolated in its own
<iframe>, loaded from its official CDN (except Ultima, which
uses the local code from this repository). Each operation is measured with
performance.now() plus a double microtask flush afterward (to
capture updates frameworks schedule via microtask, like Lit's batching or
Alpine.nextTick) — the measurement deliberately does not include the
browser's repaint: requestAnimationFrame is paused by the browser in
background tabs, which would make running this in an automated way impossible. Each operation
is repeated 5 times, and the reported value is the median of the 5. The list
scenarios use 1000 rows; the counter/conditional/binding ones perform 1000 updates in sequence.
This measures batched synchronous updates, not 1000 real user interactions — frameworks that
batch updates into a microtask tend to do better in these specific scenarios, which reflects a
real characteristic of their architecture, not an artificial advantage. VanJS has no keyed
reconciliation (the framework has no such concept — the DOM nodes returned by
van.tags are already the real elements); "swap" and "remove" are
implemented by manipulating the nodes directly in that case, the way the documentation itself
recommends. Minze documents neither keyed reconciliation nor native two-way binding; the list
implementation follows the official performance guidance (avoid a reactive array, call
rerender() manually) and the binding one is inferred (event + manual
update). Litedom has had no commits since 2020 — the numbers are still real, but the project may
not represent the current state of the art for its own approach.
Ready to run — takes about 1 to 2 minutes.
Full table
| Metric |
|---|
Sources
- Methodology inspired by the operation battery of js-framework-benchmark (create, partial update, swap, remove, clear), adapted to this comparison's own scenarios — these aren't the same numbers or the exact same methodology as that project.
- Each implementation uses each framework's officially documented public API; where the documentation doesn't confirm a feature (keyed reconciliation in Minze/VanJS, two-way binding in Minze), that's stated above, not hidden in the numbers.
- Source code for each implementation is in
benchmarks/implementations/in the repository.