Kalan's Blog

Current Theme light

Svelte — 是什麼讓我遇見這樣的你 | Svelte 系列文

When I first encountered Svelte, I thought, "Oh... another new frontend framework?" Without paying much attention, I didn't think too much about it. However, as I saw more and more blog posts and websites using it, my curiosity was piqued.

After going through the tutorials and documentation on the official website, I realized that Svelte is different from other "frontend frameworks" in that it starts from the syntax level and uses a compiler to write clean and efficient code.

This statement caught my attention:

Svelte compiles your code to tiny, framework-less vanilla JS — your app starts fast and stays fast.

After trying it out myself, although I, being accustomed to React, had some thoughts on how to do things better in certain areas, overall, I really liked it. It follows a concept that is completely different from other frameworks like React and Vue.

I highly recommend watching the Svelte author's talk at YGLF titled "Rethinking reactivity" to reconsider the essence of frontend frameworks and what we can achieve. Below are my notes and reflections on this talk.

When we talk about reactivity, what are we actually talking about?

The essence of functional reactive programming is to specify the dynamic behavior of a value completely at the time of declaration — Heinrich Apfelmus

When using frontend frameworks, we often consider two things:

  • Virtual DOM - Ensuring efficient page rendering
  • Reactivity - Tracking changes in values

The core of frontend frameworks lies in the data flow and tracking data changes.

Reasons for using Virtual DOM:

The main reason is that updating the entire page when data changes can significantly impact performance. Therefore, Virtual DOM usually implements an efficient diffing algorithm to ensure that only necessary changes are re-rendered when the page updates.

However, implementing a stable diffing algorithm and update mechanism requires a lot of effort and performance considerations to avoid performance bottlenecks when the tree depth becomes too deep.

Reactivity:

To ensure that React can track data updates (changes), React uses setState and useState to track value changes. On the other hand, Vue uses the Proxy mechanism, allowing developers to trigger reactivity when accessing values.

In React, we use useState or this.setState to ensure that React can detect value changes and have a mechanism to prevent unnecessary updates (batch updates). Take a look at the following code:

const Counter = () => {
  const [counter, setCounter] = useState(0);
	const handleClick = () => {
    setCounter(c => c + 1)
  }
	return <div onClick={handleClick}>{counter}</div>
}

Every time the component updates, useState is re-executed, and handleClick is re-evaluated. React internally saves and tracks these state changes and performs appropriate updates.

To solve this problem, optimizations such as useMemo, shouldComponentUpdate, React.PureComponent, and useCallback were introduced:

These mechanisms require significant effort to implement just to avoid re-rendering the entire page and to provide developers with optimization options to ensure Virtual DOM performance. The combination of these mechanisms results in large bundle sizes for react and react-dom.

The author even published a blog post on the official website titled Virtual DOM is pure overhead, explaining the trade-offs of using Virtual DOM. To quote the conclusion of the article:

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.

There is also an interesting perspective mentioned in a tweet in the article:

The entire Virtual DOM mechanism is designed by frameworks to free developers from worrying about state management. But why isn't this mechanism provided by the Platform itself? I find this perspective interesting, although I don't know how much effort it would take to implement it and whether it would encounter cross-browser issues and specifications. The evolution speed might be slower than that of frameworks.

Although the bundle size of react-dom is not as large as depicted in the image, when combined with the product's own code, the bundle size is still quite significant.

To summarize the above thoughts, the drawbacks of modern frontend frameworks are:

  • Runtime reactivity + Virtual DOM diff mechanism
  • Large bundle size

Some might think, why bother about bundle size and performance?

Recently, I had a new perspective after working and living in Japan:

  • Not every place has cheap internet access like Taiwan, where unlimited data plans are common. In Japan, unlimited data plans are not available, and limited data plans are expensive. This is the LINE Mobile phone plan. Although general social networking services don't count towards data usage, it is still not enough for YouTube and Netflix enthusiasts.

  • Many users use inexpensive providers with slower and less stable base stations and network speeds.

  • Internet speed noticeably decreases in subway systems, unlike in Taiwan where it remains stable.

Considering these factors, when using the internet in subways or with poor network connections, the difference in speed is evident. I have also realized that not every country enjoys the same level of internet speed as Taiwan.

Additionally, when using a Raspberry Pi, I noticed significant lag when running web pages on lower-end CPUs.

In the era of IoT, not everyone will be using high-performance CPUs and GPUs to run web pages. This is another reason why I have started to think about performance and framework choices. Does Svelte have this problem? Let's continue exploring.

In the talk "Rethinking reactivity" at 19:15, the author presents an example of Dan's React time slicing and implements it using Svelte. It is found that Svelte surpasses React in terms of performance without needing to enable Async mode or use debounced functions.

Embracing native APIs:

When reading the tutorial documentation, I noticed their event handling mechanism, which is essentially just addEventListener and CustomEvent wrappers. There is no concept of Synthetic Events (like in React) or event pooling. It is as simple as using addEventListener and CustomEvent. For example:

