<!-- title: Claude Code Svelte 5全栈：Runes到SvelteKit交付 -->

# Claude Code Svelte 5 全栈实战：Runes 响应式到 SvelteKit 项目交付

## 概述

本教程带你使用 Claude Code 从零搭建一个 Svelte 5 + SvelteKit 全栈任务管理应用。你将掌握 Runes 响应式系统（`$state`、`$derived`、`$effect`、`$props`）、SvelteKit 路由与 Server Actions、以及如何通过 CLAUDE.md 让 AI 深度理解 Svelte 5 项目上下文。适合有前端基础但第一次接触 Svelte 5 的开发者，预计 3-4 小时完成。

**适用人群**：有 HTML/CSS/JS 基础的前端开发者，了解 React/Vue 但想尝试 Svelte 5 的工程师。

## 前置条件

| 条件 | 说明 |
|------|------|
| Node.js | >= 18.x（推荐 20 LTS） |
| Claude Code | 已安装并配置好 API Key |
| 编辑器 | VS Code + Svelte 扩展 |
| 基础知识 | HTML/CSS/JS、基本终端操作 |

---

## 第一步：项目初始化与 CLAUDE.md 配置

### 1.1 创建 SvelteKit 项目

在终端中运行：

```bash
npm create svelte@latest taskflow
cd taskflow
npm install
```

创建向导中选择：
- **Skeleton project**（骨架项目，干净起步）
- **Yes, using TypeScript syntax**
- **Yes, add ESLint**
- **Yes, add Prettier**
- **Yes, add Playwright**
- **No, add Vitest**（本教程暂不涉及单元测试）

安装完成后，用 Claude Code 打开项目：

```bash
claude
```

### 1.2 编写 Svelte 5 专用的 CLAUDE.md

这一步至关重要。Svelte 5 的 Runes 语法与 Svelte 4 完全不同，如果 CLAUDE.md 不明确声明版本，Claude Code 可能生成旧语法代码。

在项目根目录创建 `CLAUDE.md`：

```markdown
# TaskFlow - Svelte 5 Task Management App

## Tech Stack
- Svelte 5 (Runes mode, NOT Svelte 4 syntax)
- SvelteKit 2.x
- TypeScript
- TailwindCSS 4
- SQLite (via better-sqlite3)

## Critical Rules
- ALWAYS use Svelte 5 Runes syntax ($state, $derived, $effect, $props, $bindable)
- NEVER use Svelte 4 syntax ($:, stores, slot, on:click, createEventDispatcher)
- Use {#snippet} and {@render} instead of <slot>
- Use onclick={} instead of on:click={}
- Use bind:value={} instead of bind:value
- Server code goes in +page.server.ts, +layout.server.ts, +server.ts
- Client state stays in +page.svelte components
- Use form actions for mutations, never direct API calls for simple CRUD

## Build & Dev Commands
- `npm run dev` — Start dev server
- `npm run build` — Production build
- `npm run preview` — Preview production build
- `npm run lint` — Run ESLint
- `npm run format` — Run Prettier

## File Structure
- src/routes/ — SvelteKit file-based routing
- src/lib/components/ — Reusable Svelte components
- src/lib/server/ — Server-only code (db, auth)
- src/lib/stores/ — Shared state utilities
- static/ — Static assets

## Database
- Using better-sqlite3 with src/lib/server/db.ts
- Schema defined in src/lib/server/schema.sql
```

**为什么这么写？** 关键在于 `Critical Rules` 部分。Svelte 5 和 Svelte 4 的语法差异巨大，必须在 CLAUDE.md 中明确禁止旧语法。否则 Claude Code 会混用两代语法，导致编译报错。

### 1.3 安装额外依赖

在 Claude Code 会话中输入：

```
请安装以下依赖：tailwindcss @tailwindcss/vite better-sqlite3 及其类型定义
```

Claude Code 会执行：

```bash
npm install tailwindcss @tailwindcss/vite better-sqlite3
npm install -D @types/better-sqlite3
```

配置 Tailwind CSS。编辑 `vite.config.ts`：

```typescript
import { sveltekit } from '@sveltejs/kit/vite';
import tailwindcss from '@tailwindcss/vite';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [tailwindcss(), sveltekit()]
});
```

