Giao diện
Tauri Framework Desktop GUI
Rust Backend + Web Frontend — Desktop apps nhẹ hơn Electron 10x
Tauri vs Electron
┌─────────────────────────────────────────────────────────────────────┐
│ TAURI vs ELECTRON COMPARISON │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Metric │ Electron │ Tauri │
│ ─────────────────┼──────────────────┼───────────────────────── │
│ Bundle Size │ 150-300MB │ 3-10MB │
│ Memory Usage │ 100-300MB │ 20-50MB │
│ Backend │ Node.js │ Rust (native) │
│ Frontend │ Chromium │ System WebView │
│ Security │ Node + Chrome │ Rust + Sandboxed WebView │
│ Startup Time │ 3-5 seconds │ 0.5-1 second │
│ │
│ Popular Apps: │
│ Electron: VS Code, Discord, Slack, Figma Desktop │
│ Tauri: 1Password, Notion (new), Codeium, Lapce │
└─────────────────────────────────────────────────────────────────────┘Architecture
┌──────────────────────────────────────────────────────────────────┐
│ TAURI APP │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────┐ ┌────────────────────────────┐ │
│ │ RUST BACKEND │ │ WEB FRONTEND │ │
│ │ │ │ │ │
│ │ • Business Logic │◀───▶│ • UI (React/Vue/Svelte) │ │
│ │ • File System Access │ IPC │ • User Interactions │ │
│ │ • Database │ │ • State Management │ │
│ │ • System APIs │ │ • Rendering │ │
│ │ • Crypto/Security │ │ │ │
│ │ │ │ │ │
│ └────────────────────────┘ └────────────────────────────┘ │
│ │ │ │
│ │ │ │
│ ┌────────▼────────────────────────────────▼────────────────┐ │
│ │ SYSTEM WEBVIEW │ │
│ │ (Edge/WebKit - NOT bundled Chromium) │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────┘1. Project Setup
Tạo project mới
bash
# Cài Tauri CLI
cargo install create-tauri-app
# Tạo app (chọn frontend framework)
cargo create-tauri-app
# Hoặc thêm Tauri vào existing frontend project
cd my-react-app
npm install @tauri-apps/cli @tauri-apps/api
npx tauri initCấu trúc thư mục
my-tauri-app/
├── src/ # Frontend (React/Vue/Svelte)
│ ├── App.tsx
│ ├── main.tsx
│ └── ...
├── src-tauri/ # Rust backend
│ ├── Cargo.toml
│ ├── tauri.conf.json # Tauri config
│ ├── src/
│ │ ├── main.rs
│ │ └── lib.rs
│ └── icons/
├── package.json
└── vite.config.ts2. Tauri Commands (IPC)
Command = Rust function callable từ JavaScript.
Định nghĩa Command trong Rust
rust
// src-tauri/src/main.rs
use tauri::Manager;
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}! From Rust 🦀", name)
}
#[tauri::command]
async fn read_file(path: String) -> Result<String, String> {
std::fs::read_to_string(&path)
.map_err(|e| e.to_string())
}
#[tauri::command]
fn get_system_info() -> SystemInfo {
SystemInfo {
os: std::env::consts::OS.to_string(),
arch: std::env::consts::ARCH.to_string(),
cpus: num_cpus::get(),
}
}
#[derive(serde::Serialize)]
struct SystemInfo {
os: String,
arch: String,
cpus: usize,
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
greet,
read_file,
get_system_info,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}Gọi Command từ JavaScript
typescript
// src/App.tsx
import { invoke } from '@tauri-apps/api/core';
function App() {
const [greeting, setGreeting] = useState('');
const [systemInfo, setSystemInfo] = useState<SystemInfo | null>(null);
const handleGreet = async () => {
// Type-safe invocation
const result = await invoke<string>('greet', { name: 'World' });
setGreeting(result);
};
useEffect(() => {
invoke<SystemInfo>('get_system_info').then(setSystemInfo);
}, []);
const handleReadFile = async () => {
try {
const content = await invoke<string>('read_file', {
path: '/path/to/file.txt'
});
console.log(content);
} catch (error) {
console.error('Failed to read file:', error);
}
};
return (
<div>
<button onClick={handleGreet}>Greet</button>
<p>{greeting}</p>
{systemInfo && (
<p>Running on {systemInfo.os} ({systemInfo.arch})</p>
)}
</div>
);
}
interface SystemInfo {
os: string;
arch: string;
cpus: number;
}3. State Management (Rust Side)
Managed State
rust
use std::sync::Mutex;
use tauri::State;
// Application state
struct AppState {
counter: Mutex<i32>,
db: Mutex<Database>,
}
#[tauri::command]
fn increment(state: State<'_, AppState>) -> i32 {
let mut counter = state.counter.lock().unwrap();
*counter += 1;
*counter
}
#[tauri::command]
async fn get_users(state: State<'_, AppState>) -> Result<Vec<User>, String> {
let db = state.db.lock().unwrap();
db.get_all_users().map_err(|e| e.to_string())
}
fn main() {
tauri::Builder::default()
.manage(AppState {
counter: Mutex::new(0),
db: Mutex::new(Database::connect("sqlite:app.db")?),
})
.invoke_handler(tauri::generate_handler![increment, get_users])
.run(tauri::generate_context!())
.expect("error");
}4. Events (Bidirectional Communication)
Emit từ Rust sang JavaScript
rust
use tauri::Manager;
#[tauri::command]
async fn start_long_task(window: tauri::Window) -> Result<(), String> {
for i in 0..100 {
// Emit progress event to frontend
window.emit("progress", ProgressPayload {
percent: i,
message: format!("Processing step {}...", i),
}).unwrap();
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
}
window.emit("completed", ()).unwrap();
Ok(())
}
#[derive(Clone, serde::Serialize)]
struct ProgressPayload {
percent: u32,
message: String,
}Listen trong JavaScript
typescript
import { listen } from '@tauri-apps/api/event';
useEffect(() => {
const unlisten = listen<ProgressPayload>('progress', (event) => {
setProgress(event.payload.percent);
setMessage(event.payload.message);
});
const unlistenComplete = listen('completed', () => {
setProgress(100);
setMessage('Done!');
});
return () => {
unlisten.then(f => f());
unlistenComplete.then(f => f());
};
}, []);5. File System Access
Tauri API (Frontend)
typescript
import { open, save } from '@tauri-apps/plugin-dialog';
import { readTextFile, writeTextFile } from '@tauri-apps/plugin-fs';
async function openFile() {
const selected = await open({
multiple: false,
filters: [{ name: 'Text', extensions: ['txt', 'md'] }]
});
if (selected) {
const content = await readTextFile(selected as string);
return content;
}
}
async function saveFile(content: string) {
const filePath = await save({
filters: [{ name: 'Text', extensions: ['txt'] }]
});
if (filePath) {
await writeTextFile(filePath, content);
}
}Via Rust Command (Recommended for complex operations)
rust
use std::path::PathBuf;
#[tauri::command]
async fn process_files(paths: Vec<PathBuf>) -> Result<ProcessResult, String> {
let mut results = vec![];
for path in paths {
let content = tokio::fs::read_to_string(&path)
.await
.map_err(|e| e.to_string())?;
// Process file...
let processed = process_content(&content);
results.push(processed);
}
Ok(ProcessResult { items: results })
}6. Security Best Practices
Capability-based Security (Tauri v2)
json
// src-tauri/capabilities/main.json
{
"identifier": "main",
"description": "Main window capabilities",
"windows": ["main"],
"permissions": [
"core:default",
"fs:read-files",
"fs:write-files",
"dialog:open",
"dialog:save"
]
}Input Validation
rust
#[tauri::command]
fn save_config(config: UserConfig) -> Result<(), String> {
// ALWAYS validate input from frontend
validate_config(&config)?;
// Sanitize paths
let safe_path = sanitize_path(&config.data_dir)?;
// Save...
Ok(())
}
fn validate_config(config: &UserConfig) -> Result<(), String> {
if config.name.len() > 100 {
return Err("Name too long".into());
}
// More validations...
Ok(())
}7. Building & Distribution
Build for current platform
bash
# Development
npm run tauri dev
# Production build
npm run tauri buildCross-platform builds
yaml
# .github/workflows/release.yml
name: Release
on:
push:
tags: ['v*']
jobs:
release:
strategy:
matrix:
platform: [macos-latest, ubuntu-22.04, windows-latest]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
- uses: dtolnay/rust-toolchain@stable
- uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}Bảng Tóm tắt
| Feature | API/Approach |
|---|---|
| Rust → JS | window.emit(), #[tauri::command] return |
| JS → Rust | invoke(), Events |
| State | State<T> + Mutex |
| File System | @tauri-apps/plugin-fs or Rust commands |
| Dialogs | @tauri-apps/plugin-dialog |
| Security | Capabilities, input validation |