Sharing Custom Rune Classes with SvelteKit

Jonathan Gamble - Feb 26 - - Dev Community

Following my last article on Svelte 5 Runes, there are cases when you need to add custom functionality to a Rune, and share it. This method is pretty simple if you have been following this thread.

Implementation

You can share classes in Svelte, but safely sharing them in SvelteKit, requires a bit more code...

Create Your Class

class MyCounter {

    current = $state(0);

    constructor(value: number) {
        this.current = value;
    }

    increment() {
        this.current += 1;
    }

    decrement() {
        this.current -= 1;
    }
}
Enter fullscreen mode Exit fullscreen mode

This could be any class you want to share. This class happens to have a reactive $state() value, and shows an example of adding custom functionality like increment(). This method works best when you have complicated classes you want to share.

Create Your Custom Sharable Class

// add SSR protection with Context
import { getContext, hasContext, setContext } from "svelte";

export class _Counter {

    readonly #key: symbol;

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

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

    get(): MyCounter {
        return getContext(this.#key);
    }

    init(initialNumber: number): MyCounter {
        // initialize any class
        const _value = new MyCounter(initialNumber);
        return setContext(this.#key, _value);
    }
}
Enter fullscreen mode Exit fullscreen mode

Export the Class

export const useCustomCounter = new _Counter('counter');
Enter fullscreen mode Exit fullscreen mode

Usage

The usage would be the same as the Rune class from before.

// initialize value
const customCounter = useCustomCounter.init(1);
...
<h1>Hello from Parent Custom: {customCounter.current}</h1>
Enter fullscreen mode Exit fullscreen mode

And use it in children with your custom methods:

const customCounter = useCustomCounter.get();

<button type="button" onclick={() => customCounter.increment()}>
Increment Custom From Child
</button>
Enter fullscreen mode Exit fullscreen mode

Advanced Shared Class

If you know you're always going to be sharing a class, then you can make a generic SharedClass class to simplify things even more.

import { getContext, hasContext, setContext } from "svelte";

type Constructor<T, Args extends unknown[]> = new (...args: Args) => T;


export class SharedClass<T, Args extends unknown[]> {

    readonly #key: symbol;
    #class: Constructor<T, Args>;

    constructor(name: string, className: Constructor<T, Args>) {
        this.#key = Symbol(name);
        this.#class = className;
    }

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

    get(): T {
        return getContext(this.#key);
    }

    init(...args: Args): T {
        const _value = new this.#class(...args);
        return setContext(this.#key, _value);
    }
}
Enter fullscreen mode Exit fullscreen mode

And use it like so:

import { SharedClass } from "./shared-class.svelte";


class MyCounter {

    current = $state(0);

    constructor(value: number) {
        this.current = value;
    }

    increment() {
        this.current += 1;
    }

    decrement() {
        this.current -= 1;
    }
}

export const useCustomCounter = new SharedClass('counter', MyCounter);
Enter fullscreen mode Exit fullscreen mode

That's it!

Updated Code

J

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .