--- name: svelte description: Modern Svelte development for reactive web apps. Use when building Svelte components, managing state with stores, implementing real-time updates via WebSocket, or migrating from vanilla JS. Covers SvelteKit, TypeScript, and integration with Node.js backends. --- # Svelte Skill ## Overview This skill provides expertise for building reactive web applications with Svelte. It covers component architecture, the reactivity system, stores for state management, real-time updates with WebSockets, and SvelteKit for full-stack applications. ## Why Svelte ### Comparison with Vanilla JS | Aspect | Vanilla JS | Svelte | |--------|------------|--------| | Reactivity | Manual DOM updates | Automatic - `count++` just works | | Components | Template strings | Single-file components | | State | Global variables | Stores with subscriptions | | Bundle size | 0kb (but more code) | ~2kb runtime | | Learning curve | None | Gentle (closest to vanilla) | ### Key Benefits 1. **Compile-time magic** - No virtual DOM, compiles to efficient vanilla JS 2. **Less boilerplate** - `let count = 0` is reactive by default 3. **Built-in transitions** - `transition:fade` for animations 4. **Scoped CSS** - Styles in components don't leak 5. **Stores** - Simple reactive state that works with WebSockets ## Core Concepts ### Reactivity Svelte's reactivity is based on assignments: ```svelte ``` ### Array/Object Reactivity Svelte tracks assignments, not mutations: ```svelte ``` ### Component Structure Single-file components with script, markup, and style: ```svelte

{name}

Cash: £{cash}

