Tauri 基础知识参考字典

Tauri 2 条目式字典:架构 → 配置 → IPC → 权限 → 打包。每条给语义、示例、边界与坑。概念见《Tauri 是什么》,实战见《Tauri 2 架构入门》。

1. 三进程模型与系统 WebView

描述

主进程(Rust 核心)+ WebView 进程(系统 WebView 跑前端)+ 可选辅助进程。不捆绑 Chromium,所以产物体积小(几 MB 级)。

语法和示例

<table>
<thead><tr><th>平台</th><th>WebView 实现</th><th>注意事项</th></tr></thead>
<tbody><tr><td>Windows</td><td>WebView2(Edge 内核,系统预装)</td><td>Win10 以下需安装运行时</td></tr><tr><td>macOS</td><td>WKWebView</td><td>Safari 内核,部分新 API 滞后</td></tr><tr><td>Linux</td><td>WebKitGTK 4.1</td><td>Tauri 2.x 起强制要求 4.1</td></tr><tr><td>移动端</td><td>Android WebView / WKWebView</td><td>Tauri 2 支持,能力子集</td></tr></tbody></table>

注意事项

  • 跨平台一致性依赖 WebView 版本——Linux 的 WebKitGTK 常落后,先测目标发行版
  • 不捆绑 Chromium 省体积但牺牲一致性,与 Electron 是明确取舍
  • WebView 进程崩溃会连带窗口白屏,要有兜底提示
  • 主进程用 Rust 意味着崩溃隔离更好,但调试栈更陡
  • Linux 发行版差异大(Ubuntu 22.04 与 Alpine 的 WebKitGTK 版本不同)

2. tauri.conf.json – 配置结构(v2)

描述

v2 用 app.windows 数组(v1 是单对象 tauri.window);build 段定前端构建;bundle 定打包目标;security 定 CSP。

语法和示例

{
  "$schema": "https://schema.tauri.app/config/2",
  "productName": "my-app",
  "version": "0.1.0",
  "identifier": "com.example.app",
  "build": {
    "frontendDist": "../dist",
    "devUrl": "http://localhost:5173",
    "beforeDevCommand": "npm run dev",
    "beforeBuildCommand": "npm run build"
  },
  "app": {
    "windows": [
      { "title": "主窗口", "width": 960, "height": 640,
        "resizable": true, "minWidth": 600 }
    ],
    "security": { "csp": "default-src 'self'; img-src 'self' asset: https:" }
  },
  "bundle": { "active": true, "targets": ["dmg", "msi", "appimage"] }
}

注意事项

  • identifier 必须全局唯一(用于 macOS bundle id 与更新校验)
  • CSP 太松有 XSS 风险,太紧会让前端库失效——从宽松调试再收紧
  • frontendDist 是相对 src-tauri 的路径,配错会「白屏无报错」
  • v1 的 tauri.window / tauri.allowlist 已不存在,迁移要重写
  • 多窗口在 windows 数组里声明,也可运行时动态创建

3. #[tauri::command] – 命令定义

描述

Rust 侧暴露给前端的函数。参数自动反序列化、返回值序列化;async 命令在异步运行时执行,State 注入状态。

语法和示例

#[tauri::command]
fn greet(name: String) -> String { format!("你好, {name}!") }

