六种跨平台桌面方案横评:Electron太胖,Tauri仅4.7MB
2026/9/19 4:17:11 网站建设 项目流程

1. 先说说我对 Electron 的复杂感情:224MB 到底装了个什么

做桌面应用这行当,谁还没跟 Electron 爱恨纠缠过几年。我第一版项目用 Electron 加 Vue,功能倒是顺风顺水,直到打包完看了一眼产物:224MB。一个连数据库都没挂的聊天工具,装完占了快半张老 SSD,用户问"你们这是塞了个虚拟机进去吗"。当时我嘴上解释这是 Chromium 内核加 Node 运行时,心里其实也虚——道理是这个道理,但 224MB 换一个右键菜单和窗口拖拽,这成本怎么看怎么不对劲。

说白了,Electron 的架构决定了它体积下不去。每一个 Electron 应用都内置了一份完整的 Chromium 浏览器内核加 Node.js 运行时,就算你的业务代码只有 2MB,打包出来也是实打实的 200MB 级别。而且这体积每个应用各带一份,系统里装了五六个 Electron 应用,磁盘上是五六个 Chromium 在互相打架。内存就更不用提,基本是"开一个应用吃 300MB,开五个吃 1.5GB"的节奏。

这也是我把目光投向其它跨平台桌面方案的直接原因。那段时间我把市面上叫得上名字的方案全过了一遍,从老牌的 Qt、PySide 到新锐的 Tauri、Flutter Desktop、Wails 甚至 egui,前后折腾了两三个星期,最终在一个内部工具项目里彻底定了型:Rust + Vue 的 Tauri 组合,安装包从 Electron 时代的 224MB 一路压到 4.7MB。

这数字确实夸张,但它不是靠压缩或者魔改打包器压出来的,而是整个架构选型换了一条路。这篇就把我横评的六种方案、每种方案适合什么人、Tauri 为什么能把包做到这么小、以及实际操作中会踩到哪些坑,一次性说清楚。如果你现在正纠结"我要做个跨平台桌面应用该选什么",这篇文章能帮你省掉两周的调研时间。

2. 六种跨平台桌面方案的同台对比:从体积、内存到开发体验的真实数据

先交代下我的评测背景。我测的这台机器是 Windows 11 + i5-1240P + 16GB 内存,目标平台是 macOS、Windows、Linux 三端覆盖;测试项目是一个带表格、图表、WebSocket 长连接和系统通知的内部运维工具,业务逻辑不算复杂,但对前端交互有一定要求。这个场景基本能代表"常见内部工具 + 中小型应用"的典型需求。

2.1 这六种方案分别是什么来头

先说清楚我横评的范围。市面上做跨平台桌面的路子大致分三派:

  • WebView 套壳派:Electron、Tauri、Wails。都是"系统 WebView + 前端框架"的架构,但底层内核和运行时完全不同。
  • 原生自绘派:Qt(C++/Python 绑定)、Flutter Desktop、egui(纯 Rust 即时模式 GUI)。界面元素不依赖 HTML/CSS,而是自己绘制。
  • 中间派:PySide6 本质是 Qt 的 Python 绑定,归入 Qt 生态;还有个 NW.js 跟 Electron 同源,我这次没放进来,因为它的体积和内存表现跟 Electron 半斤八两。

六种方案我按这个名单测的:Electron、Tauri、Wails、PySide6、Flutter Desktop、egui。

2.2 安装包体积、内存占用、启动速度的实测数据

先说大家最关心的硬指标。同一个业务功能,六种方案打包出来的体积、空载内存和冷启动速度如下:

方案安装包体积(压缩后)磁盘占用(解压后)空载内存冷启动到首帧
Electron224MB620MB310MB约2.1秒
Wails8.9MB48MB90MB约0.9秒
Tauri4.7MB31MB75MB约0.6秒
PySide652MB210MB120MB约1.3秒
Flutter Desktop18MB92MB110MB约1.1秒
egui2.1MB15MB45MB约0.4秒

