Svelte — What Made Me Cross Paths with You
When I first saw Svelte, I thought to myself, “Hmm… yet another front-end framework?”, and didn’t pay much attention. It wasn’t until I started seeing more and more blog posts introducing it and many websites adopting it that my curiosity was piqued.
After checking out the tutorials and documentation on the official website, I realized its approach is quite different from conventional “front-end frameworks”—it starts at the syntax level and pairs with a compiler to produce concise and highly efficient code.

This quote caught my eye:
Svelte compiles your code to tiny, framework-less vanilla JS — your app starts fast and stays fast.
After actually trying it out myself, although being used to React meant I was still pondering how best to handle certain things, overall, I really liked it. It represents a concept that takes a completely different path from other frameworks (like React, Vue, etc.).
I highly recommend watching Svelte creator Rich Harris’s talk at YGLF, “Rethinking reactivity,” to reconsider the fundamental nature of front-end frameworks and what we can achieve. Below are my notes and reflections on this talk.
When We Talk About Reactivity, What Are We 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 a front-end framework, we usually consider two things:
- Virtual DOM — Ensuring rendering performance
- Reactivity — Tracking changes in values
The core of front-end frameworks is data flow and tracking data changes.
Why Use a Virtual DOM?
The main reason is that re-rendering the entire UI upon data updates has a huge impact on performance. That’s why the Virtual DOM typically implements an efficient diffing algorithm to ensure that each UI update only re-renders what is strictly necessary.
But here’s the catch: implementing a stable diffing algorithm alongside an update mechanism takes an enormous amount of effort, and performance must be carefully managed so that deep trees don’t become performance bottlenecks.
Reactivity
To allow React to track data updates (changes), it uses setState and useState; Vue, on the other hand, relies on Proxies so that accessing values triggers the reactivity mechanism, thereby tracking data changes.
In React, we use useState or this.setState to ensure React perceives value changes, alongside mechanisms to prevent redundant updates (batch updates). Let’s 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, it runs useState again and re-evaluates the handleClick function; internally, React preserves and tracks these state changes to perform appropriate updates.
To address this, useMemo and various optimization techniques were introduced:
shouldComponentUpdateReact.PureComponentuseMemouseCallback
Implementing these mechanisms requires significant effort—all just to avoid re-rendering everything when the UI updates, along with providing optimization options to developers to preserve Virtual DOM performance. Together, these contribute to the substantial bundle size of react and react-dom.
In fact, the author of Svelte even published a blog post titled Virtual DOM is pure overhead, explaining the trade-offs of the Virtual DOM. Here is an excerpt from the conclusion:
It’s important to understand that virtual DOMisn’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 isgenerally 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’s also an intriguing tweet highlighted in the article:
Why is the conclusion that there's something wrong with the framework, instead of something being wrong with the platform? If the DOM provided a way to efficiently supply large trees created functionally, without React having to do a diff, we'd use that API.
— Vim Diesel ⚛️🆁 (@jordwalke) July 8, 2018
The whole Virtual DOM mechanism exists because frameworks want to free you from worrying about state management, but why isn’t such a mechanism provided by the platform itself? I find this perspective fascinating, though I wonder how much effort it would take in practice. Furthermore, if browsers were to implement it… we’d likely run into cross-browser issues and specification hurdles, and the pace of evolution might lag far behind frameworks themselves.

While it might not seem excessively large on its own, once you add your product’s actual code, the bundle size is still quite substantial.
To summarize the points above, the drawbacks of modern front-end frameworks lie in:
- Runtime reactivity + Virtual DOM diffing mechanisms
- Bloated bundle sizes
Some might wonder: why obsess over bundle size and performance so much?
Rethink Performance
I used to think that way too. Everyone has at least an i5 or i7 CPU, pulls out an iPhone 11 Pro or other flagship phones, and has unlimited data plans—do we really need to care about these things?
Recently, I developed a new perspective, shaped by my experience living and working in Japan:
- Not everywhere offers cheap unlimited data like Taiwan. In Japan, unlimited data plans are rarely an option—data is capped and terribly expensive. Take this LINE Mobile plan, for example: although regular social media doesn’t count toward the quota, as a heavy YouTube and Netflix user, it’s still far from enough.

