Electron 224MB 到 Tauri 4.7MB:跨平台桌面应用选型与迁移实践
1. 一个 224MB 的教训先搞清楚 Electron 的钱都花在哪了先说个数字同一个小工具同样的 Vue 前端Electron 打完包是 224MB换成 TauriRust Vue之后变成 4.7MB瘦了 47 倍还多。发出来之后不少朋友问我是不是虚标也有人反问那为什么全世界还有那么多团队在用 Electron我觉得后者才是真正值得聊的问题——不把 Electron 那 224MB 花在哪弄清楚你根本不知道该不该省、怎么省。Electron 的本质是往你的安装包里塞一个浏览器。它把 Chromium 渲染引擎和 Node.js 运行时整体打包进应用你的 HTML、CSS、JS 只是趴在浏览器壳上的一层皮。于是每个 Electron 应用装完之后用户硬盘上就多了一份完整的 Chromium 拷贝。这不是夸张我的那个内部运维工具只是做了日志解析、CSV 展示和几个表单界面文件拢共不到 10MB但装上 Chromium、Node 和一堆 node_modules 之后安装包 224MB装完磁盘占用直接奔着 300MB 去。比安装包更让人肉疼的是内存。Electron 跑起来是多进程架构主进程、渲染进程、GPU 进程、网络服务进程各一个谁都不能少。开一个看起来就是张表格的应用内存轻松占掉 200MB 多。我在 Windows 上同时开着它和一个 Electron 写的聊天工具16GB 内存愣是能看到明显吃紧。这还没算每个应用各自带一份 Chromium互相不共享——本质上等于同时起了两三个浏览器在后台。打个比方Electron 就像你为了住一间小卧室把整栋别墅整租下来了。客厅、厨房、车库你根本用不上但账单算你头上。你真正需要的是卧室也就是你自己的界面逻辑Chromium 和 Node 属于公共配套本不该由每个住户各买一套。这就是 224MB 的来源钱花在了你并不需要的配套上。但我也得替 Electron 说句公道话它的重和它的强是同源。Chromium 带来了近乎无敌的 Web 兼容性Node 带来了整个 npm 生态DevTools 调试体验更是一流。这也是为什么很多团队明知道重依然选它——省事遇到问题答案多招人门槛低。所以接下来对比六个方案之前咱们得先把这些重量化出来后头做选择才有依据。2. 六个方案集中亮相先建立选型坐标系这次横评我挑了六个能打的方案摆在桌面上比了一圈。为了公平我用同一个日志查看 表单 列表页的 Vue 界面每套方案都用 Vue 3 对应适配层跑了一遍避免拿不同复杂度去比体积。六个方案分别是ElectronJS/TS Chromium Node最成熟的重型方案生态最全TauriRust 后端 系统 WebView本篇主角安装包最小WailsGo 后端 系统 WebViewTauri 的 Go 兄弟理念几乎一样PySide6Python 绑定 Qt内部脚本和桌面小工具的老熟人Flutter DesktopDart 自绘引擎移动端团队过来会很顺eguiRust 原生即时模式 GUI开发者工具党的心头好先看第一轮硬参数同一台 Windows 11 机器、同一个功能集方案技术栈UI 渲染方式安装包体积实测空闲内存实测上手门槛ElectronJS/TS自带 Chromium224MB238MB低前端即可TauriRust 前端系统 WebView4.7MB52MB中需要会 RustWailsGo 前端系统 WebView6.1MB56MB中需要会 GoPySide6PythonQt 自绘118MB121MB低Python 即可Flutter DesktopDart自绘引擎24MB146MB中需要会 DarteguiRust即时模式自绘3.5MB38MB中高全 Rust这个表是第一印象里面藏着很多门道。Electron 体积和内存双高不用解释PySide6 装完也不小因为要内置 Python 解释器和 Qt 库但它的开发效率对小工具来说是真香Flutter 装包体积不夸张但运行时内存还是偏高——自绘引擎渲染开销摆在那egui 是最极端的小但随之而来的是学习曲线和生态代价。先补充一个容易踩的认知误区安装包体积小不等于安装后占用小更不等于内存占用低。Tauri 和 Wails 装完的磁盘占用确实小因为它们不携带浏览器引擎PySide6 装包看着 118MB装完加上运行库可能更多Flutter 则是典型的安装包还行、跑起来不轻。比体积的时候得把这三个口径分开安装包大小、安装后磁盘占用、运行内存占用。后面第 5 节我会把六个方案的这三项全列出来。第一轮肉眼对比我的体感是Electron 是什么都给你配齐的豪车Tauri/Wails 是只给你发动机和方向盘的性能车PySide6 是修车铺的老伙计Flutter 是造型前卫但油耗不低的城市 SUVegui 属于自己攒的卡丁车。哪个好取决于你要跑哪条赛道没有绝对赢家。3. Tauri 的瘦身原理系统 WebView 与 Rust 二进制的化学反应3.1 为什么 Tauri 不用再塞一个 ChromiumTauri 和 Electron 的分水岭在于一个策略复用系统自带的 WebView而不是自己带浏览器。在 Windows 上它调用 WebView2基于 Chromium 内核但由操作系统/Edge 提供在 macOS 上调用 WKWebView在 Linux 上调用 WebKitGTK。你的 Vue 页面照样跑在 WebView 里但浏览器引擎本体不用打包进安装包。这是本质性的差别。Electron 是同一套代码到处跑但每台机器上都要复制一份引擎Tauri 是引擎系统已经装好了我直接用。等于前面那个别墅比喻里客厅厨房车库由物业统一提供你只需要带上自己卧室那点家当入住。用这个方案省掉的量是很可观的。Chromium 光二进制就 100MB 以上加上 Node.js 运行时和各种动态库250MB 级别的减重直接消失。代价是什么你的渲染行为不再是 100% 由自己控制而要迁就系统 WebView 的版本和特性。这也是后面会提到一堆小坑的总根源。3.2 压缩 Rust 二进制几个配置文件立大功瘦身不只是靠不带浏览器Rust 后端本身的二进制也得精打细算。默认的 dev 编译产物很大release 版如果能做好配置能压到很小的体积。核心配置在src-tauri/Cargo.toml的[profile.release]段[profile.release] codegen-units 1 lto true opt-level s strip true panic abort逐条解释一下这几行的逻辑codegen-units 1编译器一次性处理整个 crate不拆分并行编译。编译时间会长不少但跨模块内联更彻底体积和性能都更好。lto true开启链接时优化。Rust 依赖的很多方法在依赖 crate 里已经是编译好的机器码LTO 会在最后链接阶段再做一次跨 crate 优化能把重复代码去掉不少。opt-level s优化目标设为以体积优先。相比z极致体积s在体积和性能之间更均衡对普通桌面工具来说性能损失完全无感。strip true去掉二进制里的符号表和调试信息。这个最立竿见影一个未 strip 的 release 二进制可能 8MBstrip 完直接掉到 3MB 左右。panic abortpanic 时不走 unwinding直接中止。省掉一小段运行时逻辑换取更小的二进制代价是 panic 时拿不到堆栈回溯在桌面应用里通常无所谓。这套组合拳打完我在 Windows 上看到的main.exe大约是 3.1MB。再加上 Vue 构建出来的 dist 资源压缩前约 2MB全部塞进安装包之后NSIS 装完 4.7MB。如果再加一层 UPX 压缩能到 3MB 以下但 UPX 会让杀毒软件误报率变高我自己不太推荐省那 1.5MB 不值得。3.3 前端资源怎么镶进二进制里Tauri v2 的构建流程是这样的beforeBuildCommand先执行前端打包一般是npm run build把 Vue 应用构建成静态资源放进dist/然后在编译 Rust 时通过tauri.conf.json里的frontendDist字段把 dist 目录嵌入二进制。// src-tauri/tauri.conf.json { build: { beforeDevCommand: npm run dev, devUrl: http://localhost:5173, beforeBuildCommand: npm run build, frontendDist: ../dist }, app: { windows: [ { title: 日志小助手, width: 1280, height: 800 } ] }, bundle: { active: true, targets: [nsis, deb, appimage, dmg], icon: [icons/icon.ico] } }这里有个重要的设计取向前端资源是编译期嵌入而不是运行时读取。好处是部署简单、一个 exe 就是一个完整应用、篡改成本高坏处是热更新不能直接替换静态文件得走 Tauri 的更新机制。对中小型工具来说这个取舍非常划算。4.7MB 的构成大约是这样Rust release 主程序 3.1MBstrip 后、Vue 构建产物 图标资源 1.2MBNSIS 压缩时又压掉一部分、安装器框架本身不到 0.5MB。就这么点东西。另外提醒一句Tauri 的安全模型和 Electron 区别很大。Electron 的主进程、渲染进程都是 Node 环境一旦渲染进程被打穿攻击面很大。Tauri 的渲染进程是纯 WebView没有 Node也没有 DOM 到 Rust 的自动暴露所有跨语言调用必须走显式注册的 command。前端侧还有 CSP内容安全策略约束。这套设计对内部工具可能感受不到但对要分发到公网环境的软件是个实打实的加分项。4. 实测迁移从 Electron 项目搬到 Rust Vue 的完整过程4.1 环境准备和新工程骨架我是先在开发机上装好了 Rust 工具链rustup 安装 stable 即可和 Node.js 18然后直接用官方脚手架生成工程npm create tauri-applatest交互式选择里我选了Vue TypeScript模板。生成完之后目录结构长这样my-app/ ├── src/ # Vue 前端 │ ├── App.vue │ └── main.ts ├── src-tauri/ # Rust 后端 │ ├── src/main.rs │ ├── src/lib.rs │ ├── Cargo.toml │ ├── tauri.conf.json │ └── capabilities/default.json └── package.json模板自带了一个greet示例命令先跑npm install再跑npm run tauri dev看到窗口弹出来整条链路就算通了。这一步通常不会有问题但有一个环境细节要注意Windows 上需要确保系统里有 WebView2 Runtime。Win11 和大部分更新过的 Win10 自带但某些精简版系统或 Windows Server 上可能没有应用启动会白屏。官方推荐的做法是安装器里带上 WebView2 bootstrapper用户机器没有时自动下载安装这个后面说。4.2 把 Electron 的 IPC 改写成 Tauri 的 command原 Electron 项目里我用了ipcMain.handle和ipcRenderer.invoke配对来做日志文件的读取解析这组逻辑搬到 Tauri 里对应的是 Rust 侧的 command 和前端侧的 invoke。先看 Rust 这段// src-tauri/src/lib.rs use serde::Serialize; use std::fs; use std::path::Path; #[derive(Serialize)] struct LogRow { line: u32, content: String, } #[tauri::command] fn read_log_file(path: String) - ResultVecLogRow, String { let content fs::read_to_string(Path::new(path)) .map_err(|e| e.to_string())?; Ok(content .lines() .enumerate() .map(|(idx, line)| LogRow { line: (idx 1) as u32, content: line.to_string(), }) .collect()) } #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![read_log_file]) .run(tauri::generate_context!()) .expect(error while running tauri application); }前端 Vue 那边调用import { invoke } from tauri-apps/api/core; const rows await invokeLogRow[](read_log_file, { path: filePath, });注意 Tauri 的 invoke 参数是 camelCaseRust 侧是 snake_case框架会自动转换别在前端写成file_path就行。这是新手最常踩的第一个小坑。如果后端要主动往前端推送数据比如解析日志时的进度用事件机制import { listen } from tauri-apps/api/event; const unlisten await listennumber(progress, (event) { progress.value event.payload; });Rust 侧在异步任务里用app.emit(progress, current)推送。这和 Electron 的webContents.send逻辑上是同一件事只是 API 形状不同。4.3 构建和测量4.7MB 是怎么出来的确认功能跑通之后直接执行npm run tauri build这个命令会做三件事构建 Vue 静态资源、以 release 模式编译 Rust、调用 tauri-bundler 生成各平台安装包。我这边的产物是Windowstarget/release/bundle/nsis/日志小助手_0.1.0_x64-setup.exe4.7MBWindows单纯 exetarget/release/日志小助手.exe3.1MBLinuxdeb日志小助手_0.1.0_amd64.deb2.8MBLinuxAppImage日志小助手_0.1.0_amd64.AppImage5.4MBdeb 比 exe 还小因为 deb 的依赖里声明了libwebkit2gtk-4.1-0安装时从系统仓库拉取再大的 WebKitGTK 都不进你的包。首次在 Linux 上跑的时候如果提示缺 webkit2gtk先装依赖再跑应用sudo apt install libwebkit2gtk-4.1-dev build-essential \ libssl-dev libayatana-appindicator3-dev librsvg2-dev4.4 排查实录deb 装完双击白屏的完整链路迁移过程中我踩过一个挺有代表性的坑在 Ubuntu 22.04 上 deb 包装完、双击图标窗口能弹出来但是一片白屏。当时我的排查链路是这样的给大家还原一下以后遇到类似问题可以照方抓药。第一步先在终端里手动启动应用看有没有报错信息。项目在src-tauri目录下cargo run --release跑起来终端直接打了这么一行警告Failed to load libwebkit2gtk-4.0.so.37。白屏的原因立刻锁定到 WebKitGTK 版本不匹配。Ubuntu 22.04 默认源里是 WebKitGTK 4.0 系列而 Tauri v2 默认要 4.1编译时依赖声明也是朝 4.1 去的。第二步检查 deb 包的依赖元信息。dpkg -I 日志小助手_0.1.0_amd64.deb看到 Depends 字段列的是libwebkit2gtk-4.1-0但我的测试机装的是 4.0。问题变成系统里没有 4.1 环境。这一步完全是我测试环境没准备干净导致的不是 Tauri 的 bug。第三步升级依赖。22.04 的默认源里不一定有 4.1需要找对应源装上libwebkit2gtk-4.1-0。装完再跑ldd确认libwebkit2gtk-4.1.so.0能被找到重新启动应用界面正常渲染。这个坑的直接教训是在干净的 Linux 环境上跑 Tauri 应用之前先把系统依赖装齐不要拿熟悉的开发机惯性去猜环境。顺带一提Electron 时代大家也遇到过 fpm 打包报错、依赖库缺失的问题本质上是同样的打包后部署环境不一致的问题只是 Electron 把引擎塞包里掩盖了系统依赖Tauri 则把问题暴露在明面上。4.5 迁移过程中最值得注意的 5 个细节整个迁移做完我归纳了五件最容易翻车的事IPC 传大数据的性能坑。日志文件可能有几十 MB一次性把整份内容塞进 command 返回值serde 序列化 JSON 传输会卡好几秒。更合理的做法是分页读取或者 Rust 端只解析后返回摘要前端要详情再单独取某一段。这跟 Electron 的 IPC 性能问题一脉相承但 Tauri 的序列化成本更明显。系统 WebView 的特性不统一。WebView2 对现代 Web API 支持很全但 WebKitGTK 某些版本对 MSE 和特定编解码器支持会漏一拍。比如你要在应用里播 m3u8 流Electron 里能跑到了 WebKitGTK 就未必Windows 的 WebView2 也分有 HEVC 扩展和无扩展两种形态。凡是涉及音视频的场景上线前必须逐平台过一遍真机测试。菜单栏的写法不同。Electron 里有现成的MenuAPITauri v2 要么在 Rust 里用 builder 写菜单要么不加菜单、靠 HTML 自绘标题栏。想保留原生菜单的话Rust 侧配置是个学习点。异步命令的函数签名。Tauri 支持 async command但要求函数返回ResultT, E并且E: Serialize。很多新手卡在异步任务跑起来了但报错信息看不懂多半是 Result 包装没写对。capabilities 权限模型。Tauri v2 引入了插件的权限声明比如要用文件对话框或 HTTP 插件必须在capabilities/default.json里显式开启 permission。忘了开功能代码写对了也会静默失败而且控制台不一定有明显报错。5. 六方案的实测数据与体感不只看安装包大小5.1 三组口径下的完整数据我把同一台机器、同样功能集的六个演示应用按安装包体积 / 安装后磁盘占用 / 空闲内存 / 冷启动时间四个维度重新统计了一遍。结果如下方案安装包安装后磁盘占用空闲内存冷启动中位数Electron224MB (NSIS)312MB238MB2.1sTauri4.7MB (NSIS)14MB52MB0.7sWails6.1MB (NSIS)17MB56MB0.8sPySide6118MB (Installer)168MB121MB1.4sFlutter Desktop24MB (MSIX)42MB146MB1.1segui3.5MB (exe)6MB38MB0.5s有几处数据是要加注释的。Tauri 的安装后磁盘占用 14MB是因为 WebView2 Runtime 走系统共享不算进应用目录如果你要在完全没有 WebView2 的机器上分发安装器会额外去下载运行时那个体积不该记在应用头上。PySide6 的 Installer 里包含了 Python 运行时和 Qt 库所以 118MB 不算冤枉。Flutter 用 MSIX 打包有系统级压缩实际解压后磁盘占用会明显大于安装包。5.2 每个方案的真实体感数字聊完说点文档上看不到的感受。Electron从 224MB 过来的人最爽的其实是 DevTools——F12 一开DOM、网络、存储一目了然调试体验至今没有对手。加上electron-builder的自动更新、各种原生模块、社区问答属于用着肉疼但省心。我保留它的唯一场景是需要一个体量不小、依赖大量 Node 生态库的项目。Tauri瘦是真的瘦但瘦意味着你对浏览器引擎的控制力下降了。WebView2 和 WebKitGTK 的版本差异、CSP 配置、插件生态目前还远不如 Electron 丰富都是需要自己扛的部分。如果你的团队有人会 Rust这个方案几乎是无痛如果不会学习 Rust 本身是第一道坎。我自己的体会是花三四周把 Rust 基本语法、所有权、async 摸个七七八八应对 Tauri 开发绰绰有余。WailsGo 写后端比 Rust 温和得多goroutine channel 处理并发也顺。它的体积和内存表现跟 Tauri 一个量级但 Go 的二进制本身就比 Rust 大一点带了 runtime所以 6.1MB 很正常。适合团队会 Go、也不想碰 Rust 的场景。PySide6Python 上手快、开发快但发行慢。因为要打包 Python 解释器 Qt体积下不来另外 PyInstaller 打的包跟某些杀毒软件有历史积怨误报率偏高。如果你的工具是给自己和三五同事用的PySide6 很香要公开分发得做好解释器体积和误报的心理准备。Flutter DesktopUI 一致性极好一套代码三端长一样自绘引擎在复杂界面下表现稳定。但桌面端的插件成熟度比移动端差一大截很多 pub 包只支持 Android/iOS桌面端要么自己写宿主调用要么绕路走 MethodChannel。装包体积中等内存偏高。适合已经有 Flutter 团队的场景不太推荐从零入坑只为了写个桌面小工具。eguiRust 的即时模式 GUI写工具类应用非常爽——你不需要维护组件状态树逻辑和界面写在一块。但它的文本编辑体验、中文输入法、无障碍支持都比较原始做不了重交互的正经应用。它的舞台是开发者工具、调试面板、游戏内覆盖层不是办公软件。5.3 打包环节对比Electron 的 fpm 老问题 vs Tauri 的打包体系话题再回到标题里的electron 打包 linux、fpm 报错这个热词上。Electron 在 Linux 下用 electron-builder 打 deb/rpm 时内部会调 fpm 这个 Ruby 工具fpm 对环境里的 Ruby 版本、依赖库很敏感我在 CI 上确实遇到过几次fpm: command not found和依赖冲突每次都要去 CI 镜像里手工补 Ruby 环境。Tauri 的打包器是自己用 Rust 写的不依赖 fpmdeb、rpm、AppImage 直接生成。我在同样的 CI 配置下从零打 Linux 包没有遇到 fpm 这类问题唯一的摩擦点反而在打包机系统版本上Ubuntu 20.04 默认 webkit2gtk 版本偏旧打包环境最好用 22.04 及以上。如果你想在 Windows 上交叉编译 Linux 包官方明确不推荐直接交叉标准做法是开一个 Linux Docker 容器来做构建。这一步对只写过 JS 的团队来说是个小学习成本。6. 选型判断什么场景该继续用 Electron什么场景该换6.1 一张场景决策表综合这一轮测试我把决策逻辑浓缩成一张表。每条都对应真实的团队场景不是拍脑袋场景特征推荐方案理由团队只会 JS项目重依赖 npm 生态Electron学习成本最低生态最全安装包体积是硬指标团队愿意学 RustTauri体积和内存优势唯一档团队会 Go不想重复造轮子Wails与 Tauri 同思路Go 门槛更低内部工具、快速交付、无分发压力PySide6开发速度最快已有 Flutter 团队要三端一致Flutter Desktop复用移动端技术栈开发者工具/面板型应用纯 Rust 团队egui极致轻量启动飞快6.2 我的三条取舍原则第一别为了轻而轻。如果你的用户是同事、装机量不超过几十台224MB 的安装包根本不是问题Electron 带来的开发效率才是真正的收益。我在做这个迁移之前先认真问了自己一句这个工具的安装包大小是否已经对分发或更新造成了真实困扰答案是日志工具要放进客户内网的分发包一个 224MB 的包在内网传输和版本更新上确实让人崩溃这才有了换的动力。纯粹为了炫技换技术栈后面大概率会后悔。第二算总账而不是算单点。安装包从 224MB 降到 4.7MB这是看得见的收益看不见的成本是团队要补 Rust 知识、要适配系统 WebView 的差异、要重新写一遍打包流程和自动更新。这些成本加起来对一个小团队来说差不多是两到四周的人力投入。只有把两边都算清楚才能判断值不值。第三守住应用形态这条底线。如果你的应用是一个需要长期驻留、频繁更新 UI、依赖大量 Web 新特性的产品Electron 的历史包袱反而是它的护城河。如果你的应用是界面相对稳定、以交互逻辑为主的工具系统 WebView 的差异几乎不会碰到你Tauri/Wails 就非常合适。一句话形态决定方案方案不要决定形态。我个人这几年的使用体会是桌面开发的格局早就不是只有 Electron了而是各有各的适用半径。这次把 224MB 干到 4.7MB最让我意外的其实不是体积本身而是 Tauri 在迁移过程中几乎没让我在原 Electron 的功能逻辑上做妥协——Vue 代码原样保留只有 IPC 层和构建脚本重写了。如果你的项目也卡在Electron 太肥但不知道往哪跑的纠结里照着这篇横评先在本地起一个 Tauri 或 Wails 的 demo把最核心的一个页面迁过去实测一把比看任何评测都更能帮你做决定。最后再分享一个小经验迁移时建议先把 IPC 封装层抽象成独立模块前端代码只依赖你自己的invoke(xxx)包装函数这样哪怕以后从 Tauri 再换回 Electron前端代码也基本不用动。