integrating-tauri-rust-frontends

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Tauri Rust/WASM Frontend Integration

Tauri Rust/WASM 前端集成

This skill covers integrating Rust-based frontend frameworks with Tauri v2 for building desktop and mobile applications with WASM.
本内容介绍如何将基于Rust的前端框架与Tauri v2集成,以构建基于WASM的桌面和移动应用。

Supported Frameworks

支持的框架

FrameworkDescriptionBundler
LeptosReactive Rust framework for building web UIsTrunk
YewComponent-based Rust frameworkTrunk
DioxusCross-platform UI frameworkTrunk
SycamoreReactive library for RustTrunk
All Rust/WASM frontends use Trunk as the bundler/dev server.
框架描述打包工具
Leptos用于构建Web UI的响应式Rust框架Trunk
Yew基于组件的Rust框架Trunk
Dioxus跨平台UI框架Trunk
SycamoreRust响应式库Trunk
所有Rust/WASM前端均使用 Trunk 作为打包工具/开发服务器。

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.
  1. 仅支持静态站点生成(SSG) - Tauri不支持基于服务器的方案(SSR)。请使用SSG、SPA或MPA方式。
  2. withGlobalTauri - 必须启用该配置,WASM前端才能通过
    window.__TAURI__
    wasm-bindgen
    访问Tauri API。
  3. WebSocket协议 - 移动端开发热重载需配置
    ws_protocol = "ws"

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)
my-tauri-app/
├── src/
│   ├── main.rs          # Rust前端入口文件
│   └── app.rs           # 应用组件
├── src-tauri/
│   ├── src/
│   │   └── main.rs      # Tauri后端
│   ├── Cargo.toml       # Tauri依赖
│   └── tauri.conf.json  # Tauri配置
├── index.html           # Trunk的HTML入口文件
├── Cargo.toml           # 前端依赖
├── Trunk.toml           # Trunk打包工具配置
└── dist/                # 构建输出(自动生成)

Configuration Files

配置文件

Tauri Configuration (src-tauri/tauri.conf.json)

Tauri配置(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
json
{
  "build": {
    "beforeDevCommand": "trunk serve",
    "devUrl": "http://localhost:1420",
    "beforeBuildCommand": "trunk build",
    "frontendDist": "../dist"
  },
  "app": {
    "withGlobalTauri": true
  }
}
关键设置:
  • beforeDevCommand
    : 在启动Tauri前运行Trunk开发服务器
  • devUrl
    : Trunk提供前端服务的URL(Leptos默认端口1420,纯Trunk默认8080)
  • beforeBuildCommand
    : 打包前构建WASM包
  • frontendDist
    : 已构建前端资源的路径
  • withGlobalTauri
    : WASM必选配置 - 暴露
    window.__TAURI__
    以访问API

Trunk Configuration (Trunk.toml)

Trunk配置(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
toml
[build]
target = "./index.html"
dist = "./dist"

[watch]
ignore = ["./src-tauri"]

[serve]
port = 1420
open = false

[serve.ws]
ws_protocol = "ws"
关键设置:
  • target
    : 包含Trunk指令的HTML入口文件
  • ignore
    : 避免监听Tauri后端的变更
  • port
    : 必须与tauri.conf.json中的
    devUrl
    端口匹配
  • open = false
    : 阻止浏览器自动打开(由Tauri负责显示)
  • ws_protocol = "ws"
    : 移动端热重载必选配置

Frontend Cargo.toml (Root)

前端Cargo.toml(根目录)

toml
[package]
name = "my-app-frontend"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
toml
[package]
name = "my-app-frontend"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]

Core WASM dependencies

核心WASM依赖

wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" js-sys = "0.3" web-sys = { version = "0.3", features = ["Window", "Document"] }
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

用于WASM的Tauri API绑定

tauri-wasm = { version = "2", features = ["all"] }
tauri-wasm = { version = "2", features = ["all"] }

Choose your framework:

选择你的框架:

For Leptos:

对于Leptos:

leptos = { version = "0.6", features = ["csr"] }
leptos = { version = "0.6", features = ["csr"] }

For Yew:

对于Yew:

yew = { version = "0.21", features = ["csr"] }

yew = { version = "0.21", features = ["csr"] }

For Dioxus:

对于Dioxus:

dioxus = { version = "0.5", features = ["web"] }

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
[profile.release] opt-level = "z" lto = true codegen-units = 1 panic = "abort"

**关键设置:**
- `crate-type = ["cdylib", "rlib"]`: WASM编译必选配置
- `tauri-wasm`: 提供Tauri API的Rust绑定
- `features = ["csr"]`: 框架的客户端渲染模式
- Release配置针对WASM二进制大小优化

HTML Entry Point (index.html)

HTML入口文件(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
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指令:
  • data-trunk rel="css"
    : 引入CSS文件
  • data-trunk rel="rust"
    : 将Rust crate编译为WASM
  • data-wasm-opt="z"
    : 针对大小进行优化

Leptos Setup

Leptos 设置

Leptos-Specific Cargo.toml

Leptos专属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
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)

Leptos主入口(src/main.rs)

rust
use leptos::*;

mod app;
use app::App;

fn main() {
    console_error_panic_hook::set_once();
    mount_to_body(|| view! { <App /> });
}
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)