这个数据测了好几轮,结论很稳定:

  • Electron 在体积和内存上是全方位垫底,它的优势全在生态和调试体验上,这个后面说;
  • Tauri 和 Wails 属于 Web 技术栈里的轻量双子星,体积比 Electron 小一整个数量级,内存大概是它的四分之一;
  • egui 在纯体积上是最小的,2.1MB 的体积确实吓人,但它没有 WebView,本质是 GPU 即时渲染,适合工具型应用但不适合做内容密集型界面;
  • PySide6 和 Flutter Desktop 居中,都是"体积可接受、内存中等"的选手,卡点不在运行时而在打包和生态。

2.3 开发体验:不是所有"轻量"都值得投入

横评不能只看体积,开发体验直接决定一个项目能走多远。我把六种方案按"上手成本 + 生态丰富度 + 调试体验 + 跨平台打包顺畅度"四个维度打过分:

方案上手成本生态丰富度调试体验跨平台打包顺畅度
Electron极高(npm 全家桶)极好(DevTools 就是 Chromium)中(Linux 打包经常报错)
Wails中(Go 生态,前端照旧)好(自带 DevTools)
Tauri中(Rust 生态,前端照旧)好(WebView DevTools + Rust 调试)中(依赖系统 WebView,环境差异多)
PySide6中高(Python 库多,UI 组件成熟)中(Qt Designer 好用,但断点调试麻烦)低(PyInstaller 打包体积大且易被杀软误报)
Flutter Desktop中(三方包偏移动端,桌面组件不够全)好(Hot Reload 一流)
egui低(Rust 生态,组件基本都是开发者在用)中(即时模式调试思路不同)好(编译期即可定位问题)

这张表大概能说明一件事:没有完美的方案,只有匹配场景的方案。Electron 的开发体验确实是天花板级别,Vue 或 React 开发者零成本上手,社区组件随便抄,这是它至今没被取代的根本原因——很多人骂 Electron 是骂体积和内存,但真让它干活的时候又真香,这是事实。而 Tauri 和 Wails 要的就是"前端体验 + 小体积"的中间路线,代价是底层语言(Rust/Go)的入门门槛,以及系统 WebView 在 Linux 不同发行版上的环境差异。

3. Tauri 凭什么是 4.7MB,而不是压缩到 4.7MB

这一节说点重的:Tauri 的安装包为什么能做到 4.7MB,它和 Electron 的本质区别在哪里。只有把原理说透了,才知道这个体积优势是可持续的,还是某个特定场景下的巧合。

3.1 架构差异:内置浏览器 vs 复用系统 WebView

Electron 的模型是"我把浏览器一起交付给你"。它内置了整个 Chromium 和 Node.js 运行时,应用启动时拉起的是自己带的那份内核。好处是:所有用户的渲染环境完全一致,你开发调试遇到什么,用户机器上就是什么。代价就是前面说的体积和内存双高,因为每一份 Electron 应用都等于带了一份浏览器。

Tauri 的模型截然不同:它不打包浏览器,而是调用操作系统自带的 WebView 组件。macOS 上用 WKWebView,Windows 上用 WebView2(基于 Chromium 但系统级共享),Linux 上用 WebKitGTK。应用真正自己带的,是一份极其精简的 Rust 核心,负责窗口创建、系统能力调用(文件系统、剪贴板、通知、托盘等)、以及前后端的 IPC 通信。

这么一算就清楚了:Electron 的 224MB 里,Chromium 内核 + Node.js 运行时大约占 180~200MB;而 Tauri 的 4.7MB 里,Rust 编译后的二进制大约 3MB,前端资源(Vue 构建产物)约 1MB 多,剩下的是一堆配置文件和小资源。前端代码的体积两者差异不大,真正拉开差距的是"我要不要自带一份浏览器"这个架构决策

这也是为什么说 Tauri 的体积优势是结构性的,不是压缩出来的。你换个压缩算法,Electron 也压不到 Tauri 的量级,因为它的体积大头是二进制运行时,压缩率有限。

3.2 Rust 在体积控制里的角色:编译期做完了所有该做的事

Rust 在这套组合里的作用经常被误解,很多人觉得"Rust 体积小是因为语言本身多厉害"。严格说,Rust 的贡献分两层:

第一层,它提供了无运行时开销的系统能力接口。Tauri 的核心需要调用操作系统的原生 API(创建窗口、注册全局快捷键、读写文件等),Rust 编译出来的二进制不依赖额外的运行时环境,编译成什么就是什么,直接交给操作系统执行。对比 Electron 里 Node.js 那层运行时,Rust 二进制省掉了一个完整的解释执行环境。

第二层,Rust 默认静态链接所有依赖,而且能通过编译特性裁剪掉用不到的代码。Tauri 的 Cargo.toml 里有一堆 feature 开关,比如屏幕保护功能、剪贴板监听功能、全局快捷键功能,都是按需编译的。没用到的功能,编译器直接不生成对应代码,这在 C++ 生态里需要靠链接优化才能做到,Rust 的 Cargo feature 机制是语言层面设计好的。我实际把默认未启用的功能全部关掉后,二进制又小了几百 KB,稳定性丝毫没影响。

另外 Rust 编译器的优化选项也要开。发布构建里我加了lto = truecodegen-units = 1,前者是链接时全局优化,后者是让编译器牺牲并行编译时间换取更好的代码生成质量。这两个选项对体积和性能都有正向影响,代价是编译时间明显增加。我的项目纯 Rust 侧代码量不大,完整发布构建大约需要 3~5 分钟,完全能接受。

3.3 WebView 的前端代码:该瘦身的还是得瘦身

Tauri 的前端资源和 Electron 一样跑在 WebView 里,这部分体积优化逻辑跟普通前端构建完全一样。Vue 3 打包后的 gzip 体积本身就很小,一个核心应用通常也就 100~300KB 级别。但要特别注意别把大依赖引进来:

  • 别用体积夸张的 UI 库当基底。Element Plus 全量引入的 gzip 体积约 350KB,这在普通 Web 项目里不算什么,但在 Tauri 应用里,它可能是你前端资源的头号大块头。按需引入能降到 100KB 以内。
  • 图表库按需注册。ECharts 全量包约 800KB,gzip 后也有 250KB 左右。如果只用柱状图和折线图,按需注册能把体积压到 80KB 上下。
  • 图片资源做 WebP 压缩。Tauri 打包会把src-tauri/icons里的图标和dist里的静态资源全部打进安装包,一张没压缩的 PNG 背景图可能就是几十 KB 甚至上百 KB。

我这项目里最终前端产物只有 1.1MB(含 gzip 压缩),这个量级放进 4.7MB 的安装包里是完全合理的。如果你的应用前端资源就有 20MB,那 Tauri 的安装包也会到 25MB 左右,体积优势依然在,但就没那么夸张了。

3.4 为什么不用更小的 egui,而选 Tauri

既然比体积,egui 的 2.1MB 比 Tauri 还小一倍多,为什么不选它?这个答案其实是我整个选型过程里最有价值的一部分。

egui 是即时模式 GUI,意思是每一帧都重新构建界面,开发和调试思路跟传统的保留模式(如 React/Vue/Qt 那种)完全不同。它写出来的界面是命令式的,类似于"我画一个按钮,画一个表格,画一个文本框",没有 DOM 概念,也没有双向绑定。对纯工具型应用(比如一个 JSON 格式化器、一个状态查看面板)来说它非常好用,启动快、内存低、跨平台编译干净。但一旦界面复杂度上来,比如需要一个多标签页的富文本编辑器、一个复杂的表单校验系统、或者带层级菜单的管理后台,egui 的开发效率会断崖式下降——你几乎是在用 Rust 手写 HTML。

而 Tauri 保留了完整的 Web 技术栈。我熟悉的 Vue 组件体系、ECharts 图表、CSS 动画、WebSocket 库全部原样可用,唯一的区别是系统能力调用从 Electron 的 Node.js API 换成了 Tauri 的 Rust 命令。我团队里的前端同事不需要学 Rust 就能参与界面开发,只有需要写系统级功能的时候才接触 Rust 命令层。

