Sharing Runes in Svelte 5 with the Rune Class

I generally don't use classes in any TypeScript I write. I believe functions make things simpler, and you no longer get Tree Shaking with methods in a class. However, using classes with Runes can actually be faster, because they don't have to compile $state variables with get and set or with value... they just work. This is what Rich Harris recommends in many cases. Sharable Rune We need a sharable Rune class, and of course we must use Context. // rune.svelte.ts import { getContext, hasContext, setContext } from "svelte"; type RCurrent = { current: TValue }; export class Rune { readonly #key: symbol; constructor(name: string) { this.#key = Symbol(name); } exists(): boolean { return hasContext(this.#key); } get(): RCurrent { return getContext(this.#key); } init(value: TRune): RCurrent { const _value = $state({ current: value }); return setContext(this.#key, _value); } update(getter: () => TRune): void { const context = this.get(); $effect(() => { context.current = getter(); }); } } And we can export custom runes where we want. // counter.svelte.ts import { Rune } from "./rune.svelte"; export const counter = new Rune('counter'); This is how most people share $state variables anyway, however, this is safe for the server (see my previous posts in this thread). We must name it like any other context. Initialize We must initialize our $state just like anywhere else, only once, in the parent component. import { counter } from '$lib/counter.svelte'; const count = counter.init(0); Read Anywhere We can use it safely in a child component and read the current method. import { counter } from '$lib/counter.svelte'; const count = counter.get(); Hello from Child: {count.current} count.current++}> Increment From Child Reactively Update Anywhere To update a shared $state, we must pass it through a function that can get called. import { counter } from '$lib/counter.svelte'; let value = $state(8); counter.update(() => value); This allows the update to be reactive. We can pass any signal or store variable, and it will update just like $derived. Update Normally Of course, you can update normally as well. import { counter } from '$lib/counter.svelte'; const count = counter.get(); count.current = 9; Repo Demo I hope you find value, J

Jan 16, 2025 - 03:59
Sharing Runes in Svelte 5 with the Rune Class

I generally don't use classes in any TypeScript I write. I believe functions make things simpler, and you no longer get Tree Shaking with methods in a class.

However, using classes with Runes can actually be faster, because they don't have to compile $state variables with get and set or with value... they just work. This is what Rich Harris recommends in many cases.

Sharable Rune

We need a sharable Rune class, and of course we must use Context.

// rune.svelte.ts
import { getContext, hasContext, setContext } from "svelte";


type RCurrent<TValue> = { current: TValue };

export class Rune<TRune> {

    readonly #key: symbol;

    constructor(name: string) {
        this.#key = Symbol(name);
    }

    exists(): boolean {
        return hasContext(this.#key);
    }

    get(): RCurrent<TRune> {
        return getContext(this.#key);
    }

    init(value: TRune): RCurrent<TRune> {
        const _value = $state({ current: value });
        return setContext(this.#key, _value);
    }

    update(getter: () => TRune): void {
        const context = this.get();
        $effect(() => {
            context.current = getter();
        });
    }
}

And we can export custom runes where we want.

// counter.svelte.ts
import { Rune } from "./rune.svelte";

export const counter = new Rune<number>('counter');

This is how most people share $state variables anyway, however, this is safe for the server (see my previous posts in this thread). We must name it like any other context.

Initialize

We must initialize our $state just like anywhere else, only once, in the parent component.

<script lang="ts">
    import { counter } from '$lib/counter.svelte';

    const count = counter.init(0);
</script>

Read Anywhere

We can use it safely in a child component and read the current method.

<script lang="ts">
    import { counter } from '$lib/counter.svelte';

    const count = counter.get();
</script>

<h1>Hello from Child: {count.current}</h1>

<button type="button" onclick={() => count.current++}>
  Increment From Child
</button>

Reactively Update Anywhere

To update a shared $state, we must pass it through a function that can get called.

<script lang="ts">
    import { counter } from '$lib/counter.svelte';

    let value = $state(8);

    counter.update(() => value);
</script>

This allows the update to be reactive. We can pass any signal or store variable, and it will update just like $derived.

Update Normally

Of course, you can update normally as well.

<script lang="ts">
    import { counter } from '$lib/counter.svelte';

    const count = counter.get();

    count.current = 9;
</script>

I hope you find value,

J