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
Top comments (8)
TypeScript in REPL will be awesome!
Even in transpile only mode
I wonder if things have changed since your last post, but when I try to use this method, I can only use
rune()
directly inside a component. I can't, for example, make a$lib/counter.svelte.ts
with this code:And then import those into a component. As soon as I do that I get the error that
hasContext
is being called outside of component initialization. Which kinda defeats the purpose of this whole abstraction for me. Did it work with an earlier alpha version of Svelte 5?See the next post in this series for TS and updates. For a full example - code.build/p/svelte-5-todo-app-wit...
Overriding the valueOf() function in your rune code will allow you to access the value from within the svelte brackets without needing to type out the property name.
Yes, but you can't set the new value to something like you can
$state
, otherwise it would reassign the variable.True, a rather unfortunate downside.
Why not make pure javascript reactive? No special syntax, no weird data manipulation, after all, if runes means that every JS file gets transpiled again to yet another intermediate js, then making everything in JS reactive just makes sense. Why bother keeping it partially problematic where you're in two states of being and everything is sort of reactive. This way, you observe everything, if it changes, it updates UI.
Please update now that release candidate is here.