David Hockley

Runes: The Answer To Svelte’s Magic Problem

What is the problem?

Svelte is a great framework. It uses its compiler to augment vanilla JS syntax with reactivity and state.

But that compiler magic is also a problem. Why? We’ll dive into that in a second.

Because to solve that problem, the Svelte team is leaning into the magic, not shying away from it, with a new feature coming in Svelte 5: Runes.

Runes use the function syntax to provide more expressive reactive magic.

What are Runes? Are they making Svelte more magical? Or is Svelte becoming un-svelte-like? Do Runes make Svelte more like… well, the framework which must not be named?

What does this mean for the future of Svelte?

We’ll look into all that and more. To understand all that, we first need to understand the problem Runes are trying to solve.

What is Svelte’s magical problem?

Svelte today uses let, =, the export keyword and the $: label to manage state. But that poses a problem. It creates a mental shortcut (or “heuristic”) where you expect this syntax to work a certain way.

As long as you stay within the context where they work, everything is peachy. But that context is limited. This syntax does not work the same way in JavaScript files.

When you’re outside that context, the mental shortcut fails, which can cause confusion.

So (for example), if we wrap the reactive let of a Svelte component into a function, it no longer works as expected.

So what is the solution?

First, let’s look at how Svelte implements reactivity.

Svelte Reactivity

A good way of understanding that is by contrasting it with how React works.

React keeps a copy of the component tree state in memory. It is what’s known as the Virtual DOM. When the state changes, React compares the nodes and then re-renders any nodes affected by a change. It’s simple to understand, but it is also a brute-force approach.

Svelte uses the compiler to analyse the code. It detects which variables express state. It determines the dependency graph that links them. I’ve explained how that works in another video.

When a variable changes somewhere, the application knows exactly which part of the interface to change. That’s what we mean by fine-grained reactivity: the compiler connects the different bits that change.

This approach has a problem. You can only express reactivity if you send the right signals to the compiler.

For instance, in the following code, the compiler can only see the dependency on the width when it analyses the code at the $:label, and only sees width.

const multiplyByHeight = (width) => width * height;
$: area = multiplyByHeight(width);

Overloading let and = is limited to the top level of Svelte components.

With complex state, the compiler’s implicit magic becomes a constraint. If you need to encapsulate your state’s logic in a JavaScript file, you can’t. If you want to put it in a function, you can’t.

To solve this problem, Svelte is announcing Runes, which will be a part of Svelte 5 (when that comes out). What are Runes?

Svelte’s Magical Solution: Runes

The blog post states: “Runes are symbols that influence the Svelte compiler to achieve the same effect the previous syntax overloads did.” Runes are a way of explicitly plugging into the compiler’s magic. Runes allow you to invoke Svelte’s compiler magic.

And I think that is why they are called runes. They remind me of compile time Macros in languages like C++.

Under the hood, Runes inform the compiler to set up reactivity using “signals”.

What are signals? And how do they work? They are a paradigm now being used in a lot of frontend frameworks, the notable exception being React.

As I mentioned, the compiler sets up a dependency graph between state variables. Signals is a run-time version of that dependency graph. Signals connect the state and the interface not just when the code is compiled, but also when it is run.

Let’s start with a simple example. To define state, we use the $state rune. So instead of

let count = 0;

We can now write:

let count = $state(0);

This makes it a lot clearer that the variable holds state.

It tells the compiler explicitly: This is intended to be a variable that holds state.

Using an explicit symbol instead of overloading JS syntax allows us to use this code more widely. Within a shared library, for example, not just at the top level of the component.

In the same way, and for the same reason, the export let syntax is used to declare props (for example, like this) :

export let width;

… will be replaced by the $props()syntax like this:

const {width} = $props();

Now, as we saw in the multiply by height example, Svelte’s reactivity and dependency tracking is currently compile time only :

const multiplyByHeight = (width) => width * height;
$: area = multiplyByHeight(width);

As mentioned above, the compiler only sees a dependency on width here, not height.

Runes allow run time reactivity where the dependencies are determined when the expression is evaluated. To declare the dependencies, we use the $derived rune. Here “area” will actually update when height changes :

const multiply = (width) => return width * height;
const area = $derived(multiply(width));

And to execute code when there are any changes we use the $effect rune.

$effect(() => {
  console.log(area);
});

Now what does all this mean in practice for Svelte? Is it a step back or a step forwards?

So What does this mean for Svelte

Well.. we’ll have to see in practice. For now, we only have a playground to mess around with. The new syntax makes much of the classic Svelte syntax obsolete. The $: syntax and the top-level let and export will no longer be useful, and will be phased out.

I liked the idea of using the compiler to define the state implicitly.

But I was afraid of the confusion it might cause. It looks like the Svelte team has come to the same conclusion. Although looking through the Svelte discord, not everyone agrees with the change.

But I think this is a step in the right direction. It’s a sign of the framework’s maturity. It brings Svelte closer to being a more widely accepted solution. It enables more complex applications. I’m just not sure the Svelte community will agree with the change.

My bottom line: By allowing you to trigger the magic explicitly, Svelte is giving you greater powers (and of course great responsibilities). Although you could also complain that Runes makes Svelte less magical.

Social
Made by kodaps · All rights reserved.
© 2023