Brahmos, a New, Small, React-like UI Framework with Concurrent Rendering — Q&A with Sudhanshu YadavOctober 12, 2020
- Brahmos is among the very few UI frameworks that implements the experimental concurrent mode API sponsored by React. Other frameworks may be waiting out, or discarding the feature entirely.
The current version of Brahmos implements a large portion of React APIs, including functional components, hooks, context, refs, forward refs, suspense, concurrent mode, and more. Example code using the previous APIs is available in a code playground.
Brahmos is another front-end framework that seeks to improve React’s performance. Preact strives to do so with a smaller codebase that targets DOM rendering. Unlike React, and like Preact, Brahmos cannot target non-DOM-based output devices (mobile, pdf, webGL, and more).
One interesting aspect of Brahmos is its implementation of concurrent mode, a feature React has introduced and worked on for several years and that is still pending an official release.
Brahmos has the reuse of existing React components as a short-term objective (this is not yet implemented). A performance benchmark that would validate Brahmos’ approach is also pending.
InfoQ interviewed Sudhanshu Yadav on the framework fundamental ideas, goals, value added and roadmap. Readers that are interested to go deeper can also review an introductory talk online.
InfoQ: Can you tell our readers about yourself?
Sudhanshu Yadav: I work at HackerRank as a Front-end Architect. I have a deep interest in understanding the internals of the tech I use and I like making theories about how things work. I also organize a meetup group to discuss the internals of different technologies. Apart from this, I keep exploring architecture, patterns, tooling, and design systems.
I am a strong believer in open source software and have authored Brahmos, react-number-format (which has ~1M/month npm installs), packagebind, and other OSS tools and libraries
InfoQ: You recently released Brahmos, which you describe as a front-end library to build user interfaces that replicate modern React APIs. This puts Brahmos in the company of other React-inspired libraries Preact and Nerv. Preact emphasizes its small size (4KB); Nerv singles out browser compatibility down to IE8. What would you describe as Brahmos’ key differentiating point vs. React? What drove you to write Brahmos?
Yadav: Improving the performance of applications is the key motivation behind Brahmos. Brahmos is heavily inspired by the idea from lit-html/hyper-html of dividing the application into static and dynamic parts. Traversal and processing can then be done in O(dynamic nodes) instead of O(nodes) which is the case of React. The rendering pattern also opens up a lot of static optimization possibilities. When I came across a talk on lit-html, I was intrigued by this idea as the majority of the portion of an application is static with only a few dynamic parts that change.
I love React and its declarative API and I wanted to try out a similar pattern with React. So, initially, I considered two options.
The first was to write React Renderer. This wasn’t possible because the main problem with implementing the pattern was React Element and virtual dom. React Element doesn’t differentiate between the static and dynamic elements and it’s hard to combine multiple static elements as one element.
The second was to use lit-html as a rendering engine and write a React API’s wrapper above it. But the lit-html API and React APIs aren’t directly mappable.
So both options were out. Then I decided to write a library implementing React APIs from scratch with a different rendering pattern.
One other motivator to start working on this was to understand how React works internally and how to build a full-fledged UI library.
Yadav: Along with the template literals, ES6 brought us a very underrated feature of tagging your templates. A template literal tag is just like another function that receives an array of strings (literal/static parts) as the first argument, and the rest of the arguments are the dynamic expression parts. One more unique behavior of tag functions is that the reference of the string array remains the same if the underlying literal string doesn’t change.
Now with regard to Brahmos, it transpiles JSX into tagged template literals, where the native elements become the static parts and the JSX expressions become the dynamic expression parts. This lets us identify the static and dynamic parts of the content and optimize them differently.
We can consider the complete static part as one virtual node, which reduces the number of virtual nodes in an application significantly. This also makes it faster to traverse through dynamic parts to identify the changes.
Since the string array reference remains constant for a given template, we can cache the string parse work (converting a string to an HTML template tag), and even if you are rendering the same component multiple times in a big list, the work doesn’t have to be duplicated. This improves the performance of the usage of components in a repeated manner.
Template literals are also more parser-friendly on script load than the object literals which are the result of JSX to create element transformations. A similar technique we use to prevent onload parsing of object literals is by converting them to a JSON string.
InfoQ: React recently added a flurry of new APIs, some of which remain experimental. Among those, Concurrent Mode has received special interest from the community, both from the possibilities it opens, and the complexity triggered by concurrent rendering. In fact, some frameworks have decided not to replicate the feature. Can you remind us what concurrent rendering does? How did Brahmos go about concurrent rendering? Do you reckon the feature is worth the inherent complexity of concurrency?
Yadav: I feel that the concurrent mode is taken in the wrong way by some people in the community. The concurrent mode isn’t just about improving performance when updating a view (update performance), but also when creating the view (mount performance). Concurrent mode allows developers to control how and when the views are loaded and the order of rendering to provide the optimal user experience.
Concurrent mode helps keep the UI interactive while the application is working in the background. Now, since there is just a single thread in the browser, the background and foreground work happens in the same thread, but the UI library (React/Brahmos) schedules and switches between it.
The version control metaphor is the perfect way to explain the behavior. In a typical git workflow, when we get a feature to implement, we branch out and move to another branch, work on it, and then merge to master. But in between, if we get a priority bug to fix, we move away from the feature branch, work on the priority bug, push the changes to master, and then continue to work in our feature branch again. Without git, we would be stuck on finishing the feature task first to even pick the bug fix.
I definitely think the pattern is worth looking into. It’s not like we can’t improve the concurrency in the app itself (maybe through debounce, managing race conditions, managing priority ourselves), but the complexity it brings to the application is huge. React with the concurrent mode is trying to hide that complexity in the library itself and provide a declarative API that hints at how an app should render.
Implementing Concurrent mode is hard and there are a lot of use cases. While implementing Concurrent mode for Brahmos, as soon as I thought I had cracked it, another use case would pop up and I would have to rethink the whole thing again. But given the possibilities it opens, it made sense to invest in this pattern.
Currently, Brahmos supports all the upcoming concurrent mode features. It supports fiber architecture, time-slicing, transitions, suspense for data fetch, and suspense list.
The API remains the same, but the architecture and the approach Brahmos has taken to solve concurrent mode is slightly different than React. For example:
Instead of dividing the behavior and heuristic into multiple priorities, Brahmos divides an update into three categories:
First, updates that have to be rendered and committed synchronously—the updates which can’t be paused in between (like updates caused by events).
Second, the updates that can be paused but the updated value can’t be changed (like updates caused by setState which don’t originate from the user interaction).
Third, the updates that are deferred, can be paused, can become stale, can be delayed (like async updates, updates inside transitions). Async and deferred updates can become stale if the foreground state changes.
Brahmos makes the high priority changes directly on the foreground fiber tree, as they have to be synchronously flushed.
Brahmos maintains a separate update list for every transition, so they can run independently and one transition doesn’t block the other one.
InfoQ: Given the similarity of API with React, how easy would it be to migrate existing React code to Brahmos? Do you recommend using Brahmos for greenfield applications?
Yadav: The end goal of Brahmos is to make migration from React as straightforward as aliasing. But the biggest obstacle to this right now is the React APIs related to children components. As Brahmos combines all the static nodes into one, the children don’t look the same as React—though we do have a workaround for this. But the support is required for third-party React component support.
Since the main optimization Brahmos does is by dividing the static and dynamic parts and transforming JSX into tagged template literals, we are also looking into how we can post-process third-party modules to convert createElement syntax into a tagged template literal. By the way, createElement syntax is also supported in Brahmos, but Brahmos makes it a dynamic part.
We have a couple of plans ahead to make the existing React App optimized using Brahmos, but it’s a long way right now.
InfoQ: Do you have an example where Brahmos shines in comparison to other frameworks?
Yadav: Brahmos’ main target is improving the performance of UI rendering in the server and the browser. Currently, Brahmos is still in development mode so we don’t have any benchmark results yet. But here’s how Brahmos is trying to improve performance in different ways:
1. Bundle size (minified + compress)
Brahmos: ~12 kb (It covers most of the React APIs including upcoming concurrent mode.)
Preact + Preact compact: ~9 kb (But the preact doesn’t have concurrent mode support.)
React + React-dom: 38.5 (It might grow a little bigger with concurrent mode support.)
There are still some ideas to reduce the Brahmos size, plus we are also looking into how we can tree-shake while supporting React’s third-party libraries.
2. Application bundle performance
Brahmos’ JSX transpiled output is smaller than React’s for application code as well. Plus, it may also reduce the overall onload parse time as the tagged template literal which is a string, doesn’t have to be parsed on script load like object literals.
3. Render/Update time
As in Brahmos, we are combining the static parts as one node, the traversal becomes O(dynamic nodes) which in React is O(nodes). So the overall traversal time to find changes to apply to the DOM is less in Brahmos.
4. Server-Side Rendering
As Brahmos transpiles JSX into tagged template literal, which is already a string, rendering the app to string will be much more performant than rendering strings from VDOM (React Elements).
Async rendering in server-side (Yet to be built.)
Apart from the performance improvement, we also plan to bring async rendering server-side. Brahmos from the start is built with async rendering in mind, so the architecture can support server-side async rendering as well. This rendering can make our server and client logic more unified and can remove a lot of convention requirements to identify the routes and API calls.
InfoQ: Conversely, what are things that may be easier to do with plain old React than with Brahmos?
Yadav: Brahmos is just targeting the browser and server rendering, so it doesn’t and won’t support other targets. It’s much easier for React to support multiple targets due to VDOM, and as there is no VDOM in Brahmos, it can’t support different renderers.
Plus you might not see much benefit from Brahmos where the app mostly comprises dynamic parts (like data visualization). Apps that are heavy on data visualization or mostly dynamic parts aren’t a great fit for Brahmos.
InfoQ: What is next on Brahmos’ roadmap?
Yadav: First, we are targeting to support React 3rd party libraries, and then the server-side rendering (SSR). In this way, Brahmos can be immediately beneficial to the community. We are also planning to bring the async rendering support in SSR with Brahmos which can make a big difference in how SSR apps are currently architectured.
We will also be looking into support for the React Dev tool for Brahmos.
About the Interviewee