-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Description
Tauri + Playwright — Deep Dive Analysis
Overview
Tauri uses the system WebView (WebView2/WebKitGTK) for UI with a Rust backend. Playwright runs as a Node.js sidecar process for browser automation. Ultra-lightweight desktop app with powerful automation.
Architecture
┌──────────────────────────────────────┐
│ Tauri App Shell │
│ ┌────────────────────────────────┐ │
│ │ Rust Backend (Tokio async) │ │
│ │ - Task scheduler │ │
│ │ - Proxy pool manager │ │
│ │ - SQLite via rusqlite │ │
│ │ - IPC command handlers │ │
│ ├────────────────────────────────┤ │
│ │ Node.js Sidecar │ │
│ │ - Playwright engine │ │
│ │ - 100 BrowserContexts │ │
│ │ - Communicates via stdio/IPC │ │
│ ├────────────────────────────────┤ │
│ │ System WebView (UI) │ │
│ │ - Svelte/React dashboard │ │
│ │ - Task monitoring │ │
│ │ - Live preview (1 task) │ │
│ └────────────────────────────────┘ │
└──────────────────────────────────────┘
Key Dependencies
# Cargo.toml
[dependencies]
tauri = { version = "2.x", features = ["shell-sidecar"] }
tokio = { version = "1", features = ["full"] }
rusqlite = "0.32"
serde = { version = "1", features = ["derive"] }
serde_json = "1"// sidecar package.json
{
"playwright-core": "^1.49.x"
}Proxy per Task (via Sidecar)
// Rust side: send task config to Node sidecar
#[tauri::command]
async fn create_task(proxy: ProxyConfig, steps: Vec<Step>) -> Result<TaskId, String> {
let task_id = Uuid::new_v4().to_string();
let msg = serde_json::to_string(&TaskRequest {
id: task_id.clone(),
proxy,
steps,
}).map_err(|e| e.to_string())?;
SIDECAR.send(msg).await.map_err(|e| e.to_string())?;
Ok(task_id)
}// Node.js sidecar: receives task, creates context
async function handleTask(request: TaskRequest) {
const context = await browser.newContext({
proxy: {
server: request.proxy.server,
username: request.proxy.username,
password: request.proxy.password,
},
});
const page = await context.newPage();
await executeSteps(page, request.steps);
await context.close();
}Concurrency Model
// Rust manages concurrency via Tokio
let semaphore = Arc::new(Semaphore::new(100));
for task in tasks {
let permit = semaphore.clone().acquire_owned().await.unwrap();
tokio::spawn(async move {
sidecar.send_task(task).await;
drop(permit);
});
}Strengths
- Tiny bundle: ~10MB (no bundled browser for UI)
- Low RAM for UI: System WebView uses minimal memory
- Rust performance: Tokio async for task scheduling
- Security: Rust memory safety, Tauri's security model
- Built-in updater: Tauri auto-update system
- Playwright features: Full API via sidecar
Weaknesses
- Sidecar complexity: Two runtimes (Rust + Node.js) to manage
- IPC overhead: Cross-process communication adds latency
- Rust learning curve: Backend requires Rust knowledge
- WebView inconsistency: Different rendering on Win/Mac/Linux
- Still needs Playwright binary: ~100MB download on first run
- Debugging: Harder to debug cross-runtime issues
Resource Estimates (100 tasks)
| Resource | Estimate |
|---|---|
| RAM | ~2 GB (Tauri UI: ~50MB, Sidecar+Contexts: ~1.9GB) |
| CPU | 4-8 cores recommended |
| Disk | ~10MB app + ~100MB Playwright browsers |
| Startup | <1s app, ~2s sidecar + browser |
When to Choose This Stack
✅ Bundle size is critical
✅ Team knows Rust
✅ Want native desktop feel with minimal overhead
✅ Need security-first architecture
✅ Building for resource-constrained environments
❌ Avoid if: team doesn't know Rust, need simple single-runtime setup, or need consistent cross-platform rendering
Verdict: 7.05/10
Excellent performance-to-size ratio, but sidecar complexity and Rust requirement raise the bar.
References issue #1 for full comparison
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels