首页
/ Dioxus Desktop 渲染器详解:用 Rust 与系统 WebView 构建跨平台桌面应用

Dioxus Desktop 渲染器详解:用 Rust 与系统 WebView 构建跨平台桌面应用

2026-09-08 12:00:12作者:裘晴惠Vivianne

dioxus-desktop 是 Dioxus 官方仓库中负责桌面的渲染器:它把 Dioxus 的 VirtualDom 渲染进平台自带的 WebView 中,让开发者用一套 Rust 代码同时产出 Windows、macOS、Linux 桌面应用,包体通常小于 5MB 且内存占用可控。本文以其源码文档 packages/desktop/src/readme.md 为主线,结合 packages/desktop 下真实模块与仓库内示例,带你走通从项目搭建、窗口配置到菜单、托盘、全局快捷键等桌面专属能力,并理解它基于 Tauri(wry + tao)的底层工作原理。

一、Dioxus Desktop 是什么

根据 packages/desktop/src/readme.md 中的定位,Dioxus Desktop Renderer 的作用是:

使用平台原生的 WebView 实现来渲染 Dioxus VirtualDom。

也就是说,你的 Dioxus 组件树并不是被编译成原生控件,而是运行在一个隐藏的 WebView 页面中,由 Dioxus 的 diff 算法把界面变更同步到 Web 内容上。这样做的好处在于:

  • 跨平台观感一致:同一套 HTML/CSS 布局在三大桌面系统上呈现一致;
  • 体量小:readme 明确说明 Dioxus 桌面应用“typically <5mb”,并且“use existing system resources”,不会占用过多内存;
  • 复用 Web 生态:CSS、SVG、Canvas、WebGL 等能力直接可用。

文档同时指出一个重要事实:Dioxus Desktop 构建于 Tauri 之上(具体是 wrytao 两个底层 crate),并在 packages/desktop/Cargo.toml 中得到印证——wry(WebView 抽象)与 tao(窗口/事件循环)是核心依赖,此外还直接依赖 muda(菜单)、tray-icon(托盘)、global-hotkey(全局快捷键)、rfd(原生文件对话框)等 Tauri 生态 crate。

1.1 需要注意:文档本身即 crate 文档

packages/desktop/src/lib.rs 的第一行可以看到 #![doc = include_str!("readme.md")]——这篇 readme 被直接作为 dioxus-desktop crate 的文档首页嵌入。因此在 docs.rs 上看到的 crate 简介、这里的仓库 readme,本质是同一份内容,本文所有引用都以仓库为准。

二、体系架构与代码布局

Dioxus 仓库采用 workspace 组织,桌面相关能力主要落在以下 crate:

路径 作用
packages/desktop 本文主角:WebView 桌面渲染器 dioxus-desktop
packages/dioxus 统一门面 crate,通过 desktop feature 重新导出桌面能力
packages/html 元素/事件定义,桌面渲染同样复用
packages/cli dx 命令行,负责桌面应用的构建、打包

packages/dioxus/src/lib.rs 中可以看到 feature 映射:

#[cfg(feature = "desktop")]
pub use dioxus_desktop as desktop;

即启用 desktop feature 后,dioxus::desktop 就是 dioxus_desktop 的别名。更有意思的是,同一个 crate 还被映射成移动端渲染器:

#[cfg(feature = "mobile")]
pub use dioxus_desktop as mobile;

结合 packages/desktop/Cargo.toml 中针对 ios/androidobjc2jnindk 等条件依赖,可以看出:桌面与 iOS/Android 共用了同一套 WebView 渲染内核,这也是 Dioxus“一套代码多端运行”的根基。

2.1 内部模块一览

packages/desktop/src/lib.rs 声明了内部模块,反映其职责划分:

  • launch.rs:启动入口(含裸 VirtualDom 启动);
  • config.rs:应用级 Config 与窗口级 WindowConfig 的构建器;
  • window_component.rsWindow 组件(支持多窗口);
  • desktop_context.rs / desktop_state.rs:窗口句柄与应用级状态上下文;
  • menubar.rstrayicon.rsshortcut.rs:菜单、托盘、全局快捷键;
  • ipc.rs:WebView 与 Rust 侧的消息通道;
  • protocol.rsassets.rs:自定义协议与资源加载;
  • hooks.rs:面向组件作者的桌面 Hook(use_windowuse_global_shortcut 等)。

lib.rs 还对外公开了大量可直接使用的类型,例如 ConfigWindowConfigWindowCloseBehaviourWindow/WindowPropsDesktopContextdefault_icon()icon_from_memory()icon_from_path() 等,并透出 taowrymuda 以便在需要时直接操纵底层窗口。

三、快速开始:从空项目到弹出一个窗口