所以选型逻辑很清晰:要极致体积和极致性能,选 egui;要有体积优势又不想放弃 Web 开发体验,选 Tauri。我选的后者,因为项目需要复杂前端交互,而且团队前端资源占大头。

4. Tauri + Vue 从零搭建到产出安装包:完整实操

以下内容是整个选型确定后,我在实际项目里走通的一整套流程。环境是 Windows 11 + VS Code,目标平台先跑 Windows,再交叉验证 Linux。前置依赖我默认你已经有 Node.js 18+ 和 Rust 工具链(rustup安装,用 stable 通道)。

4.1 初始化项目与目录结构解析

Tauri 2.x 的脚手架已经比较成熟了,官方推荐用create-tauri-app直接初始化。我用的命令:

# 创建一个基于 Vite + Vue 3 + TypeScript 的 Tauri 项目 npm create tauri-app@latest my-tauri-app -- --template vue-ts cd my-tauri-app npm install

初始化完成后,目录结构大致是这样:

my-tauri-app/ ├── src/ # Vue 前端源码 ├── src-tauri/ │ ├── src/ │ │ ├── main.rs # Rust 入口,初始化应用 │ │ └── lib.rs # 核心逻辑与命令注册 │ ├── Cargo.toml # Rust 依赖清单 │ ├── tauri.conf.json # Tauri 配置(窗口、打包、标识符等) │ ├── capabilities/ # 权限能力配置(Tauri 2.x 的权限模型) │ └── icons/ # 应用图标 ├── index.html ├── package.json └── vite.config.ts

注意src-taurisrc是两个独立的项目。src是 Vite 管的前端,src-tauri是 Cargo 管的 Rust 项目,两者通过构建流程关联:npm run tauri build会先执行 Vite 构建(生成dist),再把dist嵌入 Rust 二进制。

4.2 首次运行与关键配置项

开发阶段用npm run tauri dev,这个命令会同时启动 Vite dev server 和 Rust 的 debug 构建。第一次运行会卡在 Rust 编译上,因为要拉取和编译上百个 crate 依赖,几分钟到十几分钟不等,这是正常的。之后增量编译就会快很多。

窗口配置在tauri.conf.json里:

{ "app": { "windows": [ { "title": "内部运维助手", "width": 1280, "height": 800, "resizable": true, "fullscreen": false, "center": true, "minWidth": 960, "minHeight": 640 } ], "security": { "csp": null } }, "build": { "beforeDevCommand": "npm run dev", "devUrl": "http://localhost:1420", "beforeBuildCommand": "npm run build", "frontendDist": "../dist" } }

这里有一个我踩过的大坑:csp字段千万别图省事直接设 null。CSP(内容安全策略)是 WebView 里防止 XSS 的关键防线。我最初为了省事直接关闭了 CSP,结果把一个用户的系统 token 通过异常日志打到了外部地址——好在及时发现修掉了。之后我严格配置了"csp": "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' ipc: http://ipc.localhost",把外部连接全部拦死。如果是内部工具且需要调外部 API,再按需加connect-src的白名单域名。

4.3 前端怎么和 Rust 后端通信

Tauri 的 IPC 机制是它区别于纯 Web 应用的核心能力。前端的 Vue 组件可以通过@tauri-apps/api/core里的invoke函数调用 Rust 侧的函数,数据走 JSON 序列化。我项目里一个典型的调用长这样:

前端 Vue 里:

import { invoke } from "@tauri-apps/api/core"; // 调用 Rust 侧命令,获取系统信息 const info = await invoke("get_system_info", { target: "cpu", });

Rust 侧lib.rs里:

use serde::Serialize; #[derive(Serialize)] struct SystemInfo { os: String, cpu_arch: String, total_memory_gb: u64, } #[tauri::command] fn get_system_info(target: String) -> SystemInfo { // 实际逻辑里这里会调用系统 API 获取信息 SystemInfo { os: std::env::consts::OS.to_string(), cpu_arch: std::env::consts::ARCH.to_string(), total_memory_gb: 16, } } #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![get_system_info]) .run(tauri::generate_context!()) .expect("error while running tauri application"); }

