Skip to content

volynetstyle/js-reactivity-benchmark

Repository files navigation

JS Reactivity Benchmark

Local benchmark harness for comparing JavaScript reactivity libraries as behavior profiles across several scenario types:

  • classic propagation graphs
  • computation creation and update tests
  • larger static and dynamic dependency graphs

The project bundles the runner with esbuild, runs benchmarks under node --expose-gc, saves the raw log, and generates an HTML results page. The report ranks by relative geometric mean and keeps per-test medians, p95/p99 tails, and workload families visible.

Quick Start

pnpm install
pnpm bench

To run only one framework locally, set BENCH_FRAMEWORK:

BENCH_FRAMEWORK=ripple pnpm bench

You can also pass a comma-separated list such as BENCH_FRAMEWORK="ripple,alien-signals".

By default each benchmark row uses one measured run so the full suite stays practical for local iteration. To collect stronger distribution statistics, set BENCH_RUNS:

BENCH_RUNS=30 pnpm bench

After the run, these files are updated:

  • bench-results/latest.log - raw runner output
  • index.html - generated summary page with rankings and per-test comparisons

If you already have a log and only want to rebuild the results page:

pnpm results:page

Scripts

  • pnpm test - run vitest
  • pnpm build - bundle src/index.ts into dist/
  • pnpm run - execute the built runner with node --expose-gc
  • pnpm bench - build, run benchmarks, save bench-results/latest.log, and refresh index.html
  • pnpm results:page - rebuild index.html from an existing log

What Gets Measured

The current default run includes several benchmark groups. They are classified by workload family instead of being treated as one flat race:

  • creation - signal/computed allocation and setup cost
  • update - stable graph writes and invalidation hot paths
  • pull - dirty reads, aggregation, and chain pulls
  • push - fan-out propagation and effect delivery
  • dynamic - branch switching, dependency cleanup, and retracking
  • large_graph - scaling behavior on larger DAGs
  • baseline - historical propagation scenarios such as diamond, mux, and triangle

The dynamicBench set now also includes several more app-like presets such as dashboard selective reads, editor derived state, kanban board, and entity detail page. These use the same rectangular graph generator, but with more moderate widths/depths and partial leaf reads to better resemble common UI workloads.

The larger graph benchmark parameters are defined in src/config.ts.

The semantics and interpretation rules are documented in SEMANTICS.md. Read that file before treating results as comparable across libraries.

Reported Metrics

Rows are measured according to BENCH_RUNS (1 by default for speed). The primary terminal time column is the median, not the fastest run. Rows also emit:

  • p95 and p99 - tail latency for jitter/outliers
  • cv - coefficient of variation for stability
  • group and family - workload taxonomy
  • benchmark-specific counters such as recomputation, traversal, or checksum data

The HTML leaderboard uses:

r(L, test) = median_time(L, test) / best_median_time(test)
score(L) = geometric_mean(r(L, tests))

Lower is better. A profile score is more honest than an arithmetic average because a catastrophic slowdown in one workload family is harder to hide.

Framework Adapters

The repository contains adapters for:

  • Reflex
  • Alien Signals
  • Angular Signals
  • MobX
  • mol_wire
  • Oby
  • Preact Signals
  • Reactively
  • S.js
  • Signia
  • Solid
  • @solidjs/signals
  • Svelte
  • TC39 Signals Proposal polyfill
  • Tansu
  • uSignal
  • Vue Reactivity
  • Compostate
  • Valtio
  • Kairo

The list of adapters enabled in the default run is defined in src/config.ts.

Two adapter notes are worth calling out explicitly:

Svelte, TC39 Signals Proposal polyfill and Angular Signals is currently removed from the default benchmark set because it was too slow for the current suite, especially once the larger graph scenarios were added.

Artifacts

index.html is generated by scripts/render-results-page.mjs from the CSV-like text output produced by the benchmark runner.

Structure

Notes

  • Some adapters are disabled because parts of the suite currently hang or are incompatible. The reasons are documented inline in src/config.ts and src/index.ts.
  • bench-results/latest.log and index.html always reflect the last local run. If the active framework list changed, those artifacts may still describe an older run until you execute pnpm bench again.

Credits

Based on the original idea and structure from milomg/js-reactivity-benchmark.

About

This is a comparison of various frameworks/libraries for 2026

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors