Torna al blog

Svelte 5: Runes e Nuova Reattività - Guida Completa

Esplora Svelte 5: sistema di reattività con Runes, Snippets per riuso componenti, event handlers migliorati e performance eccezionali.

Edoardo Midali

Edoardo Midali

Developer · Content Creator

9 min di lettura
Svelte 5: Runes e Nuova Reattività - Guida Completa

Svelte 5 rappresenta la più grande evoluzione del framework con l'introduzione delle Runes, un nuovo sistema di reattività più potente, esplicito e performante. Rilasciato in versione stabile, porta miglioramenti significativi a performance, developer experience e composizione dei componenti.

🎯 Novità Principali

Runes: Il Nuovo Sistema di Reattività

Le Runes sostituiscono la reattività implicita con un sistema più esplicito e potente:

$state - Stato Reattivo

<script>
// ❌ Svelte 4 - reattività implicita
let count = 0;

function increment() {
  count += 1;
}

// ✅ Svelte 5 - Runes esplicite
let count = $state(0);

function increment() {
  count += 1;
}

// Oggetti reattivi profondi
let user = $state({
  name: 'Mario',
  settings: {
    theme: 'dark',
    notifications: true
  }
});

// Mutazioni profonde sono reattive
function toggleTheme() {
  user.settings.theme = user.settings.theme === 'dark' ? 'light' : 'dark';
}

// Array reattivi
let todos = $state([
  { id: 1, text: 'Learn Svelte 5', done: false }
]);

function addTodo(text) {
  todos.push({ id: Date.now(), text, done: false });
}
</script>

<button on:click={increment}>
  Count: {count}
</button>

<button on:click={toggleTheme}>
  Theme: {user.settings.theme}
</button>

$derived - Valori Derivati

<script>
let count = $state(0);

// ❌ Svelte 4 - reactive declarations
$: doubled = count * 2;
$: quadrupled = doubled * 2;

// ✅ Svelte 5 - $derived
let doubled = $derived(count * 2);
let quadrupled = $derived(doubled * 2);

// Con logica complessa
let items = $state([1, 2, 3, 4, 5]);
let evenItems = $derived(items.filter(n => n % 2 === 0));
let sum = $derived(items.reduce((a, b) => a + b, 0));

// Derived con side effects
let searchQuery = $state('');
let searchResults = $derived.by(() => {
  if (!searchQuery) return [];
  // Computazione pesante
  return database.search(searchQuery);
});
</script>

<div>
  <p>Count: {count}</p>
  <p>Doubled: {doubled}</p>
  <p>Quadrupled: {quadrupled}</p>
  <p>Even items: {evenItems.join(', ')}</p>
  <p>Sum: {sum}</p>
</div>

$effect - Side Effects

<script>
let count = $state(0);
let name = $state('');

// ❌ Svelte 4 - reactive statements
$: {
  console.log('Count changed:', count);
  document.title = `Count: ${count}`;
}

// ✅ Svelte 5 - $effect
$effect(() => {
  console.log('Count changed:', count);
  document.title = `Count: ${count}`;
});

// Con cleanup
$effect(() => {
  const interval = setInterval(() => {
    console.log('Current count:', count);
  }, 1000);

  // Cleanup automatico
  return () => clearInterval(interval);
});

// Pre effect (esegue prima del DOM update)
$effect.pre(() => {
  // Leggi valori prima del render
  const oldValue = count;

  // Codice eseguito prima del DOM update
});

// Effect root per controllo granulare
import { effect } from 'svelte';

const cleanup = effect.root(() => {
  $effect(() => {
    console.log('Reactive effect');
  });

  // Restituisce funzione cleanup
});

// Chiama quando necessario
cleanup();
</script>

$props - Props Tipizzate

<!-- Counter.svelte -->
<script>
// ❌ Svelte 4
export let count = 0;
export let onIncrement;

// ✅ Svelte 5 - $props con destructuring
let { count = 0, onIncrement } = $props();

// Con TypeScript
interface Props {
  count?: number;
  max?: number;
  onIncrement?: () => void;
}

let {
  count = 0,
  max = 100,
  onIncrement
}: Props = $props();

// Props reattive
let doubled = $derived(count * 2);

function increment() {
  if (count < max) {
    onIncrement?.();
  }
}
</script>

