Svelte Headless CMS

Svelte 5 Runes explained (for Svelte 4 developers)

Blog Getting Started

Svelte 5 introduces a new feature called "Runes" that changes how Svelte developers manage reactivity. Runes are essentially a way to explicitely say what is reactive, rather than "everything" potentially being reactive in Svelte 4, which leaves it to the compiler to decide.

As Svelte 4 projects grow the compiler doesn't always get it right and things can slow down. In 5, developers explicitly tag variables as changeable - and causing change instead.

Key Features of Runes

  1. Explicit Dependencies: Runes allow developers to clearly define which variables depend on others, making the flow of data and updates more explicit.
  2. Fine-Grained Reactivity: By using Runes, you can create very specific reactive statements, which can lead to more optimized and efficient updates.
  3. Declarative Syntax: Runes use a syntax that is similar to JavaScript, but with special characters and keywords that indicate reactive behavior.

The $state Rune

Wrap the function $state() around your declaration value e.g.:

<script>
    // Wrap your "0" default value with the $state() function call
    let count = $state(0);
</script>

<button on:click={() => count++}>
    clicks: {count}
</button>

In this example, count can be punched out into the HTML in the normal Svelte way.

Annoyingly yes, this is more verbose that Svelte 4 but it scales better since the compiler doesn't have to try to figure out what you want to be reactive. The real bonus however is arrays and objects are now deeply reactive - no more settings a variable to itself! e.g. let numbers = $state([1, 2, 3]); is all you need.

The $props Rune

This replaces export let ... in sub-components:

<script>
  // Old
  export let title;
  export let amount = 0;
  // New
  let { title, amount = 0 } = $props();
</script>

$bindable

Annoyingly, to use bind and make the values updateable by the component you have to be explicit inside the component e.g.:

<script>
  let { title, amount = $bindable(0) } = $props();
</script>

<!-- Passing syntax is the same though -->
<SubComponent title="Hi there" amount=bind:{amount} />

The $derived Rune

This replaces $: for example:

<script>
    let count = 0;
    let doubleCount = $derived(count * 2);

    function increment() {
        count += 1;
    }
</script>

<p>{doubleCount}</p>

$derived.by()

To do something more sophisticated than a single calculation, use $derived.by(() => {}) to effectively assign a function to recalculate something's value e.g.:

<script>
    let numbers = $state([1, 2, 3]);
    let total = $derived.by(() => {
        let total = 0;
        for (const n of numbers) {
            total += n;
        }
        return total;
    });
</script>

Comparison with Previous Versions

In previous versions of Svelte, reactivity was often implied by assignments or by using $: labels for reactive statements. For example, the above example without Runes would use:

<script>
    let count = 0;
    $: doubleCount = count * 2;

    function increment() {
        count += 1;
    }
</script>

<p>{doubleCount}</p>

The $effect Rune

Creating a derived variable is great, but sometimes you want more control - maybe set a few different variables or call some other function.

The $effect directive let you run your own function when Svelte "ticks" (any time a reactive variable's value is changed). It is recommended to only be used if nothing else works because it fires for ANY change and cannot be targetted to specific variables. Here’s an example:

let count = $state(0);

$effect(() => {
  // This runs:
  // 1. on mount
  // 2. whenever any of the state() or derived() or props() variables mentioned internally change
    console.log("Count changed to " + count);
});

Comparison with Previous Versions

In previous versions of Svelte, $: if () statements could be used to cause effects.

<script>
    let count = 0;

    let oldCount = 0;

    $: if (count > oldCount) { console.log(`Count changed to ${count}`); oldCount = count; }
</script>

Nicer, right? This is super-useful for console logging whilst debugging, but most functionality can be achieved by derived or derived.by

Snippets

The last Svelte 5 change to note (other than runes) is Snippets - functions that return HTML (or more svelte)

{#snippet someFunctionThatReturnsSvelteHTML(img)}
    <img src={img.src} alt={img.caption} width={img.width} height={img.height} />
{/snippet}

{#each images as img}
    {#if img.href}
        <a href={img.href}>
            {@render someFunctionThatReturnsSvelteHTML(img)}
        </a>
    {:else}
        {@render someFunctionThatReturnsSvelteHTML(img)}
    {/if}
{/each}

This achieves the same effect as creating a sub-component, but is just a little quicker and keeps the number of files down.


Related docs


Download the code for this blog from GitHub at https://github.com/webuildsociety/svelte-headless