readme 给出的起步步骤非常简单,前提是先装好 Rust 与 Cargo。创建一个新二进制项目并添加依赖:

cargo new --bin demo
cargo add dioxus
cargo add dioxus-desktop

3.1 最小可运行代码

readme 中的最小示例(出自 packages/desktop/src/readme.md)如下:

// main.rs
use dioxus::prelude::*;

fn main() {
    dioxus_desktop::launch(app);
}

fn app() -> Element {
    rsx! {
        div {
            "hello world!"
        }
    }
}

需要说明的是:这段代码来自该文档写作时的 API。在当前仓库中,启动 API 已演进为 统一平台启动器 的形式——examples/08-apis/shortcut.rs 给出了当前推荐的写法:

fn main() {
    dioxus::LaunchBuilder::desktop().launch(app);
}

如果你更习惯直接使用门面 crate,examples/07-fullstack/desktop/src/main.rs 展示了仅调用 dioxus::launch(app) 即可按当前平台特性启动的方式。

3.2 运行方式

桌面应用无需 Web 服务器,直接在本地编译运行:

cargo run

需要注意的是底层对 WebView 的系统依赖:

  • macOS / iOS:系统自带 WebView,开箱即用;
  • Windows:依赖 WebView2 运行时,通常随系统或自动安装,但不一定预装;
  • Linux:依赖 WebKitGTK 等,需要按发行版安装对应系统包。

(详细平台准备步骤见 packages/desktop/README.md 中指向官方指南的说明。)

四、深入启动链路:launch 到底做了什么

如果只看 launch(app) 一行代码,很难想象背后发生了什么。阅读 packages/desktop/src/launch.rs 可以看到完整链路:

1. launch(root, contexts, platform_config)launch.rs:从 platform_config 中取出并 downcastConfig;若未配置“无头根组件”,则把你传入的根组件用 WindowedRoot 包装——也就是自动套上一层 Window 组件,再把窗口级配置注入其中。这就是为什么你只写组件、不需要手动建窗口也能弹窗。

2. launch_virtual_dom(virtual_dom, config)launch.rs:负责创建 tokio 多线程运行时(tokio_runtime feature 开启时),并断言不能使用 current-thread 运行时,随后进入阻塞式启动。注意:即便不启用 tokio,也会走 launch_virtual_dom_blocking 兼容路径。

3. launch_virtual_dom_blockinglaunch.rs:构建 tao 事件循环并派发各类事件——WindowEvent::Resized 触发 resize_windowUserEvent::NewWindow 处理新窗口、Shutdown 退出循环,Windows 专属的拖拽修复、IPC 消息等也都在此分发。它会 阻塞主线程,因此必须从主线程启动(GUI 框架的普遍约束)。

这套分层意味着:launch 系列是你与系统事件循环之间的桥梁,而你在组件里写的一切 rsx! 都会被送到 WebView 中渲染。

4.1 底层渲染:VirtualDom + interpreter

Desktop 渲染并非逐像素自绘,而是依赖 dioxus-interpreter-js(见 packages/desktop/Cargo.toml)在 WebView 里执行 JS 解释器,接收 Rust 侧序列化后的 mutations。你可以把这一层理解为“DOM 补丁流”:Rust 计算差异,JS 侧应用差异,两端通过 wry 的 IPC/协议通道通信(ipc.rs)。

五、窗口与应用级配置:ConfigWindowConfig

readme 提到:“要配置 webview、菜单栏等桌面特性,请查看 API reference 中的 launch 配置”。在仓库源码中,这份配置就是 packages/desktop/src/config.rs 里两个构建器:

  • WindowConfig:单个窗口的配置(标题、大小、菜单、背景色、自定义协议等);
  • Config:应用级配置,内部内嵌一个 WindowConfig(即 launch 自动创建的那个窗口),同时管理事件循环、退出策略、托盘行为等。

两者都实现了 Default,也实现了 LaunchConfig(见 config.rs),因此可以直接喂给 LaunchBuilder::desktop().with_cfg(...)(见 packages/dioxus/src/launch.rs)。

5.1 常用 WindowConfig 配置项

以下方法均定义于 config.rs

方法 作用 源码说明
with_window(WindowBuilder) 传入 tao 窗口构建器 控制大小、标题、装饰等;当 decorations 关闭时会顺带移除菜单栏
with_title(通过 WindowBuilder) 设置窗口标题 默认取 Dioxus.toml 的 app 标题,否则为 "Dioxus App"
with_background_color((r,g,b,a)) 设置 WebView 背景色(RGBA) 在 HTML 渲染前生效,可避免页面加载白闪
with_menu(DioxusMenu) 自定义菜单栏 基于 muda 构建;未设置时使用默认菜单(default_menu_bar
with_disable_context_menu(bool) 禁用右键上下文菜单 release 构建默认禁用,debug 默认启用
with_close_behaviour(WindowCloseBehaviour) 设置窗口关闭行为 WindowCloses 真关闭 / WindowHides 仅隐藏
with_custom_protocol(name, handler) 注册同步自定义协议 处理形如 name:// 的请求
with_asynchronous_custom_protocol(name, handler) 注册异步自定义协议 返回一个 RequestAsyncResponder,可在异步任务中延迟应答
with_custom_head(String) 向文档 <head> 注入内容 适合加载 CSS/JS 库
with_custom_index(String) 替换默认 index.html 要求是合法 HTML 且含 <body>,Dioxus 会注入加载器代码
with_root_name(String) 指定 Dioxus 挂载的根元素名 类似 React 的 render() 目标
with_resource_directory(Path) 设置 release 模式资源目录 供打包后查找静态资源
with_data_directory(Path) 设置 WebView2 用户数据目录 Windows 上默认为 %LOCALAPPDATA%/<exe名>,避免写入 Program Files 等只读位置
with_icon(Icon) 设置应用/窗口图标 另有 default_icon()icon_from_path() 辅助函数
with_on_window(cb) 窗口构建后、webview 创建前回调 可在此调整窗口与 VirtualDom,如子窗口纹理 z-order

5.2 应用级 Config 常用项

Configconfig.rs)把大部分窗口配置以“转发”方式开放(如 with_window_configwith_windowwith_menu…),并额外管理:

  • with_exits_when_last_window_closes(bool):默认 true,最后一个窗口关闭即退出;
  • with_event_loop(EventLoop):注入自定义事件循环;
  • with_custom_event_handler(cb):在事件循环收到事件时插入自己的处理逻辑;
  • with_headless_root(bool):若为 true,则 launch 不再自动包一层默认窗口,根组件需要自行渲染 Window 组件(多窗口场景常用);
  • with_disable_dma_buf_on_wayland(bool):默认 true,规避部分 Linux/Wayland 系统上 DMA-BUF 导致的问题;
  • with_tray_icon_show_window_on_click(bool):控制点击托盘图标时是否同时显示并聚焦主窗口(托盘常驻类应用可设为 false)。

5.3 组合示例:带标题、菜单与背景色的窗口

把上面这些连起来,一个可运行的应用骨架大致如下(API 形态以当前仓库为准):

use dioxus::desktop::{Config, WindowCloseBehaviour, WindowConfig};
use dioxus::prelude::*;

fn main() {
    // 注意:实际传递方式取决于所用启动器,
    // 这里的 Config 实现了 LaunchConfig,可被桌面平台启动器消费
    let window = WindowConfig::new()
        .with_window(tao::window::WindowBuilder::new().with_title("My App").with_inner_size(LogicalSize::new(1024.0, 768.0)))
        .with_background_color((30, 30, 30, 255))
        .with_close_behaviour(WindowCloseBehaviour::WindowCloses);

    let config = Config::new().with_window_config(window);

    // ... 交由 dioxus::LaunchBuilder::desktop() 等入口消费 config
}

fn app() -> Element {
    rsx! { div { "hello from dioxus-desktop" } }
}

说明:不同版本的 Dioxus 在“如何把 Config 传入启动器”上 API 略有差异,实际项目中请优先查看当前使用版本的源码与文档。

六、桌面专属能力:Hook、多窗口与系统集成

readme 指出早期版本“没有对菜单栏、托盘等的 Dioxus 抽象,需要直接用 wry/tao”,并预告后续会补齐 notifications、global shortcuts、menubar 等能力。在当前的仓库版本中,这些抽象已经落地

