Web Development

Svelte 5 Runes CI/CD Automation Pipeline — คู่มือฉบับสมบูรณ์ 2026

Svelte 5 Runes CI/CD Automation Pipeline — คู่มือฉบับสมบูรณ์ 2026 | SiamCafe Blog

โดย อ.บอม กิตติทัศน์ เจริญพนาสิทธิ์ | อัปเดต 24 ก.พ. 2026 | อ่าน 15 นาที

Svelte 5 Runes CI/CD 2026

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 StateuseState()
$derived()คำนวณจาก State (Read-only)useMemo()
$effect()รัน Side Effect เมื่อ State เปลี่ยนuseEffect()
$props()รับ Props จาก Parentfunction 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

Best Practices และสรุป

Svelte 5 Runes ทำให้ Reactivity ชัดเจนและ Predictable มากขึ้น เมื่อรวมกับ CI/CD Pipeline ที่ครอบคลุม จะได้ Development Workflow ที่รวดเร็ว ปลอดภัย และ Scalable ติดตามบทความใหม่ๆ ได้ที่ SiamCafe.net

อ.บอม กิตติทัศน์ เจริญพนาสิทธิ์
IT Infrastructure Expert | Thaiware Award | ประสบการณ์กว่า 25 ปี — ผู้ก่อตั้ง SiamCafe.net Since 2000-2026

คำถามที่พบบ่อย (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

บทความแนะนำ: