使用 Rust 後端開發 Tauri v2+ 跨平台桌面與行動應用。適用於設定 tauri.conf.json、實作 Rust 指令(#[tauri::command])、設定 IPC 模式(invoke、emit、channels)、設定權限/能力、疑難排解建置問題或部署桌面/行動應用。觸發關鍵字:Tauri、src-tauri、invoke、emit、capabilities.json。
Tauri v2+ 開發技能
使用網頁前端與 Rust 後端建構跨平台桌面與行動應用。
開始之前
此技能可預防 8 種以上常見錯誤,並節省約 60% 的 token 用量。
| 指標 | 無技能 | 有技能 |
|---|---|---|
| 設定時間 | 約 2 小時 | 約 30 分鐘 |
| 常見錯誤 | 8 種以上 | 0 |
| Token 用量 | 高(探索) | 低(直接模式) |
此技能預防的已知問題
- 因缺少能力而導致的權限拒絕錯誤
- 因
generate_handler!中未註冊指令而導致的 IPC 失敗 - 因型別不符而導致的狀態管理 panic
- 因缺少 Rust 目標而導致行動建置失敗
- 因 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.rs的pub 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> |
深入參考
- 安全性與權限 →
references/capabilities-reference.md - IPC 決策指南 →
references/ipc-patterns.md - 官方外掛 →
references/plugin-reference.md - 更新器與發布 →
references/updater-distribution-reference.md - 系統匣、側載程式、深層連結 →
references/advanced-runtime-reference.md
設定參考
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 區塊(例如 store、updater)。請務必查閱特定外掛文件(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 參數(可以是undefined或null)- 複雜的列舉需要
#[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::WebviewWindow 和 app.get_webview_window("label") — v1 的 app.get_window() API 已在 v2 中移除。
打包資源
參考文件
位於 references/ 目錄:
capabilities-reference.md— 權限模式與範例ipc-patterns.md— 完整 IPC 範例plugin-reference.md— 官方外掛安裝、註冊與權限字串updater-distribution-reference.md— 簽署、HTTPS 需求與套件發布advanced-runtime-reference.md—TrayIconBuilder、側載程式、深層連結與資產協定
注意: 如需特定主題的深入探討,請參閱上述參考檔案。
依賴
必要
| 套件 | 版本 | 用途 |
|---|---|---|
@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。
官方文件
疑難排解
啟動時白畫面
症狀: 應用啟動但顯示空白白畫面
解決方案:
- 確認
devUrl與前端開發伺服器埠號相符 - 檢查
beforeDevCommand是否執行開發伺服器 - 開啟 DevTools(Cmd+Option+I / Ctrl+Shift+I)檢查錯誤
指令回傳 Undefined
症狀: invoke() 回傳 undefined 而非預期值
解決方案:
- 確認指令在
generate_handler![]中 - 檢查 Rust 指令確實有回傳值
- 確保參數名稱相符(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 之前:
- 查閱外掛頁面
v2.tauri.app/plugin/<name>/以了解平台支援矩陣 - 常見僅桌面項目:系統匣(
TrayIconBuilder)、視窗標籤/多視窗、某些 shell 外掛功能 - 行動安全模式:IPC 指令/事件/通道在所有平台上皆可運作;
tauri::AppHandle是行動安全的 - 條件編譯:使用
#[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 目標






