Tauri Rust/WASM Frontend Integration
This skill covers integrating Rust-based frontend frameworks with Tauri v2 for building desktop and mobile applications with WASM.
Supported Frameworks Framework Description Bundler Leptos Reactive Rust framework for building web UIs Trunk Yew Component-based Rust framework Trunk Dioxus Cross-platform UI framework Trunk Sycamore Reactive library for Rust Trunk
All Rust/WASM frontends use Trunk as the bundler/dev server.
Critical Requirements Static Site Generation (SSG) Only - Tauri does not support server-based solutions (SSR). Use SSG, SPA, or MPA approaches. withGlobalTauri - Must be enabled for WASM frontends to access Tauri APIs via window.TAURI and wasm-bindgen. WebSocket Protocol - Configure ws_protocol = "ws" for hot-reload on mobile development. Project Structure my-tauri-app/ ├── src/ │ ├── main.rs # Rust frontend entry point │ └── app.rs # Application component ├── src-tauri/ │ ├── src/ │ │ └── main.rs # Tauri backend │ ├── Cargo.toml # Tauri dependencies │ └── tauri.conf.json # Tauri configuration ├── index.html # HTML entry point for Trunk ├── Cargo.toml # Frontend dependencies ├── Trunk.toml # Trunk bundler configuration └── dist/ # Build output (generated)
Configuration Files Tauri Configuration (src-tauri/tauri.conf.json) { "build": { "beforeDevCommand": "trunk serve", "devUrl": "http://localhost:1420", "beforeBuildCommand": "trunk build", "frontendDist": "../dist" }, "app": { "withGlobalTauri": true } }
Key settings:
beforeDevCommand: Runs Trunk dev server before Tauri devUrl: URL where Trunk serves the frontend (default: 1420 for Leptos, 8080 for plain Trunk) beforeBuildCommand: Builds WASM bundle before packaging frontendDist: Path to built frontend assets withGlobalTauri: Required for WASM - Exposes window.TAURI for API access Trunk Configuration (Trunk.toml) [ build ] target = "./index.html" dist = "./dist"
[ watch ] ignore = ["./src-tauri"]
[ serve ] port = 1420 open = false
[ serve.ws ] ws_protocol = "ws"
Key settings:
target: HTML entry point with Trunk directives ignore: Prevents watching Tauri backend changes port: Must match devUrl in tauri.conf.json open = false: Prevents browser auto-open (Tauri handles display) ws_protocol = "ws": Required for mobile hot-reload Frontend Cargo.toml (Root) [ package ] name = "my-app-frontend" version = "0.1.0" edition = "2021"
[ lib ] crate-type = ["cdylib", "rlib"]
[ dependencies ]
Core WASM dependencies
wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" js-sys = "0.3" web-sys = { version = "0.3", features = ["Window", "Document"] }
Tauri API bindings for WASM
tauri-wasm = { version = "2", features = ["all"] }
Choose your framework:
For Leptos:
leptos = { version = "0.6", features = ["csr"] }
For Yew:
yew =
For Dioxus:
dioxus =
[ profile.release ] opt-level = "z" lto = true codegen-units = 1 panic = "abort"
Key settings:
crate-type = ["cdylib", "rlib"]: Required for WASM compilation tauri-wasm: Provides Rust bindings to Tauri APIs features = ["csr"]: Client-side rendering for framework Release profile optimized for small WASM binary size HTML Entry Point (index.html)
<html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>My Tauri App</title> <link data-trunk rel="css" href="styles.css" /> </head> <body> <link data-trunk rel="rust" href="." data-wasm-opt="z" /> </body> </html>Trunk directives:
data-trunk rel="css": Include CSS files data-trunk rel="rust": Compile Rust crate to WASM data-wasm-opt="z": Optimize for size Leptos Setup Leptos-Specific Cargo.toml [ package ] name = "my-leptos-app" version = "0.1.0" edition = "2021"
[ lib ] crate-type = ["cdylib", "rlib"]
[ dependencies ] leptos = { version = "0.6", features = ["csr"] } wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" console_error_panic_hook = "0.1" tauri-wasm = { version = "2", features = ["all"] }
[ profile.release ] opt-level = "z" lto = true
Leptos Main Entry (src/main.rs) use leptos::*;
mod app; use app::App;
fn main() {
console_error_panic_hook::set_once();
mount_to_body(|| view! {
Leptos App Component (src/app.rs) use leptos::; use wasm_bindgen::prelude::; use wasm_bindgen_futures::spawn_local;
[wasm_bindgen]
extern "C" { #[wasm_bindgen(js_namespace = ["window", "TAURI", "core"])] async fn invoke(cmd: &str, args: JsValue) -> JsValue; }
[component]
pub fn App() -> impl IntoView { let (message, set_message) = create_signal(String::new());
let greet = move |_| {
spawn_local(async move {
let args = serde_json::json!({ "name": "World" });
let args_js = serde_wasm_bindgen::to_value(&args).unwrap();
let result = invoke("greet", args_js).await;
let greeting: String = serde_wasm_bindgen::from_value(result).unwrap();
set_message.set(greeting);
});
};
view! {
<main>
<h1>"Welcome to Tauri + Leptos"</h1>
<button on:click=greet>"Greet"</button>
<p>{message}</p>
</main>
}
}
Alternative: Using tauri-wasm Crate use leptos::*; use tauri_wasm::api::core::invoke;
[component]
pub fn App() -> impl IntoView { let (message, set_message) = create_signal(String::new());
let greet = move |_| {
spawn_local(async move {
let result: String = invoke("greet", &serde_json::json!({ "name": "World" }))
.await
.unwrap();
set_message.set(result);
});
};
view! {
<main>
<button on:click=greet>"Greet"</button>
<p>{message}</p>
</main>
}
}
Tauri Backend Command
In src-tauri/src/main.rs:
[tauri::command]
fn greet(name: &str) -> String { format!("Hello, {}! You've been greeted from Rust!", name) }
fn main() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![greet]) .run(tauri::generate_context!()) .expect("error while running tauri application"); }
Development Commands
Install Trunk
cargo install trunk
Add WASM target
rustup target add wasm32-unknown-unknown
Development (runs Trunk + Tauri)
cd src-tauri && cargo tauri dev
Build for production
cd src-tauri && cargo tauri build
Trunk only (for frontend debugging)
trunk serve --port 1420
Build WASM only
trunk build --release
Mobile Development
For mobile platforms, additional configuration is needed:
Trunk.toml for Mobile [ serve ] port = 1420 open = false address = "0.0.0.0" # Listen on all interfaces for mobile
[ serve.ws ] ws_protocol = "ws" # Required for mobile hot-reload
tauri.conf.json for Mobile { "build": { "beforeDevCommand": "trunk serve --address 0.0.0.0", "devUrl": "http://YOUR_LOCAL_IP:1420" } }
Replace YOUR_LOCAL_IP with your machine's local IP (e.g., 192.168.1.100).
Accessing Tauri APIs from WASM Method 1: Direct wasm-bindgen (Recommended for control) use wasm_bindgen::prelude::*; use serde::{Serialize, Deserialize};
[wasm_bindgen]
extern "C" {
// Core invoke
#[wasm_bindgen(js_namespace = ["window", "TAURI", "core"], catch)]
async fn invoke(cmd: &str, args: JsValue) -> Result
// Event system
#[wasm_bindgen(js_namespace = ["window", "__TAURI__", "event"])]
async fn listen(event: &str, handler: &Closure<dyn Fn(JsValue)>) -> JsValue;
#[wasm_bindgen(js_namespace = ["window", "__TAURI__", "event"])]
async fn emit(event: &str, payload: JsValue);
}
// Usage
async fn call_backend() -> Result
let result = invoke("read_file", args)
.await
.map_err(|e| format!("{:?}", e))?;
serde_wasm_bindgen::from_value(result)
.map_err(|e| e.to_string())
}
Method 2: Using tauri-wasm Crate use tauri_wasm::api::{core, event, dialog, fs};
// Invoke command let result: MyResponse = core::invoke("my_command", &my_args).await?;
// Listen to events event::listen("my-event", |payload| { // Handle event }).await;
// Emit events event::emit("my-event", &payload).await;
// File dialogs let file = dialog::open(dialog::OpenDialogOptions::default()).await?;
// File system (requires permissions) let contents = fs::read_text_file("path/to/file").await?;
Troubleshooting WASM not loading Verify withGlobalTauri: true in tauri.conf.json Check browser console for WASM errors Ensure wasm32-unknown-unknown target is installed Hot-reload not working on mobile Set ws_protocol = "ws" in Trunk.toml Use address = "0.0.0.0" for mobile access Verify firewall allows connections on dev port Tauri APIs undefined withGlobalTauri must be true Check window.TAURI exists in browser console Verify tauri-wasm version matches Tauri version Large WASM binary size Enable release profile optimizations Use opt-level = "z" for size optimization Enable LTO with lto = true Consider wasm-opt post-processing Trunk build fails Check Cargo.toml has crate-type = ["cdylib", "rlib"] Verify index.html has correct data-trunk directives Ensure no server-side features enabled in framework Version Compatibility Component Version Tauri 2.x Trunk 0.17+ Leptos 0.6+ wasm-bindgen 0.2.x tauri-wasm 2.x
Always match tauri-wasm version with your Tauri version.