Rust in Everything
基于 tauri 2.12.5

源码地图

主线位置:三条主线经过的代码分布在哪些 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-build2.7.42 477src/lib.rs 的 build / try_build应用 build.rs 里调用:读配置、检查 feature 和配置是否一致、发 cfg(desktop) / cfg(dev)、生成 ACL 清单和 schema、Windows 资源文件tauri-build
tauri-macros2.7.41 632src/lib.rs过程宏入口:generate_context!、#[command]、generate_handler!、#[default_runtime]#[command] 与 generate_handler!
tauri-codegen2.7.41 770src/context.rs 的 context_codegengenerate_context! 的实际工作:内嵌并压缩前端资源(embedded_assets.rs)、CSP 哈希、图标、ACL 解析结果,生成 Contextgenerate_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.rs5 507tauri.conf.json 的全部类型(Config、WindowConfig、SecurityConfig、Csp 等)generate_context!
config_v1/3 4121.x 配置格式的类型,迁移用不讲
acl/3 459Permission、Capability、Resolved 等 ACL 的数据结构和编译期解析权限、能力、作用域、编译期解析
assets.rs、html.rs、pattern/211、442、178EmbeddedAssets 的运行时一半、HTML 里注入 CSP、隔离模式的加解密tauri:// 协议、CSP、原型冻结与隔离模式

运行时抽象:tauri-runtime 和 tauri-runtime-wry

crate版本行数入口做什么讲它的页
tauri-runtime2.12.22 893src/lib.rs 的 Runtime、RuntimeHandle、WindowDispatch、WebviewDispatch trait;webview.rs 的 PendingWebview;window.rs 的 PendingWindow一组 trait 和「待建」的窗口 / webview 描述,tauri 本体只对这组 trait 编程Runtime trait
tauri-runtime-wry2.12.36 783src/lib.rs(5 416 行)用 wry 和 tao 实现上面的 trait:Wry 持有 tao 的 EventLoop;从其他线程来的操作变成 Message 经 EventLoopProxy 送回主线程;create_window / create_webview 调 wry 的 WebViewBuildertauri-runtime-wry

tauri 的 test feature 里还有第二个实现 MockRuntime(crates/tauri/src/test/mock_runtime.rs),不开窗口,单元测试用。

平台层:wry 和 tao

两个独立的仓库,tauri-runtime-wry 是它们在 tauri 里唯一的使用者。

crate版本路径行数内容
wry0.57.1src/lib.rs2 637WebViewBuilder、WebView、跨平台的属性
src/wkwebview/4 113macOS / iOS:WKWebView、url_scheme_handler.rs(自定义协议)、class/ 下的各种 delegate
src/webview2/2 390Windows:WebView2(只读源码)
src/webkitgtk/2 109Linux:WebKitGTK(只读源码)
src/android/1 756Android,不讲
tao0.37.2src/event_loop.rs、window.rs、event.rs416、1 706、982跨平台的事件循环、窗口、事件类型;从 winit 分叉而来
src/platform_impl/macos/6 997app_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、158Builder、App、AppHandle、RunEvent;build、run、setup启动Builder 与 App
manager/2 317AppManager:配置、资源、插件、状态、监听器、invoke key;webview.rs 的 prepare_pending_webview启动、事件AppManager
webview/5 977Webview、WebviewBuilder、WebviewWindow;on_message启动、调用窗口与 webview、分发
window/2 971Window、WindowBuilder、核心插件 window启动窗口与 webview
ipc/4 466protocol.rs(ipc:// 协议和 postMessage 两条入口)、mod.rs(InvokeResolver、InvokeBody)、command.rs(CommandArg)、authority.rs(RuntimeAuthority)、channel.rs、format_callback.rs调用ipc 协议、参数与 InvokeResolver、Channel
protocol/770tauri.rs(tauri://)、asset.rs(asset://)、isolation.rs启动tauri:// 协议、asset:// 协议
event/935Listeners、emit_js_script、event_initialization_script、核心插件 event事件AppManager
plugin.rs、plugin/1 062、520Plugin trait、Builder、PluginStore启动、调用插件
state.rs、async_runtime.rs291、363manage / State<T>;全局 tokio 运行时调用状态、async_runtime
scope/、path/、resources/、image/、process.rs819、1 618、254、638、159文件系统作用域、路径解析、资源表、图标、进程—只在用到时提
menu/、tray/、vibrancy/4 405、954、205菜单、托盘、窗口毛玻璃—不讲
test/1 764MockRuntime 和测试辅助函数(test feature)—Runtime trait
lib.rs、error.rs、pattern.rs、ios.rs1 285、201、87、49Manager 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.js91app.rs 的 InvokeInitializationScript(Builder::new 时渲染一次)__TAURI_INTERNALS__.postMessage:fetch 到 ipc://,失败退回 window.ipc.postMessage;invoke key 藏在闭包里
scripts/process-ipc-message-fn.js46作为 __RAW_ 嵌进上面那个参数编码:ArrayBuffer / 类型化数组 / 数组走原始字节,其余 JSON.stringify
scripts/init.js15manager/webview.rs 的 initialization_script一个外壳,按顺序拼进下面几段和事件分发函数
scripts/freeze_prototype.js5同上,freezePrototype 配置为真时冻结 Object.prototype
scripts/pattern.js21pattern.rs__TAURI_INTERNALS__.__TAURI_PATTERN__(brownfield 或 isolation)
scripts/ipc.js210manager/webview.rs__TAURI_INTERNALS__.ipc:按模式直接发,或者先交给隔离 iframe
scripts/core.js114manager/webview.rsinvoke、回调表、convertFileSrc
scripts/isolation.js17manager/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.js1 行,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__ 上挂了什么,见一次启动的实验和初始化脚本。

📝NOTE
没有蠢问题

问: 前端项目里 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-cli2.12.425 511tauri dev / tauri build。对运行时的影响只有三件:起前端开发服务器并把地址当 devUrl、build 时打开 custom-protocol feature、用 TAURI_CONFIG 合并额外配置。本卷不讲它的源码
tauri-bundler2.10.49 136打包成 .app、.dmg、.msi 等,不讲
tauri-plugin2.7.2545写插件时在插件的 build.rs 里用,生成权限文件
tauri-driver、tauri-macos-sign、tauri-schema-generator、tauri-schema-worker421、1 971、70、222WebDriver 代理、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/)。
  • 本体 tauri 3.56 万行,webview/、ipc/、menu/、window/、app.rs、manager/ 最大;调用主线在 ipc/ 和 webview/mod.rs。
  • 页面里的 __TAURI_INTERNALS__ 由 crates/tauri/scripts/ 下的模板渲染后注入,npm 包的 invoke 只是转发给它。

💬 关联讨论

发起讨论
加载中...