创建 `src/app.css`：

```css
@import 'tailwindcss';
```

### 1.4 验证开发环境

```
请运行 npm run dev，确认开发服务器启动正常
```

预期输出：

```
  VITE v6.x.x  ready in xxx ms

  ➜  Local:   http://localhost:5173/
```

**常见问题**：

| 问题 | 原因 | 解决 |
|------|------|------|
| `Cannot find module '@sveltejs/kit'` | 未完成 npm install | 运行 `npm install` |
| Tailwind 类不生效 | 未导入 app.css | 在 +layout.svelte 中 `import '../app.css'` |
| Svelte 5 Runes 报错 | 项目不是 Svelte 5 | 检查 `package.json` 中 `svelte` 版本 >= 5.0 |

---

## 第二步：数据库层搭建

### 2.1 创建 Schema

创建 `src/lib/server/schema.sql`：

```sql
CREATE TABLE IF NOT EXISTS tasks (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  title TEXT NOT NULL,
  description TEXT DEFAULT '',
  status TEXT DEFAULT 'todo' CHECK(status IN ('todo', 'in-progress', 'done')),
  priority TEXT DEFAULT 'medium' CHECK(priority IN ('low', 'medium', 'high')),
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
```

### 2.2 创建数据库连接模块

创建 `src/lib/server/db.ts`：

```typescript
import Database from 'better-sqlite3';
import { readFileSync } from 'fs';
import { join } from 'path';

const db = new Database(join(process.cwd(), 'taskflow.db'));

// 启用 WAL 模式提升并发性能
db.pragma('journal_mode = WAL');

// 初始化表结构
const schema = readFileSync(join(process.cwd(), 'src/lib/server/schema.sql'), 'utf-8');
db.exec(schema);

export interface Task {
  id: number;
  title: string;
  description: string;
  status: 'todo' | 'in-progress' | 'done';
  priority: 'low' | 'medium' | 'high';
  created_at: string;
  updated_at: string;
}

export function getAllTasks(): Task[] {
  return db.prepare('SELECT * FROM tasks ORDER BY created_at DESC').all() as Task[];
}

export function getTaskById(id: number): Task | undefined {
  return db.prepare('SELECT * FROM tasks WHERE id = ?').get(id) as Task | undefined;
}

export function createTask(data: Pick<Task, 'title' | 'description' | 'priority'>): Task {
  const stmt = db.prepare(
    'INSERT INTO tasks (title, description, priority) VALUES (?, ?, ?)'
  );
  const result = stmt.run(data.title, data.description, data.priority);
  return getTaskById(result.lastInsertRowid as number)!;
}

export function updateTask(id: number, data: Partial<Pick<Task, 'title' | 'description' | 'status' | 'priority'>>): Task | undefined {
  const fields = Object.keys(data).map(k => `${k} = @${k}`).join(', ');
  const stmt = db.prepare(`UPDATE tasks SET ${fields}, updated_at = CURRENT_TIMESTAMP WHERE id = @id`);
  stmt.run({ ...data, id });
  return getTaskById(id);
}

export function deleteTask(id: number): boolean {
  const result = db.prepare('DELETE FROM tasks WHERE id = ?').run(id);
  return result.changes > 0;
}
```

### 2.3 Claude Code 提示词技巧

告诉 Claude Code：

```
请创建 src/lib/server/db.ts，使用 better-sqlite3 实现任务 CRUD 操作。
要求：TypeScript 类型安全、WAL 模式、参数化查询防注入。
```

Claude Code 会根据 CLAUDE.md 中定义的 `src/lib/server/` 目录结构，自动将文件放在正确位置。

---

## 第三步：Svelte 5 Runes 核心概念实战

这是本教程的核心部分。Svelte 5 引入了 Runes（符文）系统，彻底重构了响应式模型。

### 3.1 `$state` — 响应式状态

在 Svelte 4 中，赋值触发响应式：

```svelte
<!-- Svelte 4（已过时，禁止使用） -->
<script>
  let count = 0;
  function increment() {
    count += 1; // 赋值 = 响应式
  }
</script>
```

在 Svelte 5 中，使用 `$state` rune：

