Chuniversiteit logomarkChuniversiteit.nl
The Toilet Paper

Svelte really is that fast

The five most popular JS frameworks – Angular, React, Vue, Svelte and Blazor – use different rendering strategies, and it shows.

The Svelte logo dashes across the canvas
I haven’t Svelte this way about a JS framework in a very long time

If you search online, you’ll find countless benchmarks claiming to compare the performance of various JavaScript frameworks with each other. More often than not, the benchmarks are overly simplistic and fail to reflect real-world scenarios. In other cases, they compare apples and oranges, for instance by pitting a fully fledged framework against a lightweight library that cover only a small subset of the framework’s functionality.

Right now, I’m at that point in my career again where I am starting development on a new web application and need to choose the “right” JavaScript framework. This time, I decided to look for academic studies on the performance of JavaScript frameworks and sadly, didn’t find as many as I had hoped. I did come across one particular study that compares Angular, React, Vue, Svelte and Blazor with each other. Its main drawback is that the comparison was done in 2021 – a lifetime ago in tech terms – but I think it’s still worth reading.

Before I dive into the summary, I want to share something I found mildly amusing. The paper is published in the Journal of Web Engineering, and if you visit its website, you’ll notice it includes /index.php/ in the URL. I’m not sure why. Is it a deliberate choice? Or is it a sign of questionable web engineering?

It is estimated that up to 97% of websites today use JavaScript, with more than 80% also relying on a library or framework. JavaScript is often used to manage UI state changes within single page applications, allowing users to interact without reloading the entire page. While this can be done manually via the DOM API, it’s often error prone.

Theory

Link

Modern web frameworks wrap the DOM API and provide a custom declarative syntax. This means that application code can simply describe the desired UI state, and the framework automatically generates the necessary DOM API calls to reflect that state in the browser.

When using the DOM API directly, the amount of script execution required to update the UI scales linearly with the complexity of the change. However, when DOM API calls are generated dynamically by a framework, the framework must first determine exactly which updates are necessary, introducing additional overhead. Furthermore, the chosen rendering strategy can greatly affect the number of DOM API calls made. A bad strategy may result in noticeable delays even for small updates, which is why it makes sense to compare the strategies used by major JavaScript frameworks.

JavaScript versus WebAssembly

Link

The study looks at five popular frameworks: Angular, React, Vue, Svelte, and Blazor. All are JavaScript-based except for Blazor, which uses WebAssembly. Blazor applications are written in C# and run in a .NET runtime compiled to WebAssembly. Because WebAssembly modules lack direct access to the DOM, they rely on an additional JavaScript interoperability layer, which can introduce extra overhead.

Framework Version
Angular 11.2.3
React 17.0.1
Vue 3.0.7
Svelte 3.35.0
Blazor 5.0.3

Virtual DOM versus data bindings

Link

All five frameworks follow some variant of the Model-View-ViewModel (MVVM) pattern. In MVVM, developers define components that bind an application’s data sources to views, so that changes in the data are automatically reflected in the UI.

Each framework continuously tries to keep the state of the DOM tree synchronised with the state of the component tree defined in application code. This is done using two distinct methods.

The first method, known as virtual DOM (vDOM)-based rendering, is used in React, Vue, and Blazor. It works by comparing the two trees and calculating the minimum set of changes needed to transform one into the other. This generally has a time complexity of O(n3)O(n^3), but can often be simplified to O(n)O(n) by making assumptions that usually apply in browser applications.

The second method is used by Angular and Svelte. Here, there is no separate step for calculating all required changes to the DOM. Instead, each component directly updates its corresponding section of the DOM by tracking the values of its data bindings.

From a performance standpoint, using a virtual DOM can introduce overhead not present in a binding-based rendering strategy.

Creating versus updating

Link

All of the reviewed frameworks perform DOM updates within a render loop that walks through the component tree. The cost of this render loop depends on the size of the input and fixed costs.

A render loop involves two types of work: creating new elements and updating existing ones. Creating elements is generally straightforward and costs the same for each framework, regardless of rendering strategy.

Updating existing elements is where the rendering strategy makes a noticeable difference. Angular, for example, always walks through the entire component tree, resulting in a lot of unnecessary work when only a small part of the tree needs updating. React and Blazor walk only through the subtree of the component that initiates the render loop, which is usually more efficient but may still require some unnecessary work for descendants whose output has not changed. Vue and Svelte, on the other hand, process only “dirty” components whose output has changed. This requires the framework to track which components are dirty. Vue does this at runtime, Svelte handles it at compile time.

Finally, component output can be classified as either static or dynamic content, with static content remaining unchanged after the component’s initial render. Frameworks that can optimise for static content may have a performance advantage over those that cannot.

Practice

Link

Several benchmarks were conducted using Angular, React, Vue, Svelte and Blazor. The authors found significant differences in performance between the frameworks, especially as input size increased. , outperforming the others across literally every benchmark.

Svelte is the fastest framework when creating nn static elements, while React is generally among the slowest:

n Angular (ms) React (ms) Vue (ms) Svelte (ms) Blazor (ms)
100 3 2 1 1 3
500 9 9 3 2 8
1000 16 11 6 3 13
5000 85 77 28 14 61
10000 177 200 47 24 123
25000 844 956 95 63 371
50000 2520 3559 173 98 964

Svelte is also the fastest when creating nn components arranged as a binary tree. However, in this case, Blazor is the slowest by a considerable margin:

n Angular (ms) React (ms) Vue (ms) Svelte (ms) Blazor (ms)
128 20 7 16 3 17
512 75 32 53 10 59
1024 120 55 84 22 128
4096 216 137 223 83 485
8192 297 233 313 142 966
16384 469 394 485 233 1870
32768 774 733 897 482 3644

When updating the root component of a tree with nn components, :

n Angular (ms) React (ms) Vue (ms) Svelte (ms) Blazor (ms)
128 3 7 <1 <1 3
512 12 23 <1 <1 3
1024 14 42 <1 <1 2
4096 32 92 <1 <1 3
8192 32 92 <1 <1 3
16384 43 211 <1 <1 2
32768 103 379 <1 <1 3

When updating a leaf component in a component tree of nn components, Angular is the only framework that lags slightly behind. This is because it performs the same amount of work regardless of what has actually changed:

n Angular (ms) React (ms) Vue (ms) Svelte (ms) Blazor (ms)
128 3 <1 <1 <1 1
512 13 <1 <1 <1 1
1024 14 1 <1 <1 1
4096 33 4 <1 <1 3
8192 33 3 <1 <1 5
16384 44 5 <1 <1 4
32768 104 4 <1 <1 8

Finally, the table below shows the script execution time when updating the entire component tree of nn components, where each component contains primarily static content:

n Angular (ms) React (ms) Vue (ms) Svelte (ms) Blazor (ms)
128 4 34 20 2 28
256 8 44 32 3 60
512 17 66 42 5 101
1024 27 101 72 10 250
2048 29 235 91 20 502
4096 44 289 149 54 1020
8192 238 841 311 80 2013

Overall, the results are in line with what would be expected given the characteristics of each rendering strategy.

The WebAssembly-based Blazor shows significantly worse performance than its JavaScript-based competitors. However, from these benchmarks alone it’s impossible to determine whether this is due to Blazor itself or a fundamental limitation of using WebAssembly for this purpose.

Meanwhile, Svelte demonstrates three key characteristics that likely contribute most to improved performance:

  • The use of a reactivity system to automatically detect dirty components
  • An optimising compiler that generates component update code which ignores static content
  • A binding-based rendering approach rather than a virtual DOM

Summary

Link
  1. Svelte’s on fire, yo