需要注意:命令名get_system_info在 JS 里用蛇形(snake_case)调用,Rust 侧参数target在 JS 里对应传入{ target: "cpu" },键名一致即可。返回值必须是Serialize类型,Tauri 会自动转成 JSON 给前端。

性能经验:如果频繁调用命令,比如拖拽滑块实时保存配置,每次 invoke 都有序列化和进程通信开销。我实测在频繁调用场景下,Tauri 的 IPC 吞吐大约是每秒几万次级别,比 Electron 的 IPC 快一个量级,但依然不建议每帧都调。批量收集数据然后定时调用一次,性能会稳定很多。

4.4 等宽效果:前端实时显示 Rust 侧推送的日志

很多桌面工具都有"实时日志展示"的需求。Electron 里可以用 Node 的子进程或者 WebSocket;Tauri 里更推荐用官方的事件机制。Rust 侧把日志通过app_handle.emit推到前端,Vue 里用@tauri-apps/api/eventlisten接收。

import { listen } from "@tauri-apps/api/event"; const unlisten = await listen("log-update", (event) => { logList.value.unshift(event.payload as string); }); // 组件卸载时记得取消监听 onUnmounted(() => unlisten());

Rust 侧:

use tauri::{Emitter, AppHandle}; #[tauri::command] fn save_config(app: AppHandle, content: String) { std::fs::write("config.json", content).expect("写入失败"); app.emit("log-update", format!("配置已保存: {}", content.len())) .unwrap(); }

这套事件机制相当稳,传输大量日志时也没出现过明显卡顿。注意listen返回的unlisten函数一定要在组件卸载时调用,否则会内存泄漏——这是我在页面频繁切换时偶然发现的,加了onUnmounted之后内存曲线平稳多了。

5. 打包发行阶段的实际问题:从 Windows 到 Linux,fpm 报错和 WebView 依赖

打包阶段是横评里最容易翻车的一环,Tauri 在 Windows 上很顺,但到了 Linux 上就有一堆环境差异要处理。我这项目实际要发 macOS、Windows、Linux 三端,这一节把踩过的坑和最终的解决方案都放出来。

5.1 Windows 安装包:NSIS 配置与代码签名

Tauri 在 Windows 上的默认打包器是 NSIS(Nullsoft Scriptable Install System),配置在tauri.conf.jsonbundle字段里:

{ "bundle": { "active": true, "targets": ["nsis", "msi"], "icon": [ "icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico" ], "windows": { "nsis": { "installMode": "currentUser", "languages": ["SimpChinese", "English"] } } } }

installMode我用的currentUser,这样不需要管理员权限就能安装,对内部工具来说体验好很多。languages加上SimpChinese让安装界面显示中文。这两个配置都挺直觉的,按需调整就行。

代码签名是个容易被忽略的坑。Windows 上未签名的应用,SmartScreen 会弹"Windows 已保护你的电脑",用户得点"更多信息 → 仍要运行"才能装,体验大打折扣。内部工具虽然不像商业软件那么敏感,但最好还是搞一个代码签名证书。我用的是 OV 证书,在 GitHub Actions 里通过tauri-apps/tauri-action配置证书和密码环境变量自动签名,流程走通了以后基本无感。如果只是个人项目或内部使用,可以先不上签名,但要把 SmartScreen 的提示话术提前同步给同事,免得装的时候吓到人。

5.2 Linux 打包的连环坑:WebKitGTK 缺失、fpm 报错、以及 AppImage/ deb 的选择

Linux 是 Tauri 打包的重灾区,我在这上面花的时间比写代码还多。先说最常见的两个问题。

第一坑:WebKitGTK 依赖缺失。Tauri 在 Linux 上依赖 WebKitGTK 和 GTK 3 的一系列开发包。在干净的环境上跑cargo build会报一串 "requires libwebkit2gtk-4.0-dev" 之类的错误。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

注意不同 Ubuntu 版本依赖的 WebKitGTK 版本不一样,22.04 和 24.04 在仓库里的版本有差异,安装时如果找不到libwebkit2gtk-4.1-dev,试试libwebkit2gtk-4.0-dev,或者按报错提示切换版本。