```svelte
<!-- Svelte 5 Runes -->
<script lang="ts">
  let count = $state(0);
  function increment() {
    count++; // 无需赋值技巧，直接修改即触发更新
  }
</script>

<button onclick={increment}>
  Count: {count}
</button>
```

**关键区别**：`$state` 是细粒度响应式，只更新实际变化的部分，不触发整个组件重新渲染。

### 3.2 `$derived` — 派生状态

```svelte
<script lang="ts">
  let tasks = $state<Task[]>([]);
  let filter = $state<'all' | 'todo' | 'in-progress' | 'done'>('all');

  // $derived 自动追踪依赖，当 tasks 或 filter 变化时重新计算
  let filteredTasks = $derived(
    filter === 'all' ? tasks : tasks.filter(t => t.status === filter)
  );

  // 统计数据也是派生的
  let stats = $derived({
    total: tasks.length,
    todo: tasks.filter(t => t.status === 'todo').length,
    inProgress: tasks.filter(t => t.status === 'in-progress').length,
    done: tasks.filter(t => t.status === 'done').length,
  });
</script>
```

### 3.3 `$effect` — 副作用

```svelte
<script lang="ts">
  let searchQuery = $state('');
  let searchResults = $state<Task[]>([]);

  // $effect 自动追踪内部读取的响应式值
  $effect(() => {
    // 当 searchQuery 变化时自动执行
    if (searchQuery.length >= 2) {
      searchResults = tasks.filter(t =>
        t.title.toLowerCase().includes(searchQuery.toLowerCase())
      );
    } else {
      searchResults = [];
    }
  });

  // 带清理函数的 effect（如防抖、事件监听）
  $effect(() => {
    const timer = setTimeout(() => {
      console.log('搜索:', searchQuery);
    }, 300);

    return () => clearTimeout(timer); // 清理函数
  });
</script>
```

### 3.4 `$props` — 组件属性

```svelte
<!-- TaskCard.svelte -->
<script lang="ts">
  import type { Task } from '$lib/server/db';

  // Svelte 5 用 $props() 替代 export let
  let { task, onDelete, onStatusChange }: {
    task: Task;
    onDelete: (id: number) => void;
    onStatusChange: (id: number, status: Task['status']) => void;
  } = $props();

  // 基于任务状态计算样式
  let statusColor = $derived(
    task.status === 'done' ? 'bg-green-100 text-green-800' :
    task.status === 'in-progress' ? 'bg-yellow-100 text-yellow-800' :
    'bg-gray-100 text-gray-800'
  );
</script>

<div class="rounded-lg border p-4 shadow-sm hover:shadow-md transition-shadow">
  <div class="flex items-center justify-between">
    <h3 class="font-semibold">{task.title}</h3>
    <span class="px-2 py-1 rounded-full text-xs {statusColor}">
      {task.status}
    </span>
  </div>
  {#if task.description}
    <p class="mt-2 text-sm text-gray-600">{task.description}</p>
  {/if}
  <div class="mt-3 flex gap-2">
    <select
      class="text-sm border rounded px-2 py-1"
      value={task.status}
      onchange={(e) => onStatusChange(task.id, e.currentTarget.value as Task['status'])}
    >
      <option value="todo">待办</option>
      <option value="in-progress">进行中</option>
      <option value="done">已完成</option>
    </select>
    <button
      class="text-sm text-red-500 hover:text-red-700"
      onclick={() => onDelete(task.id)}
    >
      删除
    </button>
  </div>
</div>
```

### 3.5 Snippets 与 `{@render}` — 替代 Slot

```svelte
<!-- TaskList.svelte -->
<script lang="ts">
  import type { Task } from '$lib/server/db';

  let { tasks, children }: {
    tasks: Task[];
    children: import('svelte').Snippet<[{ task: Task; index: number }]>;
  } = $props();
</script>

{#if tasks.length === 0}
  <div class="text-center py-12 text-gray-400">
    暂无任务，创建第一个吧
  </div>
{:else}
  <div class="grid gap-3">
    {#each tasks as task, i (task.id)}
      {@render children({ task, index: i })}
    {/each}
  </div>
{/if}
```

使用方式：

```svelte
<TaskList {filteredTasks}>
  {#snippet children({ task, index })}
    <TaskCard
      {task}
      onDelete={handleDelete}
      onStatusChange={handleStatusChange}
    />
  {/snippet}
</TaskList>
```

---

## 第四步：SvelteKit 路由与 Server Actions

### 4.1 项目路由结构

```
src/routes/
├── +layout.svelte          # 全局布局（导航栏、侧边栏）
├── +layout.server.ts       # 全局布局数据加载
├── +page.svelte            # 首页（任务看板）
├── +page.server.ts         # 首页数据加载 + Actions
├── tasks/
│   ├── [id]/
│   │   ├── +page.svelte    # 任务详情
│   │   └── +page.server.ts # 任务详情数据
│   └── +page.svelte        # 任务列表
└── api/
    └── tasks/
        └── +server.ts      # REST API（可选）
```

### 4.2 根布局

创建 `src/routes/+layout.svelte`：

```svelte
<script lang="ts">
  import '../app.css';

  let { children } = $props();
</script>

<div class="min-h-screen bg-gray-50">
  <nav class="bg-white shadow-sm border-b">
    <div class="max-w-6xl mx-auto px-4 py-3 flex items-center justify-between">
      <a href="/" class="text-xl font-bold text-indigo-600">TaskFlow</a>
      <div class="flex gap-4 text-sm">
        <a href="/" class="hover:text-indigo-600">看板</a>
        <a href="/tasks" class="hover:text-indigo-600">列表</a>
      </div>
    </div>
  </nav>
  <main class="max-w-6xl mx-auto px-4 py-6">
    {@render children()}
  </main>
</div>
```

### 4.3 Server Actions — 表单驱动的数据变更

创建 `src/routes/+page.server.ts`：

```typescript
import { getAllTasks, createTask, updateTask, deleteTask } from '$lib/server/db';
import { fail } from '@sveltejs kit';
import type { Actions, PageServerLoad } from './$types';

export const load: PageServerLoad = async () => {
  return {
    tasks: getAllTasks()
  };
};

export const actions: Actions = {
  create: async ({ request }) => {
    const data = await request.formData();
    const title = data.get('title')?.toString()?.trim();
    const description = data.get('description')?.toString()?.trim() ?? '';
    const priority = data.get('priority')?.toString() ?? 'medium';

    if (!title || title.length < 1) {
      return fail(400, { error: '任务标题不能为空' });
    }

    createTask({ title, description, priority: priority as 'low' | 'medium' | 'high' });
    return { success: true };
  },

  updateStatus: async ({ request }) => {
    const data = await request.formData();
    const id = Number(data.get('id'));
    const status = data.get('status')?.toString();

    if (!id || !status) {
      return fail(400, { error: '参数缺失' });
    }

    updateTask(id, { status: status as 'todo' | 'in-progress' | 'done' });
    return { success: true };
  },

  delete: async ({ request }) => {
    const data = await request.formData();
    const id = Number(data.get('id'));

    if (!id) {
      return fail(400, { error: '参数缺失' });
    }

    deleteTask(id);
    return { success: true };
  }
};
```

**为什么用 Server Actions 而不是 REST API？** Server Actions 让表单无需 JavaScript 就能工作（渐进增强），且自动处理 CSRF 防护。Claude Code 在 CLAUDE.md 中看到"Use form actions for mutations"指令后，会默认使用这种模式。

### 4.4 首页看板视图

创建 `src/routes/+page.svelte`：

