Back to Blog

用 Tauri 写一个 AI Chat:3MB 的桌面应用干翻 Electron 100MB

2026-05-08T17:15:00+08:00
Tauri Rust Svelte AI Chat Desktop App Electron

用 Tauri 写一个 AI Chat:3MB 的桌面应用干翻 Electron 100MB

我之前做了一个网页版 AI Chat(纯浏览器直连 API),遇到两个大问题:API key 暴露在前端、CORS 到处报错。Electron?打包 100MB 起,内存占用动不动 500MB。

上周发现了 Tauri 2.0,用 Rust 做后端、Web 做前端,打包产物 3MB,内存占用不到 50MB。花了一个下午写了个 AI Chat 桌面应用,API key 安全存在 Rust 后端,零 CORS 问题,还支持流式输出。

这篇文章把整个过程拆解给你,从安装到打包发布,每一步都能跟着跑。

本文提纲

  1. Tauri 是什么、为什么比 Electron 好
  2. 环境准备与项目初始化
  3. Rust 后端:API 代理与流式处理
  4. Svelte 前端:聊天 UI 搭建
  5. 前后端通信:invoke 与事件系统
  6. 打包与发布

Tauri 是什么、为什么比 Electron 好

Tauri 的核心思路:用系统自带的 WebView 渲染 UI,用 Rust 处理后端逻辑

Electron 的做法是把一整个 Chromium 打包进应用,所以体积大、内存高。Tauri 不带浏览器,直接调用操作系统的 WebView(macOS 用 WebKit、Windows 用 WebView2、Linux 用 WebKitGTK),省掉了几十 MB 的运行时。

graph LR
    subgraph "Tauri Architecture"
        A[WebView
Svelte/React/Vue] -->|IPC| B[Rust Backend
System API + HTTP] end subgraph "Electron Architecture" C[Chromium
Full Browser] -->|Node.js| D[Main Process] end

关键数据对比:

指标 Tauri 2.0 Electron
打包体积 3-10 MB 100-200 MB
空载内存 ~30-50 MB ~200-500 MB
启动速度 < 1s 2-5s
语言 Rust(内存安全) Node.js
移动端 iOS + Android 不支持
安全模型 最小权限(白名单) 全权限

还有一个容易被忽略的好处:Rust 的性能让 API 代理几乎零开销。同样的流式转发,Node.js 需要处理 backpressure 和内存管理,Rust 天然高效。

但也要诚实说 Tauri 的缺点:Rust 学习曲线陡,生态不如 Node.js 丰富,遇到 WebView 兼容性问题时排查更麻烦。如果你团队没有 Rust 经验,Electron 的开发效率可能更高。

环境准备与项目初始化

第 1 步:安装依赖

macOS

xcode-select --install
rustup-init

Windows

# 安装 Visual Studio C++ Build Tools
# 安装 WebView2(Windows 11 自带,Windows 10 需手动装)
# 安装 Rust: https://rustup.rs

Linux (Ubuntu)

sudo apt install libwebkit2gtk-4.1-dev build-essential curl wget libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

第 2 步:创建项目

npm create tauri-app@latest ai-chat -- --template svelte-ts
cd ai-chat
npm install

项目结构长这样:

ai-chat/
├── src/                    # Svelte 前端
│   ├── App.svelte
│   ├── main.ts
│   └── app.css
├── src-tauri/              # Rust 后端
│   ├── src/
│   │   └── main.rs         # Rust 入口
│   ├── Cargo.toml          # Rust 依赖
│   └── tauri.conf.json     # Tauri 配置
├── package.json
└── vite.config.ts

第 3 步:验证能跑

npm run tauri dev

第一次运行会编译 Rust 依赖,比较慢(2-5 分钟)。之后热更新很快,前端秒级刷新。

看到窗口弹出来就说明环境 OK。

Rust 后端:API 代理与流式处理

这是整个应用最关键的部分。Rust 后端负责:调用 AI API、处理流式响应、通过事件推送给前端。

安装 Rust 依赖

编辑 src-tauri/Cargo.toml

[dependencies]
tauri = { version = "2", features = [] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
reqwest = { version = "0.12", features = ["json", "stream"] }
tokio = { version = "1", features = ["full"] }
futures = "0.3"

定义数据结构

src-tauri/src/main.rs 顶部:

use serde::{Deserialize, Serialize};
use tauri::Emitter;

#[derive(Debug, Serialize, Deserialize, Clone)]
struct Message {
    role: String,
    content: String,
}

#[derive(Debug, Deserialize)]
struct ChatRequest {
    messages: Vec,
    model: String,
    api_key: String,
    api_url: String,
}

实现 API 调用命令

#[tauri::command]
async fn send_chat(
    app: tauri::AppHandle,
    request: ChatRequest,
) -> Result {
    let client = reqwest::Client::new();

    let body = serde_json::json!({
        "model": request.model,
        "messages": request.messages,
        "stream": true,
        "max_tokens": 4096,
    });

    let response = client
        .post(&request.api_url)
        .header("Content-Type", "application/json")
        .header("Authorization", format!("Bearer {}", request.api_key))
        .json(&body)
        .send()
        .await
        .map_err(|e| format!("Request failed: {}", e))?;

    let mut stream = response.bytes_stream();
    use futures::StreamExt;
    let mut full_content = String::new();

    while let Some(chunk) = stream.next().await {
        let chunk = chunk.map_err(|e| format!("Stream error: {}", e))?;
        let text = String::from_utf8_lossy(&chunk);

        for line in text.lines() {
            if line.starts_with("data: ") {
                let data = &line[6..];
                if data == "[DONE]" {
                    continue;
                }
                if let Ok(parsed) = serde_json::from_str::(data) {
                    if let Some(content) = parsed["choices"][0]["delta"]["content"].as_str() {
                        full_content.push_str(content);
                        let _ = app.emit("chat-chunk", content);
                    }
                }
            }
        }
    }

    Ok(full_content)
}

这段代码做了三件事:

  1. reqwest 发送 POST 请求到 AI API(支持 OpenAI 兼容的任何 API)
  2. 解析 SSE(Server-Sent Events)流式响应
  3. 每收到一个 chunk 就通过 app.emit("chat-chunk", content) 推送给前端

注册命令

fn main() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![send_chat])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

Svelte 前端:聊天 UI 搭建

前端就是普通的 Svelte 应用,唯一的区别是调 API 用 Tauri 的 invoke 而不是 fetch

安装 Tauri API 包

npm install @tauri-apps/api

聊天主界面

替换 src/App.svelte



{#each messages as msg}
{msg.role}
{msg.content}
{/each}