本教程带你使用 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、基本终端操作 |
在终端中运行:
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 打开项目:
claude
这一步至关重要。Svelte 5 的 Runes 语法与 Svelte 4 完全不同,如果 CLAUDE.md 不明确声明版本,Claude Code 可能生成旧语法代码。
在项目根目录创建 CLAUDE.md:
# 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 会混用两代语法,导致编译报错。
在 Claude Code 会话中输入:
请安装以下依赖:tailwindcss @tailwindcss/vite better-sqlite3 及其类型定义
Claude Code 会执行:
npm install tailwindcss @tailwindcss/vite better-sqlite3
npm install -D @types/better-sqlite3
配置 Tailwind CSS。编辑 vite.config.ts:
import { sveltekit } from '@sveltejs/kit/vite';
import tailwindcss from '@tailwindcss/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [tailwindcss(), sveltekit()]
});
创建 src/app.css:
@import 'tailwindcss';
请运行 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 |
创建 src/lib/server/schema.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);
创建 src/lib/server/db.ts:
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;
}
告诉 Claude Code:
请创建 src/lib/server/db.ts,使用 better-sqlite3 实现任务 CRUD 操作。
要求:TypeScript 类型安全、WAL 模式、参数化查询防注入。
Claude Code 会根据 CLAUDE.md 中定义的 src/lib/server/ 目录结构,自动将文件放在正确位置。
这是本教程的核心部分。Svelte 5 引入了 Runes(符文)系统,彻底重构了响应式模型。
$state — 响应式状态在 Svelte 4 中,赋值触发响应式:
<!-- Svelte 4(已过时,禁止使用) -->
<script>
let count = 0;
function increment() {
count += 1; // 赋值 = 响应式
}
</script>
在 Svelte 5 中,使用 $state rune:
<!-- Svelte 5 Runes -->
<script lang="ts">
let count = $state(0);
function increment() {
count++; // 无需赋值技巧,直接修改即触发更新
}
</script>
<button onclick={increment}>
Count: {count}
</button>
关键区别:$state 是细粒度响应式,只更新实际变化的部分,不触发整个组件重新渲染。
$derived — 派生状态<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>
$effect — 副作用<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>
$props — 组件属性<!-- 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>
{@render} — 替代 Slot<!-- 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}
使用方式:
<TaskList {filteredTasks}>
{#snippet children({ task, index })}
<TaskCard
{task}
onDelete={handleDelete}
onStatusChange={handleStatusChange}
/>
{/snippet}
</TaskList>
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(可选)
创建 src/routes/+layout.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>
创建 src/routes/+page.server.ts:
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"指令后,会默认使用这种模式。
创建 src/routes/+page.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 可处理的小块:
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 中的文件结构
请在 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。
当遇到 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} |
.svelte.ts 共享响应式逻辑当需要在多个组件间共享状态时,使用 .svelte.ts 文件:
// 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 文件中受限。
创建 src/routes/tasks/[id]/+page.server.ts:
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:
<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>
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}
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
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: 看板更新(无刷新)
npm install -D @sveltejs/adapter-vercel
编辑 svelte.config.js:
import adapter from '@sveltejs/adapter-vercel';
export default {
kit: {
adapter: adapter({
runtime: 'nodejs20.x',
regions: ['hnd1'] // 东京区域,中国访问较快
})
}
};
提示词:
请将本项目配置为 Vercel 部署:
1. 安装并配置 adapter-vercel
2. 确保 svelte.config.js 正确
3. 添加 vercel.json(如果需要)
4. 确认构建命令 npm run build 可正常运行
让 Claude Code 执行完整检查:
请执行以下部署前检查:
1. npm run build 是否成功
2. 是否有 TypeScript 类型错误
3. 是否有 ESLint 警告
4. 环境变量是否需要配置
5. .env 文件是否在 .gitignore 中
如果已有 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 确认无报错。
对于大型 Svelte 项目,上下文窗口管理至关重要:
.claude/rules/ 目录:将 Svelte 5 语法规则放在独立的 rule 文件中/compact:当上下文接近上限时,执行 /compact 压缩历史对话创建 .claude/rules/svelte5.md:
# 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)}`
| 场景 | 提示词 |
|---|---|
| 创建新组件 | 请创建 [组件名].svelte,使用 Svelte 5 $props rune,功能是 [描述] |
| 修复 Runes 错误 | 这个 $derived 报错 [错误信息],请检查是否 [可能原因] |
| 添加 Server Action | 在 +page.server.ts 中添加 [action名] action,处理 [逻辑] |
| 样式调整 | 请用 Tailwind CSS 优化 [组件名] 的响应式布局 |
| 性能优化 | 分析 [组件名] 的渲染性能,是否有不必要的 $effect 或 $derived |
| 维度 | 评估 |
|---|---|
| 适合类型 | 前端开发训练营、全栈工程师速成班、AI 辅助开发工作坊 |
| 教学时长 | 3-4 小时(含实操练习) |
| 难度曲线 | 中等 — 需要前端基础,Svelte 5 新概念有学习门槛但曲线平缓 |
| 扩展方向 | 可扩展为:添加用户认证(Lucia)、实时协作(WebSocket)、PWA 离线支持 |
| 建议分组 | 2-3 人一组,每组完成不同功能模块后合并 |
[!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 等场景全覆盖。
