Skip to main content
Advertisement

17.1 Introduction to Svelte

Svelte is a compiler-based framework for building user interfaces. Unlike React or Vue, it doesn't include a runtime library in the bundle. Instead, all the magic happens at build time, converting your code into plain JavaScript. The output is pure DOM manipulation code — small and fast.


What is Svelte? The Compiler-Based Framework

Svelte was created by Rich Harris in 2016. The name means "slim, streamlined" — true to its goal of a framework with no unnecessary runtime overhead.

Key Differences

React / VueSvelte
How it worksVirtual DOM diffing at runtimeGenerates DOM manipulation code at compile time
Bundle sizeIncludes framework runtimeOnly compiled code
ReactivityRequires APIs like useState, refAutomatic reactivity via assignment
Learning curveMust learn the APISyntax close to HTML/CSS/JS

The Compilation Process

Counter.svelte
↓ (svelte compiler)
Counter.js (pure JavaScript DOM manipulation code)

A Svelte file (.svelte) isn't just a text file. During the build, the Svelte compiler reads it and transforms it into highly optimized JavaScript.


Why No Virtual DOM?

React's Virtual DOM compares previous and new virtual trees on state changes (diffing), and applies minimal updates to the real DOM. This is faster than direct DOM manipulation but the comparison itself has a cost.

Svelte takes a different approach. Since it already knows at compile time which variables affect which DOM elements, there's no need to compare at runtime.

<!-- Counter.svelte -->
<script>
let count = 0;

function increment() {
count++; // This assignment triggers a DOM update
}
</script>

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

After compilation, this roughly becomes:

// Simplified compiled output
function create_fragment(ctx) {
let button;
return {
c() {
button = element("button");
button.textContent = `Clicks: ${ctx[0]}`;
},
m(target, anchor) {
insert(target, button, anchor);
listen(button, "click", ctx[1]);
},
p(ctx, [dirty]) {
if (dirty & 1) { // Only when count changes
set_data(button_text, `Clicks: ${ctx[0]}`);
}
}
};
}

No virtual DOM diffing — only the text node is updated exactly when count changes.


Bundle Size Comparison: React / Vue / Angular vs Svelte

Approximate gzip bundle sizes for a simple counter app:

FrameworkBundle Size (gzip)Notes
Svelte~3 KBNo runtime, compiled code only
React + React DOM~45 KBIncludes virtual DOM runtime
Vue 3~33 KBIncludes Composition API runtime
Angular~60 KB+Full framework

Note: As an app grows, Svelte's compiled output also grows, narrowing the gap. Svelte's bundle size advantage is most pronounced in small to medium-sized apps.


Svelte 5 Key Changes: Introducing Runes

Svelte 5 (released late 2024) introduced the Runes system, fundamentally changing how reactivity is declared.

Svelte 4 vs Svelte 5

<!-- Svelte 4 -->
<script>
let count = 0; // Implicitly reactive
$: doubled = count * 2; // Reactive declaration
</script>

<p>Count: {count}, Doubled: {doubled}</p>
<!-- Svelte 5 (Runes) -->
<script>
let count = $state(0); // Explicit reactive state
let doubled = $derived(count * 2); // Explicit derived value
</script>

<p>Count: {count}, Doubled: {doubled}</p>

Svelte 5 Key Runes

RuneRoleDescription
$state()Reactive stateReplaces implicit let reactivity
$derived()Derived valueReplaces $: reactive declarations
$effect()Side effectsReplaces $: { ... } blocks
$props()Component propsReplaces export let
$bindable()Two-way binding propsAllows bind: from parent

Runes can be used in regular JavaScript files (.js, .ts), enabling reactive logic outside Svelte components.


When Svelte is a Good Fit

Good Use Cases

  • Small to medium-sized apps: Bundle size advantage is maximized
  • Performance-critical projects: No runtime overhead, fast initial loading
  • Content-driven sites: SvelteKit's SSR/SSG support
  • Low learning curve teams: Familiar HTML/CSS/JS-like syntax
  • Embedded widgets: Can compile to standalone web components

Less Suitable Cases

  • Large enterprise apps: Smaller ecosystem compared to React/Angular
  • When rich third-party components are needed: React UI library ecosystem is larger
  • When no Svelte-experienced team members exist: Consider hiring

Basic Component Example

A Svelte component has three sections:

<!-- Greeting.svelte -->
<script>
// JavaScript logic (Svelte 5: Runes)
let { name = 'World' } = $props();
let count = $state(0);
let message = $derived(`Hello, ${name}! (greeted ${count} times)`);

function greet() {
count++;
}
</script>

<!-- HTML template -->
<div class="greeting">
<h1>{message}</h1>
<button on:click={greet}>Say Hello</button>
</div>

