楼层: 首页/ 软件技术/ 前端进阶/ 第 3 章 Tauri 2.0:用 Web 技术写桌面 App
桌

第 3 章 Tauri 2.0:用 Web 技术写桌面 App

Tauri 2.0 — Rust Backend + WebView Frontend

想做一个桌面应用,但不想学 Swift/Kotlin/Qt?用你已经会的 HTML/CSS/JS 写 UI,Rust 写后端能力,Tauri 帮你打包成 Windows/macOS/Linux 安装包。包体小到 ~3MB(Electron 通常 100MB 起),内存低,安全。Tauri 2.0 还加了 iOS/Android 支持。

Tauri vs Electron:一句话对比

Tauri 2.0Electron
渲染系统自带 WebView(macOS WKWebView / Windows WebView2)自带 Chromium
后端Rust 编译成原生二进制Node.js
包体~3-10 MB~100-200 MB
内存低高
代表应用正在崛起VS Code / Slack / Discord / Figma
学习曲线要会一点 Rust纯 JS

项目结构

my-app/ src/ # 你的前端(React/Vue/任意框架) main.tsx src-tauri/ # Rust 后端 src/main.rs # Rust 入口、命令定义 tauri.conf.json # 窗口、图标、权限配置 Cargo.toml # Rust 依赖

前端调用 Rust 命令(核心 IPC)

// src-tauri/src/main.rs —— Rust 端定义命令 #[tauri::command] fn greet(name: String) -> String { format!("你好,{}!来自 Rust 的问候", name) } fn main() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![greet]) .run(tauri::generate_context!()) .expect("error"); }
// 前端 React 里调用 import { invoke } from "@tauri-apps/api/core"; async function sayHi() { const msg = await invoke<string>("greet", { name: "小明" }); console.log(msg); // 你好,小明!来自 Rust 的问候 }

核心插件:文件、对话框、通知、托盘

Tauri 的系统能力都是插件形式按需装:@tauri-apps/plugin-fs 文件系统、@tauri-apps/plugin-dialog 打开文件选择、@tauri-apps/plugin-notification 系统通知、@tauri-apps/plugin-global-shortcut 全局快捷键。安全模型默认是"最小权限"——前端只能调用你在 capabilities 里明确允许的命令。

打包发布

pnpm tauri dev # 开发:热更新 + 调试 pnpm tauri build # 打包:出 .dmg / .msi / .deb 安装包

上架 Mac App Store / Microsoft Store 还要做代码签名。自动更新用 @tauri-apps/plugin-updater,自带签名验证。

Tauri 架构:Rust 后端 + 系统 WebView

Tauri 2.0 就是 Electron 的瘦身版:不打包 Chromium,用操作系统自带的 WebView(Windows WebView2 / macOS WKWebView / Linux WebKitGTK),后端用 Rust 编译成原生小二进制。包体 ~3-10MB,内存低,安全模型是"最小权限"。2.0 还加了 iOS/Android。

Tauri 2.0Electron
包体3-10MB100-200MB
后端语言RustNode.js
渲染系统 WebView打包 Chromium
全平台桌面 + iOS + Android桌面为主
生态成熟度较新但快速成熟极成熟(VS Code/Discord/Figma)

前后端通信:Commands 与 Events

Rust 命令 + 前端 invoke;事件双向推送

// src-tauri/src/lib.rs —— Rust 定义命令 #[tauri::command] fn greet(name: String) -> Result<String, String> { Ok(format!("你好,{name}")) } // main 里注册:.invoke_handler(tauri::generate_handler![greet]) // 事件:Rust 主动推给前端 app_handle.emit("download-progress", 42).unwrap(); // 前端 React import { invoke } from "@tauri-apps/api/core"; import { listen } from "@tauri-apps/api/event"; const msg = await invoke<string>("greet", { name: "小明" }); const unlisten = await listen<number>("download-progress", (e) => console.log(`进度 ${e.payload}%`));

权限系统 capabilities:Tauri 2 的安全核心

前端调命令被拒?先查 capabilities

Tauri 2 默认前端什么都不能调。每个插件命令都要在 src-tauri/capabilities/*.json 里显式 allow。忘了加权限,前端 invoke 直接报错——这是 Tauri 新手第一坑。类比 Android 权限、后端最小权限原则。

// src-tauri/capabilities/default.json { "identifier": "default", "description": "主窗口权限", "windows": ["main"], "permissions": [ "core:default", "fs:allow-read-text-file", "dialog:allow-open", "notification:allow-notify" ] }

官方插件生态:plugin-fs 文件、plugin-dialog 对话框、plugin-os 系统信息、plugin-shell 执行命令、plugin-http 发请求、plugin-clipboard 剪贴板、plugin-notification 通知、plugin-global-shortcut 全局快捷键、plugin-updater 自动更新、plugin-store 本地存储、plugin-sqlite 数据库。用到哪个装哪个、在 capabilities 里开对应权限。

窗口、托盘、自动更新、打包

// 创建多窗口 / 窗口操作(前端 JS) import { WebviewWindow } from "@tauri-apps/api/webviewWindow"; const win = new WebviewWindow("settings", { url: "/settings", width: 600, height: 500 }); // 自动更新:@tauri-apps/plugin-updater import { check } from "@tauri-apps/plugin-updater"; const update = await check(); if (update) await update.downloadAndInstall((e) => /* 进度 */); // 打包 pnpm tauri build # 出 .dmg / .msi / .deb pnpm tauri ios build # iOS(需 macOS+Xcode) pnpm tauri android build # Android(需 Android Studio)

完整案例:Tauri 2 + React + TS 写一个 AI 聊天桌面应用——Rust 命令调 LLM API,用事件把 token 流式推给前端打字机效果,系统托盘常驻,plugin-store 存聊天历史,plugin-updater 自动更新。

本章面试题

Q1. Tauri 为什么包体比 Electron 小两个数量级?

看答案

Electron 每个 App 都打包一份 Chromium(100MB+);Tauri 用系统自带 WebView,Rust 后端只编译出几 MB 二进制。代价是不同系统 WebView 版本不一,要测兼容。

Q2. 什么是 Tauri 的 capabilities?

看答案

2.0 的权限白名单:前端默认什么系统能力都不能调,必须在 capabilities/*.json 里逐个 allow 插件命令和自定义命令,遵循最小权限原则。

Q3. Rust 命令返回 Result<T,E> 前端怎么处理?

看答案

invoke 返回 Promise,Err 分支会 reject,前端 try/catch 接住即可。自定义错误类型建议用 String 或实现 Serialize。