In Production · Frontend · Fundamentals

Why I wrote my own renderer

Before picking the tooling for XP’s design system, I wanted to understand what it did inside. The result was bradom: six ways to render the same interface, written from scratch.

By Published 8 min read1 interactive figure

When I had to choose the tools for the XP Inc. design system, none of them was a perfect fit. Every option had benefits and costs, and reading comparisons was not settling anything. So I raised a slightly cheeky possibility with the team: what if we built our own library? If others built one, why not us?

That question became bradom, a study monorepo with six packages, each rendering the same interface a different way:

  1. naive innerHTML;
  2. Document Fragment;
  3. a Virtual DOM without diffing;
  4. a full Virtual DOM;
  5. tagged template literals (left incomplete);
  6. a React benchmark to keep me honest.

The code is on GitHub.

Where the time goes

The browser turns bytes into characters, tokens, nodes, DOM and CSSOM, combines them into the render tree, computes layout and paints. Changing the DOM can restart part of that path, and avoiding needless reflow is most of what rendering optimization means.

The easiest approach, rewriting innerHTML, is also the worst. It throws away element state, like an input’s focus and the text in it, and it opens a door to XSS. There is a reason React calls its version dangerouslySetInnerHTML. Type into the filter below and watch what each strategy does to the list.

Fig. 1 — Three ways to update the same listcost model · 120 static nodes + 200 items

Loading interactive figure…

Static version: going from “ab” to “a”, innerHTML rebuilds everything and loses state; the Virtual DOM evaluates the whole tree and touches only what changed; the template literal evaluates only the dynamic holes.

Going from “ab” to “a”, innerHTML throws everything away and rebuilds it. The Virtual DOM lets you write code as if you were recreating everything, then compares the new tree with the old one and touches only what changed, so state survives. Its price is evaluating the whole tree on every change, including the parts that never change.

The cache nobody tells you about

Tagged templates have a quiet property. The first argument the tag function receives, the array of static strings, is the same object every time the same template runs. You can use it as a cache key. The HTML is already split into static parts and dynamic holes, so only the holes need re-evaluating. UI = f(state), recomputed only where the state changed.

So did we ship bradom? No. For Soma, the design system, I chose a technology that was not my own. Writing the renderer was never about shipping it. It was about choosing with my hands instead of with blog posts, knowing exactly where the cost would be: in nodes evaluated and DOM operations, not in lines of code.