Svelte 5: L'Approccio Compiler-Driven e il Modello Mentale
Immagina un framework JavaScript che scompare completamente a build time. Nessuna libreria da scaricare, nessun virtual DOM da riconciliare a runtime, nessun overhead di framework visibile nell'inspector del browser. Questo e Svelte: non un framework nel senso tradizionale, ma un compilatore che trasforma componenti .svelte in JavaScript puro e ottimizzato. Svelte 5, rilasciato nell'ottobre 2024, porta questo modello a un livello superiore con i Runes, un sistema di reattivita signal-based universale che ha rivoluzionato il modo in cui si gestisce lo stato nelle applicazioni.
Se vieni da React, Vue o Angular, il modello mentale di Svelte richiede un cambio di prospettiva. Non si tratta di imparare una nuova API, ma di capire che il framework stesso non esiste a runtime. Questa guida ti aiuta a costruire quel modello mentale, esplorando come il compilatore Svelte funziona internamente e perche i Runes rappresentano la naturale evoluzione di questo approccio.
Cosa Imparerai
- Come il compilatore Svelte trasforma i componenti in JavaScript puro
- Perche Svelte non ha bisogno di un virtual DOM
- Il modello mentale corretto per lavorare con Svelte 5
- Introduzione ai Runes e alla reattivita signal-based
- Confronto prestazionale con React e Vue (dati 2025-2026)
- Come creare il primo componente Svelte 5 con i Runes
Il Problema con i Virtual DOM
Per capire perche Svelte esiste, bisogna prima capire cosa risolve. React, Vue e Angular usano tutti un virtual DOM: una rappresentazione in memoria del DOM reale che il framework usa per calcolare le differenze (diffing) e applicare solo le modifiche necessarie. Questo approccio ha senso intuitivo — e piu efficiente aggiornare una rappresentazione in memoria che manipolare il DOM direttamente — ma ha un costo nascosto: la riconciliazione del virtual DOM avviene sempre, anche quando nulla e cambiato.
Rich Harris, il creatore di Svelte, ha coniato il termine "virtual DOM is pure overhead" in un articolo del 2019 che ha scatenato un vivace dibattito nella community. Il punto non e che il virtual DOM sia lento in assoluto, ma che introduce lavoro inutile che il compilatore potrebbe eliminare.
Considera un componente React che mostra un contatore:
// React: il virtual DOM riconcilia ad ogni render
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Contatore: {count}</p>
<button onClick={() => setCount(count + 1)}>
Incrementa
</button>
</div>
);
}
Ogni volta che lo stato cambia, React deve: creare un nuovo virtual DOM per il componente, confrontarlo con quello precedente (diffing), determinare cosa e cambiato, e infine aggiornare il DOM reale. Il compilatore Svelte conosce gia a build time cosa puo cambiare — solo il testo del paragrafo — e genera direttamente il codice per aggiornarlo, senza nessuno step intermedio.
Come Funziona il Compilatore Svelte
Un componente Svelte e un file .svelte che contiene tre sezioni opzionali: script, template e
stili. Durante il build, il compilatore analizza questo file e genera JavaScript che manipola direttamente il
DOM, con aggiornamenti granulari e chirurgici.
<!-- Counter.svelte - Il componente sorgente -->
<script>
let count = $state(0);
</script>
<p>Contatore: {count}</p>
<button onclick={() => count++}>
Incrementa
</button>
Il compilatore analizza il template e capisce che {count} e l'unica parte che cambia.
Il JavaScript generato contiene direttamente: crea il nodo <p>, inserisce un text node,
e registra un effetto che aggiorna solo quel text node quando count cambia. Nessun virtual DOM,
nessun diffing, nessuna riconciliazione.
Il codice compilato, semplificato, somiglia a questo:
// Output del compilatore Svelte (semplificato)
import { mount, text, element } from 'svelte/internal';
export function Counter(target) {
let count = 0;
const p = element('p');
const countText = text(`Contatore: ${count}`);
const button = element('button');
button.addEventListener('click', () => {
count++;
// Aggiorna SOLO il text node specifico
set_data(countText, `Contatore: ${count}`);
});
append(p, countText);
append(target, p);
append(target, button);
}
Questo e il cuore dell'approccio compiler-driven: il compilatore sa esattamente cosa puo cambiare e genera codice ottimale per gestirlo, invece di delegare questo lavoro a un algoritmo generico di diffing a runtime.
Confronto Bundle Size (2025)
Un componente TodoList equivalente produce bundle JavaScript di dimensioni molto diverse tra i principali framework:
- React 19 + ReactDOM: ~42 KB gzipped (runtime inclusa)
- Vue 3.5: ~22 KB gzipped (runtime inclusa)
- Angular 19 (standalone): ~35 KB gzipped (runtime inclusa)
- Svelte 5: ~2-5 KB gzipped (nessuna runtime, solo il componente)
Per applicazioni grandi con molti componenti, il vantaggio si riduce perche ogni componente Svelte include il suo codice di update. Ma per applicazioni di medie dimensioni, Svelte rimane il vincitore assoluto in termini di payload al browser.
Il Cambio di Paradigma di Svelte 5: I Runes
Svelte 4 usava un sistema di reattivita magico basato su assegnamenti: ogni variabile nel blocco
<script> era automaticamente reattiva, e il compilatore tracciava le dipendenze durante
il build. Questo funzionava bene per casi semplici, ma aveva limitazioni importanti: la reattivita funzionava
solo all'interno dei componenti .svelte, e il comportamento magico rendeva il codice difficile da ragionare.
Svelte 5 introduce i Runes: funzioni speciali con il prefisso $ che comunicano
direttamente con il compilatore. I Runes non sono funzioni normali — $state() non e una funzione
che importi e chiami — ma sintassi speciale riconosciuta dal compilatore, simile a come useState()
in React e riconosciuta dal transpiler per il lint e i type check, ma e effettivamente una funzione ordinaria.
La differenza fondamentale: i Runes funzionano ovunque, non solo nei file .svelte:
// counter.svelte.ts - Un modulo TypeScript puro con reattivita Svelte 5
export function createCounter(initial: number = 0) {
let count = $state(initial);
// $derived calcola automaticamente quando count cambia
const doubled = $derived(count * 2);
const isEven = $derived(count % 2 === 0);
function increment() { count++; }
function decrement() { count--; }
function reset() { count = initial; }
return {
get count() { return count; },
get doubled() { return doubled; },
get isEven() { return isEven; },
increment,
decrement,
reset
};
}
// Usabile in qualsiasi componente .svelte
// o in altri file TypeScript che importano questo modulo
Questo e un cambiamento radicale rispetto a Svelte 4: la logica di business con la sua reattivita puo vivere in moduli TypeScript separati, condivisibile tra componenti, testabile in isolamento con normali unit test, senza dipendenze da un ambiente di rendering.
I Quattro Runes Fondamentali
Svelte 5 introduce quattro runes principali che coprono la maggior parte dei casi d'uso:
<script lang="ts">
// $state: stato reattivo (sostituisce let reattivo di Svelte 4)
let name = $state('Federico');
let items = $state<string[]>([]);
// $derived: valori computati (sostituisce $: di Svelte 4)
const greeting = $derived(`Ciao, ${name}!`);
const itemCount = $derived(items.length);
// $effect: side effects (sostituisce $: con side effects di Svelte 4)
$effect(() => {
console.log('name cambiato:', name);
// Cleanup automatico quando l'effetto si ri-esegue
return () => console.log('cleanup prima del prossimo run');
});
// $props: props del componente (sostituisce export let di Svelte 4)
const { title, onClose = () => {} } = $props<{
title: string;
onClose?: () => void;
}>();
</script>
Svelte 4 vs Svelte 5: Differenze Chiave
Se hai esperienza con Svelte 4, i Runes cambiano la sintassi in modo significativo:
- Svelte 4:
let count = 0;(magia implicita) → Svelte 5:let count = $state(0);(esplicito) - Svelte 4:
$: doubled = count * 2;→ Svelte 5:const doubled = $derived(count * 2); - Svelte 4:
export let prop;→ Svelte 5:const { prop } = $props(); - Svelte 4:
on:click={handler}→ Svelte 5:onclick={handler}
Svelte 5 mantiene retrocompatibilita con Svelte 4 in "legacy mode", quindi la migrazione puo essere graduale.
Svelte 5 vs React: Un Confronto Pratico
Per capire concretamente la differenza di approccio, vediamo lo stesso componente implementato in React e Svelte 5:
// React 19: SearchBox con debounce
import { useState, useEffect, useCallback } from 'react';
function SearchBox({ onSearch }: { onSearch: (q: string) => void }) {
const [query, setQuery] = useState('');
const [debouncedQuery, setDebouncedQuery] = useState('');
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedQuery(query);
}, 300);
return () => clearTimeout(timer);
}, [query]);
useEffect(() => {
if (debouncedQuery) onSearch(debouncedQuery);
}, [debouncedQuery, onSearch]);
return (
<input
value={query}
onChange={e => setQuery(e.target.value)}
placeholder="Cerca..."
/>
);
}
<!-- Svelte 5: SearchBox con debounce -->
<script lang="ts">
const { onSearch }: { onSearch: (q: string) => void } = $props();
let query = $state('');
$effect(() => {
const timer = setTimeout(() => {
if (query) onSearch(query);
}, 300);
return () => clearTimeout(timer);
});
</script>
<input bind:value={query} placeholder="Cerca..." />
La versione Svelte 5 e piu concisa non perche sia "magia": e perche il compilatore gestisce la sincronizzazione
tra query e l'input tramite bind:value, e $effect traccia automaticamente
la dipendenza da query senza doverla dichiarare esplicitamente nell'array di dipendenze.
Performance Reale: Dati e Benchmark
I benchmark sintetici sono spesso fuorvianti, ma i dati del JS Framework Benchmark 2025 (eseguito su hardware reale con operazioni DOM intensive) mostrano Svelte 5 costantemente nel primo quartile per quasi tutte le operazioni testate:
- Creazione 10.000 righe: Svelte 5 ~1.2x overhead vs Vanilla JS, React ~2.1x
- Aggiornamento ogni 10a riga: Svelte 5 ~1.1x, React ~1.8x
- Select row highlight: Svelte 5 ~1.05x, React ~1.4x
- Memoria dopo creazione: Svelte 5 usa ~40% meno memoria di React
Il vantaggio e piu pronunciato su dispositivi mobile low-end, dove il parsing e l'esecuzione di JavaScript costano di piu. Su desktop moderni, la differenza pratica per applicazioni tipiche e spesso invisibile all'utente finale.
Creare il Primo Progetto Svelte 5
Il modo piu rapido per iniziare con Svelte 5 e usare SvelteKit, il framework full-stack ufficiale basato su Svelte:
# Crea un nuovo progetto SvelteKit con Svelte 5
npm create svelte@latest my-svelte-app
cd my-svelte-app
# Seleziona: Skeleton project, TypeScript, ESLint, Prettier
npm install
# Avvia il dev server
npm run dev
Verifica che il progetto usi Svelte 5 controllando il package.json:
{
"dependencies": {
"@sveltejs/kit": "^2.5.0",
"svelte": "^5.0.0"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^3.0.0",
"vite": "^5.0.0"
}
}
Crea il tuo primo componente con Runes in src/lib/components/Hello.svelte:
<script lang="ts">
// $props: tipo sicuro per le props del componente
const { name = 'Mondo' }: { name?: string } = $props();
// $state: stato locale reattivo
let clickCount = $state(0);
// $derived: valore computato da state e props
const message = $derived(
clickCount === 0
? `Ciao, ${name}!`
: `Ciao, ${name}! Hai cliccato ${clickCount} volt${clickCount === 1 ? 'a' : 'e'}`
);
</script>
<div class="hello">
<p>{message}</p>
<button onclick={() => clickCount++}>
Clicca qui
</button>
</div>
<style>
.hello {
padding: 1rem;
border: 1px solid #ccc;
border-radius: 8px;
}
button {
margin-top: 0.5rem;
padding: 0.5rem 1rem;
}
</style>
SvelteKit: Il Framework Full-Stack
Svelte da solo e solo il layer UI. SvelteKit e il framework applicativo costruito su Svelte che aggiunge routing, SSR (Server-Side Rendering), SSG (Static Site Generation), form actions, e load functions per il data fetching. E l'equivalente di Next.js per React o Nuxt per Vue.
La struttura di un progetto SvelteKit segue convenzioni basate su file:
src/
├── routes/
│ ├── +layout.svelte # Layout globale
│ ├── +page.svelte # Homepage (/)
│ ├── about/
│ │ └── +page.svelte # Pagina /about
│ └── blog/
│ ├── +page.svelte # Lista articoli (/blog)
│ ├── +page.server.ts # Data fetching server-side
│ └── [slug]/
│ ├── +page.svelte # Articolo singolo (/blog/nome)
│ └── +page.server.ts
├── lib/
│ ├── components/ # Componenti condivisibili
│ └── utils/ # Utilita
└── app.html # Template HTML base
Quando Scegliere Svelte 5
Svelte 5 e la scelta ottimale quando:
- Le performance di caricamento sono critiche (mobile, connessioni lente)
- Il bundle size deve essere minimo
- Il team e disposto a imparare un modello mentale diverso da React
- Si vuole una DX (Developer Experience) eccellente con meno boilerplate
- Si costruisce un sito content-heavy che beneficia di SSR/SSG con SvelteKit
Svelte 5 potrebbe NON essere la scelta giusta quando:
- Il team ha forte esperienza React e la migrazione costerebbe troppo
- Si cercano molte librerie di componenti UI (l'ecosistema React e piu vasto)
- Si deve integrare con librerie che assumono React (headless UI, Radix, etc.)
Il Modello Mentale: Cosa Tenere a Mente
Lavorare con Svelte 5 richiede di interiorizzare alcune differenze fondamentali rispetto ai framework basati su virtual DOM:
- Il compilatore e il tuo strumento, non il runtime. Quando qualcosa non funziona come ti aspetti, pensa a come il compilatore interpretera il tuo codice, non a un loop di rendering.
-
I Runes comunicano con il compilatore.
$state(),$derived()e gli altri non sono semplici funzioni: sono istruzioni al compilatore su come gestire la reattivita. -
La reattivita e granulare per design. Svelte aggiorna solo cio che e effettivamente
cambiato. Non c'e nessun meccanismo da ottimizzare manualmente con
useMemoouseCallback. -
I file .svelte.ts abilitano reattivita universale. Puoi usare i Runes in qualsiasi
file con estensione
.svelte.tso.svelte.js, non solo nei componenti.
Conclusioni e Prossimi Passi
Svelte 5 rappresenta l'evoluzione piu significativa del framework dalla sua creazione. L'approccio compiler-driven non e nuovo, ma i Runes portano la reattivita signal-based fuori dai confini dei componenti, abilitando pattern architetturali piu flessibili e testabili. Il risultato e un framework che combina performance eccellenti, bundle minimo, e una developer experience priva di boilerplate inutile.
Il prossimo articolo della serie esplora in profondita $state e $derived: come funziona
la reattivita profonda tramite Proxy ES6, come $derived implementa la memoizzazione, e come
usare i Runes in file TypeScript condivisi tra componenti.
Serie: Svelte 5 e Frontend Compiler-Driven
- Articolo 1 (questo): Approccio Compiler-Driven e Modello Mentale
- Articolo 2: $state e $derived — Reattivita Universale con i Runes
- Articolo 3: $effect e il Ciclo di Vita — Quando Usarlo (e Quando No)
- Articolo 4: SvelteKit SSR, Streaming e Load Functions
- Articolo 5: Transizioni e Animazioni in Svelte 5
- Articolo 6: Accessibilita in Svelte: Compiler Warnings e Best Practice
- Articolo 7: State Management Globale: Context, Runes e Stores
- Articolo 8: Migrare da Svelte 4 a Svelte 5 — Guida Pratica
- Articolo 9: Testing in Svelte 5: Vitest, Testing Library e Playwright







