这篇教程的目标很具体:从一台干净的电脑开始,到手里有一个能双击安装的桌面应用,全程约 30 分钟,产物是 4.63 MB 的 Windows 安装包。
对比一下同一个功能如果用 Electron 做:安装包起步 150 MB 以上(VS Code 的 Windows 安装包我今天实测是 224 MB,
Content-Length: 235072944)。更值得说的是——你基本不用写 Rust。Tauri 2 把「重型逻辑下沉到 Rust」做成了可选选项,前端那套 Vue/React 代码可以原样搬进来。文末我把这套技术栈做出来的成品开源了,安装包体积、功能清单全部可查。
先看清楚:学完这篇你手里会有什么
| 阶段 | 产出 | 大概耗时 |
|---|---|---|
| 环境准备 | Rust 工具链 + 系统依赖装好,tauri info 全绿 | 8–15 分钟(首次下载 Rust 最慢) |
| 脚手架 | 一个能 npm run tauri dev 弹出原生窗口的项目 | 1 分钟 |
| 打通通信 | 前端按钮 → Rust 函数 → 返回数据;Rust 主动推前端 | 10 分钟 |
| 打包 | src-tauri/target/release/bundle/ 下的 .exe / .msi / .dmg / .deb | 3–10 分钟(首次编译慢) |
不需要的:不需要会 Rust 的所有权模型,不需要学 Qt,不需要重写 UI。
一、Tauri 不是"更轻的 Electron",它是另一个物种
很多介绍把 Tauri 说成"体积小一点的 Electron",这个说法会把你带偏。它俩的差别不在优化,在架构路线。
一句话定义
Tauri 是一个把系统自带的 WebView 当作渲染层、把 Rust 编写的原生进程 当作后端、用一套白名单式权限系统把两者粘起来的桌面/移动应用框架。
进程模型:一眼看出本质区别
Electron 的模型 —— 你把浏览器打包进安装包
┌─────────────────── 你的安装包(150MB+)───────────────────┐
│ Chromium(~120MB,每个 App 各带一份) │
│ Node.js 运行时(~30MB) │
│ 你的前端产物(几百 KB) ← 你真正写的东西只有这一块 │
└────────────────────────────────────────────────────────────┘
Tauri 的模型 —— 你只发"壳 + 前端产物"
┌── 你的安装包(几 MB)──┐ ┌─── 操作系统提供 ───┐
│ Rust 核心进程(~2-3MB)│ <IPC> │ Windows: WebView2 │
│ 你的前端产物(几百 KB)│ │ macOS: WKWebView │
└─────────────────────────┘ │ Linux: WebKitGTK │
└────────────────────┘
差别就一句话:Electron 的发包成本花在"带一份 Chromium",Tauri 的成本花在"要验证三种 WebView 引擎"。
前者是钱和体积,后者是测试工作量。你怎么选,取决于你更怕哪个。
Tauri 2 相对于 1.x 的四个实质变化
这一条很重要,网上大量中文教程还停留在 Tauri 1.x,命令和配置都对不上:
- 权限系统(Capabilities)成为标配。1.x 是配置里写
allowlist,2.x 改成独立的capabilities/*.json+ 插件各自的权限标识,粒度细到单个命令。 - 插件化。
tauri-plugin-*统一了官方扩展(fs / dialog / updater / sql / store / autostart …),装上就能用,不像 1.x 那样塞进核心。 - 支持移动端。iOS / Android 复用同一套前端,
tauri ios init/tauri android init。 - 多 Webview 支持。一个窗口里可以放多个独立 WebView(对做浏览器类、分屏类应用很关键)。
当前版本(写这篇文章时从 crates.io 取的):
tauri稳定版 2.11.5,tauri-cli2.11.4,脚手架create-tauri-app4.7.4。注意 crates.io 上还有个3.0.0-alpha.0,那是预发布,别用。
二、环境准备:三张清单,照着装
2.1 系统依赖
| 平台 | 要装什么 |
|---|---|
| Windows 10/11 | ① Rust 工具链(含 rustup)② Microsoft C++ Build Tools(Visual Studio 生成工具里的"使用 C++ 的桌面开发"工作负载,含 Windows SDK)③ WebView2(Win11 和 Win10 1803+ 已内置,不用管) |
| macOS 10.15+ | xcode-select --install + Rust 工具链。没别的了(WKWebView 是系统的) |
| Linux | 一行命令搞定(各发行版不同,见下) |
Linux(Debian/Ubuntu 系,官方文档给的依赖清单):
sudo apt update
sudo apt install libwebkit2gtk-4.1-dev \
build-essential \
curl \
wget \
file \
libxdo-dev \
libssl-dev \
libayatana-appindicator3-dev \
librsvg2-dev
Arch / Fedora / openSUSE / Alpine 的命令在官方 Prerequisites 页有,别照抄 Debian 那行。
2.2 Rust 工具链
Windows 上去 rustup.rs 下载 rustup-init.exe 双击即可,或者:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
国内下载慢怎么办:给 rustup 和 cargo 配国内镜像源(这是合规且最稳的做法,改的是配置里的源地址,不需要任何额外工具):
# 走系统包管理器或镜像站,例如清华 TUNA 的 rustup 镜像
export RUSTUP_DIST_SERVER=https://mirrors.tuna.tsinghua.edu.cn/rustup
export RUSTUP_UPDATE_ROOT=https://mirrors.tuna.tsinghua.edu.cn/rustup/rustup
cargo 依赖同理,在 ~/.cargo/config.toml 里配 [source.crates-io] replace-with = "<你的镜像名>" 指向国内镜像。
2.3 验证环境(这一步别跳过)
node -v # 建议 18+(本项目用的是 20 LTS 也能跑)
rustc -V # 应该是 1.7x 及以上
cargo -V
还有个神器,装完 Tauri CLI 后跑:
npm run tauri info
它会把你的 OS、WebView 版本、Rust 版本、Node 版本、缺失依赖全部列出来,环境问题九成在这一步就能暴露。遇到 error: linker 'link.exe' not found 之类,基本都是 MSVC Build Tools 没装或没选对工作负载,重跑安装器勾上"使用 C++ 的桌面开发"就好。
三、30 秒建项目:目录结构逐行讲清楚
3.1 创建
npm create tauri-app@latest
交互式问答(我的选择,你可以按需换):
✔ Project name · my-tauri-app
✔ Identifier · com.example.myapp ← 反写域名,打包成安装包的唯一 ID,别乱填
✔ Choose which language to use for your frontend · TypeScript / JavaScript
✔ Choose your package manager · npm
✔ Choose your UI template · Vue ← React / Svelte / Solid / Angular / Vanilla 都行
✔ Choose your UI flavor · TypeScript
然后:
cd my-tauri-app
npm install
npm run tauri dev
第一次会编译几分钟(Rust 要编几百个 crate),之后增量编译就快了。看到窗口弹出来,环境就通了。
3.2 目录结构(新手最该看的 20 行)
my-tauri-app/
├── package.json ← 前端依赖 + tauri 脚本入口
├── vite.config.ts ← 前端构建配置(Tauri 不干涉这块)
├── index.html ← 前端入口页
├── src/ ← 前端代码(Vue/React…原样搬进来即可)
│ ├── App.vue
│ └── main.ts
└── src-tauri/ ← ★ 整个"原生外壳"在这里
├── Cargo.toml ← Rust 依赖清单(Rust 的 package.json)
├── build.rs ← 构建脚本,Tauri 自动生成,别动
├── tauri.conf.json ← ★ 应用配置总入口:窗口、打包、插件、CSP
├── capabilities/
│ └── default.json ← ★ 权限白名单(2.x 的核心安全机制)
├── icons/ ← 打包用的图标(用 tauri icon 生成)
└── src/
├── main.rs ← 进程入口,只调用 lib.rs
└── lib.rs ← ★ 你的 Rust 代码主要写这里
记住三个星号文件就够了:tauri.conf.json(配置)、capabilities/default.json(权限)、src-tauri/src/lib.rs(后端逻辑)。
3.3 tauri.conf.json 最小可读版
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "my-tauri-app",
"version": "0.1.0",
"identifier": "com.example.myapp",
"build": {
"beforeDevCommand": "npm run dev",
"devUrl": "http://localhost:3000",
"beforeBuildCommand": "npm run build",
"frontendDist": "../dist"
},
"app": {
"windows": [
{
"label": "main",
"title": "我的第一个 Tauri 应用",
"width": 1200,
"height": 800,
"center": true
}
],
"security": {
"csp": "default-src 'self'; img-src 'self' asset:; style-src 'self' 'unsafe-inline'; script-src 'self'"
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": ["icons/32x32.png", "icons/128x128.png", "icons/icon.icns", "icons/icon.ico"]
}
}
两个字段值得单独说:
beforeDevCommand/beforeBuildCommand:Tauri 不接管前端构建,它只是"先跑你这个命令,再去加载devUrl或frontendDist"。所以 Vite / Webpack / 任何构建工具都能用,迁移成本几乎为零。security.csp:默认就是严格模式。别为了省事改成null,那等于把你最大的安全优势丢掉。
四、核心机制一:前端怎么调 Rust(command + invoke)
这是整个 Tauri 最核心的一套 API,就两个符号:Rust 侧 #[tauri::command],JS 侧 invoke()。
4.1 最小可用例子
Rust 侧(src-tauri/src/lib.rs):
#[tauri::command]
fn greet(name: &str) -> String {
format!("你好,{}!这条消息来自 Rust 🦀", name)
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet]) // ★ 必须注册,漏了就找不到命令
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
前端侧(src/App.vue):
<script setup lang="ts">
import { ref } from 'vue'
import { invoke } from '@tauri-apps/api/core'
const name = ref('World')
const msg = ref('')
async function sayHi() {
// 第一个参数是命令名(字符串),第二个是参数对象
msg.value = await invoke<string>('greet', { name: name.value })
}
</script>
<template>
<input v-model="name" />
<button @click="sayHi">调用 Rust</button>
<p>{{ msg }}</p>
</template>
跑起来点按钮,你会看到 Vue 渲染出 Rust 返回的字符串。到这里你就打通了桌面应用最重要的那条链路。
4.2 三个新手必踩的细节
① 参数名自动转换 camelCase → snake_case
JS 里传 { userName: 'x' },Rust 函数签名写成 user_name: String。Tauri 内部会自动转换。反过来你要是不转换,会收到一个很模糊的"参数缺失"错误。
② 复杂结构要有 Deserialize / Serialize
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize)]
struct WatchItem {
code: String,
market: String,
}
#[derive(Serialize)]
struct Quote {
code: String,
price: f64,
change_pct: f64,
}
#[tauri::command]
fn add_watch(item: WatchItem) -> Result<Quote, String> {
if item.code.is_empty() {
return Err("股票代码不能为空".into());
}
// ... 业务逻辑
Ok(Quote { code: item.code, price: 12.34, change_pct: 1.23 })
}
前端拿到的是原样的对象:
const q = await invoke<Quote>('add_watch', {
item: { code: '600519', market: 'sh' },
})
③ 错误处理用 Result<T, E>,E 建议是 String
Rust 的 Result<Quote, String> 在 JS 侧会变成一个 rejected promise,所以要用 try/catch:
try {
const q = await invoke<Quote>('add_watch', { item })
} catch (e) {
// e 就是 Rust 侧 map_err / Err("...") 里的字符串
console.error('调用失败:', e)
}
这是我认为 Tauri 设计得最舒服的一点:错误不是靠字符串约定,而是语言级别的类型系统强制你处理。
4.3 共享状态:State 和托管状态
桌面应用总有"全局只初始化一次的东西"——数据库连接、行情缓存、配置对象。Tauri 的写法:
use std::sync::Arc;
use tauri::State;
struct Cache {
count: std::sync::atomic::AtomicU64,
}
#[tauri::command]
fn hit(cache: State<'_, Arc<Cache>>) -> u64 {
cache.count.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + 1
}
pub fn run() {
tauri::Builder::default()
.manage(Arc::new(Cache { count: 0.into() })) // ★ 注册到应用上下文
.invoke_handler(tauri::generate_handler![hit])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
关键点:Rust 是无数据竞争的。你这里的 AtomicU64 / Mutex 是编译器强制你加的,不是"记得加"。前端传来的并发调用打到同一个 Rust 状态上,不会出现 Electron 里那种"两个 ipcMain 回调同时改一个对象"的玄学 bug。
4.4 异步命令:别阻塞 UI
只要命令里要做网络请求或磁盘 IO,就加 async:
#[tauri::command]
async fn fetch_depth(code: String, market: String) -> Result<Depth, String> {
// await 一个 HTTP 请求
let d = reqwest_get_depth(&code, &market).await.map_err(|e| e.to_string())?;
Ok(d)
}
注意:async 命令里如果持有了 State<'_, T>,需要用 State<'_, T> 这种带生命周期的写法;而且不要在 async 里持有 MutexGuard 跨 await 点,否则编译不过(编译器会阻止你写出死锁)。这一点初学时会觉得"Rust 好烦",两次之后你会感谢它。
五、核心机制二:Rust 主动推给前端(事件系统)
invoke 是"前端问、后端答"。但桌面应用里更常见的场景是后端有持续的数据流,前端只负责渲染——行情、日志、进度条、下载速度。
传统 Electron 写法是前端 setInterval 轮询 ipcRenderer.invoke。Tauri 里正确做法是事件推送:
Rust 侧推:
use tauri::{Emitter, Manager};
use std::time::Duration;
// 在 setup 或某个命令里启动一个后台任务
tauri::Builder::default()
.setup(|app| {
let handle = app.handle().clone();
tauri::async_runtime::spawn(async move {
loop {
// ... 取数逻辑
let payload = serde_json::json!({ "code": "600519", "price": 1700.5 });
// ★ 广播给所有 webview
let _ = handle.emit("quote://tick", payload);
tokio::time::sleep(Duration::from_secs(3)).await;
}
});
Ok(())
})
前端侧听:
import { listen } from '@tauri-apps/api/event'
import { onUnmounted } from 'vue'
const unlisten = await listen<{ code: string; price: number }>(
'quote://tick',
(event) => {
// event.payload 就是 Rust 侧 emit 的对象
store.update(event.payload)
}
)
// ★ 一定要清理,否则组件销毁后监听器还在,内存和重复调用都会出问题
onUnmounted(() => unlisten())
一个真实的坑:事件可能比监听器先到
这是我做行情工具时踩到的:Rust 侧在启动阶段立刻 emit,但前端的 Vue 组件挂载、listen 注册完成是有延迟的(尤其首页要做权限校验、拉配置)。结果是前端漏掉了最早的几帧数据,界面上一段时间是空的。
两种解法:
- 拉推结合:前端
listen注册成功后再invoke('start_stream'),让后端在收到指令后才开始推。这是最稳的。 - 后端留一份最新快照:前端挂载时先
invoke('get_snapshot')拿当前值,再开始listen收增量。
我用的是第一种。这个坑在任何"事件驱动架构"里都存在,只是 Tauri 让你更早碰到它。
六、核心机制三:权限系统(Tauri 最被低估的设计)
这一节是 Tauri 2 和 Electron 在安全模型上最根本的差别,也是我认为最值得学的一块。
6.1 问题是什么
Electron 应用里,渲染进程如果要访问文件系统,通常是通过 preload 暴露一组 API,或者干脆开 nodeIntegration。它的安全边界靠你自己写对。一旦前端出现 XSS(比如渲染了未转义的第三方数据),攻击者拿到的是整个 Node 运行时。
Tauri 的模型是:后端只有被显式授权的命令才对外可见。没授权,前端连"命令名"都不知道。
6.2 capabilities 长什么样
src-tauri/capabilities/default.json:
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "主窗口的能力集",
"windows": ["main"],
"permissions": [
"core:default",
"opener:default",
"core:window:allow-set-position",
"core:window:allow-start-dragging"
]
}
逐字段解释:
windows:这套权限只对哪些窗口生效。你可以给主窗口开一堆权限,给托盘小窗一个都不开——这是 Electron 很难优雅做到的。permissions:每个字符串是一条权限,格式是<插件名>:<动作>。core:default是最小核心集,opener:default是 opener 插件的默认集。- 想用某些命令(比如让窗口可以被拖动、能改坐标),要单独把
core:window:allow-*列出来。
6.3 缺权限的报错长什么样
新手最常见的困惑是这个:
window.set_position not allowed.
Permissions associated with this command: core:window:allow-set-position
报错信息本身就告诉你该加什么权限了。看到这种,去 capabilities/default.json 的 permissions 里把那行加上,重启 dev 即可。这比"Electron 里默默生效或默默失败"友好太多。
6.4 三条实践原则
- 从最小开始,按报错加。官方默认模板给的就是
core:default,够用了。别一上来把fs:default、shell:default全打开。 - 按窗口分能力。主窗口给业务权限,悬浮窗/托盘窗只给渲染权限。
- CSP 不要关。
tauri.conf.json里的security.csp是最后一道防线,配合权限系统才有意义。
七、核心机制四:插件生态,一行命令装一个能力
Tauri 2 把官方扩展全拆成了独立插件,装起来非常统一:
npm run tauri add opener # 用系统默认程序打开链接/文件
npm run tauri add autostart # 开机自启
npm run tauri add updater # 自动更新
npm run tauri add sql # SQLite(Rust 侧 sqlx)
npm run tauri add store # 轻量 KV 持久化
npm run tauri add single-instance # 单实例
这条命令会自动改三处:package.json(JS 包)、Cargo.toml(Rust crate)、lib.rs(注册插件),并且往 capabilities 里加对应的默认权限。比手动装省掉十次踩坑。
常用插件速查:
| 插件 | 干什么 | 谁需要 |
|---|---|---|
opener | 打开浏览器链接 / 本地文件 | 几乎所有应用 |
autostart | 开机自启 | 工具类、盯盘类 |
updater | 应用内自动更新 | 有分发需求的产品 |
store | KV 配置持久化 | 存用户偏好 |
sql | SQLite | 本地数据量大的 |
single-instance | 防止开多个实例 | 常驻类 |
global-shortcut | 全局快捷键 | 效率工具 |
window-state | 记住窗口位置大小 | 体验加分项 |
log | Rust 侧日志 | 排查线上问题 |
notification | 系统通知 | 提醒类 |
一个提醒:插件也是权限的一部分。装了 opener 但 capabilities 里没写 opener:default,前端调用一样会报权限错误。装完插件顺手确认一下权限文件。
八、打包:从源码到安装包
8.1 一条命令
npm run tauri build
产物在 src-tauri/target/release/bundle/ 下,按平台自动生成。我这套技术栈做出来的项目,v1.4.7 一次 tauri build 出的全部产物(体积是从 GitHub Release API 读的字节数,最可信):
| 平台 | 文件名 | 体积 |
|---|---|---|
| Windows | *_x64-setup.exe(NSIS) | 4.63 MB |
| Windows | *_x64_zh-CN.msi(WiX) | 6.46 MB |
| Windows | *_x64-portable.zip(免安装) | 6.38 MB |
| macOS | *_universal.dmg | 13.06 MB |
| Linux | *.deb | 8.00 MB |
| Linux | *.rpm | 8.00 MB |
| Linux | *.AppImage | 79.26 MB |
这里必须诚实说一件事:Linux 的 AppImage 有 79 MB,比 Windows 大了 17 倍。原因是 AppImage 要把 WebKitGTK 相关依赖打进去(Linux 发行版之间 WebKit 版本差异太大,不打包就跑不起来)。所以"Tauri 只有几 MB"这个说法,在 Linux 桌面上是要打折的。 用 deb/rpm 走系统依赖就是 8 MB,代价是要求用户系统装了 WebKitGTK。
8.2 Windows 安装包为什么能这么小
因为 WebView2 是共享的。Win11 和 Win10 1803+ 都内置了 WebView2 Runtime,你的安装包不用带浏览器。对于更老的系统,Tauri 默认的 webviewInstallMode 是 downloadBootstrapper——安装时按需下载一个约 2 MB 的引导器。想要完全离线安装,可以改成 embedBootstrapper 或 offlineInstaller,代价是安装包会变大。
8.3 体积优化:Cargo profile
src-tauri/Cargo.toml 加上官方推荐的 release profile:
[profile.dev]
incremental = true # 分步编译,增量更快
[profile.release]
codegen-units = 1 # 让 LLVM 做更好的优化
lto = true # 链接时优化
opt-level = "s" # 优先体积(追求速度用 3)
panic = "abort" # 去掉 panic 展开,体积更小
strip = true # 剥离调试符号
还有一招(需要 tauri@2.4 及以上):
{ "build": { "removeUnusedCommands": true } }
它会在构建时对比 capabilities 里的 ACL,把前端从未被授权调用的命令从二进制里摘掉,真正做到"不用就不付成本"。
8.4 图标:别自己 P 图
准备一张 1024×1024 的 PNG,然后:
npm run tauri icon path/to/your-icon.png
它会一次性生成 Windows .ico、macOS .icns、各尺寸 PNG 和移动端全套图标,直接进 src-tauri/icons/。
九、新手必踩的 7 个坑(收藏这张表)
| # | 症状 | 原因 | 解法 |
|---|---|---|---|
| 1 | error: linker 'link.exe' not found | Windows 没装 MSVC C++ Build Tools | 装 VS 生成工具,勾"使用 C++ 的桌面开发" |
| 2 | 前端调 invoke 报命令不存在 | 忘了在 generate_handler![] 里注册 | 每个命令都要列进 generate_handler! |
| 3 | 前端报 xxx not allowed | capabilities 里没授权 | 按报错提示把权限字符串加进 permissions |
| 4 | 传参报"缺少字段" | JS 用 camelCase,Rust 用 snake_case | 靠 Tauri 自动转换,但结构体字段名要写对,且加 #[derive(Deserialize)] |
| 5 | 首次 tauri build 卡住十几分钟 | Rust 全量编译几百个 crate | 正常现象;后续增量秒级。国内配上 crates 镜像会快很多 |
| 6 | 换了图标但安装包没变 | 打包缓存 / 图标路径没更新 | 确认 bundle.icon 路径,清 target/release/bundle 重打 |
| 7 | 组件销毁后还在处理事件 | listen 的 unlisten 没调 | onUnmounted(() => unlisten()),或用 getCurrentWebview().listen 配合自动清理 |
十、数据口径与证据强度(重要)
按我自己的规矩,数字全部标来源,你自己可以复核:
| 数据 | 来源 / 取数方式 | 强度 |
|---|---|---|
| macOS 安装包 13.06 MB、Windows setup 4.63 MB、Linux AppImage 79.26 MB | gh release view -R Leaderxin/quant-desktop --json assets 读 .assets[].size 字节数 | 实测可复现 |
| VS Code Windows 安装包 224 MB | curl -sIL https://update.code.visualstudio.com/latest/win32-x64-user/stable 读 Content-Length: 235072944 | 一手来源,可复现 |
| Electron 官方 win32-x64 运行时 150.82 MB | GitHub Releases API,electron-v44.3.0-win32-x64.zip = 158,149,320 B | 一手来源 |
tauri 稳定版 2.11.5 / tauri-cli 2.11.4 / create-tauri-app 4.7.4 | crates.io /api/v1/crates/<name> 的 max_stable_version | 一手来源 |
| Tauri 仓库 star 111,049 / Electron 123,039 | GitHub API repos/tauri-apps/tauri、repos/electron/electron | 一手来源(star 是动态值,读到时点为准) |
Cargo profile 优化项、Linux 依赖清单、removeUnusedCommands 需 2.4+ | Tauri 官方文档 App Size / Prerequisites / Create a Project | 官方文档 |
| Tauri CLI 相关命令与配置字段 | 官方文档 + 本项目 tauri.conf.json 实际配置 | 官方文档 + 实测 |
证据强度说明:本文字节数均为真实读取,非估算,你可以用上面的命令自行复核。"Electron 应用安装包 100–230 MB"这种区间是社区普遍观测值,属于中等强度,仅供量级参考。
十一、我用这套技术栈做了什么(成品 + 实测结果)
上面所有代码和踩坑记录,来自我实际在维护的一个开源项目 QuantDesktop——一个免费开源的 A 股实时看盘桌面应用。

技术栈和本文完全一致:Tauri 2 + Rust + Vue 3 + TypeScript + Vite。
它的实测数据(都可查):
| 维度 | 数值 |
|---|---|
| Windows 安装包 | 4.63 MB(NSIS)/ 6.46 MB(MSI) |
| macOS 通用包 | 13.06 MB |
| 已发布版本数 | 8 个(v1.3.1 → v1.4.7,2026-08 至今) |
| Rust 侧自定义命令 | 22 个(generate_handler! 里注册,全部走权限白名单) |
| 核心功能 | 实时行情推送、K 线 / 分时图、五档盘口、自选股管理、多数据源容灾切换、系统托盘 + 悬浮行情条、应用内自动更新 |
它为什么是个好的学习样本:这个项目把本文讲的东西全用上了——#[tauri::command] 的同步/异步两种形态、State 托管共享缓存、emit 事件推动情、capabilities 按窗口分配权限、独立小窗口(alwaysOnTop 的桌面行情条)、GitHub Releases 零成本自动更新。想看真实工程里怎么组织,比看 hello world 划算得多。
项目地址:https://github.com/Leaderxin/quant-desktop
完全免费开源,Windows / macOS / Linux 都有包,装了就能用。软件本身对 A 股投资者有直接用处,对想学 Tauri 的人则是一个能跑的参考实现。
这个系列的前两篇(建议一起看,链路才完整):
- 还在用 Electron?6 种跨平台桌面方案横评:Rust + Vue 把安装包从 224MB 干到 4.7MB —— 选型阶段看这篇,6 种方案带实测数据横评
- 0 元成本搞定桌面端自动更新:Tauri + GitHub Releases 全流程实录,附完整 CI 配置和 3 个致命坑 —— 打包之后怎么让用户自动升级,全流程 + CI 配置
十二、一句话总结
Tauri 2 = 系统 WebView 做渲染 + Rust 做后端 + 白名单权限做边界,前端代码零改造,安装包小一个数量级;代价是三种 WebView 引擎要验、Rust 编译比 Node 慢、你要接受一门新语言在项目里存在。
如果你只是想"用前端技术写个桌面小工具",今天就可以用 npm create tauri-app@latest 试一次,30 分钟能拿到安装包,这是最便宜的验证方式。
最后留个问题给评论区:你手的桌面工具,愿意为了几 MB 的安装包去引入 Rust 吗? 或者反过来——你踩过 Electron 的哪个坑让你想换掉它?说说你的场景,我帮你算算换过去的成本。
本文所有命令与版本号基于 Tauri 2 稳定版实测,Ubuntu 系统依赖清单与官方文档一致;文中提及的链接与数据可通过文中给出的命令复现。
&spm=1001.2101.3001.5002&articleId=165321445&d=1&t=3&u=272fd9db715c4d228b36b5fc9c3a1bca)
482

被折叠的 条评论
为什么被折叠?