- Many users rely on MVNOs (“kakuyasu” SIMs, low-cost carriers), where base stations and connection speeds are neither fast nor stable.
- Cellular connections noticeably slow down in subways, unlike the stable connectivity in Taiwan.
Put these together: if you open a webpage on the subway or over a sluggish MVNO connection, you can really feel the difference in speed. I came to deeply appreciate that not every country takes lightning-fast, seamless internet speeds for granted like Taiwan does.
Also, when using a Raspberry Pi, I can clearly feel the stuttering when lower-end CPUs run web pages.
In an era where IoT is steadily rising, not everyone is browsing the web on beastly CPUs and GPUs. This prompted me to rethink performance and framework choices. Does Svelte avoid these issues? Let’s read on.
At 19:15, the author reimplemented Dan Abramov’s React time-slicing demo using Svelte. Svelte didn’t need async mode or debouncing at all, and its performance completely blew React out of the water.

Embracing Native APIs
Reading through the tutorial docs, I noticed their event dispatching mechanism is implemented purely with addEventListener and CustomEvent under the hood. There are no Synthetic Events (like in React), no event pooling—just plain CustomEvent.
// 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;
}
That said, browser inconsistencies or optimizations that frameworks can handle internally might not be smoothed over by Svelte, shifting the burden onto developers—things like Event Delegation or React Event Pooling. Whether that is good or bad depends on the use case.
Irresistible Syntax
I’m someone who loves solving problems at the syntax and language level. These approaches save developers from spending so much time deciphering how useEffect, useMemo, and useCallback work under the hood and why they are necessary.
Svelte provides a simple syntax (mostly standard JavaScript) and template syntax that compiles down to lower-level JavaScript code. For example:
<script>
import { createEventDispatcher } from 'svelte';
export let label;
let a = 1;
$: b = a * 2
const dispatch = createEventDispatcher();
function handleClick() {
dispatch('toggle', {
value
});
}
</script>
<button class="button" class:active on:click={handleClick}>{label}</button>
This Svelte code compiles down to something like this:
import { createEventDispatcher } from "svelte";
function create_fragment(ctx) {
let button;
let t;
let dispose;
return {
c() {
button = element("button");
t = text(/*label*/ ctx[0]);
attr(button, "class", "button");
},
m(target, anchor, remount) {
insert(target, button, anchor);
append(button, t);
if (remount) dispose();
dispose = listen(button, "click", /*handleClick*/ ctx[1]);
},
p(ctx, [dirty]) {
if (dirty & /*label*/ 1) set_data(t, /*label*/ ctx[0]);
},
i: noop,
o: noop,
d(detaching) {
if (detaching) detach(button);
dispose();
}
};
}
let a = 1;
function instance($$self, $$props, $$invalidate) {
let { label } = $$props;
const dispatch = createEventDispatcher();
function handleClick() {
dispatch("toggle", { value });
}
$$self.$set = $$props => {
if ("label" in $$props) $$invalidate(0, label = $$props.label);
};
let b;
$: b = a * 2;
return [label, handleClick];
}
class App extends SvelteComponent {...}
Notice that the element and text functions here are just thin wrappers around document.createElement and element.textContent; there’s no concept of a Virtual DOM here.
So how does Svelte achieve reactivity?
What Can a Compiler Do?
When declaring let a = '', the compiler knows that a has been declared. So when you do something like a = a + 1, the compiler transforms it behind the scenes into something like $$invalidate('a', a + 1), notifying Svelte that this value has changed. Svelte then marks this variable as dirty and schedules an update to re-render the parts of the DOM that use a.
If a isn’t actually used in the markup (inside a tag), Svelte won’t even bother compiling it into the reactive update code. For example:
<script>
let name = 'world';
function handleClick() {
name += '?'
}
</script>
<h1 on:click={handleClick}>Hello </h1>
function create_fragment(ctx) {
//
}
function instance($$self) {
let name = "world";
function handleClick() {
name += "?"; // << 注意這行,因為在 tag 當中沒有用到,所以沒有用 $$invalidate 取代
}
return [handleClick];
}
// ...
If it is used in a tag:
<script>
let name = 'world';
function handleClick() {
name += '?'
}
</script>
// 原本的 world 用變數 name 取代
<h1 on:click={handleClick}>Hello {name}</h1>
function create_fragment(ctx) {
// ...
}
function instance($$self, $$props, $$invalidate) {
let name = "world";
function handleClick() {
$$invalidate(0, name += "?"); // << 用 $$invalidate 取代
}
return [name, handleClick];
}
class App extends SvelteComponent {
constructor(options) {
super();
init(this, options, instance, create_fragment, safe_not_equal, {});
}
}
export default App;
This way, unnecessary updates are avoided, which is one of the key benefits of introducing a compilation step. As mentioned in the talk:
The article points out that through the power of compilers, not only can we effectively minimize runtime code, but we can also break free from the constraints of the host language—empowering the compiler to do more work, much like in statically typed languages.
In addition, the template syntax strikes the right balance. For example:
<ul>
{#each Object.values(list) as item}
<li>{item.name}</li>
{/each}
{#if condition}
<p>true!</p>
{/if}
<button on:click={handleClick}>click me</button>
</ul>
You can refer to the official documentation for details. What I want to convey here is that this syntax is remarkably straightforward; any experienced developer can pick it up in no time. And because it compiles, it can also perform syntax validation and static checks along the way.
Hooked from the First Try
Recently, I built a web page that organizes fish data from Animal Crossing for quick lookup. Its features include:
- Search (pure client-side)
- Table sorting
- Table rendering based on a config
And that’s it.
(The search behavior is a bit quirky; I’ll fix it when I have time XD)
This was a small project where writing it in pure JavaScript would take too much time, but using React felt like overkill—so I took the plunge with Svelte. The results delighted me: the bundled JavaScript was only 8.2 KB (gzipped), loading in just 160ms on a 3G network.
Is It Magic?
At 17:11 in Rethinking reactivity, the author also brought up this viewpoint: Evan You (creator of Vue) remarked that this isn’t really JavaScript, but rather “SvelteScript.”
That would make it technically SvelteScript, right?
— Evan You (@youyuxi) October 30, 2018
In reality, frameworks are all performing magic to some degree (abstracting away complex details). React turns complex update mechanisms into magic, Vue packages template syntax into magic—they’re all doing magic tricks. It’s just that React and Vue do it at runtime, while Svelte delegates the job to the compiler.
Well-Designed Store and Context Mechanisms
Svelte has a built-in store ready to use. Because it’s built-in, there’s no need to engage in endless flame wars over redux-xxx.
Interaction
Svelte has even more delightful syntax and features, such as built-in transitions and spring / fly / flip mechanics, paired with directives for developers. When you want to add interactions (micro-animations), there’s no need to agonize over which library to pick—just import it straight from Svelte. In practice, it 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 feels remarkably intuitive to write. Svelte’s internal design gives developers more direct control over the DOM.
Then there’s animation interruption, which is usually the most annoying part of doing animations. If a user interacts midway through an animation, without special handling, it usually waits for the transition to finish before starting over, significantly hurting the user experience.
In Svelte, transitions are interruptible out of the box. You can check out the transition tutorial chapter for details: clicking the checkbox triggers a fade-in / fade-out animation, and no matter how fast you toggle it, the transition seamlessly reverses from its current state.
Notice that you can directly apply transitions with {#if visible}, unlike React where you must keep the component mounted to maintain the animation.
Note that the transition is reversible — if you toggle the checkbox while the transition is ongoing, it transitions from the current point, rather than the beginning or the end.
If you were to do this in React, you’d likely need helper libraries like react-transition-group or react-spring to make it manageable. Svelte bakes it right into the library without requiring extra packages, integrating directly into the syntax to feel even more intuitive.
Conclusion
Having said all this, I’m not trying to disparage any framework or put Svelte on an untouchable pedestal. React undeniably reshaped the history of front-end and web development, and I genuinely love the concept of React Hooks. It’s just that Svelte’s philosophy and results are so compelling that they make me want to dig deeper into it.
Furthermore, regarding concerns about bundle size and performance, I believe that one day hardware and network speeds will eventually catch up, making the difference less pronounced. Techniques like code-splitting and dynamic imports can already effectively minimize initial bundle sizes.
In fact, paired with a powerhouse compiler like Babel, we can leverage JavaScript with Babel’s assistance to offload as much heavy lifting from the runtime as possible. With advances in low-level APIs like WebAssembly and ArrayBuffer, web development may well be entering a whole new chapter. In this day and age, beyond front-end development, we might even need to learn how to build our own simple compilers.
Related Posts
- Things to Keep in Mind When Using Images in Frontend Development Expanding on Jake Archibald's article, this post organizes how modern responsive images should be written: why width/height are still necessary, when to use CSS aspect-ratio, how to choose between AVIF and WebP, and using picture/source/srcset for art direction on mobile devices.
- CSS field-sizing — Auto-resize Form Elements with a Single Line of CSS Previously, auto-resizing a textarea required listening to scrollHeight in JavaScript. With CSS field-sizing: content, a single line replaces it all, supporting textarea, input, and select. This article covers the pain points of older approaches and how to use field-sizing.
- Make Your Link Underlines Look Better: text-underline-offset By default, underlines sit very close to the text. Some designers dislike this look, and personally, I don't think it looks great either.
- Why the Web Shouldn't Strive for Pixel Perfection You should only focus on pixel perfection when it truly matters; otherwise, it often results in a lose-lose situation.