@pixagram/virtualized
v10.1.0
Published
React components for efficiently rendering large, scrollable lists and tabular data. v9.22.1 OPTIMIZED BY PIXAGRAM.
Downloads
219
Maintainers
Readme
@pixagram/virtualized — optimized
@pixagram/virtualized (a react-virtualized fork) with a set of
behavior-preserving performance optimizations applied to the core windowing
engine, the Collection spatial index, and (on by default) the Grid scroll/render path —
plus a reproducible verification suite.
source/ The library source, with optimizations applied (ships the
*.jest.js test suite too).
- CHANGES.md ............ full changelog
- NOTES_FURTHER_OPTIMIZATIONS.md ... remaining proposals
verification/ Differential harnesses + benchmarks proving the optimizations
are bit-identical / DOM-identical to the original behavior.
- README.md ............. how to run, results
- baseline/ ............. pristine files the harnesses diff against
jest.config.js / babel.config.js Run the project's own *.jest.js suite
against this optimized tree.Optimizations applied
Pure-logic layer (verified bit-identical to the original by randomized differential harnesses — 685,064 assertions, 0 mismatches):
- Fixed-size fast path (
CellSizeAndPositionManager) — constant integerrowHeight/columnWidth⇒ O(1) arithmetic geometry, no arrays/search. ~15× faster visible-range queries; ~33.5 MB → 0 backing memory on 1M cells. - Lazy Fenwick delta allocation — point-update arrays allocated only on
first
resizeCell(another ~16.8 MB saved on 1M cells). - Non-allocating scalar accessors + renderer hoist — remove the rows×columns per-render style-input allocations.
- CellMeasurerCache capacity seeding — optional
columnCount/rowCounthints avoid column-growth relayout. - Count-based rescan mitigation (
CellMeasurerCache) — a shrink skips the O(n) max-rescan unless the last max-holder is removed. - Finger (galloping) search (
CellSizeAndPositionManager) — ~1.8× faster smooth scroll on a 500k variable-height list. - Collection de-dup without string coercion (
SectionManager/Section) —getCellIndicesuses aSet<number>+ ascending sort instead of an object-as-set +Object.keys()/map(). ~2.45× faster on a 20k-cell collection.
React component layer (verified by render-differential and the project's own test suite):
- Grid scroll range-gating (
Grid, on by default; disable withreuseChildrenOnScroll={false}) — while actively scrolling, when the rendered cell set and positions are unchanged, reuse the previously rendered children and skip the cell-range renderer. ~38% fewer renderer calls during scroll, with byte-identical DOM. Disable it only if yourcellRenderer/cellRangeRendererhave per-call side effects that must run on every scroll frame (with gating on they are not invoked on unchanged-range frames).
Third pass (10.1.0): stability + React-layer efficiency
Full details in source/CHANGES.md → "Third pass"; harnesses in
verification/README.md → section 4.
Stability:
- Range-gate soundness — a custom
cellRangeRendererthat positions fromscrollLeft/scrollTop(documented params) was frozen during sub-cell scrolling; the gate now compares the offsets for non-default renderers. - React 19 — no hard dependency on
ReactDOM.findDOMNodeanywhere (WindowScroller, Table, CellMeasurer); every component runs on React 19.3. - ResizeObserver-first resize detection (AutoSizer, WindowScroller) — no
DOM injected into your parent element, no forced
position: relative, no injected stylesheet, no capture-phase scroll listener on the parent. Legacy detector kept as fallback. - WindowScroller re-derives the child offset when the page reflows (content above the list changing size no longer misaligns it).
- Cross-frame-safe element checks; null-safe
Grid.componentDidUpdate; no work after unmount in AutoSizer / WindowScroller / InfiniteLoader.
Efficiency (DOM byte-identical, verified against the pristine tree):
- CellMeasurer batches no longer wipe Grid's caches — only the entries a batch can have changed are dropped — and style identity is preserved across the whole scroll burst: PureComponent row renders 207 → 124 in a real List + CellMeasurer scenario.
- Collection per-cell style cache: cell renders 246 → 132.
- Table
Children.toArraymemoized (was per row); no redundantsetStateper update. Cell-cache hits short-circuit the range renderer. - The "batched DOM measurement" proposal is withdrawn: a first-time batch
already costs one layout, not N (see
NOTES_FURTHER_OPTIMIZATIONS.md).
Tooling (fresh clone on Node 22 / npm 10): npm install (no flags) →
npm test → npm run build → npm run lint all succeed. The unused
extract-text-webpack-plugin (webpack 3 peer) and react-codemirror
(React <16 peer) that broke npm install on npm ≥ 7 are gone, scripts use
npm run, and source/styles.css — silently dropped by an unanchored
.gitignore rule when the fork was created, which made npm run build /
prepublishOnly fail — is restored. Details in source/CHANGES.md →
"Tooling".
Verification at a glance
- 685,064 logic-layer differential assertions, 0 mismatches.
- Grid range-gating: render-differential shows enabling the flag leaves the DOM byte-identical (150 steps across fixed/variable/scaled sizes, scroll/settle, prop changes, recompute mid-scroll, real CellMeasurer cache), with the gate confirmed firing.
- The project's own
*.jest.jssuite passes 568/568 on this optimized tree (and identically on the original), across every component — zero regressions — on React 16 and React 18; real React 19.3 smoke passes; underpreact/compatthe tree passes/fails exactly the same tests as the pristine 10.0.4 tree. - Third-pass render-differentials (pristine vs optimized): 0 DOM mismatches across all steps.
See verification/README.md to reproduce everything.
Running the project's test suite
npm i -D jest@29 jest-environment-jsdom@29 jest-jasmine2@29 babel-jest@29 \
@babel/core @babel/preset-env @babel/preset-react @babel/preset-flow \
@babel/plugin-proposal-class-properties \
react@16 react-dom@16 react-test-renderer@16 clsx dom-helpers@5 immutable jsdom
npx jest # whole suite
npx jest Grid/Grid.jest # just Grid (126 tests)The project targets the jasmine2 runner (its TestUtils registers an
afterEach lazily on first render, which jest's default circus runner rejects);
jest.config.js sets testRunner: 'jest-jasmine2' accordingly.
Not applied (proposal)
One further optimization (opt-in transform-based positioning) remains a
documented proposal in source/NOTES_FURTHER_OPTIMIZATIONS.md; it depends on
real browser compositing, which the available (jsdom) test environment does
not implement, so it can't be proven correct the way everything else here was.