第二坑:Linux 打包工具链不完整导致的 fpm 报错。Tauri 在 Linux 上打包 deb 或 rpm 包时,内部会调用打包工具链。如果你是交叉环境,或者系统缺rubygemfpm这些东西,打包会直接挂掉,报的错误五花八门。最干净的解法是直接看 Tauri 文档里 "Linux 打包依赖" 一节,把libfuse2(AppImage 需要)、rpm等工具装齐。有一个取舍经验:内部工具走deb 包比 AppImage 省心,因为 AppImage 把运行时和依赖都塞进一个文件,体积会大不少,而 deb 依赖系统已有的 WebKitGTK,体积更小,安装也更符合 Linux 用户习惯。代价是你得适配不同发行版的 WebKitGTK 版本差异,但 deb 目标在 Ubuntu/Debian 系上基本通用。

第三坑:Linux WebView 版本太老导致的功能缺失。这是最难排查的。Tauri 应用的 UI 跑在系统 WebKitGTK 上,如果用户在 CentOS 7 这种老发行版上跑,WebKitGTK 版本可能不支持你 Vue 3 里用到的新 CSS 属性,界面会莫名其妙地错位。我项目里就遇到过一个旋转动画在 Ubuntu 20.04 上变成了瞬间切换——排查到 WebKitGTK 的 CSS transform 支持差异才算完。我的建议是:如果 Linux 目标用户用的是企业级的老发行版,别硬上 Tauri,老老实实用 Electron,因为 Electron 自带内核不存在这种兼容问题。这也是 Tauri 一个真实的短板,选型时务必要知道。

5.3 macOS 的交叉编译和公证流程

macOS 的打包本身不复杂,但如果你的 CI 跑在 Linux 上,想做 macOS 交叉编译就会撞墙——Tauri 目前不支持从非 macOS 环境交叉编译 macOS 安装包。我是在 GitHub Actions 里用macos-latestrunner 跑的 macOS 构建,配置tauri-actionbundle参数即可。公证(notarization)是个硬性要求,不公证的应用在 macOS 上只能右键打开,体验很差。这个流程在tauri-action里配置APPLE_IDAPPLE_APP_SPECIFIC_PASSWORDAPPLE_TEAM_ID三个环境变量后会自动执行,我配好之后跑通就没再动过。

5.4 自动更新系统的选型

内部工具和线上产品一样需要热更新。Tauri 有官方的tauri-plugin-updater,但需要你有一个静态文件服务器托管 JSON 更新描述文件和安装包。如果你的更新逻辑比较特殊(比如内外网隔离、校验签名、灰度发布),自己用 Rust 写一个check_update命令配合后端接口也完全可行,几十行代码的事,比全都依赖插件灵活。

有一点注意:Tauri 的自动更新目前对NSIS 安装包支持最稳,MSI 也可以,但体验略差。我项目里直接锁定了 NSIS + updater 的组合,稳定用了三个多月没出过问题。Linux 下的 updater 因为发行版差异太大,我没有启用更新,直接走 deb 仓库的升级路径。

6. 我实际用 Tauri 三个月后的性能调整和插件方案

实战阶段是检验架构的试金石。我把内部工具从 Electron 迁移到 Tauri 之后,前后调了三轮性能,有一些和网上教程不一样的心得。

6.1 初始化一个 Vue + Tauri 项目时最容易犯的错

网上很多教程会让你直接npm create tauri-app然后一路默认,但实际项目里我建议趁早把下面两件事做了:

  • 配置build.rs的环境变量,控制前端构建模式。有些文章推荐的tauri.conf.json里的beforeBuildCommandnpm run build,但你要确保.env.production里的 API 地址是生产环境值,而不是开发环境的 localhost。我遇到过把开发环境接口打进生产包的低级错误,排查了半天才发现是.env文件没切。
  • 初始化.gitignorenode_modulesdistsrc-tauri/target这三样必须忽略,否则一个git status能把你心态搞炸。src-tauri/target是 Rust 编译产物,动辄几百 MB 甚至 1GB 以上。

6.2 前端性能:WebView 和 Chromium 的差异要重新适配

很多人以为 Tauri 的前端就是普通 Web 开发,但其实系统 WebView 和 Chromium 还是有细微差别的。我的项目是运维工具,里面有大量表格数据渲染和图表刷新,迁移初期遇到过一个明显的卡顿:表格滚动时掉帧。排查下来发现是 WebKitGTK 对 CSSwill-change属性的处理不如 Chromium 激进,加了之后局部重绘次数少了,滚动明显跟手了。

另外系统 WebView 的 CORS 策略比 Chromium 严格,如果你在 Vue 里直接请求跨域接口,在 Electron 里可能没问题,在 Tauri 里会被拦。解决方案要么是配置 CSP 的connect-src,要么是在 Rust 侧用reqwest做一层代理转发,把跨域请求在 Rust 侧发出再返给前端。我项目里用了后者,因为这样还能顺带做鉴权 token 的统一注入,安全性和代码整洁度都好一些。

6.3 全局快捷键和系统托盘:插件化开发效率最高

Tauri 自带的代码模板很简单,但要做成真正可用的应用,我的经验是尽早引入官方插件生态。推荐三个我实际用下来非常稳定的:

  • tauri-plugin-store:持久化 KV 存储,相当于前端的 localStorage 但支持 Rust 侧读写,适合存窗口位置、主题偏好、登录状态。
  • tauri-plugin-shell:打开外部程序或文件,比如用系统默认浏览器打开文档链接。
  • tauri-plugin-global-shortcut:全局快捷键注册,我用来做一键隐藏/呼出窗口,体验跟原生应用几乎一样。

官方插件的用法都差不多,装好之后在 Rust 侧注册一次,前端 import 对应 API 调用即可:

fn main() { tauri::Builder::default() .plugin(tauri_plugin_store::Builder::default().build()) .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_global_shortcut::Builder::new().build()) .run(tauri::generate_context!()) .expect("error while running tauri application"); }

插件的好处是它把很多系统能力封装成了 Rust 和 JS 双端可调用的统一接口,比自己从零写要省太多事。我一开始自己写了个全局快捷键的 Rust 命令,调试了两天才发现官方插件早就把 Windows 上Alt+Space的模态窗口冲突处理好了。

6.4 图标资源和多分辨率适配

这是最不起眼但是坑最多的部分。Tauri 对图标有一套严格的多分辨率要求:Windows 要.ico,macOS 要.icns,Linux 要png系列(32x32、128x128、128x128@2x、256x256、512x512 等)。我最初只放了一个 512x512 PNG,结果 Windows 打包出来图标模糊,macOS 上直接不显示图标。

正确做法是用官方推荐的tauri icon命令从一张 1024x1024 的源图生成全平台图标:

npm run tauri icon path/to/your-icon.png

这个命令会自动生成src-tauri/icons下的所有尺寸和格式,而且会覆盖默认图标。注意源图最好是无圆角的方形图,因为 macOS 会自动加圆角,Windows 不会,如果你源图自带圆角,Windows 上就会出现"双重圆角"的丑效果。我换过两次图标才明白这个道理。

7. 说完优点,说点 Tauri 现在让我不太舒服的地方

横评要客观,Tauri 不是没有短板。这几个问题我在实际使用中深有体会,如果你不是"认定了就接受"的性格,建议认真评估。

7.1 调试体验差 Electron 一截

Electron 的调试是"开发者工具 + Node 端断点"那种一键切换的丝滑。Tauri 的调试默认走系统 WebView 的开发者工具,在 Windows 上要手动打开WebView2 DevTools,在 Linux 上 WebKitGTK 的远程调试设置更麻烦。Rust 侧报错信息对新手很不友好,一堆#[tauri::command]宏展开的错误看起来像是在看天书。

我的妥协方案是:前端调试用 Chrome DevTools 连 Tauri 的 WebView,Rust 侧逻辑尽量保持薄层,复杂的系统能力先用单元测试验证,再暴露给前端。这样真正在前端做交互调试时,大部分问题都在前端层,Rust 侧因为逻辑薄所以很少出幺蛾子。

7.2 权限模型灵活但配置繁琐

