Last month release of Svelte 3 has got to be the most interesting development in frontend UI libraries so far in 2019. Even more so the accompanying article and video by Rich Harris, Rethinking Reactivity really drive home points that are too often passed over. For me that culminates at the 23–25 minute mark of the video. “How to speedup your code?”, he asks. “Get rid of it.”

After watching most of the web content at Google.IO 2019, it was clear this message was not one everyone is looking at. Where the last few years Google has painted a standards forward new simpler web, with heavy placement on Web Components and Polymer, this year was all React, Vue, and Angular (and a touch of Preact when they were being bundle size conscious), paired with a number of talks on Web Assembly and Native mobile. Throughout talks of streaming SSR, and deferred partial hydration, I kept hearing Rich’s voice come back to me going “Get rid of it.”

Different context for sure. But it definitely led me to digging deeper into that claim. I updated the JS Framework Benchmark implementation of Svelte to the latest version. It definitely slimmed down the implementation code, but the performance improvements were negligible. I did knock another kilobyte off the implementation so it did live up to those claims. And that’s the thing Svelte 3 is a big change in syntax but ultimately delivers the great performance and extremely small bundle size it always has. That means that in practice it often produces bundles smaller than Preact or your favorite “just 2kb” library and probably out performs it.

But that’s still today. What I really want to know does this approach have legs. Solid.js is another library that uses this compilation approach but differs in methodology. I’m going to look at each of Rich’s claims and contrast each library’s approach with hopes of separating the facts from the hyperbole.

Performance

Both libraries are based on Reactive fine-grained change propagation. The everything is a spreadsheet mentality. This approach has the benefit of requiring little overhead to do pinpoint updates. Classically the cost for these sort of libraries has been setting up the reactive graph on initial render. Pre-compilation significantly lowers the overhead here, making it a great match for the technique. And that is really the first thing to acknowledge here:

Svelte is still a library that gets bundled with your JavaScript code like every other library out there.

What makes it interesting is the compiler can tell what features you are using and through clever automation of generating import statements, ES Module Tree Shaking can only include the parts you use. It is a small runtime, but is something.

For performance benchmarks I’ve used the JS Frameworks Benchmark. Unfortunately the Benchmark that Rich uses in his talk is pretty much useless. It isn’t just for the reasons Dan Abramov makes in the twitter back and forth in the video. It actually tests nothing even remotely useful as noted here and agreed upon by the benchmarks author. Further in the thread it is brought to light the React implementation is poorly implemented. JS Frameworks Benchmark on the other hand provides tests across the board from startup, through update, including bundle size and even memory profiling.

As you can see it isn’t particularly close. In the scheme of things Svelte has good performance compared to other popular libraries but it is not class leading by any means. Why is Solid so much more performant? Both libraries do similar things, but there 5 things Solid does that specifically leads to superior performance:

1. Optimized DOM List Reconciliation

Svelte does have a keyed list reconciler but it uses a much more primitive implementation. Svelte’s reconciler is much smaller, but Solid’s is much more sophisticated. Svelte’s approach is not as naive as many libraries, properly handling swapping rows without moving every row in between, but it is not nearly as optimized for single item change operations. This is the majority of the performance difference when dealing with lists.

2. Template Cloning

Solid uses node.cloneNode instead of document.createElement to create nodes. This reduces temporary memory usage. It does require using comment nodes as placeholders but the overall impact on repeated list items with atleast 3 nodes in them is noticeable.

3. Event Delegation

Solid does implicit event delegation on camel case event handlers. This is more performant than letting the DOM handle adding 2 handlers per row. It obviously is possible to do explicit event delegation but it requires more code to do explicit lookup/tree traversal to tie event type and row data together. The previous version of Svelte implementation didn’t do it so I didn’t add it, and I’m currently waiting on Svelte community or Rich himself to weigh in on the relative importance of the “performance” vs “write less code”.

4. Synchronous Update Batching

When you are constantly blasting the DOM with updates deferring them with an Animation Frame or even a Promise is a must for performance. However, if you are making just a single set of changes the deferred timing of updates actually has a cost. Libraries generally either run all their updates synchronously one by one or batch them in a deferred microtask. You know the whole state not being updated after the setState call in React. Solid provides an explicit syntax for batching actions, and its setState helper can take lists of changes all applied at the same time.

5. Explicit Reactivity and Computations

Svelte’s compiler can automatically identify when you are connecting to a value, and will automatically setup everything you need to handle its value changing. But it is difficult for it to tell if you ever intend to change that value. Additionally it is difficult with plain JavaScript syntax to know the intent of dealing with the reactive atom or the value it holds. This can lead to additional synchronization on boundaries like Components. Svelte’s store mechanism uses a different syntax to handle this shortcoming. The existence of computations are some of the heaviest code for Reactive libraries so having the ability to not include their creation greatly slims down performance. Sometimes automatic isn’t better.