fastdom
Batches DOM measure and mutate calls into requestAnimationFrame queues to eliminate layout thrashing in web apps.
Repository Health
Technical Analysis
FastDom is a tiny (about 600 bytes minified and gzipped) utility that eliminates layout thrashing by batching DOM measurement and mutation work into requestAnimationFrame-driven queues. Instead of every module in an app reading and writing to the DOM whenever it feels like it — forcing the browser into repeated synchronous reflows — code calls fastdom.measure() for reads and fastdom.mutate() for writes, and FastDom guarantees all queued reads run before all queued writes on the next frame.
FastDom is designed as an app-wide singleton: whichever module calls require('fastdom') gets the same instance back, so unrelated libraries and app code all schedule against one shared queue. An extend() API lets consumers layer extensions like fastdom-promised (Promise-based measure/mutate) or fastdom-sandbox (grouped/scoped tasks) on top without breaking that singleton guarantee, and a companion fastdom-strict script can enforce the discipline in development by throwing when DOM properties are touched outside their designated phase.
What You Get
- A singleton
fastdominstance withmeasure()/mutate()methods that queue DOM reads and writes separately - Automatic requestAnimationFrame-based batching so all queued reads run before all queued writes each frame
- A
clear()method to cancel a previously scheduled measure or mutate task - An
extend()API for composing extensions (a Promise-based API, task grouping) onto the shared singleton without creating a second instance
Common Use Cases
- Batching layout reads (offsetWidth, getBoundingClientRect) and style writes across a large DOM tree to avoid forced synchronous reflow
- Coordinating DOM updates across multiple unrelated modules or third-party widgets that would otherwise thrash layout independently
- Debugging layout thrashing during development using the fastdom-strict companion, which throws on out-of-phase DOM access
- Wrapping fastdom in Promises via fastdom-promised for async/await-friendly measure/mutate chains
Under The Hood
Architecture
FastDom is a tiny singleton module (fastdom.js, ~5KB) built around a classic prototype-based constructor: a FastDom instance holds two arrays (reads, writes) and a scheduleFlush/flush pair driven by requestAnimationFrame; measure() and mutate() push bound tasks onto their respective queues and lazily schedule a single rAF callback per frame via a scheduled flag, and flush() drains reads then writes inside a try/catch so a thrown task doesn’t stop the batch (errors are re-queued and re-flushed, or routed to a user-supplied catch hook). The genuinely interesting piece is extend(), which uses Object.create(this) plus a mixin helper to let extensions (fastdom-promised, fastdom-sandbox) layer new behavior onto the same singleton instance via prototypal inheritance while still funneling every scheduled task through the same global reads/writes arrays — so the core abstraction that would break everything if changed is the singleton-plus-two-queues design living on win.fastdom.
Tech Stack
Vanilla ES5 JavaScript with zero runtime dependencies beyond an optional strictdom devDependency; built as a UMD-style IIFE exposing itself via CommonJS module.exports, AMD define(), or a global window.fastdom. Build tooling is Webpack 1.x plus uglify-js for the minified distributable; tests run under Karma against real/headless browsers using Mocha, Chai, and Sinon, with Travis CI as the historical CI target and Coveralls for coverage reporting. A hand-written TypeScript declaration file ships alongside the plain-JS source for typed consumers, and a bower.json reflects the library’s origins in the front-end-package-manager era.
Code Quality
The core test suite (nearly 400 lines) exercises ordering guarantees (reads always before writes), re-entrant scheduling from within a read/write callback, clear() cancellation, and error-recovery/catch hook behavior, with separate suites covering the fastdom-promised, fastdom-sandbox, and fastdom-strict extensions using Sinon spies and async done()-style assertions. Linting is jshint rather than ESLint, enforcing single quotes and an 80-column limit; there are no TypeScript types on the implementation itself, only a hand-authored .d.ts for consumers, so type safety is asserted rather than compiler-checked. Naming is consistent and every public method carries a JSDoc-style comment block, though the Travis CI badge points at a now-defunct pipeline, leaving current CI health unverifiable from the repo alone.
API Design
The public surface is minimal by design — measure(), mutate(), clear(), and extend() — mirroring requestAnimationFrame’s own read/write phase separation so the mental model transfers almost for free from browser rendering internals. extend() is the standout DX choice: rather than forcing users to pick between a callback version or a Promise version of the library up front, it lets them compose exactly the extensions they want onto one shared singleton, so third-party code and app code never end up scheduling against two different instances. Getting started requires nothing beyond require('fastdom') — no configuration or setup — and the README documents the full API with runnable snippets and an explicit “how it works” section, though it stops short of an interactive docs site.