#[tauri::command]
async fn load(state: tauri::State<'_, AppState>, id: u32)
  -> Result<Vec<Item>, String> {        // 错误必须是 Serialize 类型
    state.fetch(id).await.map_err(|e| e.to_string())
}

// 注册
tauri::Builder::default()
    .manage(AppState::default())
    .invoke_handler(tauri::generate_handler![greet, load])
    .run(tauri::generate_context!())?;

注意事项

  • 命令名默认是函数名的 snake_case,前端调用也用 snake_case
  • 错误类型必须实现 Serialize——常转 String 或自定义错误枚举
  • 忘记 generate_handler! 注册的命令前端调用会报「not found」
  • async 命令的返回值必须 Send(跨线程)
  • 参数名默认 camelCase 映射(Rust 的 user_id 对应前端 userId)

4. invoke – 前端调用命令

描述

@tauri-apps/api/core 的 invoke(name, args) 调用 Rust 命令,返回 Promise。参数键名按 camelCase 映射。

语法和示例

import { invoke } from '@tauri-apps/api/core';

const msg = await invoke<string>('greet', { name: 'X-Rush' });
const items = await invoke<Item[]>('load', { id: 1 });
// 错误捕获
try { await invoke('risky'); }
catch (e) { console.error('命令失败:', e); }
// v1 的路径是 @tauri-apps/api/tauri(v2 改为 /core)

注意事项

  • invoke 的参数对象键名要与 Rust 参数名 camelCase 对应
  • 错误以 reject 的字符串返回(不是 Error 对象),要自己包装
  • Windows 上 WebView2 初始化慢,首次 invoke 可能等几百毫秒
  • invoke 是异步的,不要在同步渲染函数里直接依赖返回值
  • 大量小调用有 IPC 开销——批量接口比循环调用快得多

5. emit / listen – 事件系统

描述

双向事件:Rust 用 app.emit 广播、前端 listen 订阅(返回 unlisten 必须清理)。适合进度、状态推送。

语法和示例

// Rust 侧
#[tauri::command]
async fn start(app: tauri::AppHandle) -> Result<(), String> {
    app.emit("progress", serde_json::json!({ "pct": 10 }))
        .map_err(|e| e.to_string())?;
    Ok(())
}
// 定向:app.emit_to("main", "progress", payload)

// 前端
import { listen, emit } from '@tauri-apps/api/event';
useEffect(() => {
  let un: (() => void) | undefined;
  listen<{ pct: number }>('progress', (e) => setPct(e.payload.pct))
    .then((fn) => { un = fn; });
  return () => un?.();           // 必须清理,否则重复监听
}, []);
emit('progress', { pct: 50 });   // 前端也能发

注意事项

  • listen 未清理会导致每次重新渲染都叠加监听(内存泄漏 + 重复触发)
  • 事件 payload 需可序列化(JSON)
  • emit 是广播(包括所有窗口),emit_to 定向;大量窗口时注意范围
  • 前端 emit 到 Rust 需要 Rust 侧 listen(不是 command)
  • 事件无持久化,前端启动晚于事件会丢——关键状态用 command 主动查

6. capabilities – 权限模型(v2 核心)

描述

v2 默认最小权限:每个插件/核心能力都要在 capabilities/*.json 里显式声明。这是与 v1 allowlist 的根本区别。

语法和示例

// src-tauri/capabilities/default.json
{
  "$schema": "../gen/schemas/desktop-schema.json",
  "identifier": "default",
  "description": "主窗口默认能力",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "dialog:default",
    "fs:allow-read-text-file",
    { "identifier": "fs:scope", "allow": ["$APPDATA/*"] }
  ]
}

注意事项

  • 报「not allowed」先查 capabilities,不是查代码逻辑
  • permissions 的粒度可到具体函数与路径范围(scope)
  • 不同窗口可挂不同能力集(最小权限到窗口级)
  • 插件升级可能新增权限项,升级后要重新核对清单
  • capabilities 文件在 src-tauri/capabilities/,不是 tauri.conf.json 里

7. 官方插件三步安装

描述

每个官方插件都要三件事:Rust 依赖、npm 包、权限声明。少一步就报错(最常见是漏权限)。

语法和示例

bash
1. 1. Rust 侧
cargo add tauri-plugin-dialog
1. 2. 前端侧
npm i @tauri-apps/plugin-dialog
1. 3. 权限(src-tauri/capabilities/default.json)
1.   "permissions": ["dialog:default"]
1. 4. 注册插件(lib.rs)
1.   .plugin(tauri_plugin_dialog::init())

注意事项

  • 漏第 4 步(.plugin() 注册)会报「命令未注册」而不是权限错误
  • 插件版本要与 Tauri 主版本匹配(2.x 插件不能配 1.x)
  • 移动端插件需额外配置(Android/iOS 的权限清单)
  • 自定义插件可不用官方模板,但要自己实现权限检查
  • 插件越多启动越慢,按需引入

8. 窗口管理:多窗口与生命周期

描述

Rust 侧 WebviewWindowBuilder 建窗;前端 getCurrentWindow 控制;窗口事件可拦截关闭(转托盘)。

语法和示例

// Rust:动态建窗
tauri::WebviewWindowBuilder::new(
    &app, "settings", tauri::WebviewUrl::App("settings".into()))
    .title("设置").inner_size(600.0, 400.0).build()?;
// 拦截关闭
.on_window_event(|w, e| {
    if let tauri::WindowEvent::CloseRequested { api, .. } = e {
        api.prevent_close();          // 转最小化到托盘
    }
})
// 前端
import { getCurrentWindow } from '@tauri-apps/api/window';
getCurrentWindow().setTitle('新标题');
getCurrentWindow().close();
getCurrentWindow().listen('tauri://close-requested', () => {});

注意事项

  • prevent_close 后必须自己处理(最小化/托盘),否则窗口关不掉也退不出
  • 窗口 label 唯一,重复创建会报错
  • 多窗口共享同一 Rust 状态(State 是全局的),窗口私有状态需自己管理
  • 前端 window.close() 会触发 close-requested,被拦截时关不掉属正常
  • 窗口位置尺寸要持久化(自行存配置文件,Tauri 不自动记

9. 菜单与系统托盘

描述

macOS 必须显式建主菜单(否则连编辑菜单都没有);托盘用 TrayIconBuilder,配菜单与点击事件。

语法和示例

tauri::Builder::default()
    .menu(tauri::menu::Menu::default(&handle)?)
    .on_menu_event(|app, e| match e.id().as_ref() {
        "quit" => app.exit(0),
        "about" => {},
        _ => {}
    })
    .setup(|app| {
        let tray = tauri::tray::TrayIconBuilder::new()
            .icon(app.default_window_icon().unwrap().clone())
            .menu(&tray_menu)?
            .on_tray_icon_event(|tray, e| { /* 左键显示窗口 */ })
            .build(app)?;
        Ok(())
    })