{#if expanded} {/if}
``` ### Using Components ```svelte {#each players as player (player.id)}

Ships: {player.ships?.length ?? 0}

{/each} ``` ## Stores ### Writable Stores For shared state across components: ```javascript // stores/gameState.js import { writable, derived } from 'svelte/store'; // Create a writable store export const gameState = writable(null); // Derived stores compute from other stores export const currentPlayer = derived( gameState, $state => $state?.players?.[$state?.currentPlayerIndex] ); export const isMyTurn = derived( [gameState, currentPlayer], ([$state, $player]) => $player?.id === myPlayerId ); // Helper functions to update state export function updateGameState(newState) { gameState.set(newState); } export function updatePlayer(playerId, changes) { gameState.update(state => ({ ...state, players: { ...state.players, [playerId]: { ...state.players[playerId], ...changes } } })); } ``` ### Using Stores in Components ```svelte

Turn: {$gameState?.turn}

Current player: {$currentPlayer?.name}

{#if $isMyTurn} {:else}

Waiting for {$currentPlayer?.name}...

{/if}
``` ### Custom Stores Create stores with custom methods: ```javascript // stores/player.js import { writable } from 'svelte/store'; function createPlayerStore() { const { subscribe, set, update } = writable({ cash: 0, officers: 0, engineers: 0, gasCubes: { hydrogen: 0, helium: 0 } }); return { subscribe, set, reset: () => set({ cash: 0, officers: 0, engineers: 0, gasCubes: { hydrogen: 0, helium: 0 } }), addCash: (amount) => update(p => ({ ...p, cash: p.cash + amount })), spendCash: (amount) => update(p => ({ ...p, cash: p.cash - amount })), buyGas: (type, amount) => update(p => ({ ...p, gasCubes: { ...p.gasCubes, [type]: p.gasCubes[type] + amount } })) }; } export const player = createPlayerStore(); ``` ## Real-Time Updates with WebSocket ### Socket Store Pattern ```javascript // stores/socket.js import { writable, get } from 'svelte/store'; import { io } from 'socket.io-client'; import { gameState } from './gameState.js'; export const connected = writable(false); export const connectionError = writable(null); let socket = null; export function connect(serverUrl) { socket = io(serverUrl, { reconnection: true, reconnectionAttempts: 10, reconnectionDelay: 1000 }); socket.on('connect', () => { connected.set(true); connectionError.set(null); console.log('Connected to server'); }); socket.on('disconnect', () => { connected.set(false); }); socket.on('connect_error', (error) => { connectionError.set(error.message); }); // Game state updates from server socket.on('state-update', (newState) => { gameState.set(newState); }); socket.on('state-sync', (fullState) => { gameState.set(fullState); }); return socket; } export function joinGame(gameId, playerId) { if (socket) { socket.emit('join-game', { gameId, playerId }); } } export function sendAction(action) { if (socket) { socket.emit('game-action', action); } } export function disconnect() { if (socket) { socket.disconnect(); socket = null; connected.set(false); } } ``` ### Using Socket in Components ```svelte {#if !$connected}
Connecting to server...
{:else if !$gameState}
Loading game state...
{:else}

Turn {$gameState.turn}

Current player: {$currentPlayer?.name}

{/if} ``` ## Conditional Rendering and Loops ### If/Else Blocks ```svelte {#if loading} {:else if error} {:else if items.length === 0} {:else} {/if} ``` ### Each Blocks with Keys ```svelte {#each ships as ship (ship.id)} {:else}

No ships in hangar

{/each} ``` ### Await Blocks ```svelte {#await fetchGameState()}

Loading...

{:then state} {:catch error}

Error: {error.message}

{/await} ``` ## Transitions and Animations ### Built-in Transitions ```svelte {#if visible}
Fades in and out
{/if} {#if showNotification}
Notification!
{/if} {#each items as item (item.id)}
{item.name}
{/each} ``` ### Custom Transitions ```svelte {#if show}
Whoooosh!
{/if} ``` ## Event Handling ### DOM Events ```svelte
...
e.key === 'Enter' && submit()} /> ``` ### Component Events ```svelte console.log(e.detail.name)} /> ``` ## Bindings ### Two-Way Binding ```svelte {#each ['red', 'green', 'blue'] as color} {/each} ``` ### Element Bindings ```svelte
Size: {divWidth}x{divHeight}
``` ## SvelteKit ### Project Structure ``` my-app/ ├── src/ │ ├── lib/ # Shared components and utilities │ │ ├── components/ │ │ │ ├── PlayerCard.svelte │ │ │ └── GameBoard.svelte │ │ ├── stores/ │ │ │ ├── gameState.js │ │ │ └── socket.js │ │ └── utils/ │ ├── routes/ # File-based routing │ │ ├── +page.svelte # / │ │ ├── +layout.svelte # Shared layout │ │ ├── game/ │ │ │ ├── +page.svelte # /game │ │ │ └── [id]/ │ │ │ └── +page.svelte # /game/:id │ │ └── api/ # API routes │ │ └── games/ │ │ └── +server.js │ ├── app.html │ └── app.css ├── static/ # Static assets ├── svelte.config.js └── package.json ``` ### Page Load Functions ```javascript // routes/game/[id]/+page.js export async function load({ params, fetch }) { const response = await fetch(`/api/games/${params.id}`); if (!response.ok) { throw error(404, 'Game not found'); } const game = await response.json(); return { game, gameId: params.id }; } ``` ```svelte

Game: {game.name}

``` ### API Routes ```javascript // routes/api/games/+server.js import { json } from '@sveltejs/kit'; export async function GET({ url }) { const games = await db.getGames(); return json(games); } export async function POST({ request }) { const { name, playerId } = await request.json(); const game = await db.createGame(name, playerId); return json(game, { status: 201 }); } ``` ## TypeScript Support ```svelte ``` ## Migration from Vanilla JS ### Before (Vanilla) ```javascript // Vanilla JS pattern let gameState = null; const stateElement = document.getElementById('game-state'); function render() { stateElement.innerHTML = `

Turn ${gameState.turn}

Cash: £${gameState.players[userId].cash}

${gameState.players[userId].ships.map(ship => `
${ship.name}
`).join('')} `; } async function fetchState() { const res = await fetch(`/api/state/${gameId}`); gameState = await res.json(); render(); } // Poll every 2 seconds setInterval(fetchState, 2000); ``` ### After (Svelte) ```svelte {#if $gameState}

Turn {$gameState.turn}

Cash: £{player.cash}

{#each player.ships as ship (ship.id)}
{ship.name}
{/each} {:else}

Loading...

{/if} ``` ## Best Practices ### Component Organization ``` lib/components/ ├── ui/ # Generic reusable components │ ├── Button.svelte │ ├── Modal.svelte │ └── Tooltip.svelte ├── game/ # Game-specific components │ ├── GameBoard.svelte │ ├── PlayerPanel.svelte │ └── ShipCard.svelte └── layout/ # Layout components ├── Header.svelte └── Sidebar.svelte ``` ### Props and Events Naming ```svelte ``` ### Reactive Statement Order ```svelte ``` ### Avoiding Common Mistakes ```svelte ``` ## Testing Svelte Components ```javascript // PlayerCard.test.js import { render, fireEvent } from '@testing-library/svelte'; import PlayerCard from './PlayerCard.svelte'; describe('PlayerCard', () => { it('displays player name and cash', () => { const { getByText } = render(PlayerCard, { props: { name: 'Germany', cash: 15 } }); expect(getByText('Germany')).toBeInTheDocument(); expect(getByText('Cash: £15')).toBeInTheDocument(); }); it('dispatches select event on click', async () => { const { getByRole, component } = render(PlayerCard, { props: { name: 'Germany', cash: 15 } }); const selectHandler = vi.fn(); component.$on('select', selectHandler); await fireEvent.click(getByRole('button')); expect(selectHandler).toHaveBeenCalledWith( expect.objectContaining({ detail: { name: 'Germany' } }) ); }); }); ``` ## When This Skill Activates Use this skill when: - Building Svelte components - Managing state with Svelte stores - Implementing real-time updates via WebSocket - Migrating vanilla JS to Svelte - Setting up SvelteKit projects - Adding TypeScript to Svelte - Creating reactive UI patterns - Optimizing Svelte performance