Svelte 5 Runes CI/CD Automation Pipeline — คู่มือฉบับสมบูรณ์ 2026
โดย อ.บอม กิตติทัศน์ เจริญพนาสิทธิ์ | อัปเดต 24 ก.พ. 2026 | อ่าน 15 นาที
- Svelte 5 คืออะไร — เปลี่ยนแปลงจาก Svelte 4
- Runes — ระบบ Reactivity ใหม่
- $state — สร้าง Reactive State
- $derived — Computed Value
- $effect — Side Effects
- $props และ $bindable — Component Communication
- Snippets แทน Slots — Template Reuse
- SvelteKit — Full-stack Framework
- Testing — Vitest และ Playwright
- CI/CD Pipeline ด้วย GitHub Actions
- Deploy บน Vercel / Cloudflare / Netlify
- Performance Optimization
- Best Practices และสรุป
Svelte 5 คืออะไร — เปลี่ยนแปลงจาก Svelte 4
Svelte เป็น Frontend Framework ที่แตกต่างจาก React และ Vue ตรงที่ Svelte เป็น Compiler ไม่ใช่ Runtime Library คอมไพล์ Component เป็น Vanilla JavaScript ที่ Optimized ทำให้ Bundle Size เล็กมากและ Performance สูง ไม่มี Virtual DOM Overhead
Svelte 5 เปิดตัวปลายปี 2024 มีการเปลี่ยนแปลงใหญ่ที่สุดคือ Runes ระบบ Reactivity ใหม่ที่ชัดเจนกว่า Svelte 4 ใช้ Compiler Magic ที่ let x = 0 กลายเป็น Reactive อัตโนมัติ แต่ปัญหาคือบางครั้งงง ว่า Variable ไหน Reactive ไหนไม่ Runes แก้ปัญหานี้โดยใช้ $state(), $derived(), $effect() อย่างชัดเจน
Runes — ระบบ Reactivity ใหม่
| Rune | หน้าที่ | เทียบกับ React |
|---|---|---|
| $state() | สร้าง Reactive State | useState() |
| $derived() | คำนวณจาก State (Read-only) | useMemo() |
| $effect() | รัน Side Effect เมื่อ State เปลี่ยน | useEffect() |
| $props() | รับ Props จาก Parent | function Component(props) |
| $bindable() | Props ที่ Bind สองทางได้ | ไม่มีเทียบตรง |
| $inspect() | Debug: Log เมื่อ State เปลี่ยน | console.log ใน useEffect |
$state — สร้าง Reactive State
<script>
// Svelte 5 — ใช้ $state()
let count = $state(0)
let user = $state({ name: 'สมชาย', age: 30 })
let items = $state(['Apple', 'Banana', 'Cherry'])
function increment() {
count++ // Reactive! UI อัปเดตอัตโนมัติ
}
function addItem() {
items.push('New Item') // Reactive Array Mutation!
// Svelte 5: Array/Object mutation ทำงานได้เลย
// ไม่ต้อง items = [...items, 'New Item'] แบบ Svelte 4
}
function updateName() {
user.name = 'สมหญิง' // Reactive Object Mutation!
}
</script>
<button onclick={increment}>Count: {count}</button>
<button onclick={addItem}>Add Item ({items.length})</button>
<button onclick={updateName}>{user.name}</button>
$derived — Computed Value
<script>
let price = $state(100)
let quantity = $state(2)
let vat = $state(7)
// $derived คำนวณอัตโนมัติเมื่อ Dependencies เปลี่ยน
let subtotal = $derived(price * quantity)
let vatAmount = $derived(subtotal * vat / 100)
let total = $derived(subtotal + vatAmount)
// $derived กับ Complex Logic
let items = $state([
{ name: 'Item A', price: 100, qty: 2 },
{ name: 'Item B', price: 200, qty: 1 }
])
let cartTotal = $derived(
items.reduce((sum, item) => sum + item.price * item.qty, 0)
)
</script>
<p>Subtotal: ฿{subtotal}</p>
<p>VAT ({vat}%): ฿{vatAmount}</p>
<p>Total: ฿{total}</p>
$effect — Side Effects
<script>
let searchQuery = $state('')
let results = $state([])
// $effect รันเมื่อ searchQuery เปลี่ยน
$effect(() => {
if (searchQuery.length < 3) {
results = []
return
}
// Debounce ด้วย Cleanup Function
const timer = setTimeout(async () => {
const res = await fetch(`/api/search?q=${searchQuery}`)
results = await res.json()
}, 300)
// Cleanup: ล้าง Timer เมื่อ searchQuery เปลี่ยนอีก
return () => clearTimeout(timer)
})
// $effect สำหรับ LocalStorage Sync
let theme = $state('dark')
$effect(() => {
localStorage.setItem('theme', theme)
document.documentElement.setAttribute('data-theme', theme)
})
// $inspect สำหรับ Debug (Dev Only)
$inspect(searchQuery, results)
</script>
<input bind:value={searchQuery} placeholder="ค้นหา..." />
$props และ $bindable — Component Communication
<!-- TextInput.svelte -->
<script>
let {
label,
value = $bindable(''), // Two-way Binding
placeholder = '',
oninput
} = $props()
</script>
<label>
{label}
<input bind:value {placeholder} {oninput} />
</label>
<!-- ใช้งาน -->
<script>
let name = $state('')
</script>
<TextInput label="ชื่อ" bind:value={name} placeholder="กรอกชื่อ" />
Snippets แทน Slots — Template Reuse
<!-- Svelte 5: Snippets แทน Slots -->
<script>
let items = $state(['Apple', 'Banana', 'Cherry'])
</script>
{#snippet row(item, index)}
<tr>
<td>{index + 1}</td>
<td>{item}</td>
<td><button onclick={() => items.splice(index, 1)}>ลบ</button></td>
</tr>
{/snippet}
<table>
{#each items as item, i}
{@render row(item, i)}
{/each}
</table>
SvelteKit — Full-stack Framework
// สร้าง SvelteKit Project
npx sv create my-app
cd my-app
npm install
npm run dev
// src/routes/+page.server.ts — Server-side Data Loading
import type { PageServerLoad } from './$types'
import { db } from '$lib/server/db'
export const load: PageServerLoad = async () => {
const posts = await db.post.findMany({
orderBy: { createdAt: 'desc' },
take: 10
})
return { posts }
}
// src/routes/+page.svelte — Display Data
<script>
let { data } = $props()
</script>
{#each data.posts as post}
<article>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
{/each}
Testing — Vitest และ Playwright
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import { svelte } from '@sveltejs/vite-plugin-svelte'
export default defineConfig({
plugins: [svelte({ hot: false })],
test: {
include: ['src/**/*.test.ts'],
environment: 'jsdom',
setupFiles: ['./vitest-setup.ts']
}
})
// src/lib/Counter.test.ts — Unit Test
import { render, fireEvent } from '@testing-library/svelte'
import { expect, test } from 'vitest'
import Counter from './Counter.svelte'
test('increments count on click', async () => {
const { getByText } = render(Counter)
const button = getByText('Count: 0')
await fireEvent.click(button)
expect(getByText('Count: 1')).toBeTruthy()
})
// tests/e2e/home.test.ts — Playwright E2E
import { test, expect } from '@playwright/test'
test('home page loads', async ({ page }) => {
await page.goto('/')
await expect(page.locator('h1')).toContainText('Welcome')
})
test('search works', async ({ page }) => {
await page.goto('/')
await page.fill('input[placeholder="ค้นหา..."]', 'Svelte')
await page.waitForSelector('.search-results')
const results = page.locator('.search-results li')
await expect(results).toHaveCount(3)
})
CI/CD Pipeline ด้วย GitHub Actions
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
lint-and-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm run check # svelte-check (TypeScript)
unit-test:
runs-on: ubuntu-latest
needs: lint-and-check
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22, cache: 'npm' }
- run: npm ci
- run: npm run test:unit -- --coverage
- uses: actions/upload-artifact@v4
with:
name: coverage
path: coverage/
e2e-test:
runs-on: ubuntu-latest
needs: lint-and-check
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22, cache: 'npm' }
- run: npm ci
- run: npx playwright install --with-deps
- run: npm run build
- run: npm run test:e2e
- uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-report
path: playwright-report/
deploy-staging:
runs-on: ubuntu-latest
needs: [unit-test, e2e-test]
if: github.ref == 'refs/heads/develop'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22, cache: 'npm' }
- run: npm ci
- run: npm run build
env:
PUBLIC_API_URL: ${{ secrets.STAGING_API_URL }}
- name: Deploy to Staging
run: npx wrangler pages deploy .svelte-kit/cloudflare --project-name=my-app-staging
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
deploy-production:
runs-on: ubuntu-latest
needs: [unit-test, e2e-test]
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22, cache: 'npm' }
- run: npm ci
- run: npm run build
env:
PUBLIC_API_URL: ${{ secrets.PROD_API_URL }}
- name: Deploy to Production
run: npx wrangler pages deploy .svelte-kit/cloudflare --project-name=my-app
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
Deploy บน Vercel / Cloudflare / Netlify
// svelte.config.js — เลือก Adapter ตาม Platform
// Vercel
import adapter from '@sveltejs/adapter-vercel'
// Cloudflare Pages
import adapter from '@sveltejs/adapter-cloudflare'
// Netlify
import adapter from '@sveltejs/adapter-netlify'
// Node.js Server
import adapter from '@sveltejs/adapter-node'
export default {
kit: {
adapter: adapter({
// Vercel options
runtime: 'edge', // หรือ 'nodejs22.x'
regions: ['sin1'], // Singapore
// Node.js options
// out: 'build',
// precompress: true
})
}
}
Performance Optimization
- ใช้ Server Component (load function) — Fetch Data บน Server ไม่ส่ง API Key ไป Client
- Prerender Static Pages — ตั้ง
export const prerender = trueสำหรับหน้าที่ไม่เปลี่ยนบ่อย - Lazy Load Component —
{#await import('./HeavyComponent.svelte') then { default: Component }} - Image Optimization — ใช้
@sveltejs/enhanced-imgหรือ CDN เช่น Cloudflare Images - Code Splitting — SvelteKit แยก Code ตาม Route อัตโนมัติ
- ใช้ $derived แทน $effect — $derived เร็วกว่าเพราะ Synchronous ไม่ Batch
Best Practices และสรุป
- ใช้ Runes ทั้งหมด — ไม่ Mix กับ Svelte 4 Syntax ในโปรเจกต์ใหม่
- $state สำหรับ Mutable — $derived สำหรับ Computed Read-only
- $effect เฉพาะ Side Effect — อย่าใช้ $effect คำนวณค่า ใช้ $derived แทน
- Type Check ด้วย svelte-check — ตรวจ TypeScript Error ก่อน Build
- Test ทั้ง Unit (Vitest) และ E2E (Playwright)
- CI/CD ต้อง Lint → Check → Test → Build → Deploy
- แยก Staging กับ Production — Deploy จาก develop → Staging, main → Production
- เลือก Adapter ตาม Platform — Vercel/Cloudflare สำหรับ Edge, Node.js สำหรับ Self-host
Svelte 5 Runes ทำให้ Reactivity ชัดเจนและ Predictable มากขึ้น เมื่อรวมกับ CI/CD Pipeline ที่ครอบคลุม จะได้ Development Workflow ที่รวดเร็ว ปลอดภัย และ Scalable ติดตามบทความใหม่ๆ ได้ที่ SiamCafe.net
คำถามที่พบบ่อย (FAQ)
Q: Svelte 5 Runes คืออะไร
ระบบ Reactivity ใหม่ใช้ $state(), $derived(), $effect() ชัดเจนกว่า Svelte 4 ที่ใช้ let keyword เป็น Reactive อัตโนมัติ
Q: $state กับ $derived ต่างกันอย่างไร
$state = Mutable Reactive Value เปลี่ยนค่าได้ | $derived = Read-only Computed Value คำนวณจาก State อื่น อัปเดตอัตโนมัติ
Q: CI/CD สำหรับ SvelteKit ทำอย่างไร
GitHub Actions: Lint → svelte-check → Unit Test (Vitest) → E2E (Playwright) → Build → Deploy (Vercel/Cloudflare/Netlify)
Q: SvelteKit คืออะไร
Full-stack Framework ของ Svelte (เหมือน Next.js ของ React) รองรับ SSR, SSG, API Routes, Form Actions Deploy ได้หลาย Platform