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>
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 />
rune.js
let _rune = $state(0)
export const count = {
get value() {
return _rune
},
set value(newVal) {
_rune = newVal
}
}
child.svelte
<script>
import { count } from './rune.js'
count.value++
</script>
<h1>Hello from Child: {count.value}</h1>
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 />
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
}
child.svelte
<script>
import { rune } from './rune.js'
let count = rune()
count.value++
</script>
<h1>Hello from Child: {count.value}</h1>
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