mirror of
https://github.com/bretzel-app/crumbs.git
synced 2026-10-12 12:06:57 +02:00
Install project-level skills via skills.sh CLI: - svelte5-best-practices (runes, snippets, events, migration) - sveltekit-structure (routing, layouts, error boundaries) - sveltekit-data-flow (load functions, form actions, serialization) - typescript-best-practices (type-first development, discriminated unions) - drizzle-migrations (SQLite schema, query patterns) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
6.6 KiB
6.6 KiB
Svelte 4 to Svelte 5 Migration Reference
Table of Contents
Migrating Reactive Statements
Svelte 5 replaces $: with $derived (for values) and $effect (for side effects).
Computed Values: $: to $derived
Svelte 4:
<script>
let count = 0;
$: doubled = count * 2;
$: quadrupled = doubled * 2;
</script>
Svelte 5:
<script>
let count = $state(0);
let doubled = $derived(count * 2);
let quadrupled = $derived(doubled * 2);
</script>
Complex Computed Values: $derived.by
Svelte 4:
<script>
let items = [];
let filter = 'all';
$: filteredItems = {
if (filter === 'all') return items;
if (filter === 'active') return items.filter(i => !i.done);
return items.filter(i => i.done);
};
</script>
Svelte 5:
<script>
let items = $state([]);
let filter = $state('all');
let filteredItems = $derived.by(() => {
if (filter === 'all') return items;
if (filter === 'active') return items.filter(i => !i.done);
return items.filter(i => i.done);
});
</script>
Side Effects: $: to $effect
Svelte 4:
<script>
let count = 0;
$: console.log('Count changed:', count);
$: document.title = `Count: ${count}`;
</script>
Svelte 5:
<script>
let count = $state(0);
$effect(() => { console.log('Count changed:', count); });
$effect(() => { document.title = `Count: ${count}`; });
</script>
Conditional Side Effects
Svelte 4:
<script>
let value;
$: if (value > 100) { alert('Value too high!'); }
</script>
Svelte 5:
<script>
let value = $state(0);
$effect(() => {
if (value > 100) { alert('Value too high!'); }
});
</script>
Props Migration
Svelte 4:
<script>
export let name;
export let count = 0;
$: greeting = `Hello, ${name}!`;
</script>
Svelte 5:
<script>
let { name, count = 0 } = $props();
let greeting = $derived(`Hello, ${name}!`);
</script>
Effect with Cleanup
Svelte 4:
<script>
import { onDestroy } from 'svelte';
let count = 0;
let interval;
$: {
clearInterval(interval);
interval = setInterval(() => console.log(count), 1000);
}
onDestroy(() => clearInterval(interval));
</script>
Svelte 5:
<script>
let count = $state(0);
$effect(() => {
const interval = setInterval(() => console.log(count), 1000);
return () => clearInterval(interval); // Cleanup built-in
});
</script>
Migration Cheat Sheet
| Svelte 4 Pattern | Svelte 5 Replacement |
|---|---|
let x = 0 (in component) |
let x = $state(0) |
export let prop |
let { prop } = $props() |
$: derived = expr |
let derived = $derived(expr) |
$: { complex } (value) |
let x = $derived.by(() => { ... }) |
$: console.log(x) |
$effect(() => console.log(x)) |
$: if (x) { ... } |
$effect(() => { if (x) { ... } }) |
$: document.title = x |
$effect(() => { document.title = x }) |
Automated Migration
npx sv migrate svelte-5
Migrating Stores to Runes
Svelte 5 runes can replace most store use cases with simpler, more direct reactive state.
Local Component State
Svelte 4:
<script>
import { writable } from 'svelte/store';
const count = writable(0);
function increment() { count.update(n => n + 1); }
</script>
<button on:click={increment}>Count: {$count}</button>
Svelte 5:
<script>
let count = $state(0);
function increment() { count++; }
</script>
<button onclick={increment}>Count: {count}</button>
Shared State Across Components
Svelte 4 (stores.ts):
import { writable } from 'svelte/store';
export const user = writable(null);
export const theme = writable('light');
Svelte 5 (state.svelte.ts):
export const user = $state<User | null>(null);
export const theme = $state({ current: 'light' as 'light' | 'dark' });
export function setTheme(newTheme: 'light' | 'dark') {
theme.current = newTheme;
}
<script>
import { theme, setTheme } from './state.svelte';
</script>
<p>Theme: {theme.current}</p>
<button onclick={() => setTheme('dark')}>Dark Mode</button>
Derived Store to $derived
Svelte 4:
import { writable, derived } from 'svelte/store';
export const items = writable([]);
export const completedCount = derived(items, $items =>
$items.filter(i => i.done).length
);
Svelte 5:
// state.svelte.ts
export const items = $state<Item[]>([]);
export function getCompletedCount() {
return items.filter(i => i.done).length;
}
<script>
import { items } from './state.svelte';
let completedCount = $derived(items.filter(i => i.done).length);
</script>
Custom Store to Reactive Class
Svelte 4:
function createCounter() {
const { subscribe, set, update } = writable(0);
return {
subscribe,
increment: () => update(n => n + 1),
decrement: () => update(n => n - 1),
reset: () => set(0)
};
}
export const counter = createCounter();
Svelte 5:
// counter.svelte.ts
class Counter {
value = $state(0);
increment() { this.value++; }
decrement() { this.value--; }
reset() { this.value = 0; }
}
export const counter = new Counter();
<script>
import { counter } from './counter.svelte';
</script>
<p>{counter.value}</p>
<button onclick={() => counter.increment()}>+</button>
Async State Pattern
Svelte 5:
// api.svelte.ts
export const state = $state({
data: null as Data | null,
loading: false,
error: null as Error | null
});
export async function fetchData() {
state.loading = true;
state.error = null;
try {
const res = await fetch('/api/data');
state.data = await res.json();
} catch (e) {
state.error = e as Error;
} finally {
state.loading = false;
}
}
When to Still Use Stores
- Library interop - Libraries that expect Svelte stores
- Legacy code - Gradual migration
- Observable patterns - When you need the subscribe API
- Server-side rendering - Stores have built-in SSR handling
Store to Rune Cheat Sheet
| Store Pattern | Rune Replacement |
|---|---|
writable(value) |
$state(value) |
$store (auto-subscribe) |
Direct access to $state value |
store.set(x) |
state = x |
store.update(fn) |
Direct mutation or reassignment |
derived(store, fn) |
$derived(fn()) in component |
readable(value, start) |
$state + $effect for setup |