项目结构 #

一、项目结构概述 #

良好的项目结构是可维护性和可扩展性的基础。

text
┌─────────────────────────────────────────────────────────────┐
│                    推荐项目结构                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  src/                                                       │
│  ├── lib/              共享代码                             │
│  │   ├── components/   通用组件                             │
│  │   ├── stores/       状态管理                             │
│  │   ├── utils/        工具函数                             │
│  │   └── server/       服务端代码                           │
│  ├── routes/           路由页面                             │
│  └── app.css           全局样式                             │
│                                                             │
│  static/               静态资源                             │
│  tests/                测试文件                             │
│                                                             │
└─────────────────────────────────────────────────────────────┘

二、SvelteKit 项目结构 #

2.1 基础结构 #

text
my-sveltekit-app/
├── src/
│   ├── lib/
│   │   ├── components/
│   │   │   ├── ui/              基础UI组件
│   │   │   │   ├── Button.svelte
│   │   │   │   ├── Input.svelte
│   │   │   │   └── index.ts
│   │   │   └── layout/          布局组件
│   │   │       ├── Header.svelte
│   │   │       ├── Footer.svelte
│   │   │       └── Sidebar.svelte
│   │   ├── stores/
│   │   │   ├── user.ts
│   │   │   ├── cart.ts
│   │   │   └── index.ts
│   │   ├── utils/
│   │   │   ├── format.ts
│   │   │   ├── validation.ts
│   │   │   └── api.ts
│   │   ├── server/
│   │   │   ├── database.ts
│   │   │   ├── auth.ts
│   │   │   └── email.ts
│   │   └── index.ts
│   ├── routes/
│   │   ├── +layout.svelte
│   │   ├── +layout.ts
│   │   ├── +page.svelte
│   │   ├── +page.ts
│   │   ├── +error.svelte
│   │   ├── (auth)/
│   │   │   ├── +layout.svelte
│   │   │   ├── login/
│   │   │   └── register/
│   │   ├── (app)/
│   │   │   ├── +layout.svelte
│   │   │   ├── dashboard/
│   │   │   └── settings/
│   │   └── api/
│   │       └── users/
│   │           └── +server.ts
│   ├── app.html
│   ├── app.css
│   └── app.d.ts
├── static/
│   ├── favicon.ico
│   └── images/
├── tests/
│   ├── unit/
│   └── e2e/
├── svelte.config.js
├── vite.config.ts
├── tsconfig.json
└── package.json

2.2 路径别名 #

tsconfig.json

json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "$lib": ["src/lib"],
      "$lib/*": ["src/lib/*"],
      "$components": ["src/lib/components"],
      "$stores": ["src/lib/stores"],
      "$utils": ["src/lib/utils"]
    }
  }
}

svelte.config.js

javascript
import adapter from '@sveltejs/adapter-auto';

export default {
  kit: {
    adapter: adapter(),
    alias: {
      $components: 'src/lib/components',
      $stores: 'src/lib/stores',
      $utils: 'src/lib/utils'
    }
  }
};

使用:

svelte
<script>
  import Button from '$components/ui/Button.svelte';
  import { user } from '$stores/user';
  import { formatDate } from '$utils/format';
</script>

三、组件组织 #

3.1 组件目录结构 #

text
src/lib/components/
├── ui/                    基础UI组件
│   ├── Button/
│   │   ├── Button.svelte
│   │   ├── Button.test.ts
│   │   └── index.ts
│   ├── Input/
│   ├── Modal/
│   └── index.ts
├── layout/                布局组件
│   ├── Header/
│   ├── Footer/
│   ├── Sidebar/
│   └── index.ts
├── forms/                 表单组件
│   ├── TextField/
│   ├── Select/
│   └── index.ts
├── data/                  数据展示组件
│   ├── Table/
│   ├── Card/
│   └── index.ts
└── index.ts

3.2 组件导出 #

src/lib/components/ui/Button/index.ts

typescript
export { default as Button } from './Button.svelte';
export type { ButtonProps } from './Button.svelte';

src/lib/components/ui/index.ts

typescript
export * from './Button';
export * from './Input';
export * from './Modal';

src/lib/components/index.ts

typescript
export * from './ui';
export * from './layout';
export * from './forms';
export * from './data';

使用:

svelte
<script>
  import { Button, Input, Modal } from '$lib/components';
</script>

3.3 组件文件结构 #

svelte
<script lang="ts">
  interface Props {
    variant?: 'primary' | 'secondary';
    size?: 'sm' | 'md' | 'lg';
    disabled?: boolean;
    onclick?: () => void;
  }
  
  let {
    variant = 'primary',
    size = 'md',
    disabled = false,
    onclick,
    children
  }: Props & { children?: import('svelte').Snippet } = $props();
</script>

<button
  class="btn btn-{variant} btn-{size}"
  class:disabled
  {disabled}
  {onclick}
>
  {#if children}
    {@render children()}
  {/if}
</button>

<style>
  .btn {
    padding: 0.5rem 1rem;
    border: none;
    border-radius: 4px;
    cursor: pointer;
  }
  
  .btn-primary {
    background: #ff3e00;
    color: white;
  }
  
  .btn-secondary {
    background: #666;
    color: white;
  }
  
  .btn-sm { padding: 0.25rem 0.5rem; }
  .btn-md { padding: 0.5rem 1rem; }
  .btn-lg { padding: 0.75rem 1.5rem; }
  
  .disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
</style>

四、状态管理组织 #

4.1 Store 目录结构 #

text
src/lib/stores/
├── user.ts
├── cart.ts
├── theme.ts
├── notifications.ts
└── index.ts

4.2 Store 示例 #

src/lib/stores/user.ts

typescript
import { writable, derived } from 'svelte/store';
import type { User } from '$lib/types';

function createUserStore() {
  const { subscribe, set, update } = writable<User | null>(null);
  
  return {
    subscribe,
    login: async (credentials: Credentials) => {
      const user = await loginApi(credentials);
      set(user);
    },
    logout: async () => {
      await logoutApi();
      set(null);
    },
    updateProfile: (data: Partial<User>) => {
      update(user => user ? { ...user, ...data } : null);
    }
  };
}

export const user = createUserStore();

export const isAuthenticated = derived(
  user,
  $user => $user !== null
);

export const userRole = derived(
  user,
  $user => $user?.role ?? null
);

src/lib/stores/index.ts

typescript
export * from './user';
export * from './cart';
export * from './theme';
export * from './notifications';

五、工具函数组织 #

5.1 Utils 目录结构 #

text
src/lib/utils/
├── format.ts
├── validation.ts
├── api.ts
├── storage.ts
└── index.ts

5.2 工具函数示例 #

src/lib/utils/format.ts

typescript
export function formatDate(date: Date | string): string {
  return new Date(date).toLocaleDateString('zh-CN');
}

export function formatCurrency(amount: number): string {
  return new Intl.NumberFormat('zh-CN', {
    style: 'currency',
    currency: 'CNY'
  }).format(amount);
}

export function formatBytes(bytes: number): string {
  if (bytes === 0) return '0 Bytes';
  const k = 1024;
  const sizes = ['Bytes', 'KB', 'MB', 'GB'];
  const i = Math.floor(Math.log(bytes) / Math.log(k));
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}

src/lib/utils/validation.ts

typescript
export function isEmail(value: string): boolean {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
}

export function isPhone(value: string): boolean {
  return /^1[3-9]\d{9}$/.test(value);
}

export function isUrl(value: string): boolean {
  try {
    new URL(value);
    return true;
  } catch {
    return false;
  }
}

export function required(value: unknown): boolean {
  if (typeof value === 'string') return value.trim().length > 0;
  return value !== null && value !== undefined;
}

六、服务端代码组织 #

6.1 Server 目录结构 #

text
src/lib/server/
├── database.ts
├── auth.ts
├── email.ts
├── cache.ts
└── index.ts

6.2 服务端代码示例 #

src/lib/server/database.ts

typescript
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

export const db = {
  user: {
    findById: (id: string) => prisma.user.findUnique({ where: { id } }),
    findByEmail: (email: string) => prisma.user.findUnique({ where: { email } }),
    create: (data: CreateUserInput) => prisma.user.create({ data }),
    update: (id: string, data: UpdateUserInput) => 
      prisma.user.update({ where: { id }, data }),
    delete: (id: string) => prisma.user.delete({ where: { id } })
  },
  post: {
    findMany: (params?: PostFindManyParams) => prisma.post.findMany(params),
    findById: (id: string) => prisma.post.findUnique({ where: { id } }),
    create: (data: CreatePostInput) => prisma.post.create({ data }),
    update: (id: string, data: UpdatePostInput) => 
      prisma.post.update({ where: { id }, data }),
    delete: (id: string) => prisma.post.delete({ where: { id } })
  }
};

src/lib/server/auth.ts

typescript
import { db } from './database';
import { createSession, validateSession } from './session';
import { hash, compare } from 'bcrypt';

export async function register(email: string, password: string, name: string) {
  const hashedPassword = await hash(password, 10);
  
  const user = await db.user.create({
    email,
    password: hashedPassword,
    name
  });
  
  return createSession(user.id);
}

export async function login(email: string, password: string) {
  const user = await db.user.findByEmail(email);
  
  if (!user || !(await compare(password, user.password))) {
    throw new Error('Invalid credentials');
  }
  
  return createSession(user.id);
}

export async function logout(sessionId: string) {
  await invalidateSession(sessionId);
}

export async function getUser(sessionId: string) {
  const session = await validateSession(sessionId);
  if (!session) return null;
  
  return db.user.findById(session.userId);
}

七、类型定义组织 #

7.1 Types 目录结构 #

text
src/lib/types/
├── user.ts
├── post.ts
├── api.ts
└── index.ts

7.2 类型定义示例 #

src/lib/types/user.ts

typescript
export interface User {
  id: string;
  email: string;
  name: string;
  role: 'admin' | 'user';
  createdAt: Date;
  updatedAt: Date;
}

export interface CreateUserInput {
  email: string;
  password: string;
  name: string;
}

export interface UpdateUserInput {
  name?: string;
  email?: string;
}

src/lib/types/api.ts

typescript
export interface ApiResponse<T> {
  data: T;
  success: boolean;
  message?: string;
}

export interface PaginatedResponse<T> {
  data: T[];
  total: number;
  page: number;
  limit: number;
}

export interface ApiError {
  code: string;
  message: string;
  details?: Record<string, unknown>;
}

八、测试组织 #

8.1 测试目录结构 #

text
tests/
├── unit/
│   ├── components/
│   │   └── Button.test.ts
│   ├── stores/
│   │   └── user.test.ts
│   └── utils/
│       └── format.test.ts
└── e2e/
    ├── login.test.ts
    └── dashboard.test.ts

8.2 单元测试示例 #

tests/unit/components/Button.test.ts

typescript
import { render, fireEvent } from '@testing-library/svelte';
import { describe, it, expect } from 'vitest';
import Button from '$lib/components/ui/Button/Button.svelte';

describe('Button', () => {
  it('renders with default props', () => {
    const { getByRole } = render(Button);
    expect(getByRole('button')).toBeInTheDocument();
  });
  
  it('handles click events', async () => {
    let clicked = false;
    const { getByRole } = render(Button, {
      onclick: () => { clicked = true; }
    });
    
    await fireEvent.click(getByRole('button'));
    expect(clicked).toBe(true);
  });
});

九、总结 #

目录 说明
src/lib/components 可复用组件
src/lib/stores 状态管理
src/lib/utils 工具函数
src/lib/server 服务端代码
src/lib/types 类型定义
src/routes 路由页面
static 静态资源
tests 测试文件

项目结构要点:

  • 按功能模块组织代码
  • 使用路径别名简化导入
  • 统一导出入口
  • 分离服务端和客户端代码
  • 合理组织测试文件
最后更新:2026-03-28