注意事项

  • macOS 不建菜单会导致 Cmd+C/V 等系统快捷键失效(用户会以为程序坏了)
  • 托盘图标需为模板图(macOS 会反色),否则深色模式下看不见
  • 菜单事件用 id 字符串匹配,改动 id 会静默失效
  • Linux 的系统托盘依赖 libayatana-appindicator,缺库时不显示
  • 托盘与主窗口生命周期独立,全关窗口后进程仍活(靠托盘退出)

10. updater – 自动更新

描述

内置更新器:签名密钥对 + latest.json 清单 + 权限声明。macOS 分发还需 notarize 才能通过校验。

语法和示例

bash
1. 生成签名密钥(公钥进 conf,私钥进 CI 环境变量)
tauri signer generate -w ~/.tauri/myapp.key
1. tauri.conf.json
1. "plugins": { "updater": {
1.   "endpoints": ["https://my.site/latest.json"],
1.   "pubkey": "..." } }
1. 前端
import { check } from '@tauri-apps/plugin-updater';
const update = await check();
if (update) { await update.downloadAndInstall(); }

注意事项

  • 私钥泄露等于任何人都能给你的用户推恶意更新——必须放 CI 密钥管理
  • latest.json 的 signature 与安装包必须对应,手工改包会校验失败
  • macOS 未 notarize 时更新会被 Gatekeeper 拦(开发期常被这个坑住
  • 更新会重启应用,要保存用户未提交的数据或提示
  • 内网/离线场景需自建 endpoint,且要考虑签名与版本号策略

11. IPC 大数据传输

描述

跨进程传大块数据(文件/图片/二进制):用 tauri::ipc::Response(ArrayBuffer)而非 JSON 序列化。

语法和示例

// Rust
#[tauri::command]
fn read_file() -> tauri::ipc::Response {
    tauri::ipc::Response::new(bytes)      // 前端收 ArrayBuffer
}
// 前端
const buf: ArrayBuffer = await invoke('read_file');
// 反向:前端传大块
await invoke('upload', { data: new Uint8Array(buf) });

注意事项

  • JSON 序列化二进制会产生巨大字符串并爆内存——大块必用 Response
  • IPC 传输本身仍是拷贝,超大文件建议用分块或本地路径传递
  • ArrayBuffer 在 Windows 上走 WebView2 的消息通道,速度受通道限制
  • 频繁小 IPC 调用开销明显,合并成批量接口
  • 传路径比传内容更好:Rust 侧直接读,前端只拿结果

12. 调试与构建

描述

DevTools 快捷键、Rust 日志、devtools feature、release 构建与产物路径。

语法和示例

bash
npm create tauri-app@latest
npm run tauri dev            # 热重载
npm run tauri build          # 产物在 src-tauri/target/release/bundle/
1. DevTools:Ctrl+Shift+I(Linux/Win)
1. release 要 devtools 需在 Cargo.toml 加
1. [features] devtools = ["tauri/devtools"]

注意事项

  • release 默认禁用 DevTools,看不到日志容易误判「没报错」
  • Rust 日志用 tauri-plugin-log 或 env_logger,println 在 release 可能不显示
  • Linux 构建需装 libwebkit2gtk-4.1-dev 等一整套依赖
  • 首次构建要编译全部 Rust 依赖,几分钟到十几分钟属正常
  • 跨平台构建要在对应系统或 CI 上做(不能交叉打包 macOS)
上一篇
下一篇