Svelte vs. React (overselling a framework as a revolution)
I’ve just read about a web framework called Svelte in the post Virtual DOM is pure overhead. I think the product itself sounds interesting, but that the author uses unnecessarily misleading arguments.
From what I gather, Svelte is a compile-time reconciliation generator for JSX/TSX components. This pre-calculated generator applies changes to the DOM without needing a virtual DOM and without real-time diffing or reconciliation. That is, instead of having real-time calculation, with possible performance hits, the app benefits from having all possible state changes pre-calculated and ready to apply immediately and quickly.
This all sounds pretty good, I think. I’m definitely going to take a look at the more-advanced tutorials.
However, the author wasn’t happy with just presenting his product, but seems to need to mischaracterize why products like React abstracted away from the DOM in the first place. He tells us that the virtual DOM was always slower than manipulating the DOM. But that isn’t the claim React makes. React helps users avoid common performance pitfalls in the model of programming that it replaced—it never claimed to be the final word in performance optimization.
It’s clear that something like Svelte—if it can cover all the needs of an app—is faster than maintaining a virtual DOM.
But that product isn’t what React replaced. React replaced products written in jQuery. React brought an asynchronous frame-based renderer to the web (something that products like WPF have had for decades). It brought us type-safe views (when used with TypeScript) and taught us about the advantages of immutable data structures.
He stands on their shoul-ders, then implies that they were idiots for not having been taller.
The author characterizes the notion that a virtual DOM is faster as a “meme”. This is silly and imprecise. It is true that React will be more efficient than most hand-coded web sites of a typical level of complexity. jQuery sites tended to teeter and collapse under their own weight. They were unmaintainable and very difficult to optimize without nearly rewriting them. React sites, on the other hand, are modular in nature and the library includes several standard patterns to apply and measures to take to optimize these components. It’s not always easy, but it’s better than it was in the old days.
And there are solutions in React to performance issues. The users must follow patterns and use the APIs correctly. That’s the way it is in every framework or library. Some libraries offer less leeway for users to screw up performance in the way that they shape their APIs.
Sometimes the API surface goes too much in that direction and ends up handcuffing users. That is, users can’t write what they want to write in a way that feels natural because the pattern they prefer wouldn’t perform well under their framework. Instead, the user must change how they think about writing apps just to use the framework. This isn’t necessarily a bad thing, but is definitely something to consider. It’s possible that Svelte offers all of the advantages of React with even more flexibility and less opinion.
React—and its companion Redux—was always about being very declarative about state and changes. There is no magic, even the reconciliation algorithm is very predictable. There are other approaches, like MobX, which users claim “does the right thing” with state changes, even if the user fails to declare dependencies as clearly as React would have required. I imagine that Svelte is going in this direction as well.
The claim I think that Svelte is making is that users can write code that feels more natural without changing their paradigm to match the framework. That is, Svelte must have some rules for which state the compiler observes and pre-compiles, but the claim is that it’s much more flexible and forgiving than React’s “straitjacket” (my word).
He goes on to say that React acknowledges its own slowness by giving the user control over
shouldComponentUpdate. This is a silly argument again. It’s arguing that React bamboozled people in 2013 by convincing them to use their framework instead of a library that the author purports is faster but that he only started in 2017.
There is honestly no need for this kind of bullshit. If your library offers advantages over React, describe them and let them speak for themselves. There is no need to rewrite the whole history of a product that quite clearly inspired your own, pretending that the authors of your own framework’s inspiration are your inferiors because they failed to leap directly to the concepts outlined in your library. He stands on their shoulders, then implies that they were idiots for not having been taller.
Through all of this fluff, it took to about ¾ of the way through the article to find out that Svelte generates update code at build time. I would have been much more intrigued had the author led with that. Now, I’m going to be suspicious of everything about this framework because the author went to such lengths to bamboozle and oversell me. He seems to want me to think I’ve been a fool for having used React in the first place, when his framework has been waiting for me all along, since all the way back to sometime in 2018.
But he waits until the very last paragraph to explain what Svelte actually is—even though he’s been comparing it to React the entire time. It’s a good description:
“It’s important to understand that virtual DOM isn’t a feature. It’s a means to an end, the end being declarative, state-driven UI development. Virtual DOM is valuable because it allows you to build apps without thinking about state transitions, with performance that is generally good enough. That means less buggy code, and more time spent on creative tasks instead of tedious ones.
“But it turns out that we can achieve a similar programming model without using virtual DOM — and that’s where Svelte comes in.”
This is a much fairer characterization of the two libraries: they both base on a very similar model—one that React did a tremendous amount of legwork in establishing as an attractive approach in people’s minds—but that Svelte goes a step further to improve the reconciliation mechanism, moving it from runtime to compile-time. Svelte’s improvement could be a highly welcome one, but it’s incremental, not revolutionary.
That’s wonderful! But it’s actually even more wonderful than his article indicated, because I actually don’t have to learn anything to work with Svelte instead of React. I can work pretty much the same (Svelte doesn’t have hooks because it seems it doesn’t need them) and just kind of “drop in” Svelte instead of React and have better performance, even in places where I’d never noticed I might have had problems.
That is, with Svelte instead of React, my app will be overall faster because performance no longer suffers from “death by a thousand cuts”, as the author puts it. Despite the author’s overzealous mischaracterizations and attempts at hot-take marketing, I’m still going to check out Svelte.
I’m not sure what MobX 5 is up to or what introspection it offers into the web of observables and dependencies in a more-complex application, but older versions of the library were not easy to debug when performance problems arose. From what I’ve read from users, things have gotten much better, but I’m still inclined to think that React’s declarative approach suits me better—it’s easier for me to apply well-established patterns in my own code rather than trying to figure out how to appease the MobX black box. Again, things may be different now than in earlier versions. I’m open to taking another look at MobX.
I’m also not sure how Svelte and MobX compare: MobX requires users to indicate that state is “observable” before it manages it, whereas I assume Svelte determines for itself which state-transitions it should track.↩
Update January 2022: In going through the tutorial available today, you’re very quickly introduced to <em>reactive declarations</em> to help Svelte determine which compound expressions should be “watched” for changes to sub-elements. That is, if you declare simple variable, any references to it in view code will be automatically updated, but if you derive another simple value from it and observe that value, it only updates when the derived value is updated directly. This is unlikely, as the derives value presumably implements an algorithm of some sort and should never be directly changed (i.e. it’s a <em>calculated property</em> in the parlance of other frameworks. For example, given the following code,
let count = 0; doubled = count * 2;
Any observers (i.e. embeddings in a view) of the value of
doubled will not be updated when
$:. For example,
let count = 0; doubled = count * 2;
useState() does in React.
The author disparages hooks, saying that they are even worse for performance and linking to a tweet with the words “with predictable results”. The tweet complains about atrocious performance because of constant reconciliation and rendering—but a dozen answers down is the answer: the original poster failed to tell the
useEffect() hook on which state it relied.
That’s kind of a rookie mistake—in that framework. I understand that Svelte claims that it doesn’t need these hints in order to be able to determine at compile-time when a piece of code needs to be executed because the state on which it relies has changed.
React is declarative and requires help from the user whereas presumably the selling point of Svelte is that this user would have wasted less time improving performance and more time focused on application logic because Svelte is smart enough to do all this for you.
I personally think that this sounds awesome and that it is an admirable goal, but have my doubts that Svelte doesn’t also impose its own set of limitations on what kind of state transformations you can do that the compiler can actually detect.
That is, React provides an API with which callers can “help” the reconciliation algorithm avoid work. Svelte claims that this isn’t necessary, but I’m going to guess that there are rules for how sophisticated state changes can be before the Svelte compiler no longer detects them. In that case, what does Svelte do? Fall back to using a React-like virtual DOM to reconcile changes? Or just not update when the user expects? Or just fail to compile, spitting out an error indicating what the user should do to fix them issue (my personal favorite)?↩