Tauri 开发文档
Tauri 是一个使用 Rust 后端和 WebView 前端的跨平台桌面应用框架。与 Electron 相比,Tauri 在包体积、内存占用和安全性方面具有显著优势。本文档系统介绍 Tauri v2 的核心概念、开发实践与发布流程。
1. Tauri 架构与入门
1.1 Tauri vs Electron 核心差异
1.1.1 技术架构对比
| 维度 | Tauri | Electron |
|---|---|---|
| 后端语言 | Rust | Node.js (C++) |
| 前端渲染 | 系统原生 WebView (WebView2/WKWebView/WebKitGTK) | Chromium 内核 |
| 包体积 | 约 2-10 MB (不含 WebView) | 约 50-200 MB (捆绑 Chromium) |
| 内存占用 | 约 30-100 MB | 约 100-500 MB |
| 启动速度 | 约 0.3-1.0 秒 | 约 1.0-3.0 秒 |
1.1.2 架构模型
Tauri 采用分层架构:
- Core 层:Tauri 核心运行时,提供窗口管理、进程间通信(IPC)、事件系统、文件系统访问等基础能力。Core 层是 Rust 实现的二进制库,与操作系统直接交互。
- Shell 层:Tauri Shell 是 Core 层的扩展模块,提供系统级 API 的访问能力,如对话框、通知、Shell 命令执行、全局快捷键等。Shell 层通过插件系统(
tauri-plugin-*)实现模块化扩展。 - WebView 前端:使用系统原生 WebView 渲染前端页面,前端技术栈可以是 React、Vue、Svelte、Solid 等任意现代前端框架。
1.1.3 安全模型
Tauri 的安全模型基于最小权限原则:
- 默认禁止远程访问:Tauri 应用默认禁止加载远程 URL,仅允许加载本地资源。
- 能力(Capability)系统:Tauri v2 引入能力声明机制,前端调用后端 API 必须显式声明权限。
- CSP 安全策略:通过 Content Security Policy 限制脚本执行和资源加载。
- IPC 隔离:前端与后端通过 JSON 序列化通信,Rust 端对每个 IPC 调用进行参数校验。
1.1.4 通信模式
Tauri 使用基于 invoke 的请求-响应模式和基于事件的发布-订阅模式:
- invoke:前端通过
@tauri-apps/api/core的invoke函数调用 Rust 端#[tauri::command]标记的命令函数。通信是异步的,使用 JSON 序列化。 - 事件(Event):前端和后端均可发送和监听事件,支持全局事件和窗口级事件。
- 前置事件(Prepend Events):Tauri v2 支持在窗口创建前发送事件,用于初始化场景。
1.2 开发环境搭建
1.2.1 Rust 工具链
安装 Rust 编译器和 Cargo 包管理器:
# Windows 上访问 https://rustup.rs 或使用 winget
winget install Rustlang.Rustup
# 安装完成后,确认版本
rustc --version
cargo --versionRust 工具链包括:
- rustc:Rust 编译器。
- Cargo:Rust 的包管理器和构建系统。
- rustup:Rust 版本管理工具,用于管理工具链和目标平台。
1.2.2 系统依赖
Windows:
- Microsoft Visual Studio Build Tools 或 Visual Studio(需包含 MSVC 工具链和 Windows SDK)。
- WebView2:Windows 11 自带,Windows 10 需要安装 WebView2 Runtime(通常由系统更新自动安装)。
- 安装命令(使用 winget):bash
winget install Microsoft.VisualStudio.2022.BuildTools --silent
macOS:
- Xcode Command Line Tools:bash
xcode-select --install - WKWebView:macOS 系统自带,无需额外安装。
Linux:
- WebKitGTK 及其他系统依赖:bash
# Ubuntu/Debian sudo apt install libwebkit2gtk-4.1-dev build-essential curl wget file \ libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev # Fedora sudo dnf install webkit2gtk4.1-devel openssl-devel libappindicator-gtk3-devel librsvg2-devel
1.2.3 Tauri CLI
Tauri v2 提供命令行工具 create-tauri-app 用于创建新项目:
# 使用 npm 创建 Tauri 项目
npm create tauri-app@latest my-tauri-app -- --template react-ts
# 进入项目目录
cd my-tauri-app
# 初始化 Tauri(如果使用已有前端项目)
cargo tauri init
# 开发模式运行
cargo tauri dev
# 构建生产版本
cargo tauri buildTauri CLI 的核心命令:
cargo tauri init:在已有前端项目中初始化 Tauri 配置。cargo tauri dev:启动开发服务器,同时启动桌面应用窗口,支持热重载。cargo tauri build:编译 Rust 后端并打包前端资源,生成最终安装包。cargo tauri icon:从源图片生成应用图标,自动适配各平台尺寸要求。
1.3 项目目录结构
一个典型的 Tauri v2 项目目录结构如下:
my-tauri-app/
├── src/ # 前端源代码
│ ├── App.tsx # 前端根组件
│ ├── App.css
│ ├── main.tsx # 前端入口文件
│ └── assets/
├── src-tauri/ # Tauri Rust 后端
│ ├── Cargo.toml # Rust 依赖清单
│ ├── tauri.conf.json # Tauri 主配置文件
│ ├── build.rs # Rust 构建脚本
│ ├── icons/ # 应用图标
│ ├── capabilities/ # 能力配置目录 (Tauri v2)
│ │ └── default.json # 默认能力声明
│ ├── src/
│ │ ├── main.rs # Rust 入口文件
│ │ ├── lib.rs # 库入口(Tauri v2 推荐方式)
│ │ └── commands.rs # 自定义命令模块
│ └── target/ # Rust 编译产物
├── package.json # 前端依赖配置
├── vite.config.ts # Vite 构建配置
├── tsconfig.json
└── index.html1.3.1 src-tauri/Cargo.toml
Tauri v2 的 Cargo.toml 示例:
[package]
name = "my-tauri-app"
version = "0.1.0"
edition = "2021"
[lib]
name = "my_tauri_app_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "2", features = [] }
tauri-plugin-shell = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"1.3.2 src-tauri/src/main.rs 与 lib.rs
Tauri v2 推荐将应用逻辑放在 lib.rs 中,main.rs 仅作为启动入口:
src-tauri/src/main.rs:
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
my_tauri_app_lib::run()
}src-tauri/src/lib.rs:
use tauri::Manager;
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}! You've been greeted from Rust!", name)
}
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_shell::init())
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}1.3.3 前端入口与 Vite 配置
前端使用 Vite 构建,Tauri CLI 自动集成:
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
clearScreen: false,
server: {
port: 1420,
strictPort: true,
watch: {
ignored: ["**/src-tauri/**"],
},
},
});2. Tauri 配置
2.1 tauri.conf.json 核心配置
tauri.conf.json 是 Tauri 应用的主配置文件,位于 src-tauri/ 目录下。以下是一个完整的配置示例:
{
"$schema": "https://raw.githubusercontent.com/tauri-apps/tauri/dev/crates/tauri-cli/config.schema.json",
"productName": "My Tauri App",
"version": "0.1.0",
"identifier": "com.mycompany.myapp",
"build": {
"beforeDevCommand": "npm run dev",
"devUrl": "http://localhost:1420",
"beforeBuildCommand": "npm run build",
"frontendDist": "../dist"
},
"app": {
"windows": [
{
"title": "My Tauri App",
"width": 1024,
"height": 768,
"minWidth": 800,
"minHeight": 600,
"maxWidth": 1920,
"maxHeight": 1080,
"resizable": true,
"fullscreen": false,
"decorations": true,
"transparent": false,
"opacity": 1.0,
"center": true,
"icon": "icons/icon.png"
}
],
"security": {
"csp": "default-src 'self'; img-src 'self' asset: https://asset.localhost; style-src 'self' 'unsafe-inline'"
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"windows": {
"wix": null,
"nsis": null
},
"macOS": {
"frameworks": [],
"minimumSystemVersion": "10.15"
},
"linux": {
"deb": {
"depends": []
}
}
},
"plugins": {}
}2.1.1 基础字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
productName | string | 应用名称,用于安装包和菜单栏显示 |
version | string | 应用版本号,遵循 semver 规范 |
identifier | string | 应用唯一标识符,推荐使用反向域名格式(如 com.example.app) |
2.1.2 build 配置
{
"build": {
"beforeDevCommand": "npm run dev",
"devUrl": "http://localhost:1420",
"beforeBuildCommand": "npm run build",
"frontendDist": "../dist"
}
}- beforeDevCommand:开发模式下,Tauri CLI 启动前执行的命令。通常用于启动前端开发服务器。
- devUrl:开发模式下 Tauri WebView 加载的 URL,对应前端开发服务器的地址。
- beforeBuildCommand:构建前执行的命令,通常用于构建前端资源。
- frontendDist:构建后的前端静态资源目录,相对于
src-tauri/的路径。
2.1.3 窗口配置
窗口配置位于 app.windows 数组中,支持多个窗口:
{
"app": {
"windows": [
{
"title": "主窗口",
"label": "main",
"width": 1024,
"height": 768,
"minWidth": 800,
"minHeight": 600,
"maxWidth": 1920,
"maxHeight": 1080,
"resizable": true,
"fullscreen": false,
"decorations": true,
"transparent": false,
"opacity": 0.95,
"center": true,
"focus": true,
"visible": true,
"closable": true,
"maximizable": true,
"minimizable": true,
"x": null,
"y": null,
"url": "index.html",
"icon": "icons/icon.png",
"dragDropEnabled": true,
"alwaysOnTop": false,
"skipTaskbar": false
}
]
}
}关键窗口属性:
- decorations:是否显示原生窗口装饰(标题栏、边框),设为
false可实现自定义标题栏。 - transparent:透明窗口支持,需要配合 CSS 透明背景使用。
- opacity:窗口透明度(Tauri v2 新增)。
- center:启动时是否居中显示。
- dragDropEnabled:是否启用拖放功能。
- alwaysOnTop:窗口是否置顶。
2.1.4 Tauri v2 新增配置
Tauri v2 在配置方面引入了以下变化:
- 配置结构扁平化:移除了
tauri.前缀,配置项直接位于顶层或app对象下。 identifier必填:v2 要求必须提供应用标识符。app对象:窗口、安全等配置统一放在app对象下。plugins对象:插件配置统一放在plugins下。
2.1.5 Plugin 配置与 CSP 安全策略
{
"app": {
"security": {
"csp": "default-src 'self'; img-src 'self' asset: https://asset.localhost; style-src 'self' 'unsafe-inline'",
"dangerousRemoteDomainIpcAccess": [
{
"domain": "https://api.example.com",
"windows": ["main"],
"plugins": ["shell"]
}
]
}
}
}- csp:Content Security Policy,限制页面可以加载和执行的资源。默认值非常严格,需要根据实际需求放宽。
- dangerousRemoteDomainIpcAccess:允许特定远程域名通过 IPC 调用 Tauri API。仅在需要与远程页面交互时启用。
2.2 能力配置(Capabilities)
Tauri v2 引入了能力(Capability)系统,用于精细控制前端页面可以调用的 Tauri API。
2.2.1 capabilities 目录
能力配置文件存放在 src-tauri/capabilities/ 目录下,每个文件定义一个能力声明:
src-tauri/capabilities/
├── default.json
└── admin.json2.2.2 能力声明文件格式
{
"identifier": "default",
"description": "默认能力集",
"windows": ["main"],
"permissions": [
"core:default",
"shell:allow-open",
"dialog:default",
"notification:default",
"fs:default",
{
"identifier": "fs:read",
"allow": [
{ "path": "$APPDATA/**" },
{ "path": "$RESOURCE/**" }
]
},
{
"identifier": "fs:write",
"allow": [
{ "path": "$APPDATA/**" }
]
}
]
}2.2.3 Capability 权限模型
权限模型的层级关系:
Capability(能力声明)
├── identifier # 能力唯一标识
├── windows # 适用的窗口标签列表
├── permissions # 权限列表
│ ├── core:default # 核心默认权限
│ ├── plugin:permission # 插件权限
│ └── plugin:scope # 带作用域的权限
└── webview # 适用的 WebView 标签列表(可选)2.2.4 权限声明语法
Tauri v2 的权限声明支持两种形式:
简洁形式:直接引用预定义的权限标识符。
{
"identifier": "core:default"
}对象形式:带作用域的权限声明,可以限制文件路径、URL 等参数范围。
{
"identifier": "fs:read",
"allow": [
{ "path": "$APPDATA/**" }
],
"deny": [
{ "path": "$APPDATA/config.secret" }
]
}内置插件权限:Tauri v2 为官方插件预定义了粒度权限:
core:default— 核心默认权限core:window:default— 窗口操作默认权限core:window:allow-create— 允许创建窗口core:window:allow-close— 允许关闭窗口core:window:allow-set-size— 允许设置窗口大小shell:allow-open— 允许打开外部链接dialog:default— 对话框默认权限fs:default— 文件系统默认权限notification:default— 通知默认权限
2.2.5 IPC 通信安全
IPC 通信的安全机制:
- 命令白名单:只有通过
invoke_handler注册的命令才能被前端调用。 - 权限检查:每次 IPC 调用都会检查当前窗口的能力声明。
- 类型校验:Rust 端对 IPC 参数进行严格的类型检查。
- 作用域限制:文件系统等 API 支持路径白名单和黑名单。
2.3 IPC 通信详解
2.3.1 invoke 调用
前端通过 @tauri-apps/api/core 模块的 invoke 函数调用 Rust 命令:
import { invoke } from "@tauri-apps/api/core";
// 调用无参数命令
const result = await invoke("greet");
// 调用带参数命令
const greeting = await invoke("greet", { name: "World" });
// 处理错误
try {
const data = await invoke("risky_operation", { input: "test" });
} catch (error) {
console.error("Command failed:", error);
}2.3.2 事件监听
import { listen } from "@tauri-apps/api/event";
import { emit } from "@tauri-apps/api/event";
// 监听事件
const unlisten = await listen("download-progress", (event) => {
console.log(`Progress: ${event.payload.percent}%`);
});
// 发送事件
await emit("app-ready", { timestamp: Date.now() });
// 向特定窗口发送事件
import { emitTo } from "@tauri-apps/api/event";
await emitTo("main", "window-event", { type: "focus" });
// 清理监听器
unlisten();2.3.3 Rust 端事件发送
use tauri::Emitter;
// 向前端发送事件
fn send_progress(app_handle: &tauri::AppHandle) {
app_handle.emit("download-progress", serde_json::json!({
"percent": 50,
"status": "downloading"
})).unwrap();
}2.3.4 前置事件(Prepend Events)
前置事件允许在 WebView 加载前设置事件监听,适用于需要在初始化阶段捕获事件的场景:
import { onPrepend } from "@tauri-apps/api/event";
// 注册前置事件监听
onPrepend("early-event", (event) => {
console.log("Early event received:", event.payload);
});2.3.5 事件类型说明
| 事件类型 | API | 说明 |
|---|---|---|
| 窗口事件 | window | 窗口创建、关闭、焦点变化、大小变化等 |
| 全局事件 | app | 应用级别事件,跨窗口传递 |
| Tauri 事件 | tauri:// | 框架内部事件,如 tauri://close-requested |
| 自定义事件 | 任意字符串 | 开发者自定义事件 |
2.3.6 安全注意事项
- IPC 参数和返回值通过 JSON 序列化传输,避免传输敏感二进制数据。
- Rust 命令的参数自动反序列化,确保类型安全。
- 不要在前端直接构造 Rust 内部数据结构,使用接口类型进行隔离。
- 对于需要长时间运行的操作,使用事件机制反馈进度,避免阻塞 IPC 通道。
3. Rust 后端开发
3.1 Command 系统
3.1.1 基本命令定义
使用 #[tauri::command] 宏标记 Rust 函数作为可被前端调用的命令:
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}3.1.2 命令注册
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet, get_user, save_config])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}3.1.3 复杂返回值
命令函数可以返回任意实现了 Serialize 的类型:
use serde::Serialize;
#[derive(Serialize)]
struct User {
id: u64,
name: String,
email: String,
active: bool,
}
#[tauri::command]
fn get_user(id: u64) -> Result<User, String> {
// 模拟查找用户
if id == 0 {
Err("User not found".to_string())
} else {
Ok(User {
id,
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
active: true,
})
}
}3.1.4 Result 类型与错误处理
Tauri 命令支持返回 Result<T, E>,其中 E 必须实现 Into<String>:
use serde::Serialize;
#[derive(Debug, thiserror::Error)]
enum AppError {
#[error("Database error: {0}")]
Database(String),
#[error("Not found")]
NotFound,
#[error("Permission denied")]
PermissionDenied,
}
impl Serialize for AppError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
#[tauri::command]
fn fetch_record(id: u64) -> Result<Record, AppError> {
// 业务逻辑
Err(AppError::NotFound)
}前端捕获错误:
try {
const record = await invoke("fetch_record", { id: 42 });
} catch (error) {
// error 为 "Not found" 字符串
console.error(error);
}3.1.5 Async Command
Tauri 命令支持异步函数,使用 async 关键字和 tauri::async_runtime:
use std::time::Duration;
#[tauri::command]
async fn long_running_task() -> String {
// 模拟耗时操作
tokio::time::sleep(Duration::from_secs(2)).await;
"Task completed".to_string()
}
#[tauri::command]
async fn fetch_remote_data(url: String) -> Result<String, String> {
let response = reqwest::get(&url)
.await
.map_err(|e| e.to_string())?;
let body = response.text()
.await
.map_err(|e| e.to_string())?;
Ok(body)
}3.1.6 State 状态管理
Tauri 提供 State 管理机制,用于在命令之间共享状态:
定义共享状态:
use std::sync::Mutex;
struct AppConfig {
app_name: String,
version: String,
}
struct Counter {
count: Mutex<i32>,
}初始化状态:
pub fn run() {
tauri::Builder::default()
.manage(AppConfig {
app_name: "My App".to_string(),
version: "0.1.0".to_string(),
})
.manage(Counter {
count: Mutex::new(0),
})
.invoke_handler(tauri::generate_handler![get_config, increment_counter])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}在命令中使用状态:
#[tauri::command]
fn get_config(config: tauri::State<AppConfig>) -> String {
format!("{} v{}", config.app_name, config.version)
}
#[tauri::command]
fn increment_counter(counter: tauri::State<Counter>) -> i32 {
let mut count = counter.count.lock().unwrap();
*count += 1;
*count
}线程安全注意事项:
- 使用
Mutex或RwLock包装可变状态。 - 使用
tokio::sync::Mutex在异步上下文中避免阻塞线程。 - 状态必须在
Builder::manage()中注册,不能在运行时动态添加。
3.1.7 运行时状态初始化
使用 setup 钩子在应用初始化时执行状态准备:
use tauri::Manager;
pub fn run() {
tauri::Builder::default()
.setup(|app| {
// 初始化数据库连接池
let db_pool = DatabasePool::new("sqlite://app.db");
app.manage(db_pool);
// 初始化配置文件
let config = Config::load(app.path().app_config_dir()?);
app.manage(config);
Ok(())
})
.invoke_handler(tauri::generate_handler![query_database])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}3.2 文件系统
3.2.1 tauri-plugin-fs
文件系统操作通过 tauri-plugin-fs 插件实现,需要在 Cargo.toml 中添加依赖:
[dependencies]
tauri-plugin-fs = "2"注册插件:
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_fs::init())
.invoke_handler(tauri::generate_handler![read_app_data])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}3.2.2 Rust 端文件读写
use std::fs;
use std::path::PathBuf;
use tauri::Manager;
#[tauri::command]
fn read_app_data(app_handle: tauri::AppHandle, filename: String) -> Result<String, String> {
let data_dir = app_handle.path().app_data_dir();
let file_path = data_dir.join(&filename);
fs::read_to_string(&file_path)
.map_err(|e| format!("Failed to read file: {}", e))
}
#[tauri::command]
fn write_app_data(app_handle: tauri::AppHandle, filename: String, content: String) -> Result<(), String> {
let data_dir = app_handle.path().app_data_dir();
// 确保目录存在
fs::create_dir_all(&data_dir)
.map_err(|e| format!("Failed to create directory: {}", e))?;
let file_path = data_dir.join(&filename);
fs::write(&file_path, &content)
.map_err(|e| format!("Failed to write file: {}", e))
}3.2.3 文件对话框
使用 tauri-plugin-dialog 提供文件选择对话框:
[dependencies]
tauri-plugin-dialog = "2"use tauri_plugin_dialog::DialogExt;
#[tauri::command]
async fn pick_file(app_handle: tauri::AppHandle) -> Result<Option<String>, String> {
let file = app_handle.dialog()
.file()
.add_filter("Documents", &["pdf", "txt", "md"])
.add_filter("All Files", &["*"])
.blocking_pick_file();
match file {
Some(path) => Ok(Some(path.to_string())),
None => Ok(None),
}
}3.2.4 应用数据目录与路径解析
Tauri 提供标准化的路径解析机制,通过 app_handle.path() 访问:
use tauri::Manager;
fn get_standard_paths(app_handle: &tauri::AppHandle) {
let resolver = app_handle.path();
// 应用数据目录(Windows: %APPDATA%/com.example.app, macOS: ~/Library/Application Support/com.example.app)
let app_data_dir = resolver.app_data_dir();
// 应用配置目录
let app_config_dir = resolver.app_config_dir();
// 本地数据目录
let app_local_data_dir = resolver.app_local_data_dir();
// 缓存目录
let app_cache_dir = resolver.app_cache_dir();
// 日志目录
let app_log_dir = resolver.app_log_dir();
// 资源目录(只读,存放应用打包资源)
let resource_dir = resolver.resource_dir();
// 文档目录
let document_dir = resolver.document_dir();
// 桌面目录
let desktop_dir = resolver.desktop_dir();
// 下载目录
let download_dir = resolver.download_dir();
// 用户主目录
let home_dir = resolver.home_dir();
}路径变量:在能力配置中使用路径变量确保跨平台兼容性:
| 变量 | 说明 |
|---|---|
$APPDATA | 应用数据目录 |
$RESOURCE | 应用资源目录 |
$HOME | 用户主目录 |
$CACHE | 应用缓存目录 |
$CONFIG | 应用配置目录 |
$DESKTOP | 桌面目录 |
$DOCUMENT | 文档目录 |
$DOWNLOAD | 下载目录 |
3.3 系统功能
3.3.1 对话框(tauri-plugin-dialog)
[dependencies]
tauri-plugin-dialog = "2"use tauri_plugin_dialog::DialogExt;
use tauri_plugin_dialog::MessageDialogKind;
// 文件打开对话框
#[tauri::command]
async fn open_file(app_handle: tauri::AppHandle) -> Result<Option<String>, String> {
let file = app_handle.dialog()
.file()
.set_title("选择文件")
.add_filter("Images", &["png", "jpg", "jpeg", "gif"])
.blocking_pick_file();
Ok(file.map(|f| f.to_string()))
}
// 文件保存对话框
#[tauri::command]
async fn save_file(app_handle: tauri::AppHandle) -> Result<Option<String>, String> {
let file = app_handle.dialog()
.file()
.set_title("保存文件")
.add_filter("Text", &["txt"])
.blocking_save_file();
Ok(file.map(|f| f.to_string()))
}
// 消息对话框
#[tauri::command]
async fn show_message(app_handle: tauri::AppHandle) {
app_handle.dialog()
.message("操作已完成")
.title("提示")
.kind(MessageDialogKind::Info)
.show(|_| {});
}
// 确认对话框
#[tauri::command]
async fn confirm_action(app_handle: tauri::AppHandle) -> Result<bool, String> {
let confirmed = app_handle.dialog()
.message("确定要执行此操作吗?")
.title("确认")
.kind(MessageDialogKind::Warning)
.blocking_show();
Ok(confirmed)
}3.3.2 通知(tauri-plugin-notification)
[dependencies]
tauri-plugin-notification = "2"use tauri_plugin_notification::NotificationExt;
#[tauri::command]
async fn send_notification(app_handle: tauri::AppHandle, title: String, body: String) -> Result<(), String> {
app_handle.notification()
.builder()
.title(&title)
.body(&body)
.show()
.map_err(|e| e.to_string())
}3.3.3 Shell(tauri-plugin-shell)
[dependencies]
tauri-plugin-shell = "2"use tauri_plugin_shell::ShellExt;
#[tauri::command]
async fn open_in_browser(app_handle: tauri::AppHandle, url: String) -> Result<(), String> {
app_handle.shell()
.open(&url, None)
.map_err(|e| e.to_string())
}
#[tauri::command]
async fn run_command(app_handle: tauri::AppHandle, command: String) -> Result<String, String> {
let output = app_handle.shell()
.command(&command)
.output()
.await
.map_err(|e| e.to_string())?;
Ok(String::from_utf8_lossy(&output.stdout).to_string())
}3.3.4 进程(tauri-plugin-process)
[dependencies]
tauri-plugin-process = "2"#[tauri::command]
async fn exit_app(app_handle: tauri::AppHandle, code: i32) {
app_handle.exit(code);
}
#[tauri::command]
async fn restart_app(app_handle: tauri::AppHandle) {
app_handle.restart();
}3.3.5 剪贴板(tauri-plugin-clipboard-manager)
[dependencies]
tauri-plugin-clipboard-manager = "2"use tauri_plugin_clipboard_manager::ClipboardExt;
#[tauri::command]
async fn copy_to_clipboard(app_handle: tauri::AppHandle, text: String) -> Result<(), String> {
app_handle.clipboard()
.write_text(text)
.map_err(|e| e.to_string())
}
#[tauri::command]
async fn read_from_clipboard(app_handle: tauri::AppHandle) -> Result<String, String> {
app_handle.clipboard()
.read_text()
.map_err(|e| e.to_string())
}3.3.6 全局快捷键(tauri-plugin-global-shortcut)
[dependencies]
tauri-plugin-global-shortcut = "2"use tauri_plugin_global_shortcut::GlobalShortcutExt;
use tauri_plugin_global_shortcut::Code;
use tauri_plugin_global_shortcut::Modifiers;
#[tauri::command]
async fn register_shortcut(app_handle: tauri::AppHandle) -> Result<(), String> {
app_handle.global_shortcut()
.register("Ctrl+Shift+P", move |_app_handle, _shortcut, _event| {
println!("Global shortcut triggered!");
})
.map_err(|e| e.to_string())
}
// 在应用设置中初始化快捷键
fn setup_shortcuts(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
app.handle().plugin(tauri_plugin_global_shortcut::Builder::new().build())?;
Ok(())
}3.4 插件开发
3.4.1 自定义 Tauri Plugin 结构
Tauri 插件是一个独立的 Rust crate,具有标准化的结构:
my-tauri-plugin/
├── Cargo.toml
├── src/
│ ├── lib.rs # 插件入口、命令导出
│ ├── commands.rs # 插件命令
│ ├── models.rs # 数据模型
│ └── desktop/ # 桌面平台特定实现
│ └── mod.rs
├── permissions/ # 权限声明
│ └── default.toml
├── capabilities/ # 能力声明(可选)
│ └── default.json
└── package.json # 前端 JS API 封装Cargo.toml:
[package]
name = "tauri-plugin-my-custom"
version = "0.1.0"
edition = "2021"
[lib]
name = "tauri_plugin_my_custom"
crate-type = ["staticlib", "cdylib", "rlib"]
[dependencies]
tauri = { version = "2", features = [] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
[build-dependencies]
tauri-build = { version = "2", features = [] }3.4.2 插件入口与命令导出
// src/lib.rs
use tauri::{
plugin::{Builder, TauriPlugin},
Manager, Runtime,
};
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello from plugin, {}!", name)
}
#[tauri::command]
fn get_plugin_version() -> String {
env!("CARGO_PKG_VERSION").to_string()
}
pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("my-custom")
.invoke_handler(tauri::generate_handler![greet, get_plugin_version])
.build()
}3.4.3 权限声明
在 permissions/default.toml 中声明权限:
[default]
description = "默认权限"
permissions = [
"allow-greet",
"allow-get-plugin-version",
]
[[permission]]
identifier = "allow-greet"
description = "允许调用 greet 命令"
[[permission]]
identifier = "allow-get-plugin-version"
description = "允许获取插件版本"3.4.4 前端 JS API 封装
// package.json
{
"name": "tauri-plugin-my-custom",
"version": "0.1.0",
"main": "index.js",
"types": "index.d.ts",
"type": "module"
}// index.ts
import { invoke } from "@tauri-apps/api/core";
export async function greet(name: string): Promise<string> {
return await invoke("plugin:my-custom|greet", { name });
}
export async function getPluginVersion(): Promise<string> {
return await invoke("plugin:my-custom|get_plugin_version");
}3.4.5 使用自定义插件
在主应用中注册并使用自定义插件:
// 主应用的 Cargo.toml
[dependencies]
tauri-plugin-my-custom = { path = "../my-tauri-plugin" }pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_my_custom::init())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}前端调用:
import { greet, getPluginVersion } from "tauri-plugin-my-custom";
const message = await greet("World");
const version = await getPluginVersion();4. 前端集成
4.1 前端框架选型
Tauri 支持所有主流前端框架,因为前端只依赖系统 WebView 加载 HTML/JS/CSS:
| 框架 | Vite 集成 | 状态管理 | Tauri 适配 |
|---|---|---|---|
| React | 官方模板 | Zustand/Redux | 自动集成 |
| Vue | 官方模板 | Pinia/Vuex | 自动集成 |
| Svelte | 官方模板 | Svelte stores | 自动集成 |
| Solid | 社区模板 | Signals | 自动集成 |
Tauri CLI 使用 create-tauri-app 提供了上述框架的模板选择,均基于 Vite 构建工具。
4.2 调用 Rust 命令
4.2.1 基础调用
import { invoke } from "@tauri-apps/api/core";
// 字符串参数
const result: string = await invoke("greet", { name: "Tauri" });
// 数字参数
const count: number = await invoke("increment_counter");
// 复杂对象
interface UserData {
id: number;
name: string;
email: string;
}
const user: UserData = await invoke("get_user", { id: 42 });4.2.2 异步调用与错误处理
import { invoke } from "@tauri-apps/api/core";
async function fetchData() {
try {
const result = await invoke<{ data: string[] }>("fetch_records", {
page: 1,
limit: 20,
});
return result.data;
} catch (error) {
console.error("Failed to fetch records:", error);
// 可以在 UI 上显示错误
throw error;
}
}
// 并行调用多个命令
const [user, config, records] = await Promise.all([
invoke("get_user", { id: 1 }),
invoke("get_config"),
invoke("list_records"),
]);4.2.3 事件监听与发送
import { listen, emit } from "@tauri-apps/api/event";
import { useEffect, useState } from "react";
function DownloadProgress() {
const [progress, setProgress] = useState(0);
useEffect(() => {
const unlisten = listen<{ percent: number }>("download-progress", (event) => {
setProgress(event.payload.percent);
});
return () => {
unlisten.then((fn) => fn());
};
}, []);
return <div>进度: {progress}%</div>;
}
// 发送事件
await emit("user-action", { type: "click", target: "button-save" });
// 向特定窗口发送
import { emitTo } from "@tauri-apps/api/event";
await emitTo("main", "refresh-data", { timestamp: Date.now() });4.2.4 Tauri v2 API 变更
Tauri v2 对前端 API 进行了模块化重构,@tauri-apps/api 拆分为多个子模块:
// Tauri v2 模块化导入
import { invoke } from "@tauri-apps/api/core";
import { listen, emit } from "@tauri-apps/api/event";
import { Window } from "@tauri-apps/api/window";
import { getName, getVersion } from "@tauri-apps/api/app";
import { open, save } from "@tauri-apps/plugin-dialog";
import { readTextFile, writeTextFile } from "@tauri-apps/plugin-fs";
import { sendNotification } from "@tauri-apps/plugin-notification";
import { open as openUrl } from "@tauri-apps/plugin-shell";4.2.5 窗口操作
import { getCurrentWindow } from "@tauri-apps/api/window";
const appWindow = getCurrentWindow();
// 窗口状态操作
await appWindow.minimize();
await appWindow.maximize();
await appWindow.unmaximize();
await appWindow.close();
await appWindow.hide();
await appWindow.show();
await appWindow.setFocus();
// 窗口属性
await appWindow.setTitle("新标题");
await appWindow.setSize({ width: 1024, height: 768 });
await appWindow.setMinSize({ width: 800, height: 600 });
await appWindow.setPosition({ x: 100, y: 100 });
await appWindow.setFullscreen(true);
await appWindow.setAlwaysOnTop(true);
// 监听窗口事件
await appWindow.onResized(({ payload: { width, height } }) => {
console.log(`窗口大小: ${width}x${height}`);
});
await appWindow.onFileDropEvent((event) => {
if (event.payload.type === "hover") {
console.log("文件悬停");
} else if (event.payload.type === "drop") {
console.log("文件已拖放:", event.payload.paths);
}
});4.3 状态同步
4.3.1 前端状态与 Rust 状态同步
使用全局事件实现前端与 Rust 后端的状态同步:
use std::sync::Mutex;
use tauri::Emitter;
struct AppState {
users: Mutex<Vec<User>>,
}
#[tauri::command]
fn add_user(state: tauri::State<AppState>, app_handle: tauri::AppHandle, name: String) -> Result<(), String> {
let mut users = state.users.lock().map_err(|e| e.to_string())?;
let user = User { id: users.len() as u64 + 1, name };
users.push(user.clone());
// 通知所有窗口状态已更新
app_handle.emit("state-changed", "users").map_err(|e| e.to_string())?;
Ok(())
}
#[tauri::command]
fn get_users(state: tauri::State<AppState>) -> Result<Vec<User>, String> {
let users = state.users.lock().map_err(|e| e.to_string())?;
Ok(users.clone())
}前端组件监听状态变更:
import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import { useEffect, useState } from "react";
interface User {
id: number;
name: string;
}
function UserList() {
const [users, setUsers] = useState<User[]>([]);
const refreshUsers = async () => {
const data = await invoke<User[]>("get_users");
setUsers(data);
};
useEffect(() => {
refreshUsers();
const unlisten = listen("state-changed", () => {
refreshUsers();
});
return () => {
unlisten.then((fn) => fn());
};
}, []);
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}4.3.2 组件间通信
// store/eventBus.ts
import { listen, emit } from "@tauri-apps/api/event";
// 自定义事件总线,用于组件间通信
export const eventBus = {
async emit(event: string, payload?: unknown) {
await emit(event, payload);
},
async on<T>(event: string, callback: (payload: T) => void) {
const unlisten = await listen<T>(event, (event) => {
callback(event.payload);
});
return unlisten;
},
};5. 打包与发布
5.1 应用打包
5.1.1 tauri build 命令
# 基础打包
cargo tauri build
# 指定打包目标
cargo tauri build --targets "msi,nsis"
# 指定平台(交叉编译需要安装对应工具链)
cargo tauri build --target aarch64-apple-darwin
cargo tauri build --target x86_64-pc-windows-msvc
# 调试打包
cargo tauri build --debug5.1.2 各平台安装包格式
Windows:
- NSIS(Nullsoft Scriptable Install System):轻量级安装程序,生成
.exe文件。推荐用于大多数 Windows 用户。 - MSI(Microsoft Installer):Windows Installer 包,生成
.msi文件。适用于企业批量部署场景。
配置示例:
{
"bundle": {
"windows": {
"wix": {
"language": "zh-CN",
"template": "main.wxs"
},
"nsis": {
"installMode": "currentUser",
"installerIcon": "icons/icon.ico"
}
}
}
}macOS:
- DMG:磁盘映像文件,标准的 macOS 分发格式。
- Apple Silicon 支持:Tauri v2 支持 Universal Binary(x86_64 + arm64)。
{
"bundle": {
"macOS": {
"frameworks": [],
"minimumSystemVersion": "10.15",
"exceptionDomain": "",
"signing": {
"identity": "Developer ID Application: Your Name (XXXXXXXXXX)"
}
}
}
}Linux:
- Deb:Debian/Ubuntu 系列的包格式。
- AppImage:便携式应用镜像,无需安装即可运行。
- RPM:Fedora/RHEL 系列的包格式。
{
"bundle": {
"linux": {
"deb": {
"depends": [
"libwebkit2gtk-4.1-0",
"libgtk-3-0",
"libappindicator3-1"
]
},
"rpm": {
"depends": [
"webkit2gtk4.1",
"gtk3",
"libappindicator-gtk3"
]
},
"appimage": {
"bundleMediaFramework": false
}
}
}
}5.1.3 代码签名
macOS 代码签名:
# 使用 Apple Developer ID 签名
cargo tauri build --bundles dmg
# tauri.conf.json 配置{
"bundle": {
"macOS": {
"signing": {
"identity": "Developer ID Application: Your Name (XXXXXXXXXX)"
}
}
}
}Windows 代码签名(signtool):
# 使用 signtool 签名
signtool sign /fd SHA256 /a /f certificate.pfx /p password "target/release/bundle/nsis/MyApp.exe"{
"bundle": {
"windows": {
"signCommand": "signtool sign /fd SHA256 /a /f certificate.pfx /p $PASSWORD $PATH"
}
}
}5.2 自动更新
5.2.1 updater 插件配置
Tauri v2 的自动更新通过 tauri-plugin-updater 插件实现。
Cargo.toml 添加依赖:
[dependencies]
tauri-plugin-updater = "2"
reqwest = { version = "0.12", features = ["json"] }注册插件:
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_updater::Builder::new()
.build())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}tauri.conf.json 配置:
{
"plugins": {
"updater": {
"active": true,
"endpoints": [
"https://update.example.com/api/updates/{{target}}/{{current_version}}"
],
"dialog": true,
"pubkey": "your-public-key-here"
}
}
}5.2.2 更新服务器
更新服务器需要提供符合 Tauri 更新协议的 JSON 端点:
{
"version": "1.0.1",
"pub_date": "2026-06-15T12:00:00Z",
"url": "https://releases.example.com/myapp_1.0.1_x64_en-US.msi",
"signature": "base64-encoded-signature",
"notes": "Bug fixes and performance improvements"
}前端触发更新检查:
import { check } from "@tauri-apps/plugin-updater";
import { relaunch } from "@tauri-apps/plugin-process";
async function checkForUpdates() {
const update = await check();
if (update) {
console.log(`发现新版本: ${update.version}`);
// 下载更新
let downloaded = 0;
await update.download((progress) => {
downloaded = progress;
console.log(`下载进度: ${downloaded}%`);
});
// 安装并重启
await update.install();
await relaunch();
}
}5.2.3 版本比较与渠道
Tauri 支持多更新渠道配置,可用于区分稳定版、Beta 版等:
{
"plugins": {
"updater": {
"active": true,
"endpoints": [
"https://update.example.com/stable/{{target}}/{{current_version}}",
"https://update.example.com/beta/{{target}}/{{current_version}}"
],
"dialog": true
}
}
}// Rust 端自定义更新渠道
use tauri_plugin_updater::UpdaterExt;
#[tauri::command]
async fn check_beta_update(app_handle: tauri::AppHandle) -> Result<Option<String>, String> {
let updater = app_handle.updater();
// 可以在此自定义渠道逻辑
Ok(None)
}5.3 应用商店分发
5.3.1 Microsoft Store
Tauri 应用可以打包为 MSIX 格式上架 Microsoft Store:
{
"bundle": {
"windows": {
"wix": {
"language": "zh-CN",
"template": "main.wxs"
},
"nsis": null
}
}
}MSIX 打包需要额外的配置和签名证书。推荐使用 msix 格式:
# 生成 MSIX 包(需要 Windows SDK 和签名证书)
cargo tauri build --bundles msi上架 Microsoft Store 的步骤:
- 注册 Microsoft Partner Center 账户。
- 创建应用提交,填写应用信息。
- 上传签名的 MSIX/MSI 安装包。
- 通过 Microsoft 认证测试后发布。
5.3.2 App Store (macOS)
Tauri v2 支持将应用打包为 .app 格式并提交到 Mac App Store:
{
"bundle": {
"macOS": {
"minimumSystemVersion": "10.15",
"signing": {
"identity": "Apple Distribution: Your Name (XXXXXXXXXX)"
}
}
}
}# 打包 Mac App Store 版本
cargo tauri build --bundles app
# 使用 Xcode 进行 App Store 上传
# 生成 .pkg 并使用 Application Loader 或 Xcode 上传Mac App Store 发布要求:
- 启用 App Sandbox(需要配置沙箱权限)。
- 通过 Apple 审核。
- 应用必须使用分发证书签名。
5.3.3 Linux 包管理器
Flatpak:
# 安装 flatpak 构建工具
sudo apt install flatpak flatpak-builder
# 创建 flatpak 清单
cargo tauri build --bundles appimage
# 然后使用 flatpak 工具转换Snap:
# snapcraft.yaml
name: my-tauri-app
version: '0.1.0'
summary: My Tauri Application
description: |
A cross-platform desktop application built with Tauri.
grade: stable
confinement: strict
apps:
my-tauri-app:
command: my-tauri-app
plugs:
- desktop
- desktop-legacy
- wayland
- x11
- network
- home
parts:
my-tauri-app:
plugin: dump
source: target/release/bundle/appimage/Homebrew (macOS/Linux):
# my-tauri-app.rb
class MyTauriApp < Formula
desc "My Tauri Application"
homepage "https://example.com"
version "0.1.0"
on_mac do
url "https://releases.example.com/my-tauri-app_0.1.0_x64.dmg"
sha256 "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
end
on_linux do
url "https://releases.example.com/my-tauri-app_0.1.0_amd64.AppImage"
sha256 "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
end
def install
prefix.install Dir["*"]
end
test do
system "#{bin}/my-tauri-app", "--version"
end
end5.3.4 打包配置参考
完整的 bundle 配置示例:
{
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"resources": [
"resources/**/*"
],
"copyright": "Copyright (c) 2026 Your Company",
"category": "Utility",
"shortDescription": "A short description of your app",
"longDescription": "A longer description of your application.",
"windows": {
"wix": {
"language": "zh-CN",
"template": "main.wxs"
},
"nsis": {
"installMode": "currentUser",
"installerIcon": "icons/icon.ico",
"installerHeaderIcon": "icons/icon.ico"
}
},
"macOS": {
"frameworks": [],
"minimumSystemVersion": "10.15",
"exceptionDomain": "",
"signing": {
"identity": "Developer ID Application: Your Name"
}
},
"linux": {
"deb": {
"depends": [
"libwebkit2gtk-4.1-0",
"libgtk-3-0",
"libappindicator3-1"
]
},
"appimage": {
"bundleMediaFramework": false
}
}
}
}附录
A. 常见问题
Q: Tauri v1 和 v2 的主要区别是什么?
A: Tauri v2 的主要变化包括:
- 配置文件结构扁平化,移除了
tauri.前缀。 - 引入 Capability 权限系统,替代 v1 的 whitelist 机制。
- 前端 API 模块化拆分。
- 新增移动端支持(iOS/Android,仍处于测试阶段)。
- Rust 端推荐使用
lib.rs+main.rs架构。
Q: 如何处理跨域问题?
A: Tauri 应用默认禁止加载远程资源。如果需要加载远程内容,可以:
- 在 CSP 中添加允许的域名。
- 使用
dangerousRemoteDomainIpcAccess配置。 - 通过 Rust 命令代理远程 API 调用。
Q: 如何调试 Rust 代码?
A: 推荐使用以下方法:
- 在 Rust 代码中添加
println!或log宏输出调试信息。 - 使用
cargo tauri dev运行,终端会显示 Rust 端的日志输出。 - 配置 VS Code 的 Rust 调试器(CodeLLDB 扩展)进行断点调试。
B. 常用依赖参考
# 序列化
serde = { version = "1", features = ["derive"] }
serde_json = "1"
# 异步运行时(Tauri v2 默认使用 tokio)
tokio = { version = "1", features = ["full"] }
# HTTP 客户端
reqwest = { version = "0.12", features = ["json"] }
# SQLite 数据库
rusqlite = { version = "0.31", features = ["bundled"] }
# 日志
log = "0.4"
env_logger = "0.11"
# 加密
argon2 = "0.5"
sha2 = "0.10"
# 日期时间
chrono = { version = "0.4", features = ["serde"] }
# 错误处理
thiserror = "1"
anyhow = "1"C. Tauri v2 配置 Schema 参考
完整的 tauri.conf.json schema 文件位于: https://raw.githubusercontent.com/tauri-apps/tauri/dev/crates/tauri-cli/config.schema.json
在 IDE 中配置 JSON Schema 可以启用自动补全和校验。