Leptos应用组件(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>
    }
}
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

替代方案:使用tauri-wasm库

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>
    }
}
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

Tauri 后端命令

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");
}
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
undefined
bash
undefined

Install Trunk

安装Trunk

cargo install trunk
cargo install trunk

Add WASM target

添加WASM目标

rustup target add wasm32-unknown-unknown
rustup target add wasm32-unknown-unknown

Development (runs Trunk + Tauri)

开发模式(运行Trunk + Tauri)

cd src-tauri && cargo tauri dev
cd src-tauri && cargo tauri dev

Build for production

生产构建

cd src-tauri && cargo tauri build
cd src-tauri && cargo tauri build

Trunk only (for frontend debugging)

仅启动Trunk(用于前端调试)

trunk serve --port 1420
trunk serve --port 1420

Build WASM only

仅构建WASM

trunk build --release
undefined
trunk build --release
undefined

Mobile Development

移动端开发

For mobile platforms, additional configuration is needed:
针对移动平台,需要额外配置:

Trunk.toml for Mobile

移动端Trunk.toml

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
toml
[serve]
port = 1420
open = false
address = "0.0.0.0"  # 监听所有接口以支持移动端访问

[serve.ws]
ws_protocol = "ws"   # 移动端热重载必选配置

tauri.conf.json for Mobile

移动端tauri.conf.json

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
).
json
{
  "build": {
    "beforeDevCommand": "trunk serve --address 0.0.0.0",
    "devUrl": "http://YOUR_LOCAL_IP:1420"
  }
}
YOUR_LOCAL_IP
替换为你的机器本地IP(例如:
192.168.1.100
)。

Accessing Tauri APIs from WASM

从WASM访问Tauri API

Method 1: Direct wasm-bindgen (Recommended for control)

方法1:直接使用wasm-bindgen(推荐用于精细控制)

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())
}
rust
use wasm_bindgen::prelude::*;
use serde::{Serialize, Deserialize};

#[wasm_bindgen]
extern "C" {
    // 核心invoke方法
    #[wasm_bindgen(js_namespace = ["window", "__TAURI__", "core"], catch)]
    async fn invoke(cmd: &str, args: JsValue) -> Result<JsValue, JsValue>;

    // 事件系统
    #[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);
}

// 使用示例
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

方法2:使用tauri-wasm库

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?;
rust
use tauri_wasm::api::{core, event, dialog, fs};

// 调用命令
let result: MyResponse = core::invoke("my_command", &my_args).await?;

// 监听事件
event::listen("my-event", |payload| {
    // 处理事件
}).await;

// 发送事件
event::emit("my-event", &payload).await;

// 文件对话框
let file = dialog::open(dialog::OpenDialogOptions::default()).await?;

// 文件系统(需要权限)
let contents = fs::read_text_file("path/to/file").await?;

Troubleshooting

故障排除

WASM not loading

WASM无法加载

  • Verify
    withGlobalTauri: true
    in tauri.conf.json
  • Check browser console for WASM errors
  • Ensure
    wasm32-unknown-unknown
    target is installed
  • 验证tauri.conf.json中
    withGlobalTauri: true
    是否启用
  • 检查浏览器控制台的WASM错误信息
  • 确保已安装
    wasm32-unknown-unknown
    目标

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
  • 在Trunk.toml中设置
    ws_protocol = "ws"
  • 使用
    address = "0.0.0.0"
    以支持移动端访问
  • 验证防火墙允许开发端口的连接

Tauri APIs undefined

Tauri API未定义

  • withGlobalTauri
    must be
    true
  • Check
    window.__TAURI__
    exists in browser console
  • Verify tauri-wasm version matches Tauri version
  • 必须启用
    withGlobalTauri
  • 在浏览器控制台检查
    window.__TAURI__
    是否存在
  • 确保tauri-wasm版本与Tauri版本匹配

Large WASM binary size

WASM二进制体积过大

  • Enable release profile optimizations
  • Use
    opt-level = "z"
    for size optimization
  • Enable LTO with
    lto = true
  • Consider
    wasm-opt
    post-processing
  • 启用Release配置的优化选项
  • 使用
    opt-level = "z"
    进行体积优化
  • 启用LTO(
    lto = true
  • 考虑使用
    wasm-opt
    进行后处理

Trunk build fails

Trunk构建失败

  • Check Cargo.toml has
    crate-type = ["cdylib", "rlib"]
  • Verify index.html has correct
    data-trunk
    directives
  • Ensure no server-side features enabled in framework
  • 检查Cargo.toml是否包含
    crate-type = ["cdylib", "rlib"]
  • 验证index.html是否有正确的
    data-trunk
    指令
  • 确保框架未启用服务器端特性

Version Compatibility

版本兼容性

ComponentVersion
Tauri2.x
Trunk0.17+
Leptos0.6+
wasm-bindgen0.2.x
tauri-wasm2.x
Always match
tauri-wasm
version with your Tauri version.
组件版本
Tauri2.x
Trunk0.17+
Leptos0.6+
wasm-bindgen0.2.x
tauri-wasm2.x
请始终保持
tauri-wasm
版本与Tauri版本一致。