6.1 核心 Hook(hooks.rs

Hook 用途 定义位置
use_window() 获取当前窗口的命令式句柄 DesktopContext hooks.rs
use_app() 获取应用级句柄 Rc<DesktopAppContext> hooks.rs
use_wry_event_handler(cb) 在组件内订阅 wry 事件,自动随组件清理 hooks.rs
use_muda_event_handler(cb) 订阅菜单事件 hooks.rs
use_tray_menu_event_handler(cb) 订阅托盘菜单事件 hooks.rs
use_tray_icon_event_handler(cb) 订阅托盘图标事件 hooks.rs
use_asset_handler(name, cb) 自定义资源加载回调,返回 None 回落到默认行为 hooks.rs
use_global_shortcut(accel, cb) 注册全局快捷键,应用失焦也能触发 hooks.rs

这些 Hook 均利用 use_hook_with_cleanup 在组件卸载时自动注销(快捷键 remove、事件处理器 remove),由 Rust 侧统一管理生命周期,不必手动清理。

6.2 多窗口与桌面 API 示例库

仓库 examples/08-apis 下几乎每一个示例都是桌面能力的一线演示:

shortcut.rs 的全局快捷键示例:

fn app() -> Element {
    let mut toggled = use_signal(|| false);

    _ = use_global_shortcut("ctrl+s", move |state| {
        if state == HotKeyState::Pressed {
            toggled.toggle();
        }
    });

    rsx!("toggle: {toggled}")
}

6.3 多窗口的组件化表达

前文提到 launch_virtual_dom 的文档注释(launch.rs)展示了一种更“Dioxus 化”的写法——不依赖 launch 的隐式默认窗口,而是直接在组件树里渲染 Window 组件:

use dioxus::prelude::*;
use dioxus::desktop::{Window, Config};

fn app() -> Element {
    rsx! {
        Window {
            div { "hello from a manually owned window" }
        }
    }
}

配合 Config::with_headless_root(true),根组件就完全接管了窗口的创建,WindowProps 中可传 config: WindowConfig,从而在每个 Window 上独立配置窗口参数。这正是多窗口桌面应用(如主窗口 + 设置窗口 + 无边框弹层)的标准做法。

七、Cargo feature 详解:按需裁剪桌面能力

dioxus-desktop 的 Cargo.toml 定义了如下 feature:

Feature 默认 说明
tokio_runtime 自带 tokio 多线程运行时;关闭后可用外部运行时或阻塞模式
transparent 透明窗口支持(透传 wry/transparent)
devtools 启用 devtools + dioxus-devtools 调试集成,并依赖 dioxus-signals
fullscreen 全屏窗口能力(wry/fullscreen)
gnu 面向 GNU 目标的辅助 feature
linux-libxdo Linux 上链接 libxdo.so,仅供预置的 Copy/Cut/Paste/SelectAll 菜单项使用;按需开启,避免无关应用被绑定到构建机的 libxdo

关于 linux-libxdo 的注释(Cargo.toml)是一个值得留意的工程细节:Dioxus 默认菜单含有复制粘贴等项,但它们仅在使用时才需要 X11 的 libxdo,因此默认不开启该依赖。若你的 Linux 应用用不到这些菜单项,就不必开启它。

在门面 crate 侧,还要注意 packages/dioxus/src/lib.rs 中关于 desktopmobileweb 等“平台特性”的说明:平台特性决定 launch() 将运行在哪类渲染器上,桌面开发只需:

dioxus = { version = "...", features = ["desktop"] }

7.1 热重载与调试

启用 devtools feature 且在 debug 构建下,事件循环会处理 UserWindowEvent::HotReloadMsg(见 launch.rs),配合 Dioxus CLI 的 hot-reload 能力,可在桌面开发中实现 rsx 的热更新,大幅缩短调试循环。

八、无头测试与质量保障

桌面 GUI 代码通常难以自动化测试,Dioxus 采用了一种务实的方案:headless 测试packages/desktop/Cargo.toml 声明了多个 harness = false 的测试目标,因为“这些测试需要在主线程运行,无法使用 Rust 标准测试框架”。仓库的 headless_tests 目录包含 events.rsrendering.rsforms.rseval.rsmultiwindow.rs 等用例,分别验证:

  • 事件能否正确进入 Dioxus 组件;
  • 渲染/表单行为是否符合预期;
  • eval(在 WebView 中执行 JS)是否正确;
  • 多窗口生命周期是否正常。

这提示我们:当需要为桌面交互逻辑写测试时,可以复用这种 headless + 主线程驱动的模式,而不是依赖真实窗口环境。

九、在仓库中继续探索

本仓库为你提供了“文档 → 源码 → 示例 → 测试”的完整学习闭环:

十、小结

Dioxus Desktop 的核心价值可以概括为三条:

  1. 低成本跨平台:Rust 侧全部使用 Dioxus 声明式 UI,渲染统一交给系统 WebView,应用体积小、观感一致;
  2. Tauri 生态的直接继承:窗口(tao)、WebView(wry)、菜单(muda)、托盘(tray-icon)、快捷键(global-hotkey)、文件对话框(rfd)全部可被 Dioxus 直接调用或透出;
  3. 越来越完善的“桌面抽象”:从 readme 时代“需要手动用 wry/tao”的提示,演进到今天的 use_global_shortcutuse_tray_menu_event_handlerWindow 多窗口组件与 Config/WindowConfig 构建器体系。

如果要在工程中落地,建议按“最小可用 → 配置窗口 → 尝试多窗口与系统集成 → 通过 headless 测试保护逻辑”的顺序推进,并始终以仓库当前源码(尤其 packages/desktop/src)为准,因为桌面 API 仍在快速演进中。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.81 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
920
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.79 K
1.02 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
390