```svelte
<script lang="ts">
  import type { PageData, ActionData } from './$types';
  import TaskCard from '$lib/components/TaskCard.svelte';

  let { data, form }: { data: PageData; form: ActionData } = $props();

  // 看板分组
  let columns = $derived({
    '待办': data.tasks.filter(t => t.status === 'todo'),
    '进行中': data.tasks.filter(t => t.status === 'in-progress'),
    '已完成': data.tasks.filter(t => t.status === 'done'),
  });

  // 新建任务表单状态
  let showForm = $state(false);
  let newTitle = $state('');
  let newDescription = $state('');
  let newPriority = $state<'low' | 'medium' | 'high'>('medium');
</script>

<div class="flex items-center justify-between mb-6">
  <h1 class="text-2xl font-bold">任务看板</h1>
  <button
    class="bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700"
    onclick={() => showForm = !showForm}
  >
    {showForm ? '取消' : '+ 新任务'}
  </button>
</div>

{#if form?.error}
  <div class="bg-red-50 text-red-600 px-4 py-2 rounded mb-4">{form.error}</div>
{/if}

{#if showForm}
  <form method="POST" action="?/create" class="bg-white rounded-lg shadow p-6 mb-6">
    <div class="space-y-4">
      <div>
        <label class="block text-sm font-medium mb-1">任务标题</label>
        <input
          name="title"
          bind:value={newTitle}
          class="w-full border rounded-lg px-3 py-2"
          placeholder="输入任务标题..."
          required
        />
      </div>
      <div>
        <label class="block text-sm font-medium mb-1">描述</label>
        <textarea
          name="description"
          bind:value={newDescription}
          class="w-full border rounded-lg px-3 py-2"
          rows="3"
          placeholder="可选描述..."
        ></textarea>
      </div>
      <div>
        <label class="block text-sm font-medium mb-1">优先级</label>
        <select name="priority" bind:value={newPriority} class="border rounded-lg px-3 py-2">
          <option value="low">低</option>
          <option value="medium">中</option>
          <option value="high">高</option>
        </select>
      </div>
      <button type="submit" class="bg-indigo-600 text-white px-6 py-2 rounded-lg">
        创建任务
      </button>
    </div>
  </form>
{/if}

<!-- 看板列 -->
<div class="grid grid-cols-3 gap-6">
  {#each Object.entries(columns) as [name, tasks]}
    <div>
      <h2 class="font-semibold mb-3 flex items-center gap-2">
        {name}
        <span class="text-sm text-gray-400">({tasks.length})</span>
      </h2>
      <div class="space-y-3">
        {#each tasks as task (task.id)}
          <form method="POST" action="?/updateStatus">
            <input type="hidden" name="id" value={task.id} />
            <TaskCard
              {task}
              onDelete={(id) => {
                // 使用表单提交删除
                const form = document.createElement('form');
                form.method = 'POST';
                form.action = '?/delete';
                const input = document.createElement('input');
                input.type = 'hidden';
                input.name = 'id';
                input.value = String(id);
                form.appendChild(input);
                document.body.appendChild(form);
                form.submit();
              }}
              onStatusChange={(id, status) => {
                const input = document.createElement('input');
                input.type = 'hidden';
                input.name = 'status';
                input.value = status;
                // 找到最近的 form 并添加 status 字段后提交
                const card = document.querySelector(`[data-task-id="${id}"]`);
                card?.closest('form')?.appendChild(input);
                card?.closest('form')?.submit();
              }}
            />
          </form>
        {/each}
      </div>
    </div>
  {/each}
</div>
```

---

## 第五步：Claude Code 协作模式深度实战

### 5.1 分步开发策略

不要一次性描述整个应用。将需求拆分为 Claude Code 可处理的小块：

```mermaid
flowchart TD
    A[需求描述] --> B[第1轮：数据库层]
    B --> C[第2轮：路由与数据加载]
    C --> D[第3轮：UI组件]
    D --> E[第4轮：Server Actions]
    E --> F[第5轮：样式与交互]
    F --> G[第6轮：部署配置]

    style A fill:#6366f1,color:#fff
    style G fill:#22c55e,color:#fff
```

每一轮的提示词模板：

```
## 当前状态
已完成：[已完成的模块]
进行中：[当前模块]

## 需求
[具体描述]

## 约束
- 使用 Svelte 5 Runes 语法
- TypeScript 类型安全
- 遵循 CLAUDE.md 中的文件结构
```

### 5.2 让 Claude Code 生成组件脚手架

```
请在 src/lib/components/ 下创建以下组件，每个组件使用 Svelte 5 Runes 语法：

1. TaskCard.svelte — 任务卡片，接收 task prop，支持状态变更和删除
2. TaskForm.svelte — 新建/编辑任务表单，使用 $bindable 实现双向绑定
3. TaskBoard.svelte — 看板布局，三列分组显示任务
4. StatusBadge.svelte — 状态标签，根据状态显示不同颜色
5. PriorityIcon.svelte — 优先级图标

所有组件的 props 使用 $props() rune，不要使用 export let。
```

