Introducing runes

By admin
Introducing runes

Rethinking ‘rethinking reactivity’

On this page On this page

In

2019
DATE

, Svelte

3
CARDINAL

turned JavaScript into a reactive language. Svelte is a web

UI
ORG

framework that uses a compiler to turn declarative component code like this…

< script > let count =

0
CARDINAL

; function increment () { count +=

1
CARDINAL

; } </ script > < button on : click = {increment}> clicks: {count} </ button >

…into tightly optimized

JavaScript
PRODUCT

that updates the document when state like count changes. Because the compiler can ‘see’ where count is referenced, the generated code is highly efficient, and because we’re hijacking syntax like let and = instead of using cumbersome APIs, you can write less code.

A common piece of feedback we get is ‘I wish I could write all my JavaScript like this’. When you’re used to things inside components magically updating, going back to boring old procedural code feels like going from colour to black-and-white.

Svelte

5
CARDINAL

changes all that with runes, which unlock universal, fine-grained reactivity.

Introducing runes

Before we begin permalink

Even though we’re changing how things work under the hood, Svelte

5
CARDINAL

should be a drop-in replacement for almost everyone. The new features are opt-in — your existing components will continue to work.

We don’t yet have a release date for Svelte 5. What we’re showing you here is a work-in-progress that is likely to change!

What are runes? permalink

rune /ro͞on/ noun A letter or mark used as a mystical or magic symbol.

Runes are symbols that influence the Svelte compiler. Whereas Svelte

today
DATE

uses let , = , the export keyword and the $: label to mean specific things, runes use function syntax to achieve the same things and more.

For example, to declare a piece of reactive state, we can use the $state rune:

<script> let count = 0; let count = $state(0); function increment() { count +=

1
CARDINAL

; } </script> <button on:click={increment}> clicks: {count} </button>

At

first
ORDINAL

glance, this might seem like a step back — perhaps even

un-Svelte
PRODUCT

-like. Isn’t it better if let count is reactive by default?

Well, no. The reality is that as applications grow in complexity, figuring out which values are reactive and which aren’t can get tricky. And the heuristic only works for let declarations at the top level of a component, which can cause confusion. Having code behave

one
CARDINAL

way inside .svelte files and another inside .js can make it hard to refactor code, for example if you need to turn something into a store so that you can use it in multiple places.

Beyond components permalink

With runes, reactivity extends beyond the boundaries of your .svelte files. Suppose we wanted to encapsulate our counter logic in a way that could be reused between components.

Today
DATE

, you would use a custom store in a .js or .ts file:

ts import { writable } from ‘svelte/store’ ; export function createCounter () { const { subscribe , update } = writable ( 0 ); return { subscribe , increment : () => update (( n ) => n + 1 ) }; }

Because this implements the store contract — the returned value has a subscribe method — we can reference the store value by prefixing the store name with $ :

<script> import { createCounter } from ‘./counter.js’; const counter =

createCounter
PERSON

(); let count = 0; function increment() { count +=

1
CARDINAL

; } </script> <button on:click={increment}> clicks: {count} <button on:click={counter.increment}> clicks: {$counter} </button>

This works, but it’s pretty weird! We’ve found that the store API can get rather unwieldy when you start doing more complex things.

With runes, things get much simpler:

import { writable } from ‘svelte/store’; export function createCounter() { const { subscribe, update } =

writable(0
PERSON

); let count = $state(0); return { subscribe, increment: () => update((n) => n + 1) get count() { return count }, increment: () => count +=

1
CARDINAL

}; }

<script> import { createCounter } from ‘./counter.js’; const counter =

createCounter
PERSON

(); </script> <button on:click={counter.increment}> clicks: {$counter} clicks: {counter.count} </button>

Note that we’re using a get property in the returned object, so that counter.count always refers to the current value rather than the value at the time the function was called.

Runtime reactivity permalink


Today
DATE

, Svelte uses compile-time reactivity. This means that if you have some code that uses the $: label to re-run automatically when dependencies change, those dependencies are determined when Svelte compiles your component:

< script > export let width; export let height; // the compiler knows it should recalculate `area` // when either `width` or `height` change… $ : area = width * height; // …and that it should log the value of `area` // when _it_ changes $ : console .log (area); </ script >

This works well… until it doesn’t. Suppose we refactored the code above:

ts const multiplyByHeight = ( width ) => width * height ; Parameter ‘width’ implicitly has an ‘any’ type.

Cannot
PERSON

find name ‘height’.

7006
CARDINAL

2304 Parameter ‘width’ implicitly has an ‘any’ type.

Cannot
PERSON

find name ‘height’. $ : area = multiplyByHeight ( width ); Cannot find name ‘area’.

Cannot
PERSON

find name ‘width’.

2304
CARDINAL


2304
CARDINAL

Cannot find name ‘area’.

Cannot
PERSON

find name ‘width’.

Because the $: area = … declaration can only ‘see’ width , it won’t be recalculated when height changes. As a result, code is hard to refactor, and understanding the intricacies of when Svelte chooses to update which values can become rather tricky beyond a certain level of complexity.

Svelte

5
CARDINAL

introduces the $derived and $effect runes, which instead determine the dependencies of their expressions when they are evaluated:

< script > let { width , height } = $ props (); // instead of `export let` const area = $ derived (width * height); $ effect (() => { console .log (area); }); </ script >

As with $state , $derived and $effect can also be used in your .js and .ts files.


Signal
ORG

boost permalink

Like every other framework, we’ve come to the realisation that

Knockout
PERSON

was right all along.

Svelte

5
CARDINAL

‘s reactivity is powered by signals, which are essentially what

Knockout
PERSON

was doing in

2010
DATE

. More recently, signals have been popularised by

Solid
ORG

and adopted by a multitude of other frameworks.

We’re doing things a bit differently though. In Svelte

5
CARDINAL

, signals are an under-the-hood implementation detail rather than something you interact with directly. As such, we don’t have the same API design constraints, and can maximise both efficiency and ergonomics. For example, we avoid the type narrowing issues that arise when values are accessed by function call, and when compiling in server-side rendering mode we can ditch the signals altogether, since on the server they’re nothing but overhead.

Signals unlock fine-grained reactivity, meaning that (for example) changes to a value inside a large list needn’t invalidate all the other members of the list. As such, Svelte

5
CARDINAL

is ridonkulously fast.

Simpler times ahead permalink

Runes are an additive feature, but they make a whole bunch of existing concepts obsolete:

the difference between let at the top level of a component and everywhere else

at the top level of a component and everywhere else export let

$: , with all its attendant quirks

, with all its attendant quirks different behaviour between <script> and <script context="module">

and $

$
MONEY

props and $$restProps

and lifecycle functions (things like afterUpdate can just be $effect functions)

can just be functions) the store

API
ORG

and $ store prefix (while stores are no longer necessary, they are not being deprecated)

For those of you who already use Svelte, it’s new stuff to learn, albeit hopefully stuff that makes your Svelte apps easier to build and maintain. But newcomers won’t need to learn all those things — it’ll just be in a section of the

docs
PERSON

titled ‘old stuff’.

This is just the beginning though. We have a long list of ideas for subsequent releases that will make Svelte simpler and more capable.

Try it! permalink

You can’t use Svelte

5
CARDINAL

in production yet. We’re in the thick of it at the moment and can’t tell you when it’ll be ready to use in your apps.