How we wrote the Fastest JavaScript UI Framework, Again!
This time we conquered the Server.
I have a process. I apply this to almost any sort of problem I face.
Step 1. Define the problem
This for me often takes the longest. It is absolutely critical to understand what you are trying to solve, who stakeholders are, and what's actually important.
Step 2. Propose an idealized solution no matter the cost
No.. “but”s just pure and simple how this should work if everything could go your way.
Step 3. Throw it all away and reframe the original question
I find this absolutely necessary to exhaust the prescribed train of thought so that I break apart all my initial assumptions. Only now can the real work begin.
Solid on the Server
SolidJS is a JavaScript library like React, Vue, or Angular designed to efficiently render Web UIs. It supports all the modern feature sets including first-class TypeScript support, Granular Reactivity, Async Concurrency, and JSX/Tagged Literal templating. It has also been the most performant rendering library in the browser for the last 3 years consistently topping benchmarks.
So what is the next horizon for Solid? Server Rendering was an obvious topic and the most requested feature at the time. This was a much harder task. First I needed to find out what makes the fastest Server Side renderer, and I came across MarkoJS. Used in production by eBay, they have a solution for JS on the server unmatched by any other library.
And they had one of only SSR benchmark suites that I’d seen that had a nice variety of implementations. So I had a place to start my learning…
Benchmarking on the Server
So I cloned the repo (Source found here). 2 simple tests. A color picker, and an e-commerce style search results page. You may have seen these before on the MarkoJS Website.
I implemented the samples, built,.. and… well the results were dismal. Like terribly poor. I was using DOM environments on the server(JSDOM, BasicHTML) and they just weren’t coming close to even the slowest libraries. Like 10x slower.
I looked into a few approaches like Andrea Giammarchi’s Heresy SSR, and realized while this would work well for a top-down template library it didn’t make sense for a reactive one. It wasn’t just the faux DOM was being a problem. I had the reactive graph to contend with as well.
So I looked at Svelte the only other reactive library in the benchmark and realized everyone was just rendering strings ultimately. Obvious in hindsight. Both Svelte and Marko had removed any intermediate layer. So that was the answer, compile to a different non-reactive runtime. I wasn’t really happy about this though.
Without a reactive system how do we ever update on the server? How do we handle Async Data Loading? Load it all ahead then render synchronously? That isn’t an isomorphic experience. Having different mental models between client and server is brings way too much perceived complexity.
Ultimately I realized I should rely on Solid’s strengths. Being the fastest client-side renderer had already closed the performance gap with server rendering. I realized I could use the same resources that I use for Suspense on the client to stream the values from the server and have the client render everything after the first shell.
In this way, we solve the double data problem, and remove the need for nested hydration, while leveraging faster initial paint and async request starts. I cover how this works in more detail in my article on indepth.dev:
More importantly, it freed me of this constraint and I could focus on performant synchronous rendering.
And…
I wrote a new compiler and a new runtime. I tuned it and I tuned it. And gave it a good old run and… alas I was 3rd. Just below Inferno. Respectable. I shaved hundreds of milliseconds here and there, but ultimately I knew I had a limit.
See Marko or Svelte are languages. They can analyze the grammar and don’t have extra function wrappers they need to execute, their templates are not JSX that can accept every value. They know exactly what to escape and not.
I needed to arbitrarily wrap any insertion as it could be who knows what. There is just a theoretical maximum performance even I could do with having such a dynamic system. I looked at similar approaches of blueprints that VDOM libraries were using for performance but nothing really worked here.
SSR performance as it turns out is not about finesse. There is nothing clever here. In the browser, we do this dance to avoid DOM operations. On the server it’s about how fast you can mash strings together.
Not the end of the Story…
I had resigned myself to having respectable performance when a contributor made a pull request that read: “Increase escapeHTML performance up to 10 times!”. I was in disbelief. I had made my escape function using a combination of what I thought was Svelte and Marko’s approach and thought I was in pretty good place.
It turns out it could be way faster. I updated my library and sure enough with the escaping bottleneck removed Solid was flying.
So you could say that we lucked into being the fastest for the 2nd time now thanks to the community again. What good was my process? Not sure. I didn’t give up before I started.
What comes Next?
See what I did? No, I don’t personally have plans to make a Next.js library for Solid, but I’d love to see one built. Today I’m going to just reflect on what was accomplished here thanks to help from open source community.
I’m under no illusions here. Solid’s approach will not let it stay absolute fastest on the Server. I know this from personal experience as a member of the MarkoJS core team (I was so impressed with their work I joined up). A benchmark like this doesn’t properly reflect the complexity of hydration either. But I like to raise the bar where I can.
A year ago at the end of the “How we wrote the Fastest JavaScript UI Frameworks”. I made a list of todos that we have almost completed. Solid now has Streaming Suspense Aware SSR with Data Loading and Code Splitting, Concurrent Rendering and Transitions (in beta), and A Realworld Demo. CLI tools, REPLs, 3rd party libraries for i18n. Stuff is coming along.
We still have a long way to go as community. We hit 4k Stars this week on Github and 75k downloads on NPM. Performance and benchmarks is hardly everything. But until told otherwise I’m going to keep pushing the boundaries.
The source code for the benchmark in this article is currently on a fork of the Isomorphic UI Bencharks found here:
Level Up Coding
Thanks for being a part of our community! Subscribe to our YouTube channel or join the Skilled.dev coding interview course.