tauri-v2

tauri-v2

使用 Rust 後端開發 Tauri v2+ 跨平台桌面與行動應用。適用於設定 tauri.conf.json、實作 Rust 指令(#[tauri::command])、設定 IPC 模式(invoke、emit、channels)、設定權限/能力、疑難排解建置問題或部署桌面/行動應用。觸發關鍵字:Tauri、src-tauri、invoke、emit、capabilities.json。

14星標
6分支
更新於 2026/4/20
SKILL.md
readonlyread-only
name
tauri-v2
description

使用 Rust 後端開發 Tauri v2+ 跨平台桌面與行動應用。適用於設定 tauri.conf.json、實作 Rust 指令(#[tauri::command])、設定 IPC 模式(invoke、emit、channels)、設定權限/能力、疑難排解建置問題或部署桌面/行動應用。觸發關鍵字:Tauri、src-tauri、invoke、emit、capabilities.json。

version
1.0.1

Tauri v2+ 開發技能

使用網頁前端與 Rust 後端建構跨平台桌面與行動應用。

開始之前

此技能可預防 8 種以上常見錯誤,並節省約 60% 的 token 用量。

指標 無技能 有技能
設定時間 約 2 小時 約 30 分鐘
常見錯誤 8 種以上 0
Token 用量 高(探索) 低(直接模式)

此技能預防的已知問題

  1. 因缺少能力而導致的權限拒絕錯誤
  2. generate_handler! 中未註冊指令而導致的 IPC 失敗
  3. 因型別不符而導致的狀態管理 panic
  4. 因缺少 Rust 目標而導致行動建置失敗
  5. 因 dev URL 設定錯誤而導致的白畫面問題

快速開始

步驟 1:建立 Tauri 指令

// src-tauri/src/lib.rs
#[tauri::command]
fn greet(name: String) -> String {
    format!("Hello, {}!", name)
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![greet])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

為什麼這很重要: 未在 generate_handler![] 中的指令從前端呼叫時會靜默失敗。

main.rs 保持精簡: src-tauri/src/main.rs 應僅為薄薄的傳遞層 — 所有應用邏輯都放在 lib.rs 中:

// src-tauri/src/main.rs
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
    app_lib::run();
}

這種拆分是行動建置所必需的 — Tauri 在行動目標上會將 main() 替換為 mobile_entry_point

步驟 2:從前端呼叫

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

const greeting = await invoke<string>('greet', { name: 'World' });
console.log(greeting); // "Hello, World!"

為什麼這很重要: 使用 @tauri-apps/api/core(不是 @tauri-apps/api/tauri — 那是 v1 API)。

步驟 3:新增必要權限

// src-tauri/capabilities/default.json
{
    "$schema": "../gen/schemas/desktop-schema.json",
    "identifier": "default",
    "windows": ["main"],
    "permissions": ["core:default"]
}

為什麼這很重要: Tauri v2 預設拒絕所有操作 — 所有操作都需要明確的權限。

關鍵規則

務必執行

  • tauri::generate_handler![cmd1, cmd2, ...] 中註冊每個指令
  • 從指令回傳 Result<T, E> 以進行正確的錯誤處理
  • 對多個指令存取的共享狀態使用 Mutex<T>
  • 在使用任何外掛功能前先新增能力
  • 使用 lib.rs 存放共享程式碼(行動建置必需)
  • lib.rspub fn run() 上使用 #[cfg_attr(mobile, tauri::mobile_entry_point)] 以支援行動裝置

絕對不要

  • 不要在非同步指令中使用借用型別(&str)— 使用擁有型別
  • 不要阻塞主執行緒 — 對 I/O 操作使用非同步
  • 不要硬編碼路徑 — 使用 Tauri 路徑 API(app.path()
  • 不要跳過能力設定 — 即使是「安全」操作也需要權限

常見錯誤

錯誤 — 非同步中使用借用型別:

#[tauri::command]
async fn bad(name: &str) -> String { // 編譯錯誤!
    name.to_string()
}

正確 — 擁有型別:

#[tauri::command]
async fn good(name: String) -> String {
    name
}

原因: 非同步指令無法在 await 點之間借用資料;Tauri 要求非同步指令參數使用擁有型別。

已知問題預防

問題 根本原因 解決方案
"Command not found" 未在 generate_handler! 將指令加入 handler 巨集
"Permission denied" 缺少能力 加入 capabilities/default.json
外掛功能靜默失敗 已安裝外掛但能力中未加入權限 將外掛權限字串加入 capabilities/default.json
更新器在正式環境失敗 未簽署的成品或 HTTP 端點 使用 cargo tauri signer generate 產生金鑰,僅使用 HTTPS 端點
側載程式未找到 tauri.conf.json 中未設定 externalBin 或缺少可執行檔 將路徑加入 bundle.externalBin,確保二進位檔已打包
功能在桌面正常,在行動裝置崩潰 使用了僅桌面的 API 檢查 API 是否支援行動裝置 — 某些外掛僅限桌面
存取狀態時 panic State<T> 型別不符 使用與 .manage() 完全相同的型別
啟動時白畫面 前端未建置 檢查設定中的 beforeDevCommand
IPC 逾時 阻塞的非同步指令 移除阻塞程式碼或使用 spawn
行動建置失敗 缺少 Rust 目標 執行 rustup target add <target>

深入參考

設定參考

tauri.conf.json

{
    "$schema": "./gen/schemas/desktop-schema.json",
    "productName": "my-app",
    "version": "1.0.0",
    "identifier": "com.example.myapp",
    "build": {
        "devUrl": "http://localhost:5173",
        "frontendDist": "../dist",
        "beforeDevCommand": "npm run dev",
        "beforeBuildCommand": "npm run build"
    },
    "app": {
        "windows": [{
            "label": "main",
            "title": "My App",
            "width": 800,
            "height": 600
        }],
        "security": {
            "csp": "default-src 'self'; img-src 'self' data:",
            "capabilities": ["default"]
        }
    },
    "bundle": {
        "active": true,
        "targets": "all",
        "icon": ["icons/icon.icns", "icons/icon.ico", "icons/icon.png"]
    }
}

關鍵設定:

  • build.devUrl:必須與前端開發伺服器埠號相符
  • app.security.capabilities:能力檔案識別碼的陣列

外掛設定 — 某些外掛需要額外的 tauri.conf.json 區塊(例如 storeupdater)。請務必查閱特定外掛文件(v2.tauri.app/plugin/<plugin-name>/)以了解所需的設定鍵。

專案結構

my-tauri-app/
├── src/                    # 前端原始碼
├── src-tauri/
│   ├── src/
│   │   ├── main.rs         # 薄薄的傳遞層 — 呼叫 lib::run()
│   │   └── lib.rs          # 所有應用邏輯都在這裡
│   ├── capabilities/
│   │   └── default.json    # 能力定義(在此授予權限)
│   ├── tauri.conf.json     # 應用設定(devUrl、bundle、security)
│   ├── Cargo.toml          # Rust 依賴
│   └── build.rs            # 建置腳本(tauri-build 必需)
└── package.json

為什麼 lib.rs 擁有所有邏輯: Tauri 在行動裝置上會將 main() 替換為 #[cfg_attr(mobile, tauri::mobile_entry_point)]。所有指令、狀態和 builder 設定都必須放在 lib.rs::run() 中。

Cargo.toml

[package]
name = "app"
version = "0.1.0"
edition = "2021"

[lib]
name = "app_lib"
crate-type = ["staticlib", "cdylib", "rlib"]

[build-dependencies]
tauri-build = { version = "2", features = [] }

[dependencies]
tauri = { version = "2", features = [] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"

關鍵設定:

  • [lib] 區段:行動建置必需
  • crate-type:必須包含所有三種型別以支援跨平台

常見模式

錯誤處理模式

使用 Result<T, E>thiserror 在 IPC 邊界進行型別安全的錯誤傳遞。完整實作細節請參閱 references/ipc-patterns.md

use thiserror::Error;

#[derive(Debug, Error)]
enum AppError {
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),
    #[error("Not found: {0}")]
    NotFound(String),
}

impl serde::Serialize for AppError {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where S: serde::ser::Serializer {
        serializer.serialize_str(self.to_string().as_ref())
    }
}

#[tauri::command]
fn risky_operation() -> Result<String, AppError> {
    Ok("success".into())
}

Serde 邊界規則

所有指令參數必須實作 serde::Deserialize,回傳型別必須實作 serde::Serialize。這是 Tauri 在 IPC 邊界橋接 JSON 的方式。

use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
struct CreateUserArgs {
    name: String,
    email: String,
    role: Option<String>,  // 可選欄位使用 Option<T>
}

#[derive(Serialize)]
struct User {
    id: u64,
    name: String,
}

#[tauri::command]
fn create_user(args: CreateUserArgs) -> Result<User, String> {
    Ok(User { id: 1, name: args.name })
}

常見 serde 陷阱:

  • 欄位名稱在 JS 中為 camelCase,在 Rust 中為 snake_case — Tauri 會自動轉換
  • Option<T> 對應到可選的 JS 參數(可以是 undefinednull
  • 複雜的列舉需要 #[serde(tag = "type")] 或類似方式才能安全轉為 JSON
  • 錯誤型別也必須實作 Serialize(請參閱上方錯誤處理模式)

狀態管理模式

Tauri 狀態管理跨指令的應用資料。更複雜的狀態模式請參閱 references/ipc-patterns.md

use std::sync::Mutex;
use tauri::State;

struct AppState {
    counter: u32,
}

#[tauri::command]
fn increment(state: State<'_, Mutex<AppState>>) -> u32 {
    let mut s = state.lock().unwrap();
    s.counter += 1;
    s.counter
}

// 在 builder 中:
tauri::Builder::default()
    .manage(Mutex::new(AppState { counter: 0 }))

事件發送模式

事件是發送後即忘的通知。雙向範例請參閱 references/ipc-patterns.md

use tauri::Emitter;

#[tauri::command]
fn start_task(app: tauri::AppHandle) {
    std::thread::spawn(move || {
        app.emit("task-progress", 50).unwrap();
        app.emit("task-complete", "done").unwrap();
    });
}
import { listen } from '@tauri-apps/api/event';

const unlisten = await listen('task-progress', (e) => {
    console.log('Progress:', e.payload);
});
// 完成時呼叫 unlisten()

通道串流模式

通道提供從 Rust 到前端的高頻率、型別安全串流。完整實作細節請參閱 references/ipc-patterns.md

use tauri::ipc::Channel;

#[derive(Clone, serde::Serialize)]
#[serde(tag = "event", content = "data")]
enum DownloadEvent {
    Progress { percent: u32 },
    Complete { path: String },
}

#[tauri::command]
async fn download(url: String, on_event: Channel<DownloadEvent>) {
    for i in 0..=100 {
        on_event.send(DownloadEvent::Progress { percent: i }).unwrap();
    }
    on_event.send(DownloadEvent::Complete { path: "/downloads/file".into() }).unwrap();
}
import { invoke, Channel } from '@tauri-apps/api/core';

const channel = new Channel<DownloadEvent>();
channel.onmessage = (msg) => console.log(msg.event, msg.data);
await invoke('download', { url: 'https://...', onEvent: channel });

視窗存取模式

Tauri v2 使用 WebviewWindow 統一管理視窗與網頁視圖。

use tauri::Manager;

#[tauri::command]
fn focus_window(app: tauri::AppHandle) {
    if let Some(window) = app.get_webview_window("main") {
        let _ = window.set_focus();
    }
}

為什麼這很重要: 在 v2 中使用 tauri::WebviewWindowapp.get_webview_window("label") — v1 的 app.get_window() API 已在 v2 中移除。

打包資源

參考文件

位於 references/ 目錄:

注意: 如需特定主題的深入探討,請參閱上述參考檔案。

依賴

必要

套件 版本 用途
@tauri-apps/cli ^2 (v2+) CLI 工具
@tauri-apps/api ^2 (v2+) 前端 API
tauri ^2 (v2+) Rust 核心
tauri-build ^2 (v2+) 建置腳本

*最後驗證:2026-04-02。請務必查閱官方變更日誌以了解功能時程。

選用(外掛)

套件 版本 用途 關鍵權限
tauri-plugin-fs ^2 (v2+) 檔案系統存取 fs:default
tauri-plugin-dialog ^2 (v2+) 原生對話框 dialog:default
tauri-plugin-shell ^2 (v2+) Shell 指令、開啟 URL shell:default
tauri-plugin-http ^2 (v2+) HTTP 客戶端 http:default
tauri-plugin-store ^2 (v2+) 鍵值儲存 store:default

外掛權限是強制性的。 安裝外掛但未將其權限字串加入能力檔案會導致執行時期靜默失敗。所有官方外掛的完整安裝與權限詳細資訊請參閱 references/plugin-reference.md

官方文件

疑難排解

啟動時白畫面

症狀: 應用啟動但顯示空白白畫面

解決方案:

  1. 確認 devUrl 與前端開發伺服器埠號相符
  2. 檢查 beforeDevCommand 是否執行開發伺服器
  3. 開啟 DevTools(Cmd+Option+I / Ctrl+Shift+I)檢查錯誤

指令回傳 Undefined

症狀: invoke() 回傳 undefined 而非預期值

解決方案:

  1. 確認指令在 generate_handler![]
  2. 檢查 Rust 指令確實有回傳值
  3. 確保參數名稱相符(JS 中 camelCase,Rust 中預設 snake_case)

行動建置失敗

症狀: Android/iOS 建置因缺少目標而失敗

解決方案:

# Android 目標
rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android

# iOS 目標(僅 macOS)
rustup target add aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim

桌面與行動行為差異

並非所有 Tauri API 和外掛都支援行動裝置(iOS/Android)。在行動建置中使用任何外掛或 API 之前:

  1. 查閱外掛頁面 v2.tauri.app/plugin/<name>/ 以了解平台支援矩陣
  2. 常見僅桌面項目:系統匣(TrayIconBuilder)、視窗標籤/多視窗、某些 shell 外掛功能
  3. 行動安全模式:IPC 指令/事件/通道在所有平台上皆可運作;tauri::AppHandle 是行動安全的
  4. 條件編譯:使用 #[cfg(desktop)] / #[cfg(mobile)] 處理平台特定的 Rust 邏輯
#[tauri::command]
fn platform_info() -> String {
    #[cfg(desktop)]
    return "desktop".to_string();
    #[cfg(mobile)]
    return "mobile".to_string();
}

設定檢查清單

使用此技能前,請確認:

  • [ ] npx tauri info 顯示正確的 Tauri v2 版本
  • [ ] src-tauri/capabilities/default.json 存在且至少包含 core:default
  • [ ] 所有指令已在 generate_handler![] 中註冊
  • [ ] lib.rs 包含共享程式碼(以支援行動裝置)
  • [ ] 已為目標平台安裝必要的 Rust 目標