tauri-v2 by nodnarbnitram/claude-code-extensions
npx skills add https://github.com/nodnarbnitram/claude-code-extensions --skill tauri-v2使用 Web 前端和 Rust 后端构建跨平台桌面和移动应用程序。
此技能可预防 8 种以上常见错误并节省约 60% 的 Token 使用量。
| 指标 | 不使用技能 | 使用技能 |
|---|---|---|
| 设置时间 | ~2 小时 | ~30 分钟 |
| 常见错误 | 8+ | 0 |
| Token 使用量 | 高(探索性) | 低(直接模式) |
generate_handler! 中注册导致的 IPC 失败// src-tauri/src/lib.rs
#[tauri::command]
fn greet(name: String) -> String {
format!("Hello, {}!", name)
}
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
为何重要: 未包含在 generate_handler![] 中的命令在前端调用时会静默失败。
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)。
// 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 存放共享代码(移动端构建必需)&str)—— 使用拥有所有权的类型app.path())错误示例 - 在异步中使用借用类型:
#[tauri::command]
async fn bad(name: &str) -> String { // 编译错误!
name.to_string()
}
正确示例 - 使用拥有所有权的类型:
#[tauri::command]
async fn good(name: String) -> String {
name
}
原因: 异步命令不能在跨越 await 点时借用数据;Tauri 要求异步命令参数使用拥有所有权的类型。
| 问题 | 根本原因 | 解决方案 |
|---|---|---|
| "命令未找到" | 未包含在 generate_handler! 中 | 将命令添加到处理器宏中 |
| "权限被拒绝" | 缺少能力(capability) | 添加到 capabilities/default.json |
| 访问状态时恐慌 | State<T> 中的类型不匹配 | 使用与 .manage() 中完全相同的类型 |
| 启动时白屏 | 前端未构建 | 检查配置中的 beforeDevCommand |
| IPC 超时 | 阻塞的异步命令 | 移除阻塞代码或使用 spawn |
| 移动构建失败 | 缺少 Rust 目标平台 | 运行 rustup target add <target> |
{
"$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:能力文件标识符数组[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:必须包含所有三种类型以实现跨平台use thiserror::Error;
#[derive(Debug, Error)]
enum AppError {
#[error("IO 错误: {0}")]
Io(#[from] std::io::Error),
#[error("未找到: {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())
}
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
}
// 在构建器中:
tauri::Builder::default()
.manage(Mutex::new(AppState { counter: 0 }))
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('进度:', e.payload);
});
// 完成后调用 unlisten()
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 });
位于 references/ 目录下:
capabilities-reference.md —— 权限模式和示例ipc-patterns.md —— 完整的 IPC 示例注意: 如需深入了解特定主题,请参阅上面的参考文件。
| 包 | 版本 | 用途 |
|---|---|---|
@tauri-apps/cli | ^2.0.0 | CLI 工具 |
@tauri-apps/api | ^2.0.0 | 前端 API |
tauri | ^2.0.0 | Rust 核心 |
tauri-build | ^2.0.0 | 构建脚本 |
| 包 | 版本 | 用途 |
|---|---|---|
tauri-plugin-fs | ^2.0.0 | 文件系统访问 |
tauri-plugin-dialog | ^2.0.0 | 原生对话框 |
tauri-plugin-shell | ^2.0.0 | Shell 命令,打开 URL |
tauri-plugin-http | ^2.0.0 | HTTP 客户端 |
tauri-plugin-store | ^2.0.0 | 键值存储 |
症状: 应用程序启动但显示空白白屏
解决方案:
devUrl 是否与你的前端开发服务器端口匹配beforeDevCommand 是否运行了你的开发服务器症状: invoke() 返回 undefined 而不是预期值
解决方案:
generate_handler![] 中症状: 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
使用此技能前,请验证:
npx tauri info 显示正确的 Tauri v2 版本src-tauri/capabilities/default.json 存在且至少包含 core:defaultgenerate_handler![] 中注册lib.rs 包含共享代码(用于移动端支持)每周安装量
2.7K
仓库
GitHub 星标
5
首次出现
2026年1月24日
安装于
opencode2.3K
codex2.2K
gemini-cli2.2K
github-copilot2.2K
kimi-cli2.1K
amp2.1K
Build cross-platform desktop and mobile apps with web frontends and Rust backends.
This skill prevents 8+ common errors and saves ~60% tokens.
| Metric | Without Skill | With Skill |
|---|---|---|
| Setup Time | ~2 hours | ~30 min |
| Common Errors | 8+ | 0 |
| Token Usage | High (exploration) | Low (direct patterns) |
generate_handler!// src-tauri/src/lib.rs
#[tauri::command]
fn greet(name: String) -> String {
format!("Hello, {}!", name)
}
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
Why this matters: Commands not in generate_handler![] silently fail when invoked from frontend.
import { invoke } from '@tauri-apps/api/core';
const greeting = await invoke<string>('greet', { name: 'World' });
console.log(greeting); // "Hello, World!"
Why this matters: Use @tauri-apps/api/core (not @tauri-apps/api/tauri - that's v1 API).
// src-tauri/capabilities/default.json
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"windows": ["main"],
"permissions": ["core:default"]
}
Why this matters: Tauri v2 denies everything by default - explicit permissions required for all operations.
tauri::generate_handler![cmd1, cmd2, ...]Result<T, E> from commands for proper error handlingMutex<T> for shared state accessed from multiple commandslib.rs for shared code (required for mobile builds)&str) in async commands - use owned typesapp.path())Wrong - Borrowed type in async:
#[tauri::command]
async fn bad(name: &str) -> String { // Compile error!
name.to_string()
}
Correct - Owned type:
#[tauri::command]
async fn good(name: String) -> String {
name
}
Why: Async commands cannot borrow data across await points; Tauri requires owned types for async command parameters.
| Issue | Root Cause | Solution |
|---|---|---|
| "Command not found" | Missing from generate_handler! | Add command to handler macro |
| "Permission denied" | Missing capability | Add to capabilities/default.json |
| State panic on access | Type mismatch in State<T> | Use exact type from .manage() |
| White screen on launch | Frontend not building | Check beforeDevCommand in config |
| IPC timeout |
{
"$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"]
}
}
Key settings:
build.devUrl: Must match your frontend dev server portapp.security.capabilities: Array of capability file identifiers[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"
Key settings:
[lib] section: Required for mobile buildscrate-type: Must include all three types for cross-platformuse 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())
}
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
}
// In builder:
tauri::Builder::default()
.manage(Mutex::new(AppState { counter: 0 }))
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);
});
// Call unlisten() when done
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 });
Located in references/:
capabilities-reference.md - Permission patterns and examplesipc-patterns.md - Complete IPC examplesNote: For deep dives on specific topics, see the reference files above.
| Package | Version | Purpose |
|---|---|---|
@tauri-apps/cli | ^2.0.0 | CLI tooling |
@tauri-apps/api | ^2.0.0 | Frontend APIs |
tauri | ^2.0.0 | Rust core |
tauri-build | ^2.0.0 | Build scripts |
| Package | Version | Purpose |
|---|---|---|
tauri-plugin-fs | ^2.0.0 | File system access |
tauri-plugin-dialog | ^2.0.0 | Native dialogs |
tauri-plugin-shell | ^2.0.0 | Shell commands, open URLs |
tauri-plugin-http | ^2.0.0 | HTTP client |
tauri-plugin-store | ^2.0.0 |
Symptoms: App launches but shows blank white screen
Solution:
devUrl matches your frontend dev server portbeforeDevCommand runs your dev serverSymptoms: invoke() returns undefined instead of expected value
Solution:
generate_handler![]Symptoms: Android/iOS build fails with missing target
Solution:
# Android targets
rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android
# iOS targets (macOS only)
rustup target add aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim
Before using this skill, verify:
npx tauri info shows correct Tauri v2 versionssrc-tauri/capabilities/default.json exists with at least core:defaultgenerate_handler![]lib.rs contains shared code (for mobile support)Weekly Installs
2.7K
Repository
GitHub Stars
5
First Seen
Jan 24, 2026
Installed on
opencode2.3K
codex2.2K
gemini-cli2.2K
github-copilot2.2K
kimi-cli2.1K
amp2.1K
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
102,200 周安装
| Blocking async command |
| Remove blocking code or use spawn |
| Mobile build fails | Missing Rust targets | Run rustup target add <target> |
| Key-value storage |