-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Blueprint Virtualization
This document serves as a RFC to improve Blueprint by having virtualized components, which would improve their performances.
Last updated: July 2022
Most (all ?) of Blueprint’s component are not virtualized and this affects performance. This RFC aims at improving the performance of Blueprint’s component when displaying large amount of data (list, grid, timeline, tree, ...) by using a virtualization library called react-window.
A similar discussion occurred in the past about react-virtualized, which was the bulkier predecessor of react-window. Given the improvements made in react-window (in particular the size of the dependency decreased of a magnitude) I think this is time to revisit this.
BLUF : rendering only what is visible
A virtualized component (such as a list) will load only the elements that are visible and unload them when they are not, by replacing them with new ones. They calculate which items are visible inside the area where the list is displayed (the viewport). This process is called “windowing”, and is different from the “virtual DOM” used by React (which create DOM element only when react renders elements). This is part of the performance optimization guide of React.
Non-virtualized list / from blogpost | Virtualized list = windowing to (strictly) what the user can see / from blogpost | Virtualized list with buffers = windowing to (with a bit of extra items before and after, to handling scrolling without lag) what the user can see / from blogpost |
---|---|---|
![]() |
![]() |
![]() |
A few concepts :
- Windowing : limiting the objects we want to be part of the virtual DOM that React will render into real DOM nodes. DOM nodes that exit the "window" are recycled, or immediately replaced with newer elements as the user scrolls down the list. This keeps the number of all rendered elements specific to the size of the window. (from here)
- Buffer : If we limit the objects to render to exactly what the user can see, while scrolling there might be a delay before rendering new objects, hence there are “buffers” that pre-load data just before and after the currently displayed list (but that’s still far less objects than the full list)
- Virtual DOM : Faster to iterate on (because no need to draw on screen). Difference with previous DOM will be computed to only change “what is needed” in the real DOM.
- Real DOM : slower, because need to be drawn.
![image](https://private-user-images.githubusercontent.com/723999/305091648-75d04985-e5f6-4acb-9f7a-9cf48e00972c.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjUxMDU5NTgsIm5iZiI6MTcyNTEwNTY1OCwicGF0aCI6Ii83MjM5OTkvMzA1MDkxNjQ4LTc1ZDA0OTg1LWU1ZjYtNGFjYi05ZjdhLTljZjQ4ZTAwOTcyYy5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwODMxJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDgzMVQxMjAwNThaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT1hZWY5YmExZmY1MTVmOTJlOGVkZTZjY2I5NzVhNTAxMjZiN2RhMDY1YTg0OTQ2NWZlOTk3YmMwOGVhOWE1YmU2JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.jrEIX1aDFnrWKE-i8scxrgfkzu5jBaIjzg45bY3--0w)
e.g. showing 1000 rows in a frame only displaying 8 rows at a time (from blogpost)
Non virtualized : all rows are materialized as DOM elements objects | Page display | Virtualized : more nesting, but only 8 rows are materialized as DOM elements objects, evolving as user is scrolling (like a “rolling window”) |
---|---|---|
![]() |
![]() |
![]() |
✅ Improved performance (FPS) (without vs with)
❌ Need to know the width and height of the list (or container) and the height of the row (in the container). But there are helper classes to compute them. ❌ That’s a library, so it’s adding a dependency
BLUF: react window is “better” than react virtualized.
React virtualized (23k GH stars, 950k dwl per week, 2.27MB) and react window (12k GH stars, 1M dwl per week, 800Kb) were both created by the same author (Brian Vaughn). React window is the 2nd iteration. Thanks to the author :
- It’s smaller
- It’s faster
- It has less unnecessary (thanks to the author) features and APIs
- The documentation is more user-friendly
One number :
Adding a react-virtualized list to a CRA project increases the (gzipped) build size by ~33.5 KB.
Adding a react-window list to a CRA project increases the (gzipped) build size by <2 KB.
Both are MIT licensed, greenlighted by IP Ninjas. See RTFM.
From Comparison Page on Github
BLUF : Regardless if paging can be a solution, the frontend should not be an order of magnitude worst than what someone building a front-end from scratch can get.
Vocabulary :
- lazy-loading : loading some data only when needed (e.g. a picture only when visible by the user)
- Infinite scrolling or infinite scrolling : Add new DOMs nodes as the user scrolls near to the end of the already loaded list. That still populate the DOM with many nodes, if the user scrolls a lot.
- Chunking or Paging : getting data from the back end in blocks of n rows/objects instead of all rows/objects at once.
- Paging (front-end) : renders the data in pages instead of rendering all the information at once. Less stress on the DOM. The user need to click a “next page” or a “page index” to load more data.
- Virtualization (in this context): a way to display a lot of unpaginated data without significant performance impact.
Infinite scrolling can be achieve by using the “infinite-loader” package of react-window. Such implementation will benefit from the virtualized list, and so not only “add nodes” but render only the visible nodes (+ some before and some after, as buffer). A callback has to be provided to “load more” items. (see example there). The number of nodes “not visible” but “pre-loaded” is configurable. (overscanCount
).
Pagination is an alternative to virtualized components. When loading only a few rows from the backend, the volume of data to display is much lower, and so virtualization is less important. However, if more data is rendered than shown to the user, then virtualization is still useful. Data-intensive widgets (e.g. zoomable timelines, tree table ...) cannot be paged. It’s easier for users to jump to a given page when they want to visit “far away” data instead of scrolling all the way down to it.
There is a scale discussion : If 1k nodes in a tree widget (or grid, or table...) are a problem (an order of magnitude slower than other application using virtualization), this needs to be sorted; regardless if paging is an alternative. This will also put less strain on the user’s machine.
BLUF : blueprint components can be (sometimes, depending on which ones with which data) crazy slow
Tree with default tree widget in Slate with 9k nodes | Tree in CodeSandbox in Slate (with a third party library) with 13k items | Virtualized tree based on react-window with 110k items (sandbox) |
---|---|---|
![]() |
![]() |
![]() |
Some component(s), not in core blueprint, were created using windowing/virtualized components. (Internal links removed here, see Quip for details)
@blueprintjs/table has a custom implementation of virtualized row rendering. It was created years ago when these third-party virtualization libraries were less mature, and it has not been updated much since. It’s probably worth migrating the table component to use react-window to reduce bugs and maintenance burden.
Multiple places could benefit from virtualized components : better performance for Slate tree, hierarchical widget for Workshop, hierarchal tree for Quiver, tree in Contour...
Note : This is a list of potential helpers/reviewers/relevant people involved in the initiative.
- Adi Dahiya
- Vincent Falconieri
- Robert Fidler
This is blurry and will get updated as we go
For blueprint in general :
- Create a package (tutorial) called like “blueprint-virtualized” and create the virtualized components there.
For tree component :
- Linearize the underlying tree data in memory + virtualize the rendering over it.
- react-vtree is a tree component which uses react-window, which seems like a viable solution to get a performant tree (we can style it to match Blueprint), but this library is a little under-maintained (see issue #83).
- https://github.com/palantir/blueprint/issues/2632
- https://github.com/palantir/blueprint/issues/3281
- react-virtualized : https://github.com/bvaughn/react-virtualized
- Live demo of react virtualized : https://bvaughn.github.io/react-virtualized/#/components/List
- react-window : https://github.com/bvaughn/react-window
- Comparison of react-window & react-virtualized : https://github.com/bvaughn/react-window#how-is-react-window-different-from-react-virtualized
- react-virtuoso
- react-virtual
- https://blog.logrocket.com/rendering-large-lists-with-react-virtualized-82741907a6b3/
- https://blog.theodo.com/2018/09/use-react-virtualized/
- https://yudhajitadhikary.medium.com/optimize-your-code-by-using-react-virtualized-a9c814031c27
- https://web.dev/virtualize-long-lists-react-window/
- https://javascript.plainenglish.io/how-to-handle-large-amounts-of-data-in-a-list-on-the-frontend-80725661ff51
- Example of paginated AND virtualized table : https://codesandbox.io/s/mm3kvjo0z9
- Discussion about pagination support in the virtualized library : https://github.com/bvaughn/react-virtualized/issues/24
- Very good illustrations : https://levelup.gitconnected.com/how-to-render-your-lists-faster-with-react-virtualization-5e327588c910
- GIF illustrations : https://betterprogramming.pub/virtualized-rendering-from-scratch-in-react-34c2ad482b16
- React bases : https://www.geeksforgeeks.org/reactjs-virtual-dom/#:~:text=Virtual%20DOM%3A%20React%20uses%20Virtual,that%20in%20React%20Virtual%20DOM.
- StackOverflow tag : https://stackoverflow.com/questions/tagged/react-virtualized
- react-day-picker v8 migration
- HotkeysTarget & useHotkeys migration
- PanelStack2 migration
- Table 6.0 changes