Svelte 5 is Here!
Svelte 5 was finally released after a long time in development. Fortunately, we've been able to test it for some time, and now it has a stable release.
Let's dig into its features and why this is such a significant change, even though Svelte 4 code is almost 100% compatible.
Svelte syntax everywhere
This is one of my favorite additions. Previously, Svelte syntax was limited to a component element. Refactoring code from a component and moving it to a JavaScript file worked differently. Now you can use the .svelte.js or .svelte.ts extension, which allows you to use the same syntax everywhere. It's important to note that it's a way to express that this is not just JS, and what you write may be compiled to something else, just like .svelte files are not just html even though they look very similar.
Runes
The introduction of runes is one of the most significant changes in Svelte. Many users felt attracted to variables being instantly reactive in previous versions.
`
There was, however, a lot of magic underneath and a considerable amount of work from the compiler to make it behave reactively.
$state
In Svelte 5, a reactive variable has to be explicitly declared using the $state rune, which brings a clear distinction between what is reactive and what isn’t.
`
In the previous code, $state is not an actual function being called; it's a hint for the compiler to do something special with this declaration.
Rune names always start with a dollar sign ($) and do not need to be imported.
However, there’s much more to the changes than just the way we declare reactive variables. Runes bring a lot of new features that make Svelte 5 a great improvement.
In Svelte 5, objects or arrays use Proxies to allow for granular reactivity, meaning that individual properties in an object are reactive, even if they are nested objects or arrays. If you modify a property, it will only update that property and will not trigger an update on the whole object. It also supports triggering updates when methods like array.push are called. In previous versions, an assignment was required to trigger an update:
`
To have a similar behavior of svelte 4 (no deep reactivity), use the $state.raw() rune. This syntax of .* is common for related features of a rune:
$state.raw() will require an assignment to trigger reactivity.
`
Because proxies are used, you may need to extract the underlying state instead of the proxy itself. In that case, $state.snapshot() should be used:
`
$derived
We will use $derived and $derived.by to declare a derived state. The two runes are essentially the same, except one allows us to use a function instead of an expression, allowing for more complex operations to calculate the derived values.
`
$effect
Effects are useful for running something triggered by a change in state.
One of the things you'll note from the $effect rune is that dependencies don't need to be explicit. Those will be picked from reactive values read synchronously in the body of the effect function.
`
Something to bear in mind is that these dependencies are picked each time the effect runs.
The values read during the last run become the dependencies of the effect.
`
Depending on the result of the random method, foo or bar will stop being a dependency of the effect. You should place them outside the condition so they can trigger reruns of the effect.
*Variants* of the effect rune are $effect.pre, which runs before a DOM update, and $effect.tracking(), which checks for the context of the effect (true for an effect inside an effect or in the template).
$props
This rune replaces the export let keywords used in previous versions to define a component's props.
To use the $props syntax, we can consider it to return an object with all the properties a component receives. We can use JavaScript syntax to destructure it or use the rest property if we want to retrieve every property not explicitly destructured..
`
$bindable
If you want to mutate a prop so that the change flows back to the parent, you can use the $bindable prop to make it work in both directions. A parent component can pass a value to a child component, and the child component is able to modify it, returning the data back to the parent component.
`
$inspect
The $inspect rune only works in dev mode and will track dependencies deeply. By default, it will call console.log with the provided argument whenever it detects a change. To change the underlying function, use $inspect.with().with):
`
$host
The host rune gives access to the host element when compiling as a custom element:
`
`
Other changes
Another important change is that component events are just props in Svelte 5, so you can destructure them using the $props() rune:
`
A special children property can be used to project content into a component instead of using slots. Inside the components, use the [@render[(https://svelte.dev/docs/svelte/@render) tag to place them.
`
Optional chaining (?.) prevents from attempting to render it if no children are passed in.
If you need to render different components in different places (named slots before Svelte 5, you can pass them as any other prop, and use @render with them.
Snippets
Snippets allow us to declare markup slices and render them conveniently using the render tag (@render). They can take any number of arguments.
`
Snippets can also be passed as props to other components.
`
`
Conclusion
Some exciting changes to the Svelte syntax were introduced while, at the same time, maximum compatibility efforts were made. This was a massive rewrite with numerous improvements in terms of performance, bundle size, and DX. Besides that, a new CLI has been released, making the whole experience of starting a project or adding features delightful. If you haven't tried Svelte before, it's a great time to try it now....