Svelte 치트시트
Svelte - Cybernetically Enhanced Web Apps
Svelte는 사용자 인터페이스를 구축하는 혁신적인 새로운 접근 방식입니다. React와 Vue와 같은 전통적인 프레임워크가 브라우저에서 대부분의 작업을 수행하는 반면, Svelte는 앱을 빌드할 때 발생하는 컴파일 단계로 해당 작업을 이동시킵니다.
(This appears to be an empty div, so no translation is needed)Would you like me to proceed with translating the rest of the document once you provide the missing text?```bash
Node.js version requirements
node —version # Should be 16 or later
Check npm version
npm —version
Update npm if needed
npm install -g npm@latest
### Create Svelte Project
```bash
# Create new SvelteKit project (recommended)
npm create svelte@latest my-app
# Create a basic Svelte project (without SvelteKit)
npm create vite@latest my-app -- --template svelte
# Navigate to project
cd my-app
# Install dependencies
npm install
# Start development server
npm run dev
Manual Installation (Vite)
# Create project directory
mkdir my-svelte-project
cd my-svelte-project
# Initialize package.json
npm init -y
# Install Vite and Svelte plugin
npm install --save-dev vite @sveltejs/vite-plugin-svelte
# Install Svelte
npm install svelte
# Create vite.config.js
// vite.config.js
import { defineConfig } from 'vite';
import { svelte } from '@sveltejs/vite-plugin-svelte';
export default defineConfig({
plugins: [svelte()]
});
Package.json Scripts
{
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json"
}
}
Project Creation
SvelteKit Project Setup
# Interactive setup
npm create svelte@latest my-app
# Select options:
# - Skeleton project (basic setup)
# - Demo app (with examples)
# - Library project
# Add TypeScript
# Add ESLint
# Add Prettier
# Add Playwright for browser testing
# Add Vitest for unit testing
Development Commands
# Start development server
npm run dev
# Start with specific port
npm run dev -- --port 3000
# Start with specific host
npm run dev -- --host 0.0.0.0
# Start with open browser
npm run dev -- --open
Build Commands
# Build for production
npm run build
# Preview production build
npm run preview
# Check for issues
npm run check
Project Structure
SvelteKit Project Structure
my-sveltekit-app/
├── src/
│ ├── lib/ # Library code (components, utils)
│ │ ├── components/ # Reusable components
│ │ └── utils/ # Utility functions
│ ├── routes/ # File-based routing
│ │ ├── +page.svelte # Homepage
│ │ ├── about/ # /about route
│ │ │ └── +page.svelte
│ │ └── blog/
│ │ ├── +page.svelte # /blog index
│ │ └── [slug]/
│ │ └── +page.svelte
│ ├── app.html # HTML template
│ └── app.d.ts # TypeScript declarations
├── static/ # Static assets
├── tests/ # Test files
├── package.json
├── svelte.config.js # SvelteKit configuration
└── tsconfig.json
Vite Project Structure
my-vite-svelte-app/
├── public/ # Static assets
├── src/
│ ├── assets/ # Assets
│ ├── lib/ # Svelte components
│ └── main.js # App entry point
├── index.html # HTML template
├── package.json
└── vite.config.js
Component Syntax
Basic Component
<!-- src/lib/components/Greeting.svelte -->
<script>
// Component logic
let name = 'World';
</script>
<main>
<h1>Hello, {name}!</h1>
<p>This is a Svelte component.</p>
</main>
<style>
/* Scoped styles */
main {
text-align: center;
padding: 2rem;
}
h1 {
color: #ff3e00;
font-size: 2.5rem;
}
p {
color: #333;
}
</style>
Component with Props
<!-- src/lib/components/Welcome.svelte -->
<script>
export let name;
export let message = 'Welcome to our app!';
</script>
<div>
<h2>Hello, {name}!</h2>
<p>{message}</p>
</div>
<!-- Usage -->
<!-- <Welcome name="John" /> -->
<!-- <Welcome name="Jane" message="Glad to have you here!" /> -->
Component with Logic
<!-- src/lib/components/Counter.svelte -->
<script>
let count = 0;
function increment() {
count += 1;
}
function decrement() {
count -= 1;
}
function reset() {
count = 0;
}
</script>
<div class="counter">
<h3>Counter</h3>
<p>Count: {count}</p>
<div class="buttons">
<button on:click={decrement}>-</button>
<button on:click={increment}>+</button>
<button on:click={reset}>Reset</button>
</div>
</div>
<style>
.counter {
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 1rem;
margin: 1rem 0;
text-align: center;
}
.buttons {
display: flex;
gap: 0.5rem;
justify-content: center;
margin-top: 1rem;
}
</style>
Reactivity
Reactive Assignments
<script>
let count = 0;
function handleClick() {
// Svelte automatically detects this assignment is reactive
count += 1;
}
</script>
<button on:click={handleClick}>
Clicked {count} {count === 1 ? 'time' : 'times'}
</button>
Reactive Declarations
<script>
let count = 0;
// This will be re-evaluated whenever `count` changes
$: doubled = count * 2;
$: quadrupled = doubled * 2;
function handleClick() {
count += 1;
}
</script>
<p>{count} * 2 = {doubled}</p>
<p>{doubled} * 2 = {quadrupled}</p>
<button on:click={handleClick}>Increment</button>
Reactive Statements
<script>
let count = 0;
// This block of code runs whenever `count` changes
$: {
console.log(`The count is ${count}`);
if (count >= 10) {
alert('Count is 10 or more!');
}
}
function handleClick() {
count += 1;
}
</script>
<button on:click={handleClick}>Count: {count}</button>
Updating Arrays and Objects
<script>
let numbers = [1, 2, 3];
let user = { name: 'John', age: 30 };
function addNumber() {
// To trigger reactivity, you must assign to the variable
numbers = [...numbers, numbers.length + 1];
}
function changeName() {
user.name = 'Jane';
// This won't trigger reactivity on its own
// To make it reactive, reassign the object
user = user;
}
</script>
<p>Numbers: {numbers.join(', ')}</p>
<button on:click={addNumber}>Add Number</button>
<p>User: {user.name}, {user.age}</p>
<button on:click={changeName}>Change Name</button>
Props
Declaring Props
<script>
// Declare props with `export let`
export let name;
export let age = 25; // Default value
export let tags = [];
export let user = { id: 1, name: 'Guest' };
// Renaming props
export let title as heading;
</script>
<h2>{heading}</h2>
<p>Welcome, {name} ({age})</p>
{#if tags.length > 0}
<p>Tags: {tags.join(', ')}</p>
{/if}
Passing Props
<!-- ParentComponent.svelte -->
<script>
import UserProfile from './UserProfile.svelte';
let user = {
name: 'John Doe',
age: 30,
tags: ['developer', 'svelte']
};
</script>
<UserProfile
name={user.name}
age={user.age}
tags={user.tags}
title="User Profile"
/>
<!-- Shorthand for passing props -->
<UserProfile {...user} title="User Profile" />
Type Checking with TypeScript
<script lang="ts">
export let name: string;
export let age: number;
export let active: boolean = false;
export let onSave: (data: any) => void;
</script>
<p>{name} is {age} years old.</p>
<button on:click={() => onSave({ name, age })}>
Save
</button>
Logic and Templating
Conditional Rendering
<script>
let loggedIn = false;
let user = { name: 'John' };
</script>
{#if loggedIn}
<p>Welcome back, {user.name}!</p>
{:else}
<p>Please log in.</p>
{/if}
{#if user.role === 'admin'}
<p>Admin controls</p>
{:else if user.role === 'editor'}
<p>Editor controls</p>
{:else}
<p>User controls</p>
{/if}
Looping
<script>
let cats = [
{ id: '1', name: 'Whiskers' },
{ id: '2', name: 'Mittens' },
{ id: '3', name: 'Shadow' }
];
</script>
<ul>
{#each cats as cat (cat.id)}
<li>{cat.name}</li>
{/each}
</ul>
<!-- With index -->
<ul>
{#each cats as cat, i}
<li>{i + 1}: {cat.name}</li>
{/each}
</ul>
<!-- With else block -->
{#each cats as cat}
<p>{cat.name}</p>
{:else}
<p>No cats found.</p>
{/each}
Await Blocks
<script>
async function getPosts() {
const res = await fetch('https://api.example.com/posts');
if (!res.ok) throw new Error('Failed to fetch posts');
return await res.json();
}
let promise = getPosts();
</script>
{#await promise}
<p>Loading posts...</p>
{:then posts}
<ul>
{#each posts as post}
<li>{post.title}</li>
{/each}
</ul>
{:catch error}
<p>Error: {error.message}</p>
{/await}
HTML Rendering
<script>
let htmlContent = '<h1>This is raw HTML</h1><p>Use with caution.</p>';
</script>
<!-- Render raw HTML -->
{@html htmlContent}
<!-- Debugging -->
{@debug count, user}
Events
DOM Events
<script>
function handleClick() {
alert('Button clicked!');
}
function handleInput(event) {
console.log('Input value:', event.target.value);
}
</script>
<button on:click={handleClick}>Click me</button>
<input on:input={handleInput} placeholder="Type something" />
<!-- Inline handler -->
<button on:click={() => console.log('Clicked!')}>Inline</button>
Event Modifiers
<!-- Prevent default action -->
<form on:submit|preventDefault={handleSubmit}></form>
<!-- Stop event propagation -->
<div on:click={handleOuter}>
<button on:click|stopPropagation={handleInner}>Inner</button>
</div>
<!-- Run handler only once -->
<button on:click|once={doSomething}>Once</button>
<!-- Run handler if event is not passive -->
<div on:wheel|passive={handleWheel}></div>
<!-- Run handler during capture phase -->
<div on:click|capture={handleCapture}></div>
<!-- Self modifier -->
<div on:click|self={handleSelf}></div>
Component Events
<!-- InnerComponent.svelte -->
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
function notify() {
dispatch('message', {
text: 'Hello from the inner component!'
});
}
</script>
<button on:click={notify}>Notify Parent</button>
<!-- ParentComponent.svelte -->
<script>
import InnerComponent from './InnerComponent.svelte';
function handleMessage(event) {
alert(event.detail.text);
}
</script>
<InnerComponent on:message={handleMessage} />
Event Forwarding
<!-- CustomButton.svelte -->
<button on:click>
<slot />
</button>
<!-- ParentComponent.svelte -->
<script>
import CustomButton from './CustomButton.svelte';
function handleClick() {
console.log('Custom button clicked!');
}
</script>
<CustomButton on:click={handleClick}>Click me</CustomButton>
Bindings
Value Bindings
<script>
let name = 'John';
let message = 'Hello';
let selected = 'option2';
let checked = true;
let value = 5;
</script>
<!-- Text input -->
<input bind:value={name} />
<!-- Textarea -->
<textarea bind:value={message}></textarea>
<!-- Select -->
<select bind:value={selected}>
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
</select>
<!-- Checkbox -->
<input type="checkbox" bind:checked={checked} />
<!-- Range slider -->
<input type="range" bind:value min="0" max="10" />
Group Bindings
<script>
let flavors = ['Vanilla', 'Chocolate', 'Strawberry'];
let selectedFlavors = ['Vanilla'];
let scoop = 'one';
</script>
<!-- Checkbox group -->
{#each flavors as flavor}
<label>
<input type="checkbox" bind:group={selectedFlavors} value={flavor} />
{flavor}
</label>
{/each}
<!-- Radio group -->
<label>
<input type="radio" bind:group={scoop} value="one" /> One scoop
</label>
<label>
<input type="radio" bind:group={scoop} value="two" /> Two scoops
</label>
Component Bindings
<!-- InnerComponent.svelte -->
<script>
export let value;
</script>
<input bind:value />
<!-- ParentComponent.svelte -->
<script>
import InnerComponent from './InnerComponent.svelte';
let text = 'Initial value';
</script>
<InnerComponent bind:value={text} />
<p>Value: {text}</p>
Other Bindings
<!-- bind:this -->
<script>
let myCanvas;
onMount(() => {
const ctx = myCanvas.getContext('2d');
// Draw on canvas
});
</script>
<canvas bind:this={myCanvas}></canvas>
<!-- bind:clientWidth, bind:clientHeight -->
<script>
let width, height;
</script>
<div bind:clientWidth={width} bind:clientHeight={height}>
Dimensions: {width} x {height}
</div>
Lifecycle Functions
onMount
<script>
import { onMount } from 'svelte';
onMount(() => {
// Runs after the component is first rendered to the DOM
console.log('Component mounted');
const interval = setInterval(() => {
console.log('tick');
}, 1000);
// Cleanup function
return () => {
clearInterval(interval);
console.log('Component unmounted');
};
});
</script>
beforeUpdate and afterUpdate
<script>
import { beforeUpdate, afterUpdate } from 'svelte';
let count = 0;
beforeUpdate(() => {
// Runs before the DOM is updated
console.log('Before update');
});
afterUpdate(() => {
// Runs after the DOM is updated
console.log('After update');
});
</script>
<button on:click={() => count++}>Count: {count}</button>
onDestroy
<script>
import { onDestroy } from 'svelte';
onDestroy(() => {
// Runs when the component is about to be destroyed
console.log('Component destroyed');
});
</script>
tick
<script>
import { tick } from 'svelte';
let text = 'Initial';
async function updateText() {
text = 'Updated';
// Wait for the DOM to update
await tick();
console.log('DOM updated');
}
</script>
<p>{text}</p>
<button on:click={updateText}>Update</button>
Stores
Writable Stores
// stores.js
import { writable } from 'svelte/store';
export const count = writable(0);
export const user = writable({ name: 'Guest', loggedIn: false });
<!-- Component.svelte -->
<script>
import { count, user } from './stores.js';
function increment() {
count.update(n => n + 1);
}
function login() {
user.set({ name: 'John', loggedIn: true });
}
// Auto-subscription with $ prefix
$: console.log(`Count is ${$count}`);
</script>
<h1>Count: {$count}</h1>
<button on:click={increment}>Increment</button>
{#if $user.loggedIn}
<p>Welcome, {$user.name}!</p>
{:else}
<button on:click={login}>Login</button>
{/if}
Readable Stores
// stores.js
import { readable } from 'svelte/store';
export const time = readable(new Date(), (set) => {
const interval = setInterval(() => {
set(new Date());
}, 1000);
return () => clearInterval(interval);
});
<!-- Clock.svelte -->
<script>
import { time } from './stores.js';
$: formattedTime = $time.toLocaleTimeString();
</script>
<p>Current time: {formattedTime}</p>
Derived Stores
// stores.js
import { derived } from 'svelte/store';
import { time } from './stores.js';
export const elapsed = derived(
time,
$time => Math.round(($time - new Date()) / 1000)
);
<!-- Elapsed.svelte -->
<script>
import { elapsed } from './stores.js';
</script>
<p>Elapsed time: {$elapsed}s</p>
```### 커스텀 스토어
```javascript
// stores.js
import { writable } from 'svelte/store';
function createCount() {
const { subscribe, set, update } = writable(0);
return {
subscribe,
increment: () => update(n => n + 1),
decrement: () => update(n => n - 1),
reset: () => set(0)
};
}
export const count = createCount();
```## 트랜지션과 애니메이션
### 트랜지션
```svelte
<script>
import { fade, fly, slide, scale } from 'svelte/transition';
let visible = true;
</script>
<label>
<input type="checkbox" bind:checked={visible} />
Toggle visibility
</label>
{#if visible}
<div transition:fade>Fades in and out</div>
<div transition:fly={{ y: 200, duration: 2000 }}>Flies in and out</div>
<div transition:slide>Slides in and out</div>
<div transition:scale>Scales in and out</div>
{/if}
```### 애니메이션
```svelte
<script>
import { flip } from 'svelte/animate';
let list = [1, 2, 3];
function shuffle() {
list = list.sort(() => Math.random() - 0.5);
}
</script>
<button on:click={shuffle}>Shuffle</button>
{#each list as item (item)}
<div animate:flip>{item}</div>
{/each}
```### 모션
```svelte
<script>
import { spring } from 'svelte/motion';
let coords = spring({ x: 50, y: 50 }, {
stiffness: 0.1,
damping: 0.25
});
function handleMousemove(event) {
coords.set({ x: event.clientX, y: event.clientY });
}
</script>
<div on:mousemove={handleMousemove} class="container">
<div class="dot" style="transform: translate({$coords.x}px, {$coords.y}px)"></div>
</div>
```## SvelteKit
### 라우팅
```bash
# File-based routing
src/routes/
├── +page.svelte # Homepage
├── about/+page.svelte # /about
├── blog/[slug]/+page.svelte # /blog/[slug]
└── +layout.svelte # Root layout
```### 데이터 로딩
```svelte
<!-- src/routes/blog/[slug]/+page.svelte -->
<script>
export let data;
const { post } = data;
</script>
<h1>{post.title}</h1>
<div>{@html post.content}</div>
<!-- src/routes/blog/[slug]/+page.js -->
export async function load({ params }) {
const res = await fetch(`https://api.example.com/posts/${params.slug}`);
const post = await res.json();
return { post };
}
```### API 라우트
```javascript
// src/routes/api/posts/+server.js
export async function GET() {
const posts = await getPostsFromDB();
return new Response(JSON.stringify(posts));
}
export async function POST({ request }) {
const data = await request.json();
const newPost = await createPost(data);
return new Response(JSON.stringify(newPost), { status: 201 });
}
```### 레이아웃
```svelte
<!-- src/routes/+layout.svelte -->
<header>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/blog">Blog</a>
</nav>
</header>
<main>
<slot />
</main>
<footer>
<p>© 2024 My App</p>
</footer>
```## 배포
### 정적 사이트 (어댑터 스태틱)
```javascript
// svelte.config.js
import adapter from '@sveltejs/adapter-static';
export default {
kit: {
adapter: adapter({
pages: 'build',
assets: 'build',
fallback: 'index.html'
})
}
};
```### Vercel
```javascript
// svelte.config.js
import adapter from '@sveltejs/adapter-vercel';
export default {
kit: {
adapter: adapter()
}
};
```### Netlify
```javascript
// svelte.config.js
import adapter from '@sveltejs/adapter-netlify';
export default {
kit: {
adapter: adapter()
}
};
```### Node.js 서버
```javascript
// svelte.config.js
import adapter from '@sveltejs/adapter-node';
export default {
kit: {
adapter: adapter()
}
};
```## 테스팅
### Vitest
```bash
# Install Vitest
npm install --save-dev vitest @testing-library/svelte
// tests/Counter.test.js
import { render, fireEvent } from '@testing-library/svelte';
import Counter from '../src/lib/components/Counter.svelte';
it('increments counter', async () => {
const { getByText } = render(Counter);
const button = getByText('Increment');
await fireEvent.click(button);
expect(getByText('Count: 1')).toBeInTheDocument();
});
```### Playwright
```bash
# Install Playwright
npm install --save-dev @playwright/test
// tests/e2e/home.spec.js
import { test, expect } from '@playwright/test';
test('homepage has correct title', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle(/My App/);
});
```## 모범 사례
- **컴포넌트 구성**: 컴포넌트를 작고 집중적으로 유지하세요. 재사용 가능한 컴포넌트를 위한 디렉토리를 사용하세요.
- **반응성**: 계산된 값에 대해 반응형 문(reactive statements)을 사용하세요. 템플릿에서 복잡한 로직을 피하세요.
- **스토어**: 전역 상태 관리를 위해 스토어를 사용하세요. 복잡한 로직을 위한 커스텀 스토어를 생성하세요.
- **Props**: 자식 컴포넌트로 데이터를 전달하기 위해 props를 사용하세요. props를 선언하기 위해 export를 사용하세요.
- **이벤트**: 자식에서 부모로의 통신을 위해 컴포넌트 이벤트를 사용하세요. dispatch를 사용하세요.
- **생명주기**: 데이터 가져오기와 DOM 상호작용을 위해 onMount를 사용하세요. 메모리 누수를 방지하기 위해 정리 함수를 사용하세요.
- **SvelteKit**: 라우팅, 데이터 로딩, 서버 사이드 렌더링을 기본으로 제공하는 SvelteKit을 새 프로젝트에 사용하세요.
---
## 요약
Svelte는 웹 애플리케이션을 구축하기 위한 강력하고 효율적인 프레임워크입니다. 컴파일 타임 접근 방식은 더 작은 번들과 더 빠른 성능을 제공합니다. 주요 기능은 다음과 같습니다:
- **반응성**: 간단하고 강력한 반응성 시스템
- **컴포넌트 기반**: 재사용 가능한 컴포넌트로 애플리케이션 구축
- **스코프된 스타일**: CSS는 기본적으로 컴포넌트에 스코프됨
- **스토어**: 내장된 상태 관리 솔루션
- **트랜지션과 애니메이션**: 유연한 UI를 만들기 위한 풍부한 도구 세트
- **SvelteKit**: Svelte 앱을 구축하기 위한 풀 기능 프레임워크
Svelte의 고유한 기능을 활용하여 빠르고 현대적이며 유지보수 가능한 웹 애플리케이션을 훌륭한 개발자 경험과 함께 구축할 수 있습니다.
<script>
function copyToClipboard() {
const commands = document.querySelectorAll('code');
let allCommands = '';
commands.forEach(cmd => allCommands += cmd.textContent + '\n');
navigator.clipboard.writeText(allCommands);
alert('모든 명령어가 클립보드에 복사되었습니다!');
}
function generatePDF() {
window.print();
}
</script>`lib/components` directory for reusable components.
- **Reactivity**: Use reactive statements (`$:`) for computed values. Avoid complex logic in templates.
- **Stores**: Use stores for global state management. Create custom stores for complex logic.
- **Props**: Use props for passing data down to child components. Use `export let` to declare props.
- **Events**: Use component events for communication from child to parent. Use `createEventDispatcher`.
- **Lifecycle**: Use `onMount` for fetching data and interacting with the DOM. Use cleanup functions to prevent memory leaks.
- **SvelteKit**: Use SvelteKit for new projects to get routing, data loading, and server-side rendering out of the box.
---
## Summary
Svelte is a powerful and efficient framework for building web applications. Its compile-time approach results in smaller bundles and faster performance. Key features include:
- **Reactivity**: Simple and powerful reactivity system
- **Component-based**: Build applications with reusable components
- **Scoped Styles**: CSS is scoped to components by default
- **Stores**: Built-in state management solution
- **Transitions and Animations**: Rich set of tools for creating fluid UIs
- **SvelteKit**: A full-featured framework for building Svelte apps
By leveraging Svelte's unique features, you can build fast, modern, and maintainable web applications with a great developer experience.
<script>
function copyToClipboard() {
const commands = document.querySelectorAll('code');
let allCommands = '';
commands.forEach(cmd => allCommands += cmd.textContent + '\n');
navigator.clipboard.writeText(allCommands);
alert('All commands copied to clipboard!');
}
function generatePDF() {
window.print();
}
</script>