Create the Perfect Sharable Rune in Svelte

Jonathan Gamble - Sep 25 '23 - - Dev Community

Singleton Server Issue

When Svelte 5 comes out soon, it seems Runes (Signals) will share one of the old problems Stores had: it could be dangerous to share the signal on the server. I want get into the details here, but you can read The Correct Way to Use Stores in SvelteKit.

Basically, you need to use context when handling shared stores in an outside script file. Otherwise your data could be seen by other users on the server, instead of just shared across one user.

Get and Set

However, as Fireship pointed out, there is another pain issue with runes. You have to use get and set, along with a value method, which is different from just using $state.

Let's say I want to do this:

app.svelte

<script>
let count = $state(0)
count++
</script>

<button type="button" on:click={() => count++}>
  Increment
</button>

<p>Count {count}</p>
Enter fullscreen mode Exit fullscreen mode

Demo


No problem, as the count will work as expected. You can't set a value on a variable, as JavaScript will just end up reassigning it. Svelte actually compiles to use methods under the hood (see JS Output tab). Therefore, trying to change how this works after compilation will require an actual method: value. This is exactly how SolidJS Signals, Angular Signals, Qwik Signals, Preact Signals, and Vue Ref work normally; there is a value method.

Rich Harris had a response to Fireship, although I'm not really sure he solved the problem. You still need to create the get and set, and return an object:

app.svelte

<script>
    import { count } from './rune.js'
    import Child from './Child.svelte'

    count.value++

</script>

<button type="button" on:click={() => count.value++}>
    Increment
</button>
<h1>Hello from Parent: {count.value}</h1>
<Child />
Enter fullscreen mode Exit fullscreen mode

rune.js

let _rune = $state(0)

export const count = {
    get value() {
        return _rune
    },
    set value(newVal) {
        _rune = newVal
    }
}
Enter fullscreen mode Exit fullscreen mode

child.svelte

<script>
    import { count } from './rune.js'

    count.value++

</script>

<h1>Hello from Child: {count.value}</h1>
Enter fullscreen mode Exit fullscreen mode

Demo


But, as you probably don't immediately see, this will cause problems with sharing state on the server.

So... I propose a solution to both. Create a reusable component that can be shared safely on the server.

app.svelte

<script>
    import { rune } from './rune.js'
    import Child from './Child.svelte'

    let count = rune(0)

    count.value++

</script>

<button type="button" on:click={() => count.value++}>
    Increment
</button>
<h1>Hello from Parent: {count.value}</h1>
<Child />

Enter fullscreen mode Exit fullscreen mode

rune.js (reusable wrapper)

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

export const rune = (
    startValue, 
    context = 'default'
) => {
    if (hasContext(context)) {
        return getContext(context)
    }
    let _state = $state(startValue)
    const _rune = {
        get value() {
            return _state
        },
        set value(v) {
            _state = v
        }
    }
    setContext(context, _rune)
    return _rune
}
Enter fullscreen mode Exit fullscreen mode

child.svelte

<script>
    import { rune } from './rune.js'

    let count = rune()

    count.value++

</script>

<h1>Hello from Child: {count.value}</h1>
Enter fullscreen mode Exit fullscreen mode

Demo


Now, if Rich Harris put this into the Svelte core code so that you didn't have to use value (just like $state), and it worked as expected compiling to js... that would be great... maybe a $rune().

¯\(ツ)

Notice the second (optional) argument is needed for different runes you want to share (the name of your context). This would allow you to have more than one, and use it as you see fit. This would solve the problem SvelteKit never solved with stores.

TypeScript and Final Thoughts

Svelte Team, if you're reading this, PLEASE PLEASE add a TypeScript version of REPL. This was paintful to write. Even Rich Harris finally mentioned the need for this on the Runes intro video. There are 1,222 questions alone on StackOverflow regarding Typescript with Svelte. This would save developers time, easily sharing correctly typed code. Until then, we can use SvelteLab, although not with Runes... yet.

Runes will be a gamechanger, as everyone agrees... just not quite on the implementation.

J

Currently finishing rebuild of code.build

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