369 lines
9.8 KiB
Markdown
369 lines
9.8 KiB
Markdown
---
|
|
name: tauri-v2
|
|
description: "Tauri v2 cross-platform app development with Rust backend. Use when configuring tauri.conf.json, implementing Rust commands (#[tauri::command]), setting up IPC patterns (invoke, emit, channels), configuring permissions/capabilities, troubleshooting build issues, or deploying desktop/mobile apps. Triggers on Tauri, src-tauri, invoke, emit, capabilities.json."
|
|
---
|
|
|
|
# Tauri v2 Development Skill
|
|
|
|
> Build cross-platform desktop and mobile apps with web frontends and Rust backends.
|
|
|
|
## Before You Start
|
|
|
|
**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) |
|
|
|
|
### Known Issues This Skill Prevents
|
|
|
|
1. Permission denied errors from missing capabilities
|
|
2. IPC failures from unregistered commands in `generate_handler!`
|
|
3. State management panics from type mismatches
|
|
4. Mobile build failures from missing Rust targets
|
|
5. White screen issues from misconfigured dev URLs
|
|
|
|
## Quick Start
|
|
|
|
### Step 1: Create a Tauri Command
|
|
|
|
```rust
|
|
// 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.
|
|
|
|
### Step 2: Call from Frontend
|
|
|
|
```typescript
|
|
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).
|
|
|
|
### Step 3: Add Required Permissions
|
|
|
|
```json
|
|
// 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.
|
|
|
|
## Critical Rules
|
|
|
|
### Always Do
|
|
|
|
- Register every command in `tauri::generate_handler![cmd1, cmd2, ...]`
|
|
- Return `Result<T, E>` from commands for proper error handling
|
|
- Use `Mutex<T>` for shared state accessed from multiple commands
|
|
- Add capabilities before using any plugin features
|
|
- Use `lib.rs` for shared code (required for mobile builds)
|
|
|
|
### Never Do
|
|
|
|
- Never use borrowed types (`&str`) in async commands - use owned types
|
|
- Never block the main thread - use async for I/O operations
|
|
- Never hardcode paths - use Tauri path APIs (`app.path()`)
|
|
- Never skip capability setup - even "safe" operations need permissions
|
|
|
|
### Common Mistakes
|
|
|
|
**Wrong - Borrowed type in async:**
|
|
```rust
|
|
#[tauri::command]
|
|
async fn bad(name: &str) -> String { // Compile error!
|
|
name.to_string()
|
|
}
|
|
```
|
|
|
|
**Correct - Owned type:**
|
|
```rust
|
|
#[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.
|
|
|
|
## Known Issues Prevention
|
|
|
|
| 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 | Blocking async command | Remove blocking code or use spawn |
|
|
| Mobile build fails | Missing Rust targets | Run `rustup target add <target>` |
|
|
|
|
## Configuration Reference
|
|
|
|
### tauri.conf.json
|
|
|
|
```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"]
|
|
}
|
|
}
|
|
```
|
|
|
|
**Key settings:**
|
|
- `build.devUrl`: Must match your frontend dev server port
|
|
- `app.security.capabilities`: Array of capability file identifiers
|
|
|
|
### Cargo.toml
|
|
|
|
```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"
|
|
```
|
|
|
|
**Key settings:**
|
|
- `[lib]` section: Required for mobile builds
|
|
- `crate-type`: Must include all three types for cross-platform
|
|
|
|
## Common Patterns
|
|
|
|
### Error Handling Pattern
|
|
|
|
```rust
|
|
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())
|
|
}
|
|
```
|
|
|
|
### State Management Pattern
|
|
|
|
```rust
|
|
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 }))
|
|
```
|
|
|
|
### Event Emission Pattern
|
|
|
|
```rust
|
|
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();
|
|
});
|
|
}
|
|
```
|
|
|
|
```typescript
|
|
import { listen } from '@tauri-apps/api/event';
|
|
|
|
const unlisten = await listen('task-progress', (e) => {
|
|
console.log('Progress:', e.payload);
|
|
});
|
|
// Call unlisten() when done
|
|
```
|
|
|
|
### Channel Streaming Pattern
|
|
|
|
```rust
|
|
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();
|
|
}
|
|
```
|
|
|
|
```typescript
|
|
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 });
|
|
```
|
|
|
|
## Bundled Resources
|
|
|
|
### References
|
|
|
|
Located in `references/`:
|
|
- [`capabilities-reference.md`](references/capabilities-reference.md) - Permission patterns and examples
|
|
- [`ipc-patterns.md`](references/ipc-patterns.md) - Complete IPC examples
|
|
|
|
> **Note:** For deep dives on specific topics, see the reference files above.
|
|
|
|
## Dependencies
|
|
|
|
### Required
|
|
|
|
| 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 |
|
|
|
|
### Optional (Plugins)
|
|
|
|
| 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 | Key-value storage |
|
|
|
|
## Official Documentation
|
|
|
|
- [Tauri v2 Documentation](https://v2.tauri.app/)
|
|
- [Commands Reference](https://v2.tauri.app/develop/calling-rust/)
|
|
- [Capabilities & Permissions](https://v2.tauri.app/security/capabilities/)
|
|
- [Configuration Reference](https://v2.tauri.app/reference/config/)
|
|
|
|
## Troubleshooting
|
|
|
|
### White Screen on Launch
|
|
|
|
**Symptoms:** App launches but shows blank white screen
|
|
|
|
**Solution:**
|
|
1. Verify `devUrl` matches your frontend dev server port
|
|
2. Check `beforeDevCommand` runs your dev server
|
|
3. Open DevTools (Cmd+Option+I / Ctrl+Shift+I) to check for errors
|
|
|
|
### Command Returns Undefined
|
|
|
|
**Symptoms:** `invoke()` returns undefined instead of expected value
|
|
|
|
**Solution:**
|
|
1. Verify command is in `generate_handler![]`
|
|
2. Check Rust command actually returns a value
|
|
3. Ensure argument names match (camelCase in JS, snake_case in Rust by default)
|
|
|
|
### Mobile Build Failures
|
|
|
|
**Symptoms:** Android/iOS build fails with missing target
|
|
|
|
**Solution:**
|
|
```bash
|
|
# 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
|
|
```
|
|
|
|
## Setup Checklist
|
|
|
|
Before using this skill, verify:
|
|
|
|
- [ ] `npx tauri info` shows correct Tauri v2 versions
|
|
- [ ] `src-tauri/capabilities/default.json` exists with at least `core:default`
|
|
- [ ] All commands registered in `generate_handler![]`
|
|
- [ ] `lib.rs` contains shared code (for mobile support)
|
|
- [ ] Required Rust targets installed for target platforms
|