### 5.3 调试 Svelte 5 常见问题

当遇到 Svelte 5 相关错误时，告诉 Claude Code：

```
我遇到了以下错误：
[粘贴错误信息]

请检查：
1. 是否有 Svelte 4 语法混入（$:、$store、on:click、<slot>）
2. $state/$derived 是否在 .svelte 文件的 <script> 顶层使用
3. $effect 中是否有异步操作未正确处理
```

**常见 Svelte 5 错误对照表**：

| 错误信息 | 原因 | 修复 |
|----------|------|------|
| `Cannot use $state outside a .svelte file` | Runes 只能在 .svelte 文件顶层使用 | 改用 `.svelte.ts` 文件中的 class |
| `$derived cannot be used with assignments` | $derived 是只读的 | 改用 $state + $effect |
| `on:click is not a valid attribute` | Svelte 4 事件语法 | 改为 `onclick={handler}` |
| `Unexpected token in slot` | Svelte 5 移除了 `<slot>` | 使用 `{#snippet}` + `{@render}` |

### 5.4 用 `.svelte.ts` 共享响应式逻辑

当需要在多个组件间共享状态时，使用 `.svelte.ts` 文件：

```typescript
// src/lib/stores/task-store.svelte.ts

import { getAllTasks, createTask, updateTask, deleteTask } from '$lib/server/db';
import type { Task } from '$lib/server/db';

// 使用 class 封装响应式状态（Svelte 5 推荐模式）
class TaskStore {
  tasks = $state<Task[]>([]);
  loading = $state(false);
  error = $state<string | null>(null);

  // $derived 自动计算
  get stats() {
    return {
      total: this.tasks.length,
      todo: this.tasks.filter(t => t.status === 'todo').length,
      done: this.tasks.filter(t => t.status === 'done').length,
    };
  }

  // 方法可以放在 class 内
  filterByStatus(status: Task['status'] | 'all') {
    if (status === 'all') return this.tasks;
    return this.tasks.filter(t => t.status === status);
  }
}

export const taskStore = new TaskStore();
```

> **注意**：在 `.svelte.ts` 文件中使用 runes 时，需要用 `class` + `get` 模式替代 `$derived`，因为顶层 runes 在非 .svelte 文件中受限。

---

## 第六步：完整实战 — 任务详情页与高级交互

### 6.1 动态路由

创建 `src/routes/tasks/[id]/+page.server.ts`：

```typescript
import { getTaskById } from '$lib/server/db';
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ params }) => {
  const task = getTaskById(Number(params.id));

  if (!task) {
    throw error(404, '任务不存在');
  }

  return { task };
};
```

创建 `src/routes/tasks/[id]/+page.svelte`：

```svelte
<script lang="ts">
  import type { PageData } from './$types';

  let { data }: { data: PageData } = $props();

  // 编辑模式
  let editing = $state(false);
  let editTitle = $state(data.task.title);
  let editDescription = $state(data.task.description);

  // 监听数据变化重置编辑状态
  $effect(() => {
    editTitle = data.task.title;
    editDescription = data.task.description;
  });
</script>

<div class="max-w-2xl mx-auto">
  <a href="/" class="text-indigo-600 hover:underline mb-4 inline-block">
    ← 返回看板
  </a>

  {#if editing}
    <form method="POST" action="?/update" class="bg-white rounded-lg shadow p-6">
      <input type="hidden" name="id" value={data.task.id} />
      <div class="space-y-4">
        <input name="title" bind:value={editTitle} class="w-full border rounded px-3 py-2 text-xl font-bold" />
        <textarea name="description" bind:value={editDescription} class="w-full border rounded px-3 py-2" rows="6"></textarea>
        <div class="flex gap-3">
          <button type="submit" class="bg-indigo-600 text-white px-4 py-2 rounded">保存</button>
          <button type="button" class="border px-4 py-2 rounded" onclick={() => editing = false}>取消</button>
        </div>
      </div>
    </form>
  {:else}
    <div class="bg-white rounded-lg shadow p-6">
      <div class="flex items-center justify-between mb-4">
        <h1 class="text-2xl font-bold">{data.task.title}</h1>
        <button class="text-indigo-600" onclick={() => editing = true}>编辑</button>
      </div>
      <p class="text-gray-600">{data.task.description || '暂无描述'}</p>
      <div class="mt-4 flex gap-4 text-sm text-gray-400">
        <span>优先级: {data.task.priority}</span>
        <span>创建于: {new Date(data.task.created_at).toLocaleDateString('zh-CN')}</span>
      </div>
    </div>
  {/if}
</div>
```

