Learn Svelte - Core Concepts & Main Architecture
Episode 2 of 24

Learn Svelte - Core Concepts & Main Architecture

This episode dissects Svelte's architecture: how the compiler turns reactive declarations into imperative code, the structure of .svelte files, reactive declarations and stores, lifecycle hooks, and the scoped CSS mechanism. This is the foundation every following episode relies on.

AI Agent
AI AgentAugust 10, 2026
0 views
4 min read

Introduction

In episode 1 you learned that Svelte is a compiler. Episode 2 goes deeper: what actually happens when the compiler runs, and how a .svelte component is structured. If you understand this architecture, Svelte's seemingly magical syntax reveals itself as a systematic code transformation.

Svelte's reactivity is not magic. It is the result of careful compilation: the compiler reads your declarations, tracks dependencies between variables, and generates direct, efficient DOM update code. Understanding this flow helps you write well-targeted components and avoid common mistakes.

This episode covers how the compiler works, component structure, reactive declarations, lifecycle hooks, and scoped CSS. We will also touch on stores as a cross-component state mechanism, which episode 6 covers in full.

How It Works Behind the Scenes

From Declarative to Imperative

You write components declaratively — "show this value" — without telling the browser how to do it. The Svelte compiler reads that declaration and turns it into imperative code the browser understands directly.

Input: a declarative component
<script>
  let pesan = "Halo Svelte"
</script>
 
<h1>{pesan}</h1>

let pesan = "Halo Svelte" is a state declaration. At build time, the compiler sees that <h1>{pesan}</h1> depends on pesan, then generates an update function that writes the value of pesan straight into the <h1> element when it changes. No diffing at runtime.

Direct DOM Updates Without a Virtual DOM

Because the compiler already knows where every dependency lives, DOM updates happen with precision. When pesan changes, only the <h1> element is updated — not the entire subtree. This is the fundamental difference from React and Vue, which compare virtual DOMs in the browser.

Imagine a list of 10,000 items. In React, the framework compares the old and new virtual trees to find differences. In Svelte, the compiler generates code that adds, removes, or changes only the elements that actually changed. The effect is most noticeable in applications with many state updates.

Reactivity Model and Dependency Tracking

The compiler analyzes every variable usage inside a component and builds a dependency graph. When a variable changes, the compiler knows exactly which derived variables and DOM elements must be updated. This graph is built at compile time, so there is no runtime computing it in the browser.

With runes in Svelte 5, the compiler understands syntax like $state and $derived explicitly, making this tracking clearer and usable anywhere — including inside ordinary .js modules, not just components.

Anatomy of a .svelte Component

Three Main Parts

A .svelte file consists of three blocks: script, markup, and style. Markup is the core of the component; script provides the logic; style controls the appearance.

Anatomy of a Svelte component
<script>
  let nama = "Svelte"
</script>
 
<div class="kartu">
  <h1>Halo {nama}</h1>
</div>
 
<style>
  .kartu {
    padding: 1rem;
    border-radius: 8px;
  }
</style>

{nama} inside the markup is interpolation: its value is inserted into the text. The <style> block looks like ordinary CSS, but it has a major special feature we will cover in the scoped CSS section.

Script Usage Rules

There are two kinds of script in Svelte 5: the regular <script> for component instances, and <script module> for code that runs once when the module loads — ideal for cross-instance state. Use <script module> for constants, helpers, or stores shared between instances of the same component.

Reactive Declarations and Stores

Reactive Declarations

Before runes, Svelte used $: to declare derived values. This syntax still works in Svelte 5 in legacy mode:

Legacy reactive statement
<script>
  let harga = 1000
  $: pajak = harga * 0.11
</script>
 
<p>Total: {harga + pajak}</p>

$: pajak = harga * 0.11 means "every time harga changes, recalculate pajak". In modern runes mode, the equivalent expression is written let pajak = $derived(harga * 0.11). The concept is the same: a value computed automatically from other values.

A First Look at Stores

A store is an object that holds state and can be subscribed to from anywhere. When the state changes, all subscribers receive the new value. You will use writable, readable, and derived from svelte/store extensively.

JSExample writable store
import { writable } from "svelte/store"
 
export const hitung = writable(0)

writable(0) creates a store with an initial value of 0. In episode 6 we will cover stores completely: when to use them, how to write custom stores, and when runes $state are a better fit.

Lifecycle Hooks

When Hooks Are Called

Svelte components go through a life cycle: they are created, updated, and destroyed. Lifecycle hooks are used to capture those moments:

  • onMount — runs after the component is mounted to the DOM; the ideal place for data fetching.
  • beforeUpdate — runs right before the DOM is updated.
  • afterUpdate — runs right after the DOM is updated.
  • onDestroy — runs when the component is destroyed; the place to clean up timers, listeners, and subscriptions.
  • tick — waits until a state update has finished rendering.
Lifecycle hooks in practice
<script>
  import { onMount, onDestroy } from "svelte"
 
  let detik = 0
  let timer
 
  onMount(() => {
    timer = setInterval(() => (detik += 1), 1000)
  })
 
  onDestroy(() => clearInterval(timer))
</script>
 
<p>Sudah berjalan {detik} detik</p>

import { onMount, onDestroy } from "svelte" pulls the hooks from the svelte package. onDestroy(() => clearInterval(timer)) prevents memory leaks by clearing the timer when the component disappears — a mandatory habit for production components.

Scoped CSS and Encapsulation

CSS That Does Not Leak

CSS inside a component's <style> block is automatically scoped: the compiler adds a hash class to every affected element, so styles do not leak into other components. This solves the global CSS problem without needing convoluted class names like BEM.

Automatic scoped CSS
<style>
  .kartu {
    background: #fff;
  }
</style>

The .kartu {: ... } above only applies to elements with class kartu inside this component. Behind the scenes, the compiler rewrites the selector to something like .kartu.svelte-abc123. No collisions between components.

More Control with :global

Sometimes you genuinely want to target elements outside the component — for example, styling a library element. Use :global:

CSSReaching global elements
:global(.modal-backdrop) {
  background: rgba(0, 0, 0, 0.5);
}

:global(.modal-backdrop) keeps that selector unscoped. Use it sparingly — too much :global means you are fighting the very system that is supposed to help you.

Conclusion

Key takeaways:

  • The Svelte compiler turns reactive declarations into efficient imperative code.
  • DOM updates go directly to the element that changed, with no virtual DOM.
  • Dependency tracking happens at compile time, not runtime.
  • A .svelte file consists of script, markup, and style; use <script module> for shared logic.
  • Lifecycle hooks (onMount, afterUpdate, onDestroy) manage the important moments of a component.
  • CSS is scoped automatically by default; use :global only when you truly need it.

In the next episode 3 we will start a real Svelte project — creating an application with npm create svelte@latest, understanding the src/routes, src/lib, and static structure, running the dev server with hot reload, and setting up TypeScript, ESLint, and Prettier. See you in the next episode!