Svelte i SvelteKit - Nowa era tworzenia aplikacji frontendowych
Svelte SvelteKit Nowa
frontendSvelte i SvelteKit - Nowa era tworzenia aplikacji frontendowych
Świat frontendu nie stoi w miejscu. Po latach dominacji React i Vue na scenę wkracza Svelte - framework, który fundamentalnie zmienia sposób myślenia o budowaniu interfejsów użytkownika. Zamiast działać w przeglądarce jak tradycyjne frameworki, Svelte przenosi ciężar pracy na etap kompilacji, generując zoptymalizowany, imperatywny kod JavaScript. W połączeniu ze SvelteKit - pełnoprawnym meta-frameworkiem do budowy aplikacji - otrzymujemy narzędzie, które łączy wyjątkową wydajność z niespotykaną prostotą kodu.
Kompilator zamiast Virtual DOM#
Tradycyjne frameworki jak React czy Vue wykorzystują Virtual DOM - abstrakcyjną reprezentację drzewa DOM w pamięci. Przy każdej zmianie stanu framework porównuje nowy Virtual DOM ze starym (diffing), a następnie aplikuje minimalne zmiany do prawdziwego DOM. Choć to podejście działa dobrze, generuje narzut wydajnościowy i wymaga dostarczenia kodu frameworka do przeglądarki.
Svelte odrzuca ten paradygmat. Zamiast interpretować komponenty w runtime, Svelte jest kompilatorem, który podczas budowania aplikacji przekształca deklaratywny kod komponentów w zoptymalizowany JavaScript. Ten wygenerowany kod bezpośrednio manipuluje DOM-em, bez żadnej warstwy pośredniej.
<!-- Svelte kompiluje to do natywnych operacji DOM -->
<script>
let count = $state(0);
function increment() {
count++;
}
</script>
<button onclick={increment}>
Kliknięto {count} razy
</button>
Powyższy komponent Svelte zostanie skompilowany do kodu, który bezpośrednio wywołuje element.textContent = ... i element.addEventListener(...). Nie ma tu żadnego Virtual DOM, żadnego diffingu - jedynie precyzyjne, chirurgiczne aktualizacje DOM.
Korzyści podejścia kompilacyjnego#
- Mniejsze bundle'e - przeglądarka nie musi pobierać kodu frameworka (brak runtime)
- Szybsze renderowanie - brak narzutu związanego z Virtual DOM i reconciliation
- Niższe zużycie pamięci - nie trzeba utrzymywać drzewa Virtual DOM w pamięci
- Lepsza wydajność na słabszych urządzeniach - mniej obliczeń w runtime
System reaktywności w Svelte 5 - Runes#
Svelte 5 wprowadził rewolucyjny system reaktywności oparty na Runes - specjalnych sygnałach kompilatorowych, które rozpoczynają się od znaku $. Runes zastąpiły wcześniejszy, oparty na przypisaniach system reaktywności, oferując większą precyzję, lepszą kompozycję i pełną kompatybilność z TypeScript.
$state - reaktywny stan#
Rune $state deklaruje reaktywną zmienną stanu. Każda zmiana tej zmiennej automatycznie aktualizuje wszystkie miejsca w DOM, które od niej zależą.
<script>
let name = $state('Svelte');
let items = $state([1, 2, 3]);
// Obiekty i tablice są głęboko reaktywne
function addItem() {
items.push(items.length + 1); // automatycznie wykryte!
}
</script>
<h1>Witaj, {name}!</h1>
<p>Elementy: {items.join(', ')}</p>
<button onclick={addItem}>Dodaj element</button>
$derived - wartości pochodne#
Rune $derived tworzy wartość obliczaną na podstawie innych stanów reaktywnych. Svelte automatycznie śledzi zależności i przelicza wartość tylko wtedy, gdy zależności się zmienią.
<script>
let width = $state(10);
let height = $state(20);
// Automatycznie przeliczane gdy width lub height się zmieni
let area = $derived(width * height);
// Dla bardziej złożonych obliczeń
let description = $derived.by(() => {
if (area > 100) return 'Duży prostokąt';
if (area > 50) return 'Średni prostokąt';
return 'Mały prostokąt';
});
</script>
<p>Powierzchnia: {area} ({description})</p>
$effect - efekty uboczne#
Rune $effect pozwala reagować na zmiany stanu i wykonywać efekty uboczne, takie jak logowanie, synchronizacja z zewnętrznymi API czy manipulacja DOM.
<script>
let query = $state('');
let results = $state([]);
$effect(() => {
// Automatycznie śledzi zależność od `query`
if (query.length < 3) {
results = [];
return;
}
const controller = new AbortController();
fetch(`/api/search?q=${query}`, { signal: controller.signal })
.then(r => r.json())
.then(data => { results = data; });
// Funkcja czyszcząca
return () => controller.abort();
});
</script>
<input bind:value={query} placeholder="Szukaj..." />
{#each results as result}
<p>{result.title}</p>
{/each}
$props - właściwości komponentów#
Rune $props definiuje właściwości przyjmowane przez komponent, z pełnym wsparciem dla destrukturyzacji i wartości domyślnych.
<!-- Button.svelte -->
<script>
let {
variant = 'primary',
size = 'md',
disabled = false,
children,
onclick,
...rest
} = $props();
</script>
<button
class="btn btn-{variant} btn-{size}"
{disabled}
{onclick}
{...rest}
>
{@render children()}
</button>
Anatomia komponentu Svelte#
Komponenty Svelte to pliki .svelte zawierające trzy sekcje: logikę (<script>), szablon HTML i style (<style>). Style są domyślnie scoped - dotyczą wyłącznie danego komponentu.
<script lang="ts">
import type { User } from '$lib/types';
let { user }: { user: User } = $props();
let isExpanded = $state(false);
let initials = $derived(
user.firstName[0] + user.lastName[0]
);
</script>
<div class="user-card" class:expanded={isExpanded}>
<div class="avatar">{initials}</div>
<div class="info">
<h3>{user.firstName} {user.lastName}</h3>
<p>{user.email}</p>
</div>
<button onclick={() => isExpanded = !isExpanded}>
{isExpanded ? 'Zwiń' : 'Rozwiń'}
</button>
{#if isExpanded}
<div class="details">
<p>Dołączył: {user.joinDate.toLocaleDateString('pl-PL')}</p>
<p>Rola: {user.role}</p>
</div>
{/if}
</div>
<style>
.user-card {
display: flex;
flex-wrap: wrap;
gap: 1rem;
padding: 1rem;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
transition: box-shadow 0.2s;
}
.user-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.avatar {
width: 48px;
height: 48px;
border-radius: 50%;
background: #6366f1;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
.expanded {
border-color: #6366f1;
}
</style>
Stores - globalny stan aplikacji#
Svelte oferuje wbudowany system zarządzania globalnym stanem za pomocą stores. Stores to reaktywne kontenery danych, które mogą być współdzielone między komponentami.
// src/lib/stores/cart.ts
import { writable, derived } from 'svelte/store';
interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
}
export const cartItems = writable<CartItem[]>([]);
export const cartTotal = derived(cartItems, ($items) =>
$items.reduce((sum, item) => sum + item.price * item.quantity, 0)
);
export const cartCount = derived(cartItems, ($items) =>
$items.reduce((sum, item) => sum + item.quantity, 0)
);
export function addToCart(product: { id: string; name: string; price: number }) {
cartItems.update(items => {
const existing = items.find(i => i.id === product.id);
if (existing) {
existing.quantity++;
return [...items];
}
return [...items, { ...product, quantity: 1 }];
});
}
export function removeFromCart(id: string) {
cartItems.update(items => items.filter(i => i.id !== id));
}
Użycie w komponencie ze składnią autosubscribe ($):
<script>
import { cartItems, cartTotal, cartCount, removeFromCart } from '$lib/stores/cart';
</script>
<h2>Koszyk ({$cartCount} produktów)</h2>
{#each $cartItems as item (item.id)}
<div class="cart-item">
<span>{item.name} x{item.quantity}</span>
<span>{(item.price * item.quantity).toFixed(2)} zł</span>
<button onclick={() => removeFromCart(item.id)}>Usuń</button>
</div>
{/each}
<p class="total">Suma: {$cartTotal.toFixed(2)} zł</p>
SvelteKit - pełny meta-framework#
SvelteKit to oficjalny meta-framework dla Svelte, analogiczny do Next.js dla React czy Nuxt dla Vue. Oferuje wszystko, czego potrzebujesz do budowy nowoczesnych aplikacji web: routing oparty na systemie plików, renderowanie po stronie serwera, generowanie statyczne i wiele więcej.
Routing oparty na plikach#
SvelteKit wykorzystuje system routingu oparty na strukturze katalogów w folderze src/routes. Każdy katalog z plikiem +page.svelte tworzy nową trasę.
src/routes/
├── +page.svelte → /
├── +layout.svelte → Layout dla całej aplikacji
├── about/
│ └── +page.svelte → /about
├── blog/
│ ├── +page.svelte → /blog
│ ├── +page.server.ts → Load function dla /blog
│ └── [slug]/
│ ├── +page.svelte → /blog/:slug
│ └── +page.server.ts
├── api/
│ └── products/
│ └── +server.ts → API endpoint: /api/products
└── (auth)/
├── +layout.svelte → Layout grupowy (bez wpływu na URL)
├── login/
│ └── +page.svelte → /login
└── register/
└── +page.svelte → /register
Layouts - współdzielone szablony#
Layouts w SvelteKit pozwalają definiować wspólną strukturę strony (nagłówek, nawigacja, stopka), która jest współdzielona przez podstrony.
<!-- src/routes/+layout.svelte -->
<script>
import Header from '$lib/components/Header.svelte';
import Footer from '$lib/components/Footer.svelte';
let { children } = $props();
</script>
<div class="app">
<Header />
<main>
{@render children()}
</main>
<Footer />
</div>
<style>
.app {
display: flex;
flex-direction: column;
min-height: 100vh;
}
main {
flex: 1;
padding: 2rem;
max-width: 1200px;
margin: 0 auto;
}
</style>
Load functions - ładowanie danych#
Funkcje load to serce mechanizmu pobierania danych w SvelteKit. Mogą działać zarówno na serwerze (+page.server.ts), jak i uniwersalnie (+page.ts).
// src/routes/blog/+page.server.ts
import type { PageServerLoad } from './$types';
import { db } from '$lib/server/database';
export const load: PageServerLoad = async ({ url, locals }) => {
const page = Number(url.searchParams.get('page')) || 1;
const limit = 10;
const posts = await db.post.findMany({
where: { published: true },
orderBy: { createdAt: 'desc' },
skip: (page - 1) * limit,
take: limit,
select: {
slug: true,
title: true,
excerpt: true,
createdAt: true,
author: { select: { name: true } }
}
});
const total = await db.post.count({ where: { published: true } });
return {
posts,
pagination: {
page,
totalPages: Math.ceil(total / limit),
total
}
};
};
Dane zwrócone przez load są automatycznie dostępne w komponencie strony:
<!-- src/routes/blog/+page.svelte -->
<script lang="ts">
import type { PageData } from './$types';
import PostCard from '$lib/components/PostCard.svelte';
let { data }: { data: PageData } = $props();
</script>
<h1>Blog</h1>
<div class="posts-grid">
{#each data.posts as post (post.slug)}
<PostCard {post} />
{/each}
</div>
{#if data.pagination.totalPages > 1}
<nav class="pagination">
{#each Array(data.pagination.totalPages) as _, i}
<a
href="/blog?page={i + 1}"
class:active={data.pagination.page === i + 1}
>
{i + 1}
</a>
{/each}
</nav>
{/if}
Strategie renderowania: SSR, SSG i CSR#
SvelteKit oferuje elastyczny wybór strategii renderowania na poziomie poszczególnych tras.
// SSR (domyślnie) - renderowanie na serwerze przy każdym żądaniu
export const ssr = true;
// SSG - statyczne generowanie w czasie budowania
export const prerender = true;
// CSR - renderowanie wyłącznie po stronie klienta
export const ssr = false;
export const csr = true;
// Hybrydowe - statyczna strona z hydracją klienta
export const prerender = true;
export const csr = true;
Form Actions - obsługa formularzy#
Form Actions to elegancki mechanizm SvelteKit do obsługi formularzy po stronie serwera, działający bez JavaScript (progressive enhancement).
// src/routes/contact/+page.server.ts
import type { Actions } from './$types';
import { fail } from '@sveltejs/kit';
import { z } from 'zod';
const contactSchema = z.object({
name: z.string().min(2, 'Imię musi mieć co najmniej 2 znaki'),
email: z.string().email('Nieprawidłowy adres email'),
message: z.string().min(10, 'Wiadomość musi mieć co najmniej 10 znaków')
});
export const actions: Actions = {
default: async ({ request }) => {
const formData = await request.formData();
const data = {
name: formData.get('name') as string,
email: formData.get('email') as string,
message: formData.get('message') as string
};
const result = contactSchema.safeParse(data);
if (!result.success) {
return fail(400, {
data,
errors: result.error.flatten().fieldErrors
});
}
await sendEmail(result.data);
return { success: true };
}
};
<!-- src/routes/contact/+page.svelte -->
<script lang="ts">
import { enhance } from '$app/forms';
import type { ActionData } from './$types';
let { form }: { form: ActionData } = $props();
</script>
<form method="POST" use:enhance>
<label>
Imię
<input name="name" value={form?.data?.name ?? ''} />
{#if form?.errors?.name}
<span class="error">{form.errors.name[0]}</span>
{/if}
</label>
<label>
Email
<input name="email" type="email" value={form?.data?.email ?? ''} />
{#if form?.errors?.email}
<span class="error">{form.errors.email[0]}</span>
{/if}
</label>
<label>
Wiadomość
<textarea name="message">{form?.data?.message ?? ''}</textarea>
{#if form?.errors?.message}
<span class="error">{form.errors.message[0]}</span>
{/if}
</label>
<button type="submit">Wyślij</button>
{#if form?.success}
<p class="success">Wiadomość została wysłana!</p>
{/if}
</form>
API Routes#
SvelteKit pozwala tworzyć endpointy API za pomocą plików +server.ts:
// src/routes/api/products/+server.ts
import { json, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { db } from '$lib/server/database';
export const GET: RequestHandler = async ({ url }) => {
const category = url.searchParams.get('category');
const page = Number(url.searchParams.get('page')) || 1;
const products = await db.product.findMany({
where: category ? { category } : undefined,
skip: (page - 1) * 20,
take: 20
});
return json(products);
};
export const POST: RequestHandler = async ({ request, locals }) => {
if (!locals.user?.isAdmin) {
throw error(403, 'Brak uprawnień');
}
const body = await request.json();
const product = await db.product.create({ data: body });
return json(product, { status: 201 });
};
Przejścia i animacje#
Jedną z najbardziej wyróżniających cech Svelte jest wbudowany system przejść i animacji. Zamiast sięgać po zewnętrzne biblioteki, możesz elegancko animować elementy za pomocą dyrektyw.
<script>
import { fade, fly, slide, scale } from 'svelte/transition';
import { flip } from 'svelte/animate';
import { quintOut } from 'svelte/easing';
let items = $state([
{ id: 1, text: 'Pierwszy' },
{ id: 2, text: 'Drugi' },
{ id: 3, text: 'Trzeci' }
]);
let showPanel = $state(false);
function addItem() {
items = [...items, {
id: Date.now(),
text: `Element ${items.length + 1}`
}];
}
function removeItem(id: number) {
items = items.filter(i => i.id !== id);
}
</script>
<button onclick={() => showPanel = !showPanel}>
Pokaż panel
</button>
{#if showPanel}
<div transition:fly={{ y: -20, duration: 300 }}>
<p>Panel z animacją fly</p>
</div>
{/if}
<button onclick={addItem}>Dodaj element</button>
<ul>
{#each items as item (item.id)}
<li
animate:flip={{ duration: 300 }}
in:scale={{ duration: 200, easing: quintOut }}
out:fade={{ duration: 150 }}
>
{item.text}
<button onclick={() => removeItem(item.id)}>×</button>
</li>
{/each}
</ul>
Wsparcie dla TypeScript#
Svelte i SvelteKit oferują doskonałe wsparcie dla TypeScript. Typy generowane automatycznie przez SvelteKit zapewniają bezpieczeństwo typów dla load functions, form actions i parametrów tras.
<script lang="ts">
import type { PageData } from './$types';
interface Product {
id: string;
name: string;
price: number;
inStock: boolean;
}
let { data }: { data: PageData } = $props();
let searchQuery = $state('');
let sortBy = $state<'name' | 'price'>('name');
let filteredProducts = $derived(
data.products
.filter((p: Product) =>
p.name.toLowerCase().includes(searchQuery.toLowerCase())
)
.sort((a: Product, b: Product) =>
sortBy === 'price' ? a.price - b.price : a.name.localeCompare(b.name)
)
);
</script>
Svelte vs React vs Vue - porównanie#
Rozmiar bundle'a#
| Framework | Minimalny bundle (gzip) | |-----------|------------------------| | Svelte 5 | ~2 KB | | Vue 3 | ~33 KB | | React 18 | ~42 KB |
Svelte generuje znacząco mniejsze bundle'e, ponieważ kod frameworka jest wkompilowany - przeglądarka pobiera tylko to, co jest faktycznie używane.
Developer Experience#
| Cecha | Svelte | React | Vue |
|------------------------|-----------------|------------------|------------------|
| Krzywa uczenia się | Łagodna | Średnia | Łagodna |
| Boilerplate | Minimalny | Znaczny (hooks) | Umiarkowany |
| Wbudowane animacje | Tak | Nie | Tak (ograniczone)|
| Scoped CSS | Domyślnie | CSS Modules/CSS-in-JS | Scoped atrybut|
| Wiązanie dwukierunkowe | bind:value | Manualne | v-model |
| Zarządzanie stanem | Runes + Stores | useState/Redux | ref/Pinia |
Wydajność runtime#
Svelte regularnie zajmuje czołowe pozycje w benchmarkach wydajności frontendowych frameworków. Brak narzutu Virtual DOM i kompilacja do imperatywnego kodu przekłada się na:
- Szybsze aktualizacje DOM
- Niższe zużycie pamięci
- Lepszą responsywność na słabszych urządzeniach
- Szybszy Time to Interactive (TTI)
Kiedy wybrać Svelte i SvelteKit?#
Svelte sprawdzi się doskonale w następujących scenariuszach:
- Aplikacje wymagające najwyższej wydajności - sklepy internetowe, dashboardy, aplikacje real-time
- Projekty z ograniczonym budżetem na transfer danych - aplikacje mobilne, rynki rozwijające się
- Strony z intensywnymi animacjami - wbudowany system przejść eliminuje potrzebę zewnętrznych bibliotek
- MVP i prototypy - minimalna ilość boilerplate'u przyspiesza development
- Projekty statyczne z elementami interaktywnymi - SSG w SvelteKit w połączeniu z wyspami interaktywności
- Aplikacje full-stack - SvelteKit z form actions i API routes eliminuje potrzebę oddzielnego backendu
Kiedy rozważyć alternatywy?#
- Ogromny ekosystem bibliotek UI jest kluczowy - React ma największy wybór gotowych komponentów
- Zespół ma bogate doświadczenie z React/Vue - koszt przeszkolenia może być czynnikiem
- Wymagana jest integracja z React Native - Svelte nie ma odpowiednika dla aplikacji mobilnych
Podsumowanie#
Svelte i SvelteKit reprezentują nową filozofię budowania aplikacji frontendowych. Podejście kompilacyjne, intuicyjny system reaktywności oparty na Runes, wbudowane animacje i elegancki routing SvelteKit tworzą ekosystem, który jest jednocześnie potężny i prosty w użyciu.
Rosnąca popularność Svelte, coraz większa baza komponentów i narzędzi, a także wsparcie ze strony Vercel (które zatrudniło twórcę Svelte - Richa Harrisa) świadczą o tym, że Svelte to nie chwilowy trend, lecz technologia, która na stałe zagościła w krajobrazie nowoczesnego frontendu.
Potrzebujesz nowoczesnej aplikacji frontendowej?#
W MDS Software Solutions Group tworzymy wydajne aplikacje webowe z wykorzystaniem najnowocześniejszych technologii, w tym Svelte i SvelteKit. Oferujemy:
- Budowę aplikacji webowych od podstaw w Svelte/SvelteKit
- Migrację istniejących projektów z React lub Vue do Svelte
- Optymalizację wydajności istniejących aplikacji
- Konsultacje technologiczne i dobór najlepszego stosu technologicznego
- Wsparcie i utrzymanie aplikacji produkcyjnych
Skontaktuj się z nami, aby omówić Twój projekt i odkryć, jak Svelte może przyspieszyć rozwój Twojej aplikacji!
Zespół ekspertów programistycznych specjalizujących się w nowoczesnych technologiach webowych.