Tauri 2.x 引入了capabilities权限模型,每个系统 API 的调用都需要在capabilities/*.json里显式声明。好处是真出安全事故时边界很清晰,坏处是开发效率打折。我项目里第一次调用writeFile就遇到权限不足的报错,翻文档才想起来要在 capabilities 里加tauri:fs:allow-write-file

我的做法是:正式开发阶段用一个通用的default.json,把常用权限(fs、shell、event、store)先加好,到发布前再根据安全审查结果逐一裁剪。这样既能保持开发速度,又不至于把权限全部向src目录敞开——虽然麻烦了点,但比 Electron 那种"什么都能干"的默认状态要踏实不少。

7.3 大前端生态库的兼容偶有意外

Tauri 的 WebView 不是 Chromium 最新版,Linux 上尤其明显。比如你引用的某个 npm 包明确要求chromium >= 100,在 Windows WebView2 上没问题,但 Ubuntu 20.04 的 WebKitGTK 可能只有 90 级别的内核支持,就会出现运行时异常。我在项目里遇到过一个拖拽库在 WebKitGTK 上初始化崩溃的问题,最后只能换了个纯 JS 实现。选型时建议把"目标用户的系统版本"和"WebView 版本"两个因素一起写进评估表,忽略任何一个,上线都会出幺蛾子。

8. 如果我再来一次选型,会怎么做决策

横评了一圈,最后把这套思考模型分享给你,也是我在做技术选型时每次都走的一套流程。

第一,先分清应用类型,再谈框架。你的应用是内部工具还是面向大众的产品?内部工具可以让用户装依赖、对体积和启动速度不敏感,那 Electron 的生态和调试体验就是最大优势;面向大众的产品,安装包每大 10MB 下载转化率都会掉一个档位,Tauri 的体积优势就极其重要。

第二,分清团队基因。团队主语言是 Node/TypeScript,Electron 和 Tauri 都是舒适区;团队主语言是 Python,PySide6 是可能的上限选择;团队主语言是 Rust,Tauri 和 egui 都有天然优势。选型不是选最好的,是选团队能长期维护的。

第三,写一个「面包板」原型再定。不要看文档拍脑袋,花一个周末把你要做的应用的核心页面 + 一次系统调用在两三个候选框架里各跑一遍,看看开发体验、构建产物大小、运行占用这三个指标,再结合前两条做决定。我那次横评之所以选了 Tauri,就是因为那个 4.7MB 的构建产物和 75MB 的空载内存,让"发给同事安装"这个动作的成本降到了一个我心里很舒服的水平。

9. 最后分享一个自动更新迭代的小技巧

目前这个 Tauri 内部运维工具已经稳定跑了三个多月,安装包从最初的 4.7MB 因为加了离线语音合成资源涨到 6.2MB,但相比 Electron 时代的 224MB 依然是云泥之别。我给团队交付时做过一个小统计:迁移前,同事从拿到安装包到看到主界面平均要等 40 秒(下载 + SmartScreen 警告 + 安装 + 首启加载);迁移后缩短到 10 秒以内,启动应用基本是秒开。这个体验差异是实打实的,团队反响很好。

技巧方面,如果你也准备在内部工具里采用 Tauri,最值得投入的是把自动更新从第一天就接好,别攒到用户多了再补。内部工具最大的成本是"挨个去同事机器上手动更新安装包",有了tauri-plugin-updater,把安装包放到一个内网静态服务器上,客户端重启时自动检查并静默更新,这个体验能拯救你和运维同事的无数个下午。

最后再提一句,如果你对 Rust 侧代码没有绝对的把握,一个稳妥的做法是前期把所有系统能力调用都写成一个独立的 Rust crate,通过单测优先验证底层逻辑,再暴露给 Tauri 的命令层。这样既能控制 Rust 代码的复杂度,也能让你在迁移到别的界面框架时(比如将来想换 egui 或者 Yew 做深度系统集成)不用返工。这是我这次横评到落地过程中收获的最大一条经验:技术选型的核心不是选一个永不后悔的答案,而是选一个能让试错成本足够低的起点。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询