Svelte 5 (Oktober 2024) ist die größte Version seit Jahren. Das Reactivity-Modell ist neu (Runes), Slots wurden durch Snippets ersetzt, Events haben eine neue Syntax und Komponenten sind keine Klassen mehr, sondern Funktionen. Bestehender Svelte-4-Code läuft weiter, aber neuer Code sollte den modernen Stil nutzen. Dieser Artikel zeigt die wichtigsten Änderungen mit Vorher/Nachher-Beispielen und stellt das automatische Migrations-Tool vor.
Überblick: Was hat sich geändert?
| Bereich | Svelte 4 | Svelte 5 |
|---|---|---|
| Reaktive Variable | let count = 0; | let count = $state(0); |
| Abgeleiteter Wert | $: doubled = count * 2; | let doubled = $derived(count * 2); |
| Side Effect | $: console.log(count); | $effect(() => { console.log(count); }); |
| Props | export let name; | let { name } = $props(); |
| Default-Wert | export let name = 'Anna'; | let { name = 'Anna' } = $props(); |
| Event | on:click={handler} | onclick={handler} |
| Component-Event | createEventDispatcher | Callback-Prop |
| Slot | <slot />, <slot name> | Snippet ({#snippet}, {@render}) |
| Mount | new Component(...) | mount(Component, ...) |
| Dynamic Component | <svelte:component this={…}> | <{Component} /> direkt |
Bestehender Code läuft im Legacy-Modus weiter — Svelte 5 erkennt am Code, ob ein Modul noch in Svelte-4-Konventionen geschrieben ist.
Reaktivität: Runes statt impliziter Reaktivität
In Svelte 4 war jede let-Variable im Top-Level einer Komponente automatisch reaktiv. In Svelte 5 markiert man das explizit mit $state(...).
<script>
let count = 0;
$: doubled = count * 2;
$: if (count > 5) console.log('groß!');
</script>
<button on:click={() => count++}>
{count} (doppelt: {doubled})
</button><script>
let count = $state(0);
let doubled = $derived(count * 2);
$effect(() => {
if (count > 5) console.log('groß!');
});
</script>
<button onclick={() => count++}>
{count} (doppelt: {doubled})
</button>Vorteile der Runes:
- Explizit. Auf einen Blick sichtbar, was reaktiv ist.
- Außerhalb von Komponenten nutzbar. In
.svelte.jsoder.svelte.ts-Dateien lassen sich eigene reaktive Helfer schreiben. - Klar abgegrenzt zwischen Ableitung (
$derived) und Side Effect ($effect). Beides war in Svelte 4 derselbe$:-Block.
Props mit $props()
Statt einzelner export let-Statements gibt es eine destrukturierte Variante:
<script>
export let name;
export let age = 30;
</script><script>
let { name, age = 30 } = $props();
</script>Mit Rest-Operator funktionieren auch unbekannte Props:
<script>
let { class: cssClass, ...rest } = $props();
</script>
<button class={cssClass} {...rest}>
...
</button>Events: on:click -> onclick
In Svelte 5 sind DOM-Event-Handler ganz normale Attribute — keine eigene Syntax mehr:
<button on:click={handle} on:mouseenter={hover}>
Klick
</button><button onclick={handle} onmouseenter={hover}>
Klick
</button>Event-Modifier wie on:click|preventDefault|once gibt es nicht mehr. Stattdessen schreibt man die Logik im Handler selbst:
<!-- Svelte 4 -->
<form on:submit|preventDefault={save}>...</form>
<!-- Svelte 5 -->
<form onsubmit={(e) => { e.preventDefault(); save(); }}>...</form>Component-Events als Callback-Props
createEventDispatcher ist Geschichte. Custom Component Events werden als Callback-Props modelliert:
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
</script>
<button on:click={() => dispatch('save', { value: 42 })}>
Speichern
</button><script>
let { onSave } = $props();
</script>
<button onclick={() => onSave({ value: 42 })}>
Speichern
</button><SaveButton onSave={(detail) => console.log(detail)} />Dieser Wechsel macht Component-APIs typisierbar und einheitlich mit DOM-Events.
Slots -> Snippets
Slots sind durch das mächtigere Snippet-System abgelöst. Inhalte zwischen Komponenten-Tags werden zur Snippet-Prop children:
<div class="card">
<slot />
</div><script>
let { children } = $props();
</script>
<div class="card">
{@render children()}
</div>Named Slots werden zu eigenen Snippet-Props:
<article>
<slot name="header" />
<slot />
<slot name="footer" />
</article><script>
let { header, footer, children } = $props();
</script>
<article>
{@render header?.()}
{@render children()}
{@render footer?.()}
</article><Article>
{#snippet header()}
<h2>Titel</h2>
{/snippet}
<p>Hauptinhalt — landet automatisch in children.</p>
{#snippet footer()}
<small>Quelle</small>
{/snippet}
</Article>Snippets können — anders als Slots — Argumente annehmen. Damit lassen sich Slots, die früher mit let:variable exportiert wurden, sauber typisieren.
Komponenten als Funktionen
In Svelte 4 waren Komponenten Klassen, die mit new instanziiert wurden:
import App from './App.svelte';
const app = new App({
target: document.getElementById('app'),
props: { name: 'Welt' },
});In Svelte 5 sind Komponenten Funktionen — gemountet wird mit mount(...):
import { mount } from 'svelte';
import App from './App.svelte';
const app = mount(App, {
target: document.getElementById('app'),
props: { name: 'Welt' },
});Auch bind:this auf Komponenten gibt heute keinen Klassen-Instanz-Wert mehr zurück, sondern ein Objekt mit den exportierten Werten/Funktionen.
Dynamische Komponenten
<svelte:component this={...} /> wird seltener gebraucht — eine Komponenten-Variable kann direkt als Tag-Name benutzt werden:
<script>
import IconA from './IconA.svelte';
import IconB from './IconB.svelte';
let Icon = $state(IconA);
</script>
<Icon />
<button onclick={() => Icon = IconB}>Tauschen</button>Automatische Migration
Das offizielle CLI-Tool sv enthält einen Migrationsbefehl, der einen Großteil des Codes automatisch umstellt:
npx sv migrate svelte-5Was das Tool typischerweise erledigt:
let⇒$state(mit Heuristik, was tatsächlich reaktiv ist)$:⇒$derivedoder$effectexport let⇒$props()on:event⇒onevent
Manuell nachzuziehen sind meist:
createEventDispatcher⇒ Callback-Props- Komplexe
beforeUpdate/afterUpdate-Logik ⇒$effect.pre/$effect - Slots mit Pattern, die sich nicht 1:1 abbilden lassen
Tipp: Migrations-Diffs klein halten — Datei für Datei migrieren, jeden Schritt committen, Tests dazwischen ausführen.
Was bleibt gleich?
.svelte-Dateiformat mit<script>, Markup und<style>.- Stores (
writable,readable,derived) funktionieren weiter. - Lifecycle-Funktionen
onMountundonDestroyexistieren weiter. - Transitions, Actions, Animations sind im Wesentlichen unverändert.
- SvelteKit läuft mit Svelte 5; ältere Projekte können schrittweise migrieren.