<!-- Scoped styles (automatically component-scoped) -->
<style>
.greeting {
font-family: sans-serif;
padding: 1rem;
border: 2px solid #ff3e00;
border-radius: 8px;
}

button {
background-color: #ff3e00;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
}

button:hover {
background-color: #dd3700;
}
</style>

Svelte File Structure

  1. <script>: Component logic, Runes, imports
  2. HTML template: Markup, {expressions}, logic blocks
  3. <style>: CSS (component-scoped by default, class names are hashed)

Each section is optional and can appear in any order.


Todo App Example

<!-- TodoApp.svelte -->
<script>
let todos = $state([
{ id: 1, text: 'Learn Svelte', done: false },
{ id: 2, text: 'Learn SvelteKit', done: false },
{ id: 3, text: 'Master Runes', done: false },
]);

let newTodo = $state('');

let remaining = $derived(todos.filter(t => !t.done).length);

function addTodo() {
if (!newTodo.trim()) return;
todos.push({
id: Date.now(),
text: newTodo.trim(),
done: false,
});
newTodo = '';
}

function toggleTodo(id) {
const todo = todos.find(t => t.id === id);
if (todo) todo.done = !todo.done;
}

function removeTodo(id) {
todos = todos.filter(t => t.id !== id);
}

function handleKeydown(event) {
if (event.key === 'Enter') addTodo();
}
</script>

<div class="app">
<h1>Todo List <span class="badge">{remaining}</span></h1>

<div class="input-row">
<input
bind:value={newTodo}
on:keydown={handleKeydown}
placeholder="Add a new todo..."
/>
<button on:click={addTodo}>Add</button>
</div>

<ul>
{#each todos as todo (todo.id)}
<li class:done={todo.done}>
<input
type="checkbox"
checked={todo.done}
on:change={() => toggleTodo(todo.id)}
/>
<span>{todo.text}</span>
<button class="remove" on:click={() => removeTodo(todo.id)}>×</button>
</li>
{/each}
</ul>

{#if todos.length === 0}
<p class="empty">No todos. Add one above!</p>
{/if}
</div>

<style>
.app {
max-width: 400px;
margin: 2rem auto;
font-family: sans-serif;
}

.badge {
background: #ff3e00;
color: white;
border-radius: 999px;
padding: 0.1rem 0.5rem;
font-size: 0.8rem;
}

.input-row {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}

input[type="text"], input:not([type]) {
flex: 1;
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 4px;
}

ul { list-style: none; padding: 0; }

li {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
border-bottom: 1px solid #eee;
}

li.done span { text-decoration: line-through; color: #999; }
li span { flex: 1; }

.remove { background: none; border: none; color: #999; cursor: pointer; font-size: 1.2rem; }
.empty { text-align: center; color: #999; }
</style>

The Svelte REPL

Svelte provides an official online editor called the REPL (Read-Eval-Print Loop).

What you can do in the REPL:

  • Run code and see results instantly
  • View compiled JavaScript output (the "JS output" tab)
  • Share examples (shareable URLs)
  • Linked to official tutorials

REPL Tips

  1. Check the "JS output" tab to understand how Svelte works internally
  2. The "CSS output" tab shows scoped CSS after compilation
  3. Add multiple component files to test component communication

Pro Tips

Tip 1: Analyze Svelte's Compiled Output

To truly understand Svelte, always look at the compiled output:

# Bundle analysis
npm run build -- --report

# Bundle visualization with Vite
npx vite-bundle-visualizer

Tip 2: Using Svelte 4 and 5 Side by Side

A SvelteKit project can mix Svelte 4 and 5 syntax. Runes mode can be enabled per file, allowing gradual migration.

<!-- Force Svelte 5 Runes mode for this file -->
<svelte:options runes={true} />

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

Tip 3: Compile to Web Components

Svelte components can be compiled to standard Web Components (Custom Elements):

<svelte:options customElement="my-counter" />

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

<button on:click={() => count++}>Count: {count}</button>
<!-- Usable in any HTML file -->
<script src="./my-counter.js"></script>
<my-counter></my-counter>

Tip 4: Understanding Performance Benchmarks

Svelte consistently ranks near the top of js-framework-benchmark. However, "Svelte is always faster than React" is an overgeneralization. Real-world app performance depends more on architecture and implementation quality.


Summary

ConceptDescription
Compiler frameworkGenerates optimized JS at build time
No virtual DOMEliminates runtime diffing cost
Runes (Svelte 5)$state, $derived, $effect, $props
Component structure<script> + HTML + <style>
Scoped CSSStyles apply only to the component
Bundle sizeSignificantly smaller than React/Vue

The next chapter covers setting up your project with SvelteKit.

Advertisement