<button on:click={increment} disabled={count >= max}>
  Count: {count} (doubled: {doubled})
</button>

<!-- Usage -->
<script>
let value = $state(0);
</script>

<Counter
  count={value}
  max={50}
  onIncrement={() => value++}
/>

$bindable - Two-way Binding

<!-- TextField.svelte -->
<script>
// Props bindable dall'esterno
let { value = $bindable('') } = $props();
</script>

<input bind:value />

<!-- Parent.svelte -->
<script>
let text = $state('');
</script>

<!-- Two-way binding -->
<TextField bind:value={text} />
<p>You typed: {text}</p>

Snippets: Riuso Template

I Snippets sostituiscono gli slots nominati con un sistema più potente:

<script>
let items = $state([
  { id: 1, name: 'Apple', price: 1.5 },
  { id: 2, name: 'Banana', price: 0.8 }
]);
</script>

<!-- Definizione snippet -->
{#snippet itemCard(item)}
  <div class="card">
    <h3>{item.name}</h3>
    <p>${item.price}</p>
  </div>
{/snippet}

<!-- Snippet con parametri multipli -->
{#snippet productDetails(item, index, isLast)}
  <div class="product" class:last={isLast}>
    <span class="index">#{index + 1}</span>
    <h4>{item.name}</h4>
    <p class="price">${item.price}</p>
  </div>
{/snippet}

<!-- Uso snippet -->
<div class="grid">
  {#each items as item, i}
    {@render itemCard(item)}
  {/each}
</div>

<div class="list">
  {#each items as item, i}
    {@render productDetails(item, i, i === items.length - 1)}
  {/each}
</div>

<!-- Snippet da props -->
<script>
let {
  items,
  renderItem = $props()
} = $props();
</script>

<div>
  {#each items as item}
    {@render renderItem(item)}
  {/each}
</div>

<!-- Usage -->
<ItemList {items}>
  {#snippet renderItem(item)}
    <div class="custom-item">{item.name}</div>
  {/snippet}
</ItemList>

Event Handlers Modernizzati

<script>
let count = $state(0);

// ❌ Svelte 4 - on: directive
// <button on:click={increment}>

// ✅ Svelte 5 - onclick prop
function increment() {
  count++;
}

// Con event object
function handleClick(event) {
  console.log('Clicked at', event.clientX, event.clientY);
  count++;
}

// Custom events
function handleCustom(detail) {
  console.log('Custom event:', detail);
}
</script>

<button onclick={increment}>
  Count: {count}
</button>

<button onclick={handleClick}>
  Click with event
</button>

<!-- Custom components -->
<CustomButton
  onclick={() => console.log('clicked')}
  ondblclick={() => console.log('double clicked')}
/>

🚀 Performance Improvements

Reattività Ottimizzata

<script>
// Svelte 5: tracking granulare

let data = $state({
  user: { name: 'Mario', age: 30 },
  settings: { theme: 'dark' },
  posts: []
});

// Solo i componenti che usano data.user.name si ri-renderizzano
let userName = $derived(data.user.name);

// Fine-grained reactivity
$effect(() => {
  // Traccia solo data.user.name, non tutto l'oggetto
  console.log('Username:', data.user.name);
});
</script>

Bundle Size Ridotto

<!-- Svelte 5 genera codice più compatto -->

<!-- Svelte 4: ~4KB runtime -->
<!-- Svelte 5: ~2.5KB runtime -->

<!-- 40% riduzione bundle size! -->

Compilazione Più Veloce

  • 50% più veloce compile time
  • Meno codice generato dal compiler
  • Tree-shaking migliorato

🎨 Nuove Features

Class Directives Migliorate

<script>
let isActive = $state(false);
let theme = $state('dark');
</script>

<!-- Sintassi più concisa -->
<div class:active={isActive}>Content</div>

<!-- Shorthand quando nome variabile = classe -->
<div class:isActive>Content</div>

<!-- Multiple classes -->
<div
  class:active={isActive}
  class:dark={theme === 'dark'}
  class:light={theme === 'light'}
>
  Themed content
</div>

Style Directives

<script>
let color = $state('#ff3e00');
let size = $state(16);
</script>

<!-- Inline styles reattive -->
<div
  style:color
  style:font-size="{size}px"
  style:background-color={color}
>
  Styled content
</div>

Transitions Migliorate

<script>
import { fade, fly, slide } from 'svelte/transition';

let visible = $state(false);
</script>

<!-- Transitions più performanti in Svelte 5 -->
{#if visible}
  <div
    in:fly={{ y: 200, duration: 500 }}
    out:fade={{ duration: 200 }}
  >
    Animated content
  </div>
{/if}

🔧 Migration da Svelte 4

Migration Tool Automatico

# Installa npx migration tool
npx sv migrate svelte-5

# Opzioni
npx sv migrate svelte-5 --help

Esempio Migrazione

<!-- Before (Svelte 4) -->
<script>
export let count = 0;
export let max = 100;

$: doubled = count * 2;

$: if (count > max) {
  count = max;
}

$: {
  console.log('Count:', count);
}
</script>

<button on:click={() => count++}>
  {count}
</button>

<!-- After (Svelte 5) -->
<script>
let { count = 0, max = 100 } = $props();

let doubled = $derived(count * 2);

$effect(() => {
  if (count > max) {
    count = max;
  }
});

$effect(() => {
  console.log('Count:', count);
});
</script>

<button onclick={() => count++}>
  {count}
</button>

🔄 Come Aggiornare a Svelte 5

Installazione

# npm
npm install svelte@latest

# pnpm
pnpm add svelte@latest

# Aggiorna anche SvelteKit se usato
npm install @sveltejs/kit@latest

Setup Vite

// vite.config.js
import { defineConfig } from "vite";
import { svelte } from "@sveltejs/vite-plugin-svelte";

export default defineConfig({
  plugins: [
    svelte({
      compilerOptions: {
        // Abilita Runes
        runes: true,
      },
    }),
  ],
});

TypeScript

// tsconfig.json
{
  "extends": "./.svelte-kit/tsconfig.json",
  "compilerOptions": {
    "strict": true,
    "moduleResolution": "bundler",
    "types": ["svelte"]
  }
}

⚠️ Breaking Changes

1. Reattività Implicita Deprecata

<!-- ❌ Non più reattivo automaticamente -->
<script>
let count = 0; // Non reattivo!
</script>

<!-- ✅ Usa $state -->
<script>
let count = $state(0); // Reattivo
</script>

2. $ Label Riservato

<!-- ❌ Errore in Svelte 5 -->
<script>
$: doubled = count * 2; // Solo con legacy mode
</script>

<!-- ✅ Usa $derived -->
<script>
let doubled = $derived(count * 2);
</script>

3. on: Directive Deprecata

<!-- ❌ Legacy -->
<button on:click={handler}>Click</button>

<!-- ✅ Nuovo -->
<button onclick={handler}>Click</button>

🎓 Best Practices

1. Usa Runes per Reattività

<script>
// ✅ Sempre
let state = $state(initialValue);
let derived = $derived(computation);
$effect(() => { /* side effects */ });
</script>

2. Snippets per Riuso

<!-- ✅ DRY con snippets -->
{#snippet card(item)}
  <div class="card">{item.name}</div>
{/snippet}

{#each items as item}
  {@render card(item)}
{/each}

3. $effect con Cleanup

<script>
// ✅ Sempre cleanup
$effect(() => {
  const timer = setInterval(() => {}, 1000);
  return () => clearInterval(timer);
});
</script>

📊 Confronto Performance

Metrica Svelte 4 Svelte 5 Miglioramento
Bundle size 4KB 2.5KB -38%
Compile time 200ms 100ms 2x
Runtime perf Baseline 1.3x +30%
Memory usage 100% 80% -20%

🔗 Risorse Utili

💡 Conclusioni

Svelte 5 è un upgrade rivoluzionario:

Runes per reattività esplicita e potente ✅ Snippets per composizione migliorata ✅ Performance 30% miglioriBundle size -38%Developer Experience modernizzata

Quando aggiornare:

  • Nuovi progetti: usa Svelte 5
  • ⚠️ Progetti esistenti: migration tool disponibile
  • 📚 Graduale: backward compatibility con legacy mode

Svelte 5 mantiene la semplicità che ha reso famoso Svelte, aggiungendo potenza e scalabilità!