DEV Community

Jonathan Gamble
Jonathan Gamble

Posted on

3

Sharing Custom Rune Classes with SvelteKit

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

Sentry image

Hands-on debugging session: instrument, monitor, and fix

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

RSVP here →

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs