Capabilities

Performance that stays on the platform

NativeCoreJS updates the DOM through signals and bind — no virtual DOM, no full re-render of the page. Explore live demos, then see a lab snapshot of the reactive micro-benchmarks.

What this page shows

  • One signal can drive many bound elements without rebuilding the tree
  • Fine-grained updates stay cheap as subscriber count grows
  • You can re-run the same suite in the monorepo with npm run bench

Surgical DOM updates

A single signal ticks below. Only the bound nodes change — the surrounding markup is not recreated. That is the same path your controllers use with this.signal / this.state and this.bind.

onMount() { const [count, setCount] = this.signal(0); this.bind(count, this.tickCountEl); this.on(this.tickBtn, 'click', () => { setCount(n => n + 1); // one write → bound nodes update }); }

Live example

Tick once Start auto Reset

Updates: 0 · DOM writes: 0

Idle

Each tick sets signal state once; bind writes text into the live nodes. No template re-parse, no virtual tree diff.

One signal, many listeners

Forty-eight cells subscribe to the same counter. Incrementing once fans out to every cell — useful for dashboards, badges, and shared UI chrome.

const [value, setValue] = this.signal(0); this.bind(value, this.fanValueEl); // cells = the <span> elements we append into ref="fanGridEl" const cells = Array.from({ length: 48 }, () => { const cell = document.createElement('span'); this.fanGridEl.appendChild(cell); return cell; }); for (const cell of cells) { this.bind(value, cell); // same signal → 48 listeners } this.on(this.fanIncBtn, 'click', () => { setValue(n => n + 1); // one set, many DOM writes });

Live example

Increment +100 burst Reset

Shared value: 0

000000000000000000000000000000000000000000000000

Live micro-bench (this browser)

Run a short in-page stress test: create a signal, set it many times, and measure throughput on your machine. Results vary by CPU and tab load — that is expected.

import { useState } from '@core/state.js'; const N = 100_000; const s = useState(0); const start = performance.now(); for (let i = 0; i < N; i++) s.value = i; // set throughput const setMs = performance.now() - start; // Also measured: get loops, and 1 watcher + N updates const opsPerSec = Math.round(N / (setMs / 1000));
Run in this tab

Ready — click to run.

    Lab snapshot

    From the monorepo suite (npm run bench) on Node v22.13.0, recorded . This is a capability snapshot, not a leaderboard.

    import { useState, computed, effect } from '@core/state.js'; // useState — create / set / get bench('set value 100k times', 100_000, () => { const s = useState(0); for (let i = 0; i < 100_000; i++) s.value = i; }); // computed — propagate upstream changes bench('propagate through 1 computed 50k×', 50_000, () => { const src = useState(0); const dbl = computed(() => src.value * 2); for (let i = 0; i < 50_000; i++) src.value = i; dbl.dispose(); }); // effect — dependency fires bench('effect fires 50k×', 50_000, () => { const src = useState(0); const stop = effect(() => { void src.value; }); for (let i = 0; i < 50_000; i++) src.value = i; stop(); }); // watch — fan-out subscribers bench('100 subscribers, 5k updates', 5_000, () => { const s = useState(0); const unsubs = Array.from({ length: 100 }, () => s.watch(() => {})); for (let i = 0; i < 5_000; i++) s.value = i; unsubs.forEach(u => u()); });
    Scenario ops/s
    create 100k state instances717,721
    set value 100k times4,350,092
    get value 100k times57,000,194
    set + get 100k pairs4,065,365
    propagate through 1 computed 50k×1,833,502
    read computed 50k×71,162,221
    effect fires 50k× (1 dep)2,274,729
    1 subscriber, 100k updates4,069,338
    10 subscribers, 20k updates3,100,419
    100 subscribers, 5k updates890,951
    npm run bench

    Full suite: benchmarks/state.bench.ts — create/set/get, computed chains, effects with cleanup, and 1 / 10 / 100 subscriber fan-out.

    Production budgets

    0 runtime production npm deps for scaffold apps
    <100ms warm route navigation target (cached HTML + controller)
    Lazy controllers load per route — unused pages stay unloaded
    SSG static HTML + same client hydrate path (build:ssg)