### 6.2 添加过渡动画

Svelte 内置过渡系统：

```svelte
<script lang="ts">
  import { fly, fade } from 'svelte/transition';
</script>

{#each tasks as task (task.id)}
  <div transition:fly={{ y: 20, duration: 300 }}>
    <TaskCard {task} />
  </div>
{/each}
```

---

## 第七步：完整工作流架构图

```mermaid
flowchart TB
    subgraph Client["浏览器端 (Svelte 5 Runes)"]
        A[$state — 响应式状态] --> B[$derived — 派生计算]
        B --> C[$effect — 副作用]
        D[$props — 组件属性] --> E["{#snippet} + {@render}"]
    end

    subgraph SvelteKit["SvelteKit 路由层"]
        F["+page.server.ts<br/>Load 函数"] --> G["+page.svelte<br/>UI 渲染"]
        H["Form Actions<br/>create / update / delete"] --> I["Server-only<br/>数据变更"]
    end

    subgraph Server["服务端 (Node.js)"]
        J["db.ts<br/>better-sqlite3"] --> K["schema.sql<br/>SQLite 数据库"]
        L["$lib/server/<br/>服务端代码"] --> J
    end

    Client -->|HTTP Request| SvelteKit
    SvelteKit -->|SQL Query| Server
    Server -->|JSON Response| SvelteKit
    SvelteKit -->|HTML / Data| Client

    style Client fill:#6366f1,color:#fff
    style SvelteKit fill:#f59e0b,color:#fff
    style Server fill:#22c55e,color:#fff
```

```mermaid
sequenceDiagram
    participant U as 用户
    participant P as +page.svelte
    participant PS as +page.server.ts
    participant DB as SQLite

    U->>P: 访问首页
    P->>PS: load() 请求数据
    PS->>DB: getAllTasks()
    DB-->>PS: Task[]
    PS-->>P: { tasks: Task[] }
    P-->>U: 渲染看板

    U->>P: 填写新任务表单
    U->>P: 点击"创建"
    P->>PS: POST ?/create
    PS->>DB: createTask(data)
    DB-->>PS: 新 Task
    PS-->>P: { success: true }
    PS->>DB: getAllTasks()（自动重新加载）
    DB-->>PS: Task[]
    PS-->>P: 更新后的 { tasks }
    P-->>U: 看板更新（无刷新）
```

---

## 第八步：部署配置

### 8.1 Vercel 部署

```bash
npm install -D @sveltejs/adapter-vercel
```

编辑 `svelte.config.js`：

```javascript
import adapter from '@sveltejs/adapter-vercel';

export default {
  kit: {
    adapter: adapter({
      runtime: 'nodejs20.x',
      regions: ['hnd1']  // 东京区域，中国访问较快
    })
  }
};
```

### 8.2 让 Claude Code 配置部署

提示词：

```
请将本项目配置为 Vercel 部署：
1. 安装并配置 adapter-vercel
2. 确保 svelte.config.js 正确
3. 添加 vercel.json（如果需要）
4. 确认构建命令 npm run build 可正常运行
```

### 8.3 部署检查清单

让 Claude Code 执行完整检查：

```
请执行以下部署前检查：
1. npm run build 是否成功
2. 是否有 TypeScript 类型错误
3. 是否有 ESLint 警告
4. 环境变量是否需要配置
5. .env 文件是否在 .gitignore 中
```

---

## 第九步：Claude Code + Svelte 5 高级技巧

### 9.1 用 Claude Code 批量迁移 Svelte 4 到 Svelte 5

如果已有 Svelte 4 项目：

```
请帮我将本项目从 Svelte 4 迁移到 Svelte 5。按照以下规则逐文件修改：

1. `let x = 0` → `let x = $state(0)`
2. `$: doubled = x * 2` → `let doubled = $derived(x * 2)`
3. `export let prop` → `let { prop } = $props()`
4. `on:click={handler}` → `onclick={handler}`
5. `<slot />` → `{@render children()}`
6. `createEventDispatcher` → 直接使用回调函数 props
7. `writable/readable stores` → `.svelte.ts` 中的 class

每次只修改一个文件，修改后运行 npm run build 确认无报错。
```

