源码地图
主线位置:三条主线经过的代码分布在哪些 crate 里。启动主线从编译期(
tauri-macros→tauri-codegen)一路走到平台层(wry、tao);调用主线和事件主线几乎全在tauri本体的ipc/、webview/、event/和scripts/里,只在收发请求时下到 wry。
flowchart TB app["应用 crate"] --> tauri["tauri 本体"] app -.->|build.rs| tbuild["tauri-build"] tauri --> macros["tauri-macros"] macros --> codegen["tauri-codegen"] tbuild --> codegen codegen --> utils["tauri-utils"] tauri --> rt["tauri-runtime"] tauri --> rtwry["tauri-runtime-wry"] rtwry --> rt rtwry --> wry["wry"] rtwry --> tao["tao"] wry --> wk["WKWebView 等系统 webview"] tao --> win["NSWindow 和事件循环"]
行数是 git show <tag>:<path> | wc -l 数的 .rs 文件行数(含注释和空行,不含 tests/ 目录),只用来比较各块的大小。tauri 工作区按 Cargo.toml 的 members 有 14 个 crate,另有集成测试、bench 和示例;下面按层分组。
编译期:tauri-build、tauri-macros、tauri-codegen
| crate | 版本 | 行数 | 入口 | 做什么 | 讲它的页 |
|---|---|---|---|---|---|
tauri-build | 2.7.4 | 2 477 | src/lib.rs 的 build / try_build | 应用 build.rs 里调用:读配置、检查 feature 和配置是否一致、发 cfg(desktop) / cfg(dev)、生成 ACL 清单和 schema、Windows 资源文件 | tauri-build |
tauri-macros | 2.7.4 | 1 632 | src/lib.rs | 过程宏入口:generate_context!、#[command]、generate_handler!、#[default_runtime] | #[command] 与 generate_handler! |
tauri-codegen | 2.7.4 | 1 770 | src/context.rs 的 context_codegen | generate_context! 的实际工作:内嵌并压缩前端资源(embedded_assets.rs)、CSP 哈希、图标、ACL 解析结果,生成 Context | generate_context! |
tauri-macros 本身很薄,generate_context! 把活交给 tauri-codegen;tauri-build 也依赖 tauri-codegen(codegen feature),用来在构建脚本里预先生成上下文。三者共用 tauri-utils 里的配置和 ACL 类型。
共用的 tauri-utils
tauri-utils 2.10.2,16 259 行,编译期和运行时都用它。几块大的:
| 路径 | 行数 | 内容 | 讲它的页 |
|---|---|---|---|
config.rs | 5 507 | tauri.conf.json 的全部类型(Config、WindowConfig、SecurityConfig、Csp 等) | generate_context! |
config_v1/ | 3 412 | 1.x 配置格式的类型,迁移用 | 不讲 |
acl/ | 3 459 | Permission、Capability、Resolved 等 ACL 的数据结构和编译期解析 | 权限、能力、作用域、编译期解析 |
assets.rs、html.rs、pattern/ | 211、442、178 | EmbeddedAssets 的运行时一半、HTML 里注入 CSP、隔离模式的加解密 | tauri:// 协议、CSP、原型冻结与隔离模式 |
运行时抽象:tauri-runtime 和 tauri-runtime-wry
| crate | 版本 | 行数 | 入口 | 做什么 | 讲它的页 |
|---|---|---|---|---|---|
tauri-runtime | 2.12.2 | 2 893 | src/lib.rs 的 Runtime、RuntimeHandle、WindowDispatch、WebviewDispatch trait;webview.rs 的 PendingWebview;window.rs 的 PendingWindow | 一组 trait 和「待建」的窗口 / webview 描述,tauri 本体只对这组 trait 编程 | Runtime trait |
tauri-runtime-wry | 2.12.3 | 6 783 | src/lib.rs(5 416 行) | 用 wry 和 tao 实现上面的 trait:Wry 持有 tao 的 EventLoop;从其他线程来的操作变成 Message 经 EventLoopProxy 送回主线程;create_window / create_webview 调 wry 的 WebViewBuilder | tauri-runtime-wry |
tauri 的 test feature 里还有第二个实现 MockRuntime(crates/tauri/src/test/mock_runtime.rs),不开窗口,单元测试用。
平台层:wry 和 tao
两个独立的仓库,tauri-runtime-wry 是它们在 tauri 里唯一的使用者。
| crate | 版本 | 路径 | 行数 | 内容 |
|---|---|---|---|---|
| wry | 0.57.1 | src/lib.rs | 2 637 | WebViewBuilder、WebView、跨平台的属性 |
src/wkwebview/ | 4 113 | macOS / iOS:WKWebView、url_scheme_handler.rs(自定义协议)、class/ 下的各种 delegate | ||
src/webview2/ | 2 390 | Windows:WebView2(只读源码) | ||
src/webkitgtk/ | 2 109 | Linux:WebKitGTK(只读源码) | ||
src/android/ | 1 756 | Android,不讲 | ||
| tao | 0.37.2 | src/event_loop.rs、window.rs、event.rs | 416、1 706、982 | 跨平台的事件循环、窗口、事件类型;从 winit 分叉而来 |
src/platform_impl/macos/ | 6 997 | app_delegate.rs、app_state.rs(applicationDidFinishLaunching 里发出 StartCause::Init)、window.rs | ||
src/platform_impl/windows/、linux/ | 9 847、3 896 | 只读源码 |
讲它们的页:wry 在 macOS 上、tao。dioxus-desktop 也建在这两个库上(dioxus 0.7.10 锁的是 wry 0.53.5、tao 0.34.8),对照见设计取舍。
本体:tauri
tauri 2.12.5,35 601 行。按目录:
| 路径 | 行数 | 内容 | 主线 | 讲它的页 |
|---|---|---|---|---|
app.rs、app/ | 2 833、158 | Builder、App、AppHandle、RunEvent;build、run、setup | 启动 | Builder 与 App |
manager/ | 2 317 | AppManager:配置、资源、插件、状态、监听器、invoke key;webview.rs 的 prepare_pending_webview | 启动、事件 | AppManager |
webview/ | 5 977 | Webview、WebviewBuilder、WebviewWindow;on_message | 启动、调用 | 窗口与 webview、分发 |
window/ | 2 971 | Window、WindowBuilder、核心插件 window | 启动 | 窗口与 webview |
ipc/ | 4 466 | protocol.rs(ipc:// 协议和 postMessage 两条入口)、mod.rs(InvokeResolver、InvokeBody)、command.rs(CommandArg)、authority.rs(RuntimeAuthority)、channel.rs、format_callback.rs | 调用 | ipc 协议、参数与 InvokeResolver、Channel |
protocol/ | 770 | tauri.rs(tauri://)、asset.rs(asset://)、isolation.rs | 启动 | tauri:// 协议、asset:// 协议 |
event/ | 935 | Listeners、emit_js_script、event_initialization_script、核心插件 event | 事件 | AppManager |
plugin.rs、plugin/ | 1 062、520 | Plugin trait、Builder、PluginStore | 启动、调用 | 插件 |
state.rs、async_runtime.rs | 291、363 | manage / State<T>;全局 tokio 运行时 | 调用 | 状态、async_runtime |
scope/、path/、resources/、image/、process.rs | 819、1 618、254、638、159 | 文件系统作用域、路径解析、资源表、图标、进程 | — | 只在用到时提 |
menu/、tray/、vibrancy/ | 4 405、954、205 | 菜单、托盘、窗口毛玻璃 | — | 不讲 |
test/ | 1 764 | MockRuntime 和测试辅助函数(test feature) | — | Runtime trait |
lib.rs、error.rs、pattern.rs、ios.rs | 1 285、201、87、49 | Manager trait、Context、generate_invoke_key、错误类型;ios.rs 是 iOS 的一点胶水 | — | — |
注入页面的 JS
调用主线和事件主线的 JS 一半不在 npm 包里,而是在 crates/tauri/scripts/ 和几个模块目录里,编译时进二进制,建 webview 时作为初始化脚本注入。两种进法:
serialize_to_javascript的#[default_template("...")]:脚本里写__TEMPLATE_xxx__(填 JSON 序列化后的值)或__RAW_xxx__(原样拼接),渲染时替换。core.js、ipc-protocol.js、ipc.js、init.js、pattern.js、isolation.js都是这样。- 直接
include_str!:process-ipc-message-fn.js、freeze_prototype.js、webview/scripts/print.js。
| 文件 | 行数 | 在哪里渲染 | 做什么 |
|---|---|---|---|
scripts/ipc-protocol.js | 91 | app.rs 的 InvokeInitializationScript(Builder::new 时渲染一次) | __TAURI_INTERNALS__.postMessage:fetch 到 ipc://,失败退回 window.ipc.postMessage;invoke key 藏在闭包里 |
scripts/process-ipc-message-fn.js | 46 | 作为 __RAW_ 嵌进上面那个 | 参数编码:ArrayBuffer / 类型化数组 / 数组走原始字节,其余 JSON.stringify |
scripts/init.js | 15 | manager/webview.rs 的 initialization_script | 一个外壳,按顺序拼进下面几段和事件分发函数 |
scripts/freeze_prototype.js | 5 | 同上,freezePrototype 配置为真时 | 冻结 Object.prototype |
scripts/pattern.js | 21 | pattern.rs | __TAURI_INTERNALS__.__TAURI_PATTERN__(brownfield 或 isolation) |
scripts/ipc.js | 210 | manager/webview.rs | __TAURI_INTERNALS__.ipc:按模式直接发,或者先交给隔离 iframe |
scripts/core.js | 114 | manager/webview.rs | invoke、回调表、convertFileSrc |
scripts/isolation.js | 17 | manager/webview.rs,isolation feature | 插入隔离 iframe |
src/event/init.js、src/path/init.js、src/window/scripts/drag.js 等 | 10、10、129 | 各核心插件的 js_init_script | 插件自己的初始化脚本 |
scripts/bundle.global.js | 1 行,41 101 字节 | withGlobalTauri 为真时由 generate_context! 带进来 | 打包好的 @tauri-apps/api,挂到 window.__TAURI__ |
bundle.global.js 的路径走的是构建脚本链:tauri 的 build.rs 打印 cargo:GLOBAL_API_SCRIPT_PATH=...,应用的构建脚本从 DEP_TAURI_GLOBAL_API_SCRIPT_PATH 拿到它并记进 OUT_DIR,generate_context! 再从那里读出来。这些脚本按什么顺序注入、各自往 __TAURI_INTERNALS__ 上挂了什么,见一次启动的实验和初始化脚本。
问: 前端项目里 import { invoke } from '@tauri-apps/api/core',用的是 npm 包里的代码,为什么要读 crates/tauri/scripts/core.js?
答: npm 包里的 invoke 只有一行转发:packages/api/src/core.ts 里 return window.__TAURI_INTERNALS__.invoke(cmd, args, options)。真正登记回调、编码参数、发请求的代码在 __TAURI_INTERNALS__ 上,而它来自上表里由 Rust 注入的那几段脚本。所以 @tauri-apps/api(同一个 tag 下版本是 2.12.4)可以单独发版,协议细节却跟着 tauri crate 走:JS 和 Rust 两头总是同一个版本。
工具
| crate | 版本 | 行数 | 说明 |
|---|---|---|---|
tauri-cli | 2.12.4 | 25 511 | tauri dev / tauri build。对运行时的影响只有三件:起前端开发服务器并把地址当 devUrl、build 时打开 custom-protocol feature、用 TAURI_CONFIG 合并额外配置。本卷不讲它的源码 |
tauri-bundler | 2.10.4 | 9 136 | 打包成 .app、.dmg、.msi 等,不讲 |
tauri-plugin | 2.7.2 | 545 | 写插件时在插件的 build.rs 里用,生成权限文件 |
tauri-driver、tauri-macos-sign、tauri-schema-generator、tauri-schema-worker | 421、1 971、70、222 | WebDriver 代理、macOS 签名、JSON schema 生成,不讲 |
小结
- 编译期三个 crate:
tauri-build(构建脚本)、tauri-macros(宏入口)、tauri-codegen(真正生成Context),共用tauri-utils的配置和 ACL 类型。 - 运行时抽象两层:
tauri-runtime只有 trait,tauri-runtime-wry用 wry + tao 实现它;MockRuntime是第二个实现。 - 平台层:wry 管 webview(macOS 上是
src/wkwebview/),tao 管窗口和事件循环(macOS 上是src/platform_impl/macos/)。 - 本体
tauri3.56 万行,webview/、ipc/、menu/、window/、app.rs、manager/最大;调用主线在ipc/和webview/mod.rs。 - 页面里的
__TAURI_INTERNALS__由crates/tauri/scripts/下的模板渲染后注入,npm 包的invoke只是转发给它。