Skip to content

v2.4.0

Latest

Choose a tag to compare

@sathvikc sathvikc released this 21 Jul 02:31
· 20 commits to main since this release

Lume.js v2.4.0 — 2026-07-20

Three performance fixes land: a kernel fast-path that skips flush work entirely for stores nobody observes, effect() re-runs that reuse subscriptions instead of rebuilding them, and repeat() reorders that move only the DOM nodes actually out of place.


Highlights

  • repeat() list reorders now cost O(k) DOM moves for a k-item change instead of cascading into every node after the first mismatch — a 167-swap storm across 2,000 tiles dropped from ~2,000 to ~310 moves per tick.
  • effect() re-runs that read the same dependencies as their previous run now make zero subscribe/unsubscribe calls — a 3,000-store effect re-ran 7.5x faster (23.8ms → 3.2ms).
  • Writes to a store with no subscribers and no $beforeFlush hooks skip the flush pipeline entirely — worst write-pass frame on a 3,000-cell stress grid dropped 156ms → 16ms (~10x) under a 20x CPU throttle.

New Features

  • N/A — no new public APIs in this release.

Bug Fixes

  • N/A — no bug fixes in this release; see Performance below for behavioral changes tied to the perf work.

Performance

  • Kernel no-subscriber fast-path: the set trap now skips pending-notification bookkeeping, batch enqueue, and microtask flush when a store has no subscribers on any key and no $beforeFlush hooks. High-churn stores read back by polling (e.g. a render loop) get reactive writes within ~1–2x of plain-object writes. Kernel cost: +0.03 KB gz (1.46 → 1.49). One subtle sharpening: a subscriber registered between a write and its microtask no longer receives a duplicate delivery of that write on flush — the immediate $subscribe call already handed it the current value.
  • effect() subscription reuse: auto-tracked dependencies persist across runs in a per-effect registry instead of tearing down and rebuilding every run. computed() inherits the gain since it's built on effect(). Tracking semantics unchanged; a run that throws now keeps its subscriptions instead of leaking listeners.
  • repeat() minimal-move reconciliation: removes departed rows first, then keeps the longest stable chain of survivors (LIS over previous positions) and re-inserts only the rest. Full permutations are unchanged (nearly everything must move regardless); sparse reorders (swaps, drag-drop, sort-by-column) are the case this fixes. Same final order, lifecycle callbacks, and focus/scroll preservation; rows hosting iframes, videos, or running transitions no longer reset when other rows move past them.

Full measurement protocol and numbers for all three: see the 2.4.0 CHANGELOG entry.


Breaking Changes

Change Migration
— —

Deprecations

  • N/A — nothing deprecated in this release.

Tests

475 tests passing, 100% statement/branch/function/line coverage maintained.


Bundle Sizes

Entry point Gzipped Budget
dist/state.mjs (universal core) 1.49 KB 1.75 KB
dist/index.mjs (core + DOM) 2.77 KB 3.00 KB
dist/addons.mjs 4.96 KB 6.00 KB
dist/handlers.mjs 1.23 KB 2.00 KB
dist/lume.global.js (CDN) 7.75 KB 8.00 KB

Install

npm install lume-js@2.4.0

CDN:

<script type="module">
  import { state, bindDom, effect } from 'https://cdn.jsdelivr.net/npm/lume-js@2.4.0/dist/index.mjs';
</script>

Global (no-module CDN):

<script src="https://cdn.jsdelivr.net/npm/lume-js@2.4.0/dist/lume.global.js"></script>

Full Changelog

CHANGELOG.md · v2.4.0

Compare: v2.3.1...v2.4.0