Tauri 的架构:探索一个包含 15 个 Crate 的 Rust Monorepo
前置知识
- ›基础 Rust 知识(trait、泛型、模块)
- ›熟悉 Cargo workspace 与 crate 依赖管理
- ›对桌面端 webview 框架的基本认识
Tauri 的架构:探索一个包含 15 个 Crate 的 Rust Monorepo
Tauri 是 Rust 开源生态中颇具野心的项目之一——一个多语言应用框架,让你能够以 Web 前端搭配 Rust 后端的方式构建桌面和移动应用。然而,第一次克隆仓库时,迎接你的是由 15 个 crate、多个 TypeScript 包以及横跨编译期与运行期的构建流水线组成的复杂迷宫。本文将帮你建立清晰的心智模型,从而自如地在其中导航。
Monorepo 总览
仓库的顶层目录结构清晰明了:
| 目录 | 用途 |
|---|---|
crates/ |
所有 Rust crate——核心框架、运行时、CLI、打包器、宏以及辅助工具 |
packages/ |
TypeScript/JavaScript 包——@tauri-apps/api JS API 与基于 NAPI 的 CLI 封装 |
examples/ |
示例应用,包含一个完整的 API 展示项目 |
bench/ |
性能基准测试套件 |
工作区根目录的 Cargo.toml 将一切串联在一起,声明了 15 个成员 crate 以及测试和示例项目:
graph TB
subgraph "Workspace Root"
CT[Cargo.toml]
end
subgraph "crates/"
tauri[tauri]
runtime[tauri-runtime]
wry_rt[tauri-runtime-wry]
macros[tauri-macros]
utils[tauri-utils]
build[tauri-build]
codegen[tauri-codegen]
plugin[tauri-plugin]
cli[tauri-cli]
bundler[tauri-bundler]
sign[tauri-macos-sign]
schema[tauri-schema-generator]
schema_w[tauri-schema-worker]
driver[tauri-driver]
end
subgraph "packages/"
api["@tauri-apps/api"]
cli_napi["@tauri-apps/cli (NAPI)"]
end
CT --> tauri
CT --> cli
CT --> api
工作区设置了 resolver = "2",并要求 Rust 最低版本为 1.77.2。发布构建配置对产物体积进行了激进的优化(opt-level = "s"、启用 LTO、单一代码生成单元)——这正体现了 Tauri 对极小二进制体积的核心承诺。
提示:
Cargo.toml底部的[patch.crates-io]部分将tauri、tauri-plugin和tauri-utils固定到本地路径。这意味着工作区内所有 crate 始终使用本地版本,而非 crates.io 上已发布的版本。在阅读依赖版本号时请牢记这一点。
核心 Crate:tauri
tauri crate 是整个项目的核心,也是应用开发者所依赖的直接入口,它几乎重新导出了开发者所需的一切。crates/tauri/src/lib.rs 中的模块声明揭示了内部的组织方式:
| 模块 | 可见性 | 用途 |
|---|---|---|
app |
pub(crate) |
Builder 模式、App、AppHandle、RunEvent |
ipc |
pub |
IPC 命令、channel、authority |
plugin |
pub |
Plugin trait 与 builder |
webview |
pub |
Webview 与 WebviewWindow 类型 |
window |
pub |
窗口管理 |
event |
私有 | 事件系统 |
manager |
私有 | AppManager——中央调度器 |
protocol |
pub(crate) |
自定义 URI 协议处理器 |
state |
私有 | 基于 TypeId 的状态管理 |
menu |
pub(桌面端) |
菜单 API |
tray |
pub(桌面端 + feature) |
系统托盘 API |
该 crate 定义了三个基础 trait——Manager、Listener 和 Emitter——它们共同构成了公开 API 面。App、AppHandle、Window、Webview 和 WebviewWindow 均实现了这些 trait,从而拥有一致的接口来访问状态、发送事件和管理资源。
classDiagram
class Manager {
+app_handle() AppHandle
+config() Config
+get_webview_window(label) Option~WebviewWindow~
+state~T~() State~T~
}
class Listener {
+listen(event, handler) EventId
+once(event, handler) EventId
+unlisten(id)
}
class Emitter {
+emit(event, payload) Result
+emit_to(target, event, payload) Result
}
class ManagerBase {
<<sealed>>
+manager() AppManager
+runtime() RuntimeOrDispatch
}
Manager --|> ManagerBase : depends on
Listener --|> ManagerBase : depends on
Emitter --|> ManagerBase : depends on
App ..|> Manager
AppHandle ..|> Manager
Window ..|> Manager
Webview ..|> Manager
WebviewWindow ..|> Manager
第 1059 行定义的 sealed trait ManagerBase 体现了一个精妙的设计模式:它要求访问内部的 AppManager,但由于其位于 pub(crate) 模块中,外部代码无法实现它。这意味着 Manager、Listener 和 Emitter 实际上是 sealed 的——你可以使用它们,但永远无法在自己的类型上实现它们。
运行时抽象层
Tauri 将"框架对 webview 运行时的需求"与"WRY/TAO 的具体实现"分离开来,这一设计跨越了两个 crate:
tauri-runtime 定义了抽象 trait。Runtime trait 规定了 dispatcher、handle 和事件循环 proxy 的关联类型,以及创建窗口、webview 和查询显示器的方法。配套的 RuntimeHandle trait 提供了一个满足 Send + Sync + Clone 的 handle,可在任意线程中使用。
tauri-runtime-wry 通过 WRY(webview 渲染)和 TAO(窗口管理)提供具体实现,同时重新导出这两个库并将其桥接到抽象 trait 上。
flowchart TB
subgraph "Application Code"
App["Your App"]
end
subgraph "tauri crate"
Manager["Manager / Builder"]
end
subgraph "tauri-runtime"
RT["Runtime trait"]
RTH["RuntimeHandle trait"]
WD["WindowDispatch trait"]
WVD["WebviewDispatch trait"]
end
subgraph "tauri-runtime-wry"
Wry["Wry struct"]
end
subgraph "External"
WRY["WRY (webview)"]
TAO["TAO (windowing)"]
end
App --> Manager
Manager --> RT
RT -.->|"impl"| Wry
Wry --> WRY
Wry --> TAO
tauri 主 crate 定义了一个类型别名 Wry,对 tauri_runtime_wry::Wry<EventLoopMessage> 进行封装。结合 #[default_runtime] 过程宏,最终用户几乎不需要直接面对泛型参数 R: Runtime——当启用 wry feature 时,它会被自动填入。
编译期 Crate:utils、codegen、macros、build
Tauri 有相当一部分工作发生在编译期,由以下四个 crate 共同构成这条流水线:
flowchart LR
CONFIG["tauri.conf.json"] --> UTILS["tauri-utils<br/>Config 解析、ACL 类型"]
UTILS --> CODEGEN["tauri-codegen<br/>资源嵌入、Context 生成"]
CODEGEN --> MACROS["tauri-macros<br/>#[command], generate_context!, generate_handler!"]
UTILS --> BUILD["tauri-build<br/>build.rs 辅助工具、ACL 解析"]
BUILD --> CODEGEN
MACROS --> APP["编译后的应用"]
BUILD --> APP
tauri-utils 是整个体系的基础——它定义了 Config 结构体(tauri.conf.json 的 Rust 表示)、所有 ACL 类型(能力、权限、作用域)以及共享工具函数。由于它不依赖运行时层,因此可以安全地在构建脚本和过程宏中使用。
tauri-codegen 负责读取配置、发现并压缩前端资源,以及将 Context 结构体生成为 token stream。其中的 get_config 函数处理 TAURI_CONFIG 环境变量的覆盖逻辑——CLI 正是通过这种方式将合并后的配置传递给编译步骤的。
tauri-macros 提供了开发者直接使用的过程宏:用于标注命令函数的 #[command]、用于构建 dispatch 表的 generate_handler!、用于嵌入编译期 context 的 generate_context!,以及用于自动填充泛型运行时参数的 #[default_runtime]。
tauri-build 设计用于在用户的 build.rs 中调用,负责解析 ACL 权限、复制资源文件、生成 Windows manifest,并可选地委托 tauri-codegen 完成资源嵌入。
工具链 Crate:CLI、打包器与签名
面向开发者的工具链由三个 crate 组成:
tauri-cli 驱动 cargo tauri 命令。其 main.rs 会检测当前是作为 cargo-tauri(Cargo 子命令)还是直接调用,剥离多余的 tauri 参数,然后转交给 tauri_cli::run()。基于 Clap 构建的命令结构定义在 lib.rs 中,包含 dev、build、bundle、init、add、android、ios、plugin、icon、signer 等子命令。
tauri-bundler 接收编译好的二进制文件,生成对应平台的安装包——macOS 的 DMG 和 .app bundle、Windows 的 MSI/NSIS 安装程序,以及 Linux 的 DEB/RPM/AppImage。
tauri-macos-sign 负责处理 macOS 代码签名与公证(notarization)。
CLI 还以 NAPI 模块的形式封装在 packages/cli/ 中,以 @tauri-apps/cli 的形式通过 npm 分发。大多数使用 JavaScript 框架的用户正是通过这种方式与 Tauri 交互的。
JS API 包
packages/api/ 目录下的 @tauri-apps/api 包提供了前端代码与 Rust 后端通信所使用的 TypeScript 接口。核心模块(packages/api/src/core.ts)导出了关键的 invoke() 函数:
async function invoke<T>(
cmd: string,
args: InvokeArgs = {},
options?: InvokeOptions
): Promise<T> {
return window.__TAURI_INTERNALS__.invoke(cmd, args, options)
}
该函数将调用委托给 window.__TAURI_INTERNALS__,后者由 Tauri 注入每个 webview 的初始化脚本负责设置。Channel 类(第 77–154 行)支持以保序的方式将数据从 Rust 流式传输到 JavaScript——这一模式被各类插件广泛用于进度上报和实时更新。
sequenceDiagram
participant Frontend as JS Frontend
participant Internals as __TAURI_INTERNALS__
participant Protocol as ipc:// Protocol
participant Rust as Rust Backend
Frontend->>Internals: invoke("greet", {name: "World"})
Internals->>Protocol: HTTP POST with headers
Protocol->>Rust: parse_invoke_request()
Rust->>Rust: ACL check + command dispatch
Rust-->>Protocol: Response
Protocol-->>Frontend: Promise resolves
Crate 依赖关系图
下图展示了各 crate 之间的实际依赖关系。注意编译期 crate(左侧)与运行时 crate(右侧)之间清晰的职责分离:
graph TD
tauri_utils["tauri-utils"]
tauri_runtime["tauri-runtime"]
tauri_runtime_wry["tauri-runtime-wry"]
tauri_codegen["tauri-codegen"]
tauri_macros["tauri-macros"]
tauri_build["tauri-build"]
tauri_plugin["tauri-plugin"]
tauri_main["tauri"]
tauri_cli["tauri-cli"]
tauri_bundler["tauri-bundler"]
tauri_utils --> tauri_runtime
tauri_utils --> tauri_codegen
tauri_utils --> tauri_build
tauri_utils --> tauri_plugin
tauri_utils --> tauri_cli
tauri_utils --> tauri_bundler
tauri_runtime --> tauri_runtime_wry
tauri_runtime --> tauri_main
tauri_runtime_wry --> tauri_main
tauri_codegen --> tauri_macros
tauri_codegen --> tauri_build
tauri_macros --> tauri_main
tauri_build -.->|"build.rs"| tauri_main
tauri_plugin -.->|"build.rs"| tauri_main
tauri_bundler --> tauri_cli
这里的关键在于清晰的分层结构:tauri-utils 处于最底层(无任何 Tauri 特定依赖),tauri-runtime 定义抽象接口,tauri-runtime-wry 提供具体实现,tauri 负责将一切整合在一起。编译期 crate(codegen、macros、build)则在并行轨道上运作,它们消费 tauri-utils 中的类型,但从不依赖运行时层。
提示: 在查找某个功能的代码位置时,可以遵循以下规则:若涉及类型或配置,先看
tauri-utils;若是构建期流程,看tauri-build或tauri-codegen;若是运行时行为,看tauricrate 的内部模块;若与 webview 或窗口创建相关,看tauri-runtime或tauri-runtime-wry。
在下一篇文章中,我们将完整追踪一个 Tauri 应用的生命周期——从编译期通过 generate_context! 宏嵌入资源,到 Builder 模式的初始化过程,再到驱动应用持续运行的事件循环。