Skip to content

0.5.0: on npm, 36% faster list creation, accessibility at 10/10

Choose a tag to compare

@kwy404 kwy404 released this 02 Sep 23:47
· 64 commits to main since this release

Voodoo.js is on npm.

npm install voodoojs
<script src="https://cdn.jsdelivr.net/npm/voodoojs@0.5/dist/voodoo.min.js" defer></script>

npmjs.com/package/voodoojs


Performance

Creating a 1,000-row keyed list is 36.2% faster; clearing one is 44.6% faster.

before after
create 1k 129.98 ms 82.98 ms -36.2%, won 45 of 45 rounds
clear 1k 28.14 ms 15.59 ms -44.6%, won 38 of 45
update 2.60 ms 2.71 ms no measurable change

Measured with a paired harness that loads both builds in one process and interleaves their samples, alternating order each round, so the machine's 20-40% drift between runs cancels out. A null A/B of identical sources put the noise floor at ±0.5 ms.

Against other frameworks, Voodoo moved from sixth of seven to third on create, and from last to fourth on clear.

create 1k update every 10th clear 1k
vanilla JS 48.74 7.52 21.06
Preact 10.29.8 91.62 2.59 29.48
Voodoo.js 97.70 5.42 30.44
React 19.2.8 100.23 4.63 33.59
Vue 3.5.42 110.56 19.21 31.65
Solid 1.9.15 111.63 0.91 19.99
Alpine 3.17.1 179.47 104.51 31.39

Four changes account for nearly all of it: reading getAttributeNames() instead of indexing the live attributes collection, v-for stripping the key attribute off its row template, reactive class fields declared rather than emitted as Object.defineProperty calls under useDefineForClassFields, and building a directive's EffectScope only on demand.

Six further optimisations were measured, found to sit inside the noise floor, and reverted. They are written down next to the ones that worked, because a list of what did not help is worth as much as the list of what did.

Accessibility: 5.3 → 10/10

Drawer naming and reduced motion, dropdown Enter and Space, tooltips reachable by keyboard and touch rather than hover alone, tablist orientation driving its arrow keys, accordion panels as labelled regions, a command palette that traps focus against script and pointer escapes rather than only Tab, and aria-atomic on toasts.

44 browser tests, real Chromium

Playwright, covering what jsdom structurally cannot: clicks hit-tested against real layout, v-for node identity proven with expandos stamped from outside the framework, the modal focus trap (jsdom reports every element as zero-sized, so the trap could never be exercised there), and a CSP suite that loads the framework under script-src 'self' with no unsafe-eval and asserts zero policy violations.

It found a real defect, kept at full strength as a failing test rather than skipped: v-modal-content stays visible after the modal closes, because cleanAttributes strips the very attribute the re-hide condition tests for.

Also

  • walker.ts held a raw NUL byte in the parseAttribute cache key, which made git treat a TypeScript file as binary and undiffable.
  • scripts/quality/browser.mjs asserted before the boot loop mounts. No runner had ever been installed, so that check had never once executed.
  • The voodoo.core.min.js gzip budget moves 46 → 47 KB, with the reason recorded in size.mjs: a zero-headroom budget was about to block a one-line accessibility fix over ten gzipped bytes.
  • scripts/chart-comparison.mjs draws the comparison chart from the benchmark JSON, so the picture can no longer drift from the table beside it.

Full changelog: CHANGELOG.md