// https://github.com/sveltejs/svelte/blob/master/src/runtime/internal/dom.ts#L60-L63
export function listen(node: Node, event: string, handler: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions | EventListenerOptions) {
	node.addEventListener(event, handler, options);
	return () => node.removeEventListener(event, handler, options);
}

// https://github.com/sveltejs/svelte/blob/master/src/runtime/internal/dom.ts#L275-L279
export function custom_event<T=any>(type: string, detail?: T) {
	const e: CustomEvent<T> = document.createEvent('CustomEvent');
	e.initCustomEvent(type, false, false, detail);
	return e;
}

Although this approach may introduce bugs or optimizations that cannot be handled by Svelte due to differences between browsers or need to be handled by developers themselves, such as Event Delegation or React Event Pool, whether it is good or bad depends on the use case.

The mechanism of Virtual DOM is designed to free developers from worrying about state management. However, why isn't this mechanism provided by the Platform itself? I find this perspective interesting, although I don't know how much effort it would take to implement it and whether it would encounter cross-browser issues and specifications. The evolution speed might be slower than that of frameworks.

Although the image depicting the react-dom bundle size is not entirely accurate, when combined with the product's own code, the bundle size is still quite significant.

To summarize the above thoughts, the drawbacks of modern frontend frameworks are:

  • Runtime reactivity + Virtual DOM diff mechanism
  • Large bundle size

Some might think, why bother about bundle size and performance?

I used to think the same way. CPUs are now i5 and i7, smartphones are iPhone 11 Pro and flagship devices, and internet access is almost always unlimited. Is it really necessary to worry about these things?

Recently, I have had a new perspective from my experience working and living in Japan:

  • Not every place has cheap internet access like Taiwan, where unlimited data plans are common. In Japan, unlimited data plans are not available, and limited data plans are expensive. This is the LINE Mobile phone plan. Although general social networking services don't count towards data usage, it is still not enough for YouTube and Netflix enthusiasts.

  • Many users use inexpensive providers with slower and less stable base stations and network speeds.

  • Internet speed noticeably decreases in subway systems, unlike in Taiwan where it remains stable.

Considering these factors, when using the internet in subways or with poor network connections, the difference in speed is evident. I have also realized that not every country enjoys the same level of internet speed as Taiwan.

Additionally, when using a Raspberry Pi, I noticed significant lag when running web pages on lower-end CPUs.

In the era of IoT, not everyone will be using high-performance CPUs and GPUs to run web pages. This is another reason why I have started to think about performance and framework choices. Does Svelte have this problem? Let's continue exploring.

In the talk "Rethinking reactivity" at 17:11, the author mentions that Evan (the creator of Vue) said, "This is not JavaScript; it's SvelteScript."

Frameworks are essentially performing magic by abstracting away complex details. React turns complex update mechanisms into magic, and Vue wraps template syntax into magic. Everyone is performing magic, but React and Vue do it at runtime, while Svelte hands the work over to the compiler.

Svelte itself has built-in stores that can be used easily without the need for additional libraries like "redux-xxx".

Svelte has more surprising syntax and implementations, such as built-in transitions and mechanisms like spring/fly/flip, along with corresponding directives for developers to use. When you want to add interactions (micro-animations), you don't need to worry about which library to use; you can simply use the ones provided by Svelte. The actual implementation looks like this:

// copied from https://svelte.dev/examples#tweened
<script>
	import { tweened } from 'svelte/motion';
	import { cubicOut } from 'svelte/easing';

	const progress = tweened(0, {
		duration: 400,
		easing: cubicOut
	});
</script>

<style>
	progress {
		display: block;
		width: 100%;
	}
</style>

<progress value={$progress}></progress>

It is intuitive to write, and the internal mechanism of Svelte allows developers to have more control over the DOM.

Another challenging aspect of animations is handling interruptions. If a user performs an action while an animation is in progress, without special handling, the animation usually restarts after completing, which greatly affects the user experience.

Svelte's built-in transitions are interruptible. You can refer to the transition section in the tutorial, where clicking the checkbox triggers a fade-in/fade-out animation. Even if you click rapidly, it starts the transition from the current point instead of restarting from the beginning or end.

If we were to rewrite these things in React, we might need libraries like react-transition-group or react-spring to assist us. Svelte provides these features out of the box, without the need for additional packages. Furthermore, it integrates with the syntax, making it more intuitive to write.

In conclusion:

After all the criticism, I don't intend to criticize any framework or praise Svelte excessively. React undoubtedly revolutionized the history of frontend and web development, and I really like the concept of React hooks. However, the concepts and achievements of Svelte are fascinating, and I want to continue exploring it.

Regarding concerns about bundle size and performance, I believe that one day, hardware devices and internet speeds will catch up. At that time, the difference won't be as significant, and techniques like code splitting and dynamic imports can effectively reduce the initial bundle size.

With the powerful transpiler Babel, we can use JavaScript with the assistance of Babel to minimize the complex tasks performed at runtime. Additionally, advancements in low-level APIs like WebAssembly and ArrayBuffer may bring new possibilities to web development. In this era, besides frontend development, we may also need to learn how to create simple compilers.

If you found this article helpful, please consider buy me a drink ☕️ It'll make my ordinary day shine✨

Buy me a coffee