I’m making an Oscars fan website using the Svelte 5 beta. I figured it’d be the best way to try runes.
I was skeptical about runes, but on balance, they’re an improvement.
The good
Explicitness
Runes give variables clear roles. Updating a $state()
variable updates the component. $derived()
variables also update the component, but obviously you don’t manage those.
Simplicity
Runes simplify Svelte syntax.
$: doubled = someVal * 2
➡️$derived(someVal * 2)
export let someInput;
➡️let { someInput } = $props();
let someComponentState = 0;
➡️let someComponentState = $state(0)
Experienced developers are used to export let
and $
, but spare a thought for future Svelte newbies. $props
is more obvious.
Unified change detection
Any complex frontend has a structure and a superstructure. The structure is UI components, what the user sees.
The superstructure is everything else. It’s:
- The class that sends API requests
- The code sharing state among multiple components
- The class that caches app data to
localStorage
…and so on.
As an app grows more complex, it accumulates superstructure. However, in Svelte 4, that superstructure has a different change detection system than the components upon which it rests. let
variables are normal JavaScript, and updating them does not update your components. Stores exist to solve this problem.
Runes bypass the problem by unifying change detection. There’re only $state
variables in the structure and superstructure.
<script>
// via https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_stores
import { alert } from '../stores.js';
import { onDestroy } from 'svelte';
// alertContent is redundant! Runes let me use alert directly
let alertContent = '';
const unsubscribe = alert.subscribe(value => (alertContent = value));
onDestroy(unsubscribe);
</script>
{#if alertContent}
<div on:click={() => (alertContent = '')}>
<p>{alertContent}</p>
</div>
{/if}
The bad
$derived
callbacks
No Update February 2024: This feature has been added.
To create derived state in Svelte 5:
<script>
let count = $state(0);
let doubled = $derived(count * 2);
</script>
The bit inside $derived()
, count * 2
, is a JavaScript expression. Expressions evaluate to a value.
This is fine for short pieces of logic, but painful for calculating derived state with multiple steps. There’s no way to split it up without using more than one $derived
variable. I would love to be wrong about this. Please let me know if I am.
One workaround to use an immediately invoked function expression (IIFE). Here’s one I wrote for my Oscars fan site:
let knownForCredits = $derived(
(() => {
let knownForJobTitle = 'Actor';
switch (data.details.known_for_department) {
case 'Acting':
knownForJobTitle = 'Actor';
break;
case 'Directing':
knownForJobTitle = 'Director';
break;
case 'Production':
knownForJobTitle = 'Producer';
break;
case 'Writing':
knownForJobTitle = 'Writer';
break;
}
let jobs = data.credits.crew.filter(
credit => credit.job === knownForJobTitle
);
if (knownForJobTitle === 'Actor') {
jobs = jobs.concat(data.credits.cast);
}
return jobs
.filter(credit => new Date(credit.release_date) < now)
.sort((a, b) => {
return b.vote_count - a.vote_count;
})
.filter(credit => credit.vote_average > 7)
.slice(0, 5);
})()
);
It would be nice to be able to provide $derived()
an anonymous callback function. Then I could split the calculations multiple lines without (() => { ... })()
.
I got this idea from Angular. Their version of $derived()
, computed()
, accepts a callback:
// via angular.io
const showCount = signal(false);
const count = signal(0);
const conditionalCount = computed(() => {
if (showCount()) {
return `The count is ${count()}.`;
} else {
return 'Nothing to see here!';
}
});
This is more flexible and less verbose than IIFEs. I always misplace a parenthesis writing those.
Edit January 2024: Please visit the GitHub issue discussing this issue if you wish to weigh in.
$props
TypeScript syntax for I wish the TypeScript syntax for passing props into a component were shorter.
let { details, credits } = $props<{
details: TMDBPersonDetails;
credits: TMDBPersonCreditsResponse;
}>();
Writing details
and credits
twice feels duplicative. A huge part of Svelte’s appeal to me is writing less code, so I hate to see stuff like this sneak in.
Conclusion
Overall, I think runes are a good idea. They are simpler, more explicit, and reduce unnatural framework syntax. I just wish they didn’t make me write IIFEs.