Skip to main content

tauri-app-dev

Expert guidance for building cross-platform desktop applications with Tauri 2.0 and Rust. Use when developing Tauri apps including commands and IPC, file system operations, window management, state management, system tray, menus, plugin development, security configuration (capabilities/permissions), bundling/distribution, and auto-updates. Covers patterns for editor applications requiring file dialogs, native menus, and frontend-backend communication.

소스 정보

저장소
xiaolai/vmark
최근 소스 활동
2026년 2월 18일 11:30
감지된 SKILL.md 언어
영어
스타
803
포크
126

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

파일 탐색기
6 개 파일

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
tauri-app-dev
description
Expert guidance for building cross-platform desktop applications with Tauri 2.0 and Rust. Use when developing Tauri apps including commands and IPC, file system operations, window management, state management, system tray, menus, plugin development, security configuration (capabilities/permissions), bundling/distribution, and auto-updates. Covers patterns for editor applications requiring file dialogs, native menus, and frontend-backend communication.
# Tauri 2.0 App Development Tauri is a framework for building small, fast, secure desktop apps using web frontends and Rust backends. ## Architecture Overview ``` ┌─────────────────────────────────────────┐ │ Frontend (Webview) │ │ HTML/CSS/JS • React/Vue/Svelte │ └────────────────┬────────────────────────┘ │ IPC (invoke/events) ┌────────────────▼────────────────────────┐ │ Tauri Core (Rust) │ │ Commands • State • Plugins • Events │ └────────────────┬────────────────────────┘ │ TAO (windows) + WRY (webview) ┌────────────────▼────────────────────────┐ │ Operating System │ │ macOS • Windows • Linux • Mobile │ └─────────────────────────────────────────┘ ``` ## Project Structure ``` my-app/ ├── src/ # Frontend source ├── src-tauri/ │ ├── Cargo.toml # Rust dependencies │ ├── tauri.conf.json # Tauri configuration │ ├── capabilities/ # Security permissions (v2) │ │ └── default.json │ ├── src/ │ │ ├── main.rs # Desktop entry point │ │ └── lib.rs # Main app logic + mobile entry │ └── icons/ └── package.json ``` ## Commands (Frontend → Rust) Define commands in Rust with `#[tauri::command]`: ```rust // src-tauri/src/lib.rs #[tauri::command] fn greet(name: String) -> String { format!("Hello, {}!", name) } #[tauri::command] async fn read_file(path: String) -> Result<String, String> { std::fs::read_to_string(&path).map_err(|e| e.to_string()) } pub fn run() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![greet, read_file]) .run(tauri::generate_context!()) .expect("error running app"); } ``` Call from frontend (direct): ```typescript import { invoke } from '@tauri-apps/api/core'; const greeting = await invoke<string>('greet', { name: 'World' }); const content = await invoke<string>('read_file', { path: '/tmp/test.txt' }); ``` **Project convention:** Wrap `invoke()` with TanStack Query for caching and state management: ```typescript import { useQuery, useMutation } from '@tanstack/react-query'; import { invoke } from '@tauri-apps/api/core'; // Query (read operations) const { data: content } = useQuery({ queryKey: ['file', path], queryFn: () => invoke<string>('read_file', { path }), }); // Mutation (write operations) const { mutate: saveFile } = useMutation({ mutationFn: (content: string) => invoke('write_file', { path, content }), }); ``` **Key rules:** - Arguments must implement `serde::Deserialize` - Return types must implement `serde::Serialize` - Use `Result<T, E>` for fallible operations - Async commands run on thread pool (non-blocking) - Snake_case in Rust → camelCase in JS arguments ## State Management Share state across commands: ```rust use std::sync::Mutex; use tauri::State; struct AppState { counter: Mutex<i32>, db: Mutex<Option<Database>>, } #[tauri::command] fn increment(state: State<'_, AppState>) -> i32 { let mut counter = state.counter.lock().unwrap(); *counter += 1; *counter } pub fn run() { tauri::Builder::default() .manage(AppState { counter: Mutex::new(0), db: Mutex::new(None), }) .invoke_handler(tauri::generate_handler![increment]) .run(tauri::generate_context!()) .expect("error running app"); } ``` **Access via AppHandle** (for background threads): ```rust use tauri::Manager; #[tauri::command] async fn background_task(app: tauri::AppHandle) { let state = app.state::<AppState>(); // use state... } ``` ## Events (Rust → Frontend) Emit events from Rust: ```rust use tauri::Emitter; #[tauri::command] fn start_process(app: tauri::AppHandle) { std::thread::spawn(move || { for i in 0..100 { app.emit("progress", i).unwrap(); std::thread::sleep(std::time::Duration::from_millis(50)); } app.emit("complete", "Done!").unwrap(); }); } ``` Listen in frontend: ```typescript import { listen } from '@tauri-apps/api/event'; const unlisten = await listen<number>('progress', (event) => { console.log(`Progress: ${event.payload}%`); }); // Clean up when done unlisten(); ``` ## Essential Plugins Install plugins: `cargo add <plugin>` in src-tauri, `pnpm add <package>` in frontend. | Plugin | Cargo Crate | NPM Package | Purpose | |--------|-------------|-------------|---------| | File System | `tauri-plugin-fs` | `@tauri-apps/plugin-fs` | Read/write files | | Dialog | `tauri-plugin-dialog` | `@tauri-apps/plugin-dialog` | Open/save dialogs | | Clipboard | `tauri-plugin-clipboard-manager` | `@tauri-apps/plugin-clipboard-manager` | Copy/paste | | Shell | `tauri-plugin-shell` | `@tauri-apps/plugin-shell` | Run external commands | | Store | `tauri-plugin-store` | `@tauri-apps/plugin-store` | Key-value persistence | | Updater | `tauri-plugin-updater` | `@tauri-apps/plugin-updater` | Auto-updates | Register in Rust: ```rust pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_clipboard_manager::init()) .run(tauri::generate_context!()) .expect("error running app"); } ``` ## Security: Capabilities & Permissions Tauri 2.0 uses capabilities (in `src-tauri/capabilities/`) to control what APIs each window can access. **src-tauri/capabilities/default.json:** ```json { "$schema": "../gen/schemas/desktop-schema.json", "identifier": "main-capability", "windows": ["main"], "permissions": [ "core:default", "fs:default", "fs:allow-read-text-file", "dialog:default", { "identifier": "fs:scope", "allow": [{ "path": "$APPDATA/**" }, { "path": "$DOCUMENT/**" }] } ] } ``` **Scope variables:** `$APPDATA`, `$APPCONFIG`, `$DOCUMENT`, `$DOWNLOAD`, `$HOME`, `$TEMP`, etc. ## File Operations (Editor Pattern) ```typescript import { open, save } from '@tauri-apps/plugin-dialog'; import { readTextFile, writeTextFile } from '@tauri-apps/plugin-fs'; // Open file dialog const path = await open({ filters: [{ name: 'Markdown', extensions: ['md'] }], multiple: false, }); if (path) { const content = await readTextFile(path); // Edit content... await writeTextFile(path, modifiedContent); } // Save as dialog const savePath = await save({ filters: [{ name: 'Markdown', extensions: ['md'] }], defaultPath: 'untitled.md', }); if (savePath) { await writeTextFile(savePath, content); } ``` ## Window Management **Create windows at runtime:** ```rust use tauri::{WebviewUrl, WebviewWindowBuilder}; #[tauri::command] async fn open_settings(app: tauri::AppHandle) -> Result<(), String> { WebviewWindowBuilder::new(&app, "settings", WebviewUrl::App("settings.html".into())) .title("Settings") .inner_size(600.0, 400.0) .build() .map_err(|e| e.to_string())?; Ok(()) } ``` **Configure in tauri.conf.json:** ```json { "app": { "windows": [ { "label": "main", "title": "My App", "width": 1200, "height": 800, "decorations": true, "resizable": true } ] } } ``` ## Custom Titlebar Set `decorations: false` in config, then: ```html <div data-tauri-drag-region class="titlebar"> <span>My App</span> <button id="minimize">−</button> <button id="maximize">□</button> <button id="close">×</button> </div> ``` ```typescript import { getCurrentWindow } from '@tauri-apps/api/window'; const appWindow = getCurrentWindow(); document.getElementById('minimize')?.addEventListener('click', () => appWindow.minimize()); document.getElementById('maximize')?.addEventListener('click', () => appWindow.toggleMaximize()); document.getElementById('close')?.addEventListener('click', () => appWindow.close()); ``` ## Building & Distribution ```bash # Development pnpm tauri dev # Production build pnpm tauri build # Build specific targets pnpm tauri build --target universal-apple-darwin # macOS universal pnpm tauri build --bundles deb,appimage # Linux only pnpm tauri build --bundles nsis # Windows NSIS ``` **Output locations:** - macOS: `target/release/bundle/macos/*.app`, `*.dmg` - Windows: `target/release/bundle/nsis/*-setup.exe`, `msi/*.msi` - Linux: `target/release/bundle/deb/*.deb`, `appimage/*.AppImage` ## Quick Reference | Task | Resource | |------|----------| | Commands, IPC, channels | See [references/commands-and-ipc.md](references/commands-and-ipc.md) | | Plugin usage & development | See [references/plugins.md](references/plugins.md) | | Security configuration | See [references/security.md](references/security.md) | | Bundling & distribution | See [references/bundling.md](references/bundling.md) | | Common app patterns | See [references/patterns.md](references/patterns.md) | ## Test-Driven Development (TDD) **CRITICAL:** Always follow TDD - write tests BEFORE implementation. ### TDD Workflow ``` 1. RED → Write failing test first 2. GREEN → Write minimal code to pass 3. REFACTOR → Clean up, keep tests green ``` ### Testing Stack | Layer | Tool | Purpose | |-------|------|---------| | Rust Unit | `cargo test` | Test commands, business logic | | React Unit | Vitest | Test components, hooks, stores | | Integration | Vitest + MSW | Test frontend with mocked IPC |
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기