### 9.2 上下文管理技巧

对于大型 Svelte 项目，上下文窗口管理至关重要：

1. **分模块开发**：每次只聚焦一个路由或组件，不要让 Claude Code 一次读取全部文件
2. **使用 `.claude/rules/` 目录**：将 Svelte 5 语法规则放在独立的 rule 文件中
3. **利用 `/compact`**：当上下文接近上限时，执行 `/compact` 压缩历史对话

创建 `.claude/rules/svelte5.md`：

```markdown
# Svelte 5 编码规则

## Runes 语法
- 状态声明：`let x = $state(initialValue)`
- 派生计算：`let y = $derived(expression)`
- 副作用：`$effect(() => { ... })`
- 组件属性：`let { a, b } = $props()`

## 事件处理
- 使用 `onclick={handler}` 而非 `on:click={handler}`
- 使用 `onchange`、`oninput`、`onsubmit` 等

## 模板
- 条件渲染：`{#if}...{:else}...{/if}`
- 循环：`{#each items as item (item.id)}...{/each}`
- Snippets：`{#snippet name(args)}...{/snippet}`
- 渲染：`{@render name(args)}`
```

### 9.3 实用 Claude Code 提示词模板

| 场景 | 提示词 |
|------|--------|
| 创建新组件 | `请创建 [组件名].svelte，使用 Svelte 5 $props rune，功能是 [描述]` |
| 修复 Runes 错误 | `这个 $derived 报错 [错误信息]，请检查是否 [可能原因]` |
| 添加 Server Action | `在 +page.server.ts 中添加 [action名] action，处理 [逻辑]` |
| 样式调整 | `请用 Tailwind CSS 优化 [组件名] 的响应式布局` |
| 性能优化 | `分析 [组件名] 的渲染性能，是否有不必要的 $effect 或 $derived` |

---

## 延伸阅读

- [Svelte 5 官方文档 — Runes](https://svelte.dev/docs/svelte/$state) — Runes 系统 API 参考
- [SvelteKit 官方文档](https://svelte.dev/docs/kit) — 路由、加载、Actions 完整文档
- [Svelte 5 迁移指南](https://svelte.dev/docs/svelte/v5-migration-guide) — Svelte 4 到 5 的完整迁移说明
- [Claude Code Best Practices](https://code.claude.com/docs/en/best-practices) — 上下文管理与项目配置官方指南

---

## 训练营适用性评估

| 维度 | 评估 |
|------|------|
| 适合类型 | 前端开发训练营、全栈工程师速成班、AI 辅助开发工作坊 |
| 教学时长 | 3-4 小时（含实操练习） |
| 难度曲线 | 中等 — 需要前端基础，Svelte 5 新概念有学习门槛但曲线平缓 |
| 扩展方向 | 可扩展为：添加用户认证（Lucia）、实时协作（WebSocket）、PWA 离线支持 |
| 建议分组 | 2-3 人一组，每组完成不同功能模块后合并 |

## 参考来源

1. [Svelte 5 Runes: The Complete Guide — Full Stack SvelteKit](https://fullstacksveltekit.com/blog/svelte-5-runes) — Runes 系统 $state/$derived/$effect/$props 完整解析
2. [Claude Code Best Practices — Anthropic Docs](https://code.claude.com/docs/en/best-practices) — 上下文管理、CLAUDE.md 配置官方最佳实践
3. [SvelteKit Form Actions — Svelte Docs](https://svelte.dev/docs/kit/form-actions) — Server Actions 表单处理官方文档
4. [Claude Instructions for Svelte Developers — Dejan Vasic](https://dejan.vasic.com.au/blog/2025/10/my-svelte-claude-md-instructions) — CLAUDE.md Svelte 5 配置实战经验
5. [Svelte 5 Migration Guide — Svelte Docs](https://svelte.dev/docs/svelte/v5-migration-guide) — Svelte 4 到 5 的语法差异与迁移步骤

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
