Agent skill

integrating-tauri-rust-frontends

Guides the user through integrating Rust-based WASM frontend frameworks with Tauri v2, covering Leptos and Trunk setup, WASM compilation configuration, Cargo.toml dependencies, Trunk.toml bundler settings, and withGlobalTauri API access.

Stars 12
Forks 1

Install this agent skill to your Project

npx add-skill https://github.com/dchuk/claude-code-tauri-skills/tree/main/tauri/tauri-frontend-rust

SKILL.md

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

  1. Static Site Generation (SSG) Only - Tauri does not support server-based solutions (SSR). Use SSG, SPA, or MPA approaches.
  2. withGlobalTauri - Must be enabled for WASM frontends to access Tauri APIs via window.__TAURI__ and wasm-bindgen.
  3. 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)

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)

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)

toml
[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 = { version = "0.21", features = ["csr"] }
# For Dioxus:
# dioxus = { version = "0.5", features = ["web"] }

[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
<!DOCTYPE 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>
    <div id="app"></div>
    <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

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)

rust
use leptos::*;

mod app;
use app::App;

fn main() {
    console_error_panic_hook::set_once();
    mount_to_body(|| view! { <App /> });
}

Leptos App Component (src/app.rs)

rust
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

rust
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:

rust
#[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

bash
# 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

toml
[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

json
{
  "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)

rust
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<JsValue, JsValue>;

    // 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<String, String> {
    let args = serde_wasm_bindgen::to_value(&serde_json::json!({
        "path": "/some/path"
    })).map_err(|e| e.to_string())?;

    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

rust
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.

Expand your agent's capabilities with these related and highly-rated skills.

dchuk/claude-code-tauri-skills

understanding-tauri-ipc

Teaches the assistant about Tauri IPC (Inter-Process Communication) patterns including brownfield and isolation approaches for secure message passing between frontend and Rust backend.

12 1
Explore
dchuk/claude-code-tauri-skills

testing-tauri-apps

Guides developers through testing Tauri applications including unit testing with mock runtime, mocking Tauri APIs, WebDriver end-to-end testing with Selenium and WebdriverIO, and CI integration with GitHub Actions.

12 1
Explore
dchuk/claude-code-tauri-skills

migrating-tauri-apps

Assists users with migrating Tauri applications from v1 to v2 stable, and from v2 beta to v2 stable, covering breaking changes, configuration updates, API migrations, and plugin system changes.

12 1
Explore
dchuk/claude-code-tauri-skills

developing-tauri-plugins

Guides the user through Tauri plugin development, including creating plugin extensions, configuring permissions, and building mobile plugins for iOS and Android platforms.

12 1
Explore
dchuk/claude-code-tauri-skills

managing-tauri-plugin-permissions

Guides users through configuring Tauri plugin permissions, capabilities, and security. Covers platform-specific capabilities, window-targeted permissions, using official and community plugin permissions, and writing custom plugin permissions with scopes.

12 1
Explore
dchuk/claude-code-tauri-skills

optimizing-tauri-binary-size

Guides users through Tauri binary size optimization techniques to produce small, efficient desktop application bundles using Cargo profile settings and build configurations.

12 1
Explore

Didn't find tool you were looking for?

Be as detailed as possible for better results