1. Pengenalan Svelte
Svelte adalah framework JavaScript compiler-based yang mengambil pendekatan berbeda dari React atau Vue. Alih-alih melakukan pekerjaan di browser (runtime), Svelte mengubah kode Anda menjadi vanilla JavaScript yang sangat optimal saat build time. Hasilnya? Bundle yang lebih kecil dan performa yang lebih cepat — tanpa virtual DOM.
Svelte dibuat oleh Rich Harris pada tahun 2016 dan sejak itu tumbuh menjadi salah satu framework yang paling dicintai oleh developer. Dalam survei State of JS, Svelte secara konsisten menduduki peringkat tertinggi untuk kepuasan pengembang.
Mengapa Svelte Berbeda?
| Keunggulan | Penjelasan |
|---|---|
| Compile-time | Tidak ada runtime framework — kode langsung jadi vanilla JS yang optimal |
| Tanpa Virtual DOM | Update DOM langsung tanpa diffing, lebih cepat dari React/Vue |
| Bundle Kecil | Aplikasi Svelte biasanya 10-30x lebih kecil dari React |
| Sintaks Sederhana | Menulis lebih sedikit kode, lebih dekat ke HTML/CSS/JS standar |
| Reactivity Built-in | Tidak perlu hooks atau decorator — reaktivitas otomatis |
| Built-in Features | Transitions, animations, dan scoped styles tanpa library tambahan |
Svelte vs React vs Vue
| Aspek | Svelte | React | Vue |
|---|---|---|---|
| Pendekatan | Compile-time | Runtime (Virtual DOM) | Runtime (Proxy + VDOM) |
| Bundle Size | ~2 KB (gzip) | ~42 KB (gzip) | ~33 KB (gzip) |
| Syntax | .svelte (HTML-based) | JSX | .vue (template) |
| State | Reactive variables | useState/useReducer | ref/reactive |
| Learning Curve | 🟢 Mudah | 🟡 Sedang | 🟢 Mudah |
| Full-stack | SvelteKit | Next.js | Nuxt.js |
┌───────────────────────────────────────────────────────┐
│ SVELTE COMPILE PROCESS │
│ │
│ SOURCE CODE (.svelte) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ <script> │ │
│ │ let count = 0; │ │
│ │ function increment() { count += 1; } │ │
│ │ </script> │ │
│ │ │ │
│ │ <button on:click={increment}> │ │
│ │ Klik: {count} kali │ │
│ │ </button> │ │
│ │ │ │
│ │ <style> │ │
│ │ button { background: #6366f1; color: white; } │ │
│ │ </style> │ │
│ └─────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Svelte Compiler │ │
│ │ • Parse template → AST │ │
│ │ • Analyze reactivity │ │
│ │ • Generate vanilla JS │ │
│ │ • Extract & scope CSS │ │
│ └─────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ COMPILED OUTPUT (vanilla JS) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ // Tanpa runtime framework! │ │
│ │ const button = element("button"); │ │
│ │ let count = 0; │ │
│ │ button.$$on("click", () => { │ │
│ │ count += 1; │ │
│ │ set_text(button, `Klik: ${count} kali`); │ │
│ │ }); │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ Bundle: ~2-5 KB vs React ~42 KB │
└───────────────────────────────────────────────────────┘
2. Setup Proyek Svelte
Svelte dapat di-setup dengan sangat cepat menggunakan Vite dan SvelteKit (framework full-stack untuk Svelte). Untuk belajar dasar, kita bisa mulai dengan proyek Svelte standalone.
# Opsi 1: Svelte standalone (belajar dasar) npm create vite@latest belajar-svelte -- --template svelte # Opsi 2: SvelteKit (full-stack, production-ready) npm create svelte@latest belajar-sveltekit # Masuk ke direktori cd belajar-svelte # Instal dependencies npm install # Jalankan development server npm run dev # Output: # VITE v5.x ready in 200 ms # ➜ Local: http://localhost:5173/ # Build untuk production npm run build # Preview build npm run preview
Struktur Proyek Svelte
belajar-svelte/ ├── node_modules/ ├── public/ │ └── favicon.png ├── src/ │ ├── lib/ ← Komponen & utility │ │ ├── Counter.svelte ← Komponen contoh │ │ └── stores.js ← Svelte stores │ ├── routes/ ← (SvelteKit) routing │ │ ├── +page.svelte ← Halaman utama │ │ ├── +layout.svelte ← Layout wrapper │ │ └── about/ │ │ └── +page.svelte ← /about │ ├── App.svelte ← Root component (standalone) │ ├── app.css ← Global styles │ └── main.js ← Entry point ├── index.html ├── package.json ├── svelte.config.js ← Svelte config └── vite.config.js ← Vite config
Svelte adalah compiler/UI framework. SvelteKit adalah framework full-stack yang dibangun di atas Svelte — menyediakan routing, SSR, API endpoints, dan banyak lagi. Untuk produksi, gunakan SvelteKit. Untuk belajar dasar-dasar Svelte, standalone sudah cukup.
3. Komponen Svelte
Setiap file .svelte adalah satu komponen. Komponen Svelte terdiri dari tiga bagian opsional: <script> (logika), markup (template HTML), dan <style> (CSS yang di-scope otomatis).
Struktur Komponen
<!-- Greeting.svelte -- Komponen Svelte pertama -->
<!-- 1. SCRIPT: Logika JavaScript -->
<script>
// Variabel yang dideklarasi di sini langsung reaktif
let nama = 'Budi';
let waktu = new Date().toLocaleTimeString('id-ID');
// Fungsi biasa
function gantiNama() {
const namaBaru = prompt('Masukkan nama:');
if (namaBaru) nama = namaBaru;
}
// Update waktu setiap detik
setInterval(() => {
waktu = new Date().toLocaleTimeString('id-ID');
}, 1000);
</script>
<!-- 2. MARKUP: Template HTML dengan Svelte syntax -->
<div class="greeting">
<h1>Halo, {nama}! 👋</h1>
<p>Waktu sekarang: {waktu}</p>
<button on:click={gantiNama}>
Ganti Nama
</button>
</div>
<!-- 3. STYLE: CSS yang otomatis di-scope ke komponen ini -->
<style>
/* Style ini HANYA berlaku untuk komponen ini */
/* Tidak perlu BEM, CSS Modules, atau styled-components */
.greeting {
padding: 24px;
background: #1e1e2e;
border-radius: 12px;
text-align: center;
}
h1 {
color: #cdd6f4;
font-size: 28px;
}
button {
margin-top: 12px;
padding: 10px 20px;
background: #6366f1;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
}
</style>
<!--
Svelte compiler mengubah class names menjadi unique identifiers.
Contoh: .greeting → .greeting.svelte-abc123
Sehingga style TIDAK bocor ke komponen lain!
-->
Komposisi Komponen
<!-- Badge.svelte -- Komponen kecil -->
<script>
export let text;
export let color = '#6366f1';
</script>
<span class="badge" style="background: {color}">
{text}
</span>
<style>
.badge {
display: inline-block;
padding: 2px 10px;
border-radius: 12px;
font-size: 12px;
color: white;
}
</style>
<!-- UserCard.svelte -- Komponen yang menggunakan Badge -->
<script>
import Badge from './Badge.svelte';
export let nama;
export let role = 'Member';
export let avatar = `https://ui-avatars.com/api/?name=${encodeURIComponent(nama)}`;
</script>
<div class="card">
<img src={avatar} alt={nama} />
<div class="info">
<h3>{nama}</h3>
<Badge text={role} />
</div>
</div>
<style>
.card {
background: #1e1e2e;
border: 1px solid #313244;
border-radius: 12px;
padding: 20px;
display: flex;
gap: 16px;
align-items: center;
}
img {
width: 56px;
height: 56px;
border-radius: 50%;
}
h3 { margin: 0 0 8px; color: #cdd6f4; }
</style>
<!-- App.svelte -- Menggunakan UserCard -->
<script>
import UserCard from './UserCard.svelte';
</script>
<div class="grid">
<UserCard nama="Budi Santoso" role="Admin" />
<UserCard nama="Sari Dewi" role="Developer" />
<UserCard nama="Andi Pratama" role="Designer" />
</div>
4. Reactivity & State
Fitur paling menarik dari Svelte adalah reaktivitas built-in. Di Svelte, cukup dengan mengubah nilai variabel, UI akan otomatis diperbarui. Tidak perlu hooks, tidak perlu setState — compiler Svelte yang menangani semuanya.
Reactive Variables
<script>
// ====== Reactive variables: cukup deklarasi dan ubah ======
let count = 0;
function increment() {
count += 1; // UI otomatis update!
}
function decrement() {
count -= 1;
}
function reset() {
count = 0;
}
// ====== Reactive declarations: $: ======
// Dijalankan ulang setiap kali dependensi berubah
$: doubled = count * 2;
$: tripled = count * 3;
$: isPositive = count > 0;
$: isNegative = count < 0;
// Reactive statement dengan block
$: {
console.log(`Count berubah menjadi: ${count}`);
console.log(`Doubled: ${doubled}, Tripled: ${tripled}`);
}
// Reactive if
$: if (count > 10) {
console.log('Count sudah lebih dari 10!');
}
// ====== Derived values ======
$: color = count > 0 ? '#a6e3a1' : count < 0 ? '#f38ba8' : '#cdd6f4';
$: label = count === 0 ? 'Nol' : count > 0 ? 'Positif' : 'Negatif';
</script>
<div class="counter" style="border-color: {color}">
<h2 style="color: {color}">{count}</h2>
<p>Doubled: {doubled} | Tripled: {tripled} | Status: {label}</p>
<div class="buttons">
<button on:click={decrement}>− Kurangi</button>
<button on:click={reset}>↺ Reset</button>
<button on:click={increment}>+ Tambah</button>
</div>
</div>
<style>
.counter {
text-align: center;
padding: 32px;
background: #1e1e2e;
border: 3px solid #313244;
border-radius: 16px;
transition: border-color 0.3s;
}
h2 { font-size: 64px; margin: 0; }
p { color: #a6adc8; margin: 8px 0 24px; }
.buttons { display: flex; gap: 8px; justify-content: center; }
button {
padding: 10px 20px;
border: 2px solid #45475a;
background: transparent;
color: #cdd6f4;
border-radius: 8px;
cursor: pointer;
font-size: 16px;
}
</style>
Reactive dengan Array & Object
<script>
// ====== Array reactivity ======
// Di Svelte, reassignment = reaktif
let items = [
{ id: 1, nama: 'Roti', harga: 15000 },
{ id: 2, nama: 'Susu', harga: 12000 },
{ id: 3, nama: 'Telur', harga: 25000 },
];
let namaBaru = '';
let hargaBaru = 0;
function tambahItem() {
if (!namaBaru.trim()) return;
// Push langsung reaktif di Svelte!
items = [...items, {
id: Date.now(),
nama: namaBaru,
harga: Number(hargaBaru)
}];
namaBaru = '';
hargaBaru = 0;
}
function hapusItem(id) {
// Filter membuat array baru → reaktif!
items = items.filter(item => item.id !== id);
}
// ====== Reactive derived ======
$: totalHarga = items.reduce((sum, item) => sum + item.harga, 0);
$: jumlahItem = items.length;
$: rataRata = jumlahItem > 0 ? totalHarga / jumlahItem : 0;
// Format rupiah
function formatRupiah(angka) {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0
}).format(angka);
}
</script>
<div class="belanja">
<h2>🛒 Keranjang Belanja</h2>
<!-- Input form -->
<form on:submit|preventDefault={tambahItem}>
<input bind:value={namaBaru} placeholder="Nama barang" />
<input bind:value={hargaBaru} type="number" placeholder="Harga" />
<button type="submit">Tambah</button>
</form>
<!-- Daftar items -->
{#if items.length > 0}
<table>
<thead>
<tr><th>No</th><th>Nama</th><th>Harga</th><th>Aksi</th></tr>
</thead>
<tbody>
{#each items as item, i (item.id)}
<tr>
<td>{i + 1}</td>
<td>{item.nama}</td>
<td>{formatRupiah(item.harga)}</td>
<td>
<button class="hapus" on:click={() => hapusItem(item.id)}>
🗑️
</button>
</td>
</tr>
{/each}
</tbody>
</table>
<div class="summary">
<p>Total: <strong>{formatRupiah(totalHarga)}</strong></p>
<p>Jumlah: {jumlahItem} item | Rata-rata: {formatRupiah(rataRata)}</p>
</div>
{:else}
<p class="empty">Keranjang kosong</p>
{/if}
</div>
5. Props & Events
Di Svelte, props dideklarasikan dengan export let dan events dikirim menggunakan createEventDispatcher atau on: event forwarding.
<!-- ====== TodoItem.svelte — Komponen dengan props & events ====== -->
<script>
import { createEventDispatcher } from 'svelte';
// Props dengan nilai default
export let todo;
export let showDate = false;
// Event dispatcher
const dispatch = createEventDispatcher();
function toggleComplete() {
dispatch('toggle', { id: todo.id });
}
function remove() {
dispatch('remove', { id: todo.id });
}
</script>
<div class="todo-item" class:completed={todo.selesai}>
<input
type="checkbox"
checked={todo.selesai}
on:change={toggleComplete}
/>
<span class="text">{todo.teks}</span>
{#if showDate}
<span class="date">{todo.tanggal}</span>
{/if}
<button class="delete" on:click={remove}>✕</button>
</div>
<style>
.todo-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
background: #1e1e2e;
border: 1px solid #313244;
border-radius: 8px;
transition: opacity 0.3s;
}
.completed { opacity: 0.5; }
.completed .text { text-decoration: line-through; }
.text { flex: 1; color: #cdd6f4; }
.date { font-size: 12px; color: #6c7086; }
.delete {
background: none;
border: none;
color: #f38ba8;
cursor: pointer;
font-size: 16px;
}
</style>
<!-- ====== TodoApp.svelte — Komponen induk ====== -->
<script>
import TodoItem from './TodoItem.svelte';
let todos = [
{ id: 1, teks: 'Belajar Svelte', selesai: false, tanggal: '26/06/2026' },
{ id: 2, teks: 'Buat proyek', selesai: false, tanggal: '26/06/2026' },
{ id: 3, teks: 'Baca dokumentasi', selesai: true, tanggal: '25/06/2026' },
];
let teksBaru = '';
function tambahTodo() {
if (!teksBaru.trim()) return;
todos = [...todos, {
id: Date.now(),
teks: teksBaru,
selesai: false,
tanggal: new Date().toLocaleDateString('id-ID'),
}];
teksBaru = '';
}
// Handler untuk event dari TodoItem
function handleToggle(e) {
todos = todos.map(t =>
t.id === e.detail.id ? { ...t, selesai: !t.selesai } : t
);
}
function handleRemove(e) {
todos = todos.filter(t => t.id !== e.detail.id);
}
$: sisaTugas = todos.filter(t => !t.selesai).length;
</script>
<div class="todo-app">
<h2>📝 Daftar Tugas ({sisaTugas} belum selesai)</h2>
<form on:submit|preventDefault={tambahTodo}>
<input bind:value={teksBaru} placeholder="Tambah tugas baru..." />
<button type="submit">Tambah</button>
</form>
<div class="todo-list">
{#each todos as todo (todo.id)}
<TodoItem
{todo}
showDate={true}
on:toggle={handleToggle}
on:remove={handleRemove}
/>
{/each}
</div>
</div>
6. Svelte Stores
Svelte Stores menyediakan cara untuk menyimpan data yang bisa diakses dari banyak komponen tanpa prop drilling. Svelte menyediakan tiga jenis store: writable, readable, dan derived.
// ====== stores.js — Definisikan stores ======
import { writable, readable, derived } from 'svelte/store';
// ====== 1. WRITABLE STORE — bisa dibaca dan ditulis ======
export const count = writable(0);
export const user = writable({
nama: '',
email: '',
isLoggedIn: false,
});
export const cartItems = writable([]);
export const theme = writable('dark'); // 'dark' atau 'light'
// Writable store dengan custom logic
function createTodoStore() {
const { subscribe, set, update } = writable([]);
return {
subscribe,
tambah: (teks) => update(items => [
...items,
{ id: Date.now(), teks, selesai: false }
]),
toggle: (id) => update(items =>
items.map(item =>
item.id === id ? { ...item, selesai: !item.selesai } : item
)
),
hapus: (id) => update(items =>
items.filter(item => item.id !== id)
),
reset: () => set([]),
};
}
export const todos = createTodoStore();
// ====== 2. READABLE STORE — hanya bisa dibaca ======
export const waktu = readable(new Date(), function start(set) {
const interval = setInterval(() => {
set(new Date());
}, 1000);
return function stop() {
clearInterval(interval);
};
});
export const windowWidth = readable(window.innerWidth, function start(set) {
function handleResize() {
set(window.innerWidth);
}
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
});
// ====== 3. DERIVED STORE — dihitung dari store lain ======
export const waktuFormatted = derived(waktu, ($waktu) =>
$waktu.toLocaleTimeString('id-ID')
);
export const tanggalFormatted = derived(waktu, ($waktu) =>
$waktu.toLocaleDateString('id-ID', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
})
);
export const jumlahBelanja = derived(cartItems, ($items) =>
$items.reduce((total, item) => total + item.jumlah, 0)
);
export const totalBelanja = derived(cartItems, ($items) =>
$items.reduce((total, item) => total + (item.harga * item.jumlah), 0)
);
export const isMobile = derived(windowWidth, ($width) => $width < 768);
export const activeTodos = derived(todos, ($todos) =>
$todos.filter(t => !t.selesai)
);
export const completedTodos = derived(todos, ($todos) =>
$todos.filter(t => t.selesai)
);
Menggunakan Stores di Komponen
<!-- ====== Menggunakan stores di komponen ====== -->
<script>
import { count, theme, waktuFormatted, activeTodos, todos } from './stores.js';
// ====== Cara 1: Auto-subscription dengan $ prefix ======
// Svelte otomatis subscribe/unsubscribe saat komponen mount/unmount
// $count langsung reactive!
// ====== Cara 2: Manual subscription ======
// import { get } from 'svelte/store';
// let currentValue = get(count); // sekali baca tanpa subscribe
</script>
<!-- Gunakan $ prefix untuk auto-subscribe -->
<div class="dashboard" class:dark={$theme === 'dark'}>
<h2>{$waktuFormatted}</h2>
<p>Counter: {$count}</p>
<!-- Tulis ke store langsung -->
<button on:click={() => $count++}>+</button>
<button on:click={() => $count--}>−</button>
<!-- Toggle theme -->
<button on:click={() => $theme = $theme === 'dark' ? 'light' : 'dark'}>
{$theme === 'dark' ? '☀️' : '🌙'} Ganti Tema
</button>
<!-- Derived store -->
<p>Tugas aktif: {$activeTodos.length}</p>
<!-- Custom store methods -->
<button on:click={() => todos.tambah('Tugas baru')}>
Tambah Todo
</button>
</div>
7. Transitions & Animations
Svelte memiliki sistem transitions built-in yang powerful. Anda bisa menambahkan animasi masuk/keluar pada elemen tanpa library animasi tambahan. Transition diterapkan menggunakan directive transition:, in:, dan out:.
<script>
import { fade, fly, slide, scale, blur, draw } from 'svelte/transition';
import { quintOut, elasticOut, bounceOut } from 'svelte/easing';
import { flip } from 'svelte/animate';
let visible = true;
let items = [
{ id: 1, teks: 'Belajar Svelte' },
{ id: 2, teks: 'Buat transisi' },
{ id: 3, teks: 'Tambah animasi' },
];
let nextId = 4;
function toggle() {
visible = !visible;
}
function addItem() {
items = [...items, { id: nextId++, teks: `Item baru #${nextId - 1}` }];
}
function removeItem(id) {
items = items.filter(i => i.id !== id);
}
</script>
<!-- Toggle visibility dengan fade -->
<button on:click={toggle}>
{visible ? 'Sembunyikan' : 'Tampilkan'}
</button>
{#if visible}
<!-- fade: transisi opacity -->
<div transition:fade={{ duration: 300 }}>
<p>Ini menghilang dengan fade!</p>
</div>
{/if}
{#if visible}
<!-- fly: masuk dari arah tertentu -->
<div transition:fly={{ y: 200, duration: 500 }}>
<p>Terbang dari bawah!</p>
</div>
{/if}
{#if visible}
<!-- slide: geser masuk/keluar -->
<div transition:slide={{ duration: 400 }}>
<p>Slide masuk dari atas!</p>
</div>
{/if}
{#if visible}
<!-- scale: membesar/mengecil -->
<div transition:scale={{ start: 0.5, duration: 300, easing: elasticOut }}>
<p>Scale dengan efek elastic!</p>
</div>
{/if}
{#if visible}
<!-- blur: efek blur -->
<div transition:blur={{ amount: 10, duration: 400 }}>
<p>Masuk dengan efek blur!</p>
</div>
{/if}
<!-- ====== Transitions dengan {#each} dan FLIP animation ====== -->
<button on:click={addItem}>+ Tambah Item</button>
{#each items as item (item.id)}
<div
animate:flip={{ duration: 300 }}
in:fly={{ x: -50, duration: 300 }}
out:fade={{ duration: 200 }}
class="list-item"
>
<span>{item.teks}</span>
<button on:click={() => removeItem(item.id)}>✕</button>
</div>
{/each}
<!-- ====== Custom CSS Transition ====== -->
<script>
function typewriter(node, { speed = 50 }) {
const text = node.textContent;
const duration = text.length / (speed * 0.001);
return {
duration,
tick: (t) => {
const i = Math.trunc(text.length * t);
node.textContent = text.slice(0, i);
}
};
}
</script>
{#if visible}
<p transition:typewriter={{ speed: 40 }}>
Efek mengetik dengan custom transition Svelte!
</p>
{/if}
8. Actions & Lifecycle
Actions adalah cara Svelte untuk menambahkan perilaku pada elemen DOM yang di-render. Mereka sangat berguna untuk interop dengan library pihak ketiga, tooltip, click-outside, lazy loading, dan banyak lagi. Lifecycle callbacks memungkinkan Anda menjalankan kode pada momen penting komponen.
<script>
import { onMount, onDestroy, beforeUpdate, afterUpdate, tick } from 'svelte';
// ====== ACTIONS ======
// Action: click outside detection
function clickOutside(node, callback) {
function handleClick(event) {
if (!node.contains(event.target)) {
callback();
}
}
document.addEventListener('click', handleClick, true);
return {
destroy() {
document.removeEventListener('click', handleClick, true);
}
};
}
// Action: tooltip
function tooltip(node, text) {
let tooltipEl;
function showTooltip() {
tooltipEl = document.createElement('div');
tooltipEl.className = 'tooltip';
tooltipEl.textContent = text;
document.body.appendChild(tooltipEl);
const rect = node.getBoundingClientRect();
tooltipEl.style.left = `${rect.left + rect.width / 2}px`;
tooltipEl.style.top = `${rect.top - 8}px`;
}
function hideTooltip() {
tooltipEl?.remove();
}
node.addEventListener('mouseenter', showTooltip);
node.addEventListener('mouseleave', hideTooltip);
return {
update(newText) {
text = newText;
if (tooltipEl) tooltipEl.textContent = newText;
},
destroy() {
node.removeEventListener('mouseenter', showTooltip);
node.removeEventListener('mouseleave', hideTooltip);
tooltipEl?.remove();
}
};
}
// Action: long press
function longpress(node, { duration = 500, callback }) {
let timer;
function start() {
timer = setTimeout(() => {
callback();
}, duration);
}
function cancel() {
clearTimeout(timer);
}
node.addEventListener('mousedown', start);
node.addEventListener('mouseup', cancel);
node.addEventListener('mouseleave', cancel);
return {
destroy() {
clearTimeout(timer);
node.removeEventListener('mousedown', start);
node.removeEventListener('mouseup', cancel);
node.removeEventListener('mouseleave', cancel);
}
};
}
// ====== LIFECYCLE ======
let data = [];
let containerEl;
let isLoading = true;
let isDropdownOpen = false;
// Dipanggil saat komponen pertama kali mount di DOM
onMount(async () => {
console.log('Komponen mounted!');
// Fetch data
const response = await fetch('/api/items');
data = await response.json();
isLoading = false;
// Mengembalikan fungsi cleanup (sama seperti onDestroy)
return () => {
console.log('Cleanup on unmount');
};
});
// Dipanggil saat komponen di-unmount
onDestroy(() => {
console.log('Komponen destroyed!');
// Cleanup: timers, subscriptions, event listeners, dll.
});
// Dipanggil SEBELUM DOM update
beforeUpdate(() => {
console.log('DOM akan di-update...');
// Berguna untuk menyimpan scroll position
});
// Dipanggil SETELAH DOM update
afterUpdate(() => {
console.log('DOM sudah di-update!');
// Berguna untuk scroll ke bottom, focus, dll.
});
// Tunggu sampai DOM selesai update
async function scrollToBottom() {
await tick(); // Tunggu DOM update selesai
containerEl.scrollTop = containerEl.scrollHeight;
}
</script>
<!-- Menggunakan actions dengan use: directive -->
<div
class="dropdown"
use:clickOutside={() => isDropdownOpen = false}
>
<button on:click={() => isDropdownOpen = !isDropdownOpen}>
Menu ▾
</button>
{#if isDropdownOpen}
<div class="dropdown-menu">
<a href="#">Profil</a>
<a href="#">Pengaturan</a>
<a href="#">Logout</a>
</div>
{/if}
</div>
<!-- Tooltip action -->
<button use:tooltip={'Klik tombol ini untuk aksi'}>
Hover saya
</button>
<!-- Long press action -->
<div
use:longpress={{ duration: 800, callback: () => alert('Long press!') }}
class="longpress-area"
>
Tekan dan tahan di sini selama 0.8 detik
</div>
9. SvelteKit Overview
SvelteKit adalah framework full-stack resmi untuk Svelte. Ia menyediakan file-based routing, server-side rendering (SSR), API endpoints, prerendering, dan banyak fitur production-ready lainnya. SvelteKit adalah cara yang direkomendasikan untuk membangun aplikasi web dengan Svelte.
// ====== Struktur Routes SvelteKit ======
src/routes/
├── +page.svelte → /
├── +layout.svelte → Layout wrapper untuk semua halaman
├── +error.svelte → Error page
├── +page.server.js → Server-side load function untuk /
├── about/
│ └── +page.svelte → /about
├── blog/
│ ├── +page.svelte → /blog
│ ├── +page.server.js → Load data blog dari server
│ └── [slug]/
│ ├── +page.svelte → /blog/:slug (dynamic route)
│ └── +page.server.js → Load artikel per slug
├── api/
│ └── users/
│ ├── +server.js → GET /api/users
│ └── [id]/
│ └── +server.js → GET/PUT/DELETE /api/users/:id
└── (auth)/
├── login/
│ └── +page.svelte → /login (group tanpa URL segment)
└── register/
└── +page.svelte → /register
<!-- ====== src/routes/+layout.svelte — Layout Utama ====== -->
<script>
import { page } from '$app/stores';
import Navbar from '$lib/components/Navbar.svelte';
</script>
<Navbar currentPath={$page.url.pathname} />
<main>
<!-- Slot: konten halaman di-render di sini -->
<slot />
</main>
<footer>
<p>© 2026 My App</p>
</footer>
<!-- ====== src/routes/blog/+page.svelte — Halaman Blog ====== -->
<script>
// Data dari +page.server.js di-props secara otomatis
export let data;
</script>
<h1>Blog</h1>
{#each data.posts as post}
<article>
<h2><a href="/blog/{post.slug}">{post.title}</a></h2>
<p>{post.excerpt}</p>
<time>{post.date}</time>
</article>
{/each}
// ====== src/routes/blog/+page.server.js — Server Load ======
import { db } from '$lib/server/database';
export async function load() {
const posts = await db.posts.findMany({
orderBy: { date: 'desc' },
take: 10,
});
return { posts };
}
// ====== src/routes/api/users/+server.js — API Endpoint ======
import { json } from '@sveltejs/kit';
import { db } from '$lib/server/database';
// GET /api/users
export async function GET({ url }) {
const limit = Number(url.searchParams.get('limit') || 10);
const users = await db.users.findMany({ take: limit });
return json(users);
}
// POST /api/users
export async function POST({ request }) {
const body = await request.json();
const user = await db.users.create({ data: body });
return json(user, { status: 201 });
}
// ====== src/routes/blog/[slug]/+page.server.js ======
import { error } from '@sveltejs/kit';
import { db } from '$lib/server/database';
export async function load({ params }) {
const post = await db.posts.findUnique({
where: { slug: params.slug }
});
if (!post) {
throw error(404, 'Artikel tidak ditemukan');
}
return { post };
}
10. Quiz: Uji Pemahamanmu!
Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang Svelte: