Svelte集成 #

一、项目创建 #

1.1 使用脚手架创建 #

bash
# 创建 Svelte + Tauri 项目
npm create tauri-app@latest my-svelte-app

# 选择配置
✔ Project name · my-svelte-app
✔ Choose which language to use for your frontend · TypeScript / JavaScript
✔ Choose your package manager · pnpm
✔ Choose your UI template · Svelte
✔ Choose your UI flavor · TypeScript

1.2 项目结构 #

text
my-svelte-app/
├── src/
│   ├── lib/
│   │   ├── components/
│   │   │   ├── Header.svelte
│   │   │   └── Sidebar.svelte
│   │   └── stores/
│   │       └── app.ts
│   ├── routes/
│   │   ├── +page.svelte
│   │   └── settings/
│   │       └── +page.svelte
│   ├── app.html
│   └── app.css
├── src-tauri/
│   ├── src/
│   │   └── lib.rs
│   ├── Cargo.toml
│   └── tauri.conf.json
├── package.json
├── svelte.config.js
├── vite.config.ts
└── tsconfig.json

1.3 安装依赖 #

bash
# 安装 Tauri API
pnpm add @tauri-apps/api

# 安装常用插件
pnpm add @tauri-apps/plugin-shell
pnpm add @tauri-apps/plugin-dialog
pnpm add @tauri-apps/plugin-fs

二、基础配置 #

2.1 Vite 配置 #

typescript
// vite.config.ts
import { defineConfig } from 'vite';
import { sveltekit } from '@sveltejs/kit/vite';

export default defineConfig({
    plugins: [sveltekit()],
    clearScreen: false,
    server: {
        port: 1420,
        strictPort: true,
    },
    envPrefix: ['VITE_', 'TAURI_'],
    build: {
        target: ['es2021', 'chrome100', 'safari13'],
        minify: !process.env.TAURI_DEBUG ? 'esbuild' : false,
        sourcemap: !!process.env.TAURI_DEBUG,
    },
});

2.2 SvelteKit 配置 #

typescript
// svelte.config.js
import adapter from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';

const config = {
    preprocess: vitePreprocess(),
    kit: {
        adapter: adapter({
            pages: 'build',
            assets: 'build',
            fallback: 'index.html',
            precompress: false,
            strict: true,
        }),
    },
};

export default config;

三、工具函数 #

3.1 命令调用 #

typescript
// lib/utils/commands.ts
import { invoke } from '@tauri-apps/api/core';
import { writable } from 'svelte/store';

export function createCommand<T, P = void>(command: string) {
    const data = writable<T | null>(null);
    const loading = writable(false);
    const error = writable<string | null>(null);

    async function execute(params?: P) {
        loading.set(true);
        error.set(null);

        try {
            const result = await invoke<T>(command, params);
            data.set(result);
            return result;
        } catch (err) {
            error.set(String(err));
            throw err;
        } finally {
            loading.set(false);
        }
    }

    return { data, loading, error, execute };
}

3.2 事件监听 #

typescript
// lib/utils/events.ts
import { onMount, onDestroy } from 'svelte';
import { listen, UnlistenFn } from '@tauri-apps/api/event';

export function useEvent<T>(event: string, callback: (payload: T) => void) {
    let unlisten: UnlistenFn | null = null;

    onMount(async () => {
        unlisten = await listen<T>(event, (e) => {
            callback(e.payload);
        });
    });

    onDestroy(() => {
        if (unlisten) {
            unlisten();
        }
    });
}

3.3 窗口状态 #

typescript
// lib/utils/window.ts
import { writable } from 'svelte/store';
import { onMount, onDestroy } from 'svelte';
import { getCurrentWindow } from '@tauri-apps/api/window';

export function useWindowState() {
    const isMaximized = writable(false);
    const window = getCurrentWindow();
    let unlisten: (() => void) | null = null;

    onMount(async () => {
        isMaximized.set(await window.isMaximized());
        unlisten = await window.onMaximized(({ payload }) => {
            isMaximized.set(payload);
        });
    });

    onDestroy(() => {
        if (unlisten) {
            unlisten();
        }
    });

    async function toggleMaximize() {
        await window.toggleMaximize();
    }

    async function minimize() {
        await window.minimize();
    }

    async function close() {
        await window.close();
    }

    return { isMaximized, toggleMaximize, minimize, close };
}

四、组件开发 #

4.1 标题栏组件 #

svelte
<script lang="ts">
    import { useWindowState } from '../lib/utils/window';

    const { isMaximized, toggleMaximize, minimize, close } = useWindowState();
</script>

<div class="titlebar" data-tauri-drag-region>
    <div class="titlebar-title">My App</div>
    <div class="titlebar-controls">
        <button class="titlebar-button" on:click={minimize}>
            <svg width="12" height="12" viewBox="0 0 12 12">
                <rect y="5" width="12" height="2" />
            </svg>
        </button>
        <button class="titlebar-button" on:click={toggleMaximize}>
            {#if $isMaximized}
                <svg width="12" height="12" viewBox="0 0 12 12">
                    <rect x="2" y="4" width="6" height="6" fill="none" stroke="currentColor" />
                    <path d="M4 4V2h6v6h-2" fill="none" stroke="currentColor" />
                </svg>
            {:else}
                <svg width="12" height="12" viewBox="0 0 12 12">
                    <rect x="2" y="2" width="8" height="8" fill="none" stroke="currentColor" />
                </svg>
            {/if}
        </button>
        <button class="titlebar-button close" on:click={close}>
            <svg width="12" height="12" viewBox="0 0 12 12">
                <path d="M1 1l10 10M11 1L1 11" stroke="currentColor" stroke-width="2" />
            </svg>
        </button>
    </div>
</div>

<style>
    .titlebar {
        display: flex;
        justify-content: space-between;
        align-items: center;
        height: 32px;
        background: #1e1e1e;
        padding: 0 8px;
        user-select: none;
    }

    .titlebar-title {
        color: #fff;
        font-size: 13px;
    }

    .titlebar-controls {
        display: flex;
        gap: 4px;
    }

    .titlebar-button {
        width: 32px;
        height: 24px;
        border: none;
        background: transparent;
        color: #fff;
        cursor: pointer;
        display: flex;
        align-items: center;
        justify-content: center;
        border-radius: 4px;
    }

    .titlebar-button:hover {
        background: rgba(255, 255, 255, 0.1);
    }

    .titlebar-button.close:hover {
        background: #e81123;
    }
</style>

4.2 文件选择组件 #

svelte
<script lang="ts">
    import { open } from '@tauri-apps/plugin-dialog';

    export let filters: { name: string; extensions: string[] }[] = [
        { name: 'All Files', extensions: ['*'] }
    ];

    let selectedPath: string | null = null;

    async function handleOpen() {
        const selected = await open({
            multiple: false,
            filters,
        });

        if (selected) {
            selectedPath = selected as string;
            dispatch('fileSelected', selectedPath);
        }
    }

    const dispatch = createEventDispatcher();
</script>

<div class="file-picker">
    <button on:click={handleOpen}>选择文件</button>
    {#if selectedPath}
        <span class="selected-path">{selectedPath}</span>
    {/if}
</div>

4.3 通知组件 #

svelte
<script lang="ts">
    import { onMount, onDestroy } from 'svelte';
    import { listen } from '@tauri-apps/api/event';

    interface NotificationData {
        title: string;
        message: string;
        type: 'info' | 'success' | 'error';
    }

    let notification: NotificationData | null = null;
    let timeout: ReturnType<typeof setTimeout> | null = null;
    let unlisten: (() => void) | null = null;

    onMount(async () => {
        unlisten = await listen<NotificationData>('notification', (event) => {
            notification = event.payload;

            if (timeout) {
                clearTimeout(timeout);
            }

            timeout = setTimeout(() => {
                notification = null;
            }, 3000);
        });
    });

    onDestroy(() => {
        if (unlisten) {
            unlisten();
        }
        if (timeout) {
            clearTimeout(timeout);
        }
    });
</script>

{#if notification}
    <div class="notification notification-{notification.type}">
        <strong>{notification.title}</strong>
        <p>{notification.message}</p>
    </div>
{/if}

<style>
    .notification {
        position: fixed;
        top: 20px;
        right: 20px;
        padding: 16px;
        border-radius: 8px;
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    }

    .notification-info {
        background: #e6f7ff;
        border: 1px solid #1890ff;
    }

    .notification-success {
        background: #f6ffed;
        border: 1px solid #52c41a;
    }

    .notification-error {
        background: #fff2f0;
        border: 1px solid #ff4d4f;
    }
</style>

五、状态管理 #

5.1 Svelte Store #

typescript
// lib/stores/app.ts
import { writable, derived } from 'svelte/store';
import { invoke } from '@tauri-apps/api/core';

interface User {
    id: number;
    name: string;
}

function createAppStore() {
    const user = writable<User | null>(null);
    const theme = writable<'light' | 'dark'>('light');

    return {
        user,
        theme,
        async loadUser() {
            const result = await invoke<User>('get_current_user');
            user.set(result);
        },
        setTheme(newTheme: 'light' | 'dark') {
            theme.set(newTheme);
        },
    };
}

export const appStore = createAppStore();

5.2 使用 Store #

svelte
<script lang="ts">
    import { onMount } from 'svelte';
    import { appStore } from '../lib/stores/app';

    onMount(() => {
        appStore.loadUser();
    });
</script>

<div class="app {$appStore.theme}">
    {#if $appStore.user}
        <h1>Welcome, {$appStore.user.name}</h1>
    {/if}
</div>

六、路由 #

6.1 SvelteKit 路由 #

text
src/routes/
├── +page.svelte          # 首页 /
├── +layout.svelte        # 布局
├── settings/
│   └── +page.svelte      # 设置页 /settings
└── about/
    └── +page.svelte      # 关于页 /about

6.2 布局文件 #

svelte
<script lang="ts">
    import TitleBar from '../lib/components/TitleBar.svelte';
</script>

<TitleBar />

<nav>
    <a href="/">Home</a>
    <a href="/settings">Settings</a>
    <a href="/about">About</a>
</nav>

<main>
    <slot />
</main>

七、最佳实践 #

7.1 组件 Props #

svelte
<script lang="ts">
    interface Props {
        title: string;
        count?: number;
        items: string[];
    }

    let { title, count = 0, items }: Props = $props();
</script>

7.2 组件事件 #

svelte
<script lang="ts">
    interface Events {
        click: MouseEvent;
        update: { value: string };
    }

    let { onclick, onupdate }: Events = $props();

    function handleClick(e: MouseEvent) {
        onclick?.(e);
        onupdate?.({ value: 'new value' });
    }
</script>

7.3 响应式声明 #

svelte
<script lang="ts">
    let count = $state(0);
    let doubled = $derived(count * 2);

    function increment() {
        count++;
    }
</script>

<button onclick={increment}>
    Count: {count} (doubled: {doubled})
</button>

八、总结 #

8.1 核心要点 #

要点 说明
工具函数 封装 Tauri API
组件开发 使用 Svelte 语法
状态管理 使用 Svelte Store
路由配置 使用 SvelteKit

8.2 下一步 #

现在你已经掌握了 Svelte 集成,接下来让我们学习 TypeScript支持,了解如何在 Tauri 项目中使用 TypeScript!

最后更新:2026-03-28