mcpField
I
integrating-tauri-rust-frontends

integrating-tauri-rust-frontends

github.com/dchuk开发36241 次安装40 周前更新
代码仓库

关于 integrating-tauri-rust-frontends

面向使用 Leptos、Yew、Dioxus 或 Sycamore 的开发者,指导将 Rust/WASM 前端接入 Tauri v2,涵盖配置、开发和移动端热重载。

主要功能

  1. 01支持 Leptos、Yew、Dioxus 和 Sycamore
  2. 02使用 Trunk 构建与运行前端
  3. 03配置 Tauri 与 WASM API 访问
  4. 04说明移动端热重载设置
  5. 05提供构建故障排查方法

适用场景

  1. 01搭建 Rust/WASM 桌面应用
  2. 02为 Tauri 配置 SPA 或 SSG 前端
  3. 03调试移动端开发热重载
  4. 04排查 WASM 加载与构建问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dchuk/claude-code-tauri-skills --skill integrating-tauri-rust-frontends

安装后在 Claude Code 中输入/integrating-tauri-rust-frontends确认已加载。

详细介绍

Tauri Rust/WASM 前端集成

本技能介绍如何将基于 Rust 的前端框架与 Tauri v2 集成,以使用 WASM 构建桌面和移动应用。

支持的框架

框架描述打包工具
Leptos用于构建 Web UI 的响应式 Rust 框架Trunk
Yew基于组件的 Rust 框架Trunk
Dioxus跨平台 UI 框架Trunk
SycamoreRust 响应式库Trunk

所有 Rust/WASM 前端都使用 Trunk 作为打包工具/开发服务器。

关键要求

  1. 仅限静态站点生成(SSG) - Tauri 不支持基于服务器的解决方案(SSR)。请使用 SSG、SPA 或 MPA 方案。
  2. withGlobalTauri - 必须启用此选项,WASM 前端才能通过 window.__TAURI__ 和 wasm-bindgen 访问 Tauri API。
  3. WebSocket 协议 - 配置 ws_protocol = "ws",以便在移动开发中使用热重载。

项目结构

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)

配置文件

Tauri 配置(src-tauri/tauri.conf.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 配置(Trunk.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":移动端热重载必需
前端 Cargo.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"

关键设置:

  • crate-type = ["cdylib", "rlib"]:WASM 编译必需
  • tauri-wasm:提供用于访问 Tauri API 的 Rust 绑定
  • features = ["csr"]:为框架启用客户端渲染
  • 发布配置针对较小的 WASM 二进制体积进行了优化
HTML 入口点(index.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 设置

Leptos 专用 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 主入口(src/main.rs)
use leptos::*;

mod app;
use app::App;

fn main() {
    console_error_panic_hook::set_once();
    mount_to_body(|| view! { <App /> });
}
Leptos 应用组件(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>
    }
}
替代方案:使用 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 后端命令

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

开发命令

# 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

移动开发

移动平台需要额外配置:

移动端 Trunk.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
{
  "build": {
    "beforeDevCommand": "trunk serve --address 0.0.0.0",
    "devUrl": "http://YOUR_LOCAL_IP:1420"
  }
}

将 YOUR_LOCAL_IP 替换为你机器的本地 IP(例如 192.168.1.100)。

从 WASM 访问 Tauri API

方法 1:直接使用 wasm-bindgen(推荐,便于控制)
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())
}
方法 2:使用 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?;

故障排除

WASM 未加载
  • 确认 tauri.conf.json 中的 withGlobalTauri: true
  • 检查浏览器控制台中的 WASM 错误
  • 确保已安装 wasm32-unknown-unknown 目标
移动端热重载无法运行
  • 在 Trunk.toml 中设置 ws_protocol = "ws"
  • 设置 address = "0.0.0.0" 以便移动设备访问
  • 确认防火墙允许通过开发端口连接
Tauri API 未定义
  • withGlobalTauri 必须为 true
  • 在浏览器控制台中检查 window.__TAURI__ 是否存在
  • 确认 tauri-wasm 版本与 Tauri 版本相匹配
WASM 二进制体积过大
  • 启用发布配置优化
  • 使用 opt-level = "z" 优化体积
  • 使用 lto = true 启用 LTO
  • 考虑使用 wasm-opt 进行后处理
Trunk 构建失败
  • 检查 Cargo.toml 是否包含 crate-type = ["cdylib", "rlib"]
  • 确认 index.html 中的 data-trunk 指令正确
  • 确保未在框架中启用服务端功能

版本兼容性

组件版本
Tauri2.x
Trunk0.17+
Leptos0.6+
wasm-bindgen0.2.x
tauri-wasm2.x

请始终确保 tauri-wasm 版本与 Tauri 版本匹配。

评论

支持 Markdown