资讯详情

资讯详情

Electron迁移Tauri实战:224MB到4.7MB体积优化与Rust+Vue开发

前阵子我把一个维护了两年的 Electron 内部工具做了次大迁移起因很简单安装包 224MB内网用户每次升级都要等半天还有人把安装包放微信里传直接被拒收。领导问我能不能把体积压下来我花了两周时间整理了一份横评方案最终选了 Rust Vue 的组合把产物压到了 4.7MB 左右。这篇文章就把这次选型过程、迁移实操、踩过的坑和最终结论完整写出来。1. 224MB 的“臃肿”不该由用户买单一个内部工具的迁移起因1.1 Electron 工具的真实体积拆解先说我们这个内部工具的背景一个给运营用的数据报表桌面端功能并不复杂主要就是登录、拉接口、展示表格和图表、导出一个 Excel附带一个简单的更新提示。当初图快用了 Electron Vue 2 Element UI开发确实快但从没想过安装包体积这个问题。有一天我看了一下打包产物目录224MB拆开看大致是这么个构成Electron 框架本体含 Chromium 内核约 160MBnode_modules 里各种依赖约 35MB前端构建产物JS/CSS/图片等约 20MB其他资源、图标、初始化脚本约 9MB这就是 Electron 方案的本质问题不管你写多少业务代码Chromium 和 Node.js 这套运行时的底子是省不掉的。这个工具业务代码可能只有几百 KB但要背着 220 多兆的浏览器引擎到处跑。对开发者来说这无所谓但对用户来说下载慢、占磁盘、内存占用高这些都是实打实的体验成本。1.2 为什么我当时选了 Electron以及现在为什么要换代选 Electron 不丢人它是目前生态最成熟的跨平台桌面方案之一VS Code、Slack、Discord 都在用它。当时选它核心原因是团队只有前端没有原生开发经验Electron 能让我们用一套 JavaScript 代码直接搞定桌面端成本最低。但这次迁移不是拍脑袋而是三个具体痛点让我下决心换体积敏感内网分发平台限制单文件 100MBElectron 的 224MB 被迫走网盘体验很割裂。内存占用高用户电脑配置普遍不高这个工具常驻内存经常到 400MB 以上展开几十个标签页的 Chromium 很吃资源。启动速度机械硬盘上冷启动经常要 4~5 秒用户已经不止一次提过“这个工具怎么这么慢”。这三个问题都属于 Electron 架构层面的“原罪”靠优化代码解决不了根本。所以我把视角放到更底层能不能换一个不打包浏览器的方案2. 六种跨平台桌面方案的核心维度对比体积、生态与开发效率2.1 参评方案的选型范围我把市面上主流的跨平台桌面方案都拉了一遍最终筛出六个有代表性的按技术路线分三类WebView 派、原生 UI 派、跨平台运行时派。Electron基准方案Chromium Node.js。TauriRust核心思路是调用系统 WebView后端用 Rust。WailsGo同样调 WebView后端用 Go。QtC/QML经典原生方案但可以通过 QML 做出接近前端的开发体验。Flutter Desktop自绘引擎不用系统 WebView也不打包浏览器。pywebviewPython轻量级 Python 库顾名思义就是 Python 系统 WebView。2.2 核心维度逐项对比对比不能只看体积我从工程落地的角度选了六个维度打包体积、内存占用、开发语言、生态成熟度、UI 还原度、上手成本。结果如下表方案打包体积典型最小内存占用后端语言UI 技术生态成熟度适合团队Electron150-250MB高JavaScriptHTML/CSS/JS极高纯前端团队Tauri4-10MB低RustHTML/CSS/JS中高前端 愿意学 RustWails8-15MB低GoHTML/CSS/JS中前端 Go 团队Qt30-80MB中低CQML/Widgets高原生开发团队Flutter Desktop20-40MB中DartFlutter Widgets中Flutter 团队pywebview10-30MB中PythonHTML/CSS/JS中低Python 工具链团队体积方面Tauri 和 Wails 这类基于系统 WebView 的方案优势明显但要注意这只是“安装包体积”系统 WebView 本身是操作系统自带的Windows 上是 WebView2macOS 上是 WKWebViewLinux 上是 webkit2gtk这部分资源不占你的安装包。2.3 我这轮横评后划掉的选项排掉方案是有明确理由的不是它们不好而是不适合这个项目。Electron继续保留作为对照组不优先考虑。WailsGo 后端很香但项目里没有人长期写 Go且 Wails 在某些 Linux 发行版上的 WebView 兼容性踩坑记录比 Tauri 多。QtC 这门语言对前端团队陡峭维护成本太高而且做数据报表界面不如 HTML 灵活。Flutter Desktop桌面端还在完善期和 Vue 生态完全不搭意味着前端资源没法复用。pywebviewPython 做桌面小程序确实快但对于需要内嵌数据缓存、做系统托盘、深度操作文件系统的场景Python 打包和分发也比较折腾。最终胜出的是Tauri理由后面实操部分展开。这里先给一个直觉类比Electron 相当于你开一家餐厅为了一个顾客专门背了一套完整的中央厨房设备Tauri 相当于让顾客用自己家的厨房你只负责带食材和调料。设备不用你带体积自然小。3. Rust Vue 的 Tauri 方案为何能打出 4.7MB 的安装包3.1 Tauri 的体积秘密复用系统 WebView Rust 原生二进制Tauri 最关键的设计是不打包浏览器内核。在 Windows 上它调用 WebView2 运行时Edge Chromium 内核macOS 上调用 WKWebViewLinux 上调用 webkit2gtk。这些 WebView 都是系统级组件安装包不需要把它们塞进去。应用本体由一个 Rust 编译出的原生可执行文件和前端构建产物组成。我的项目实际结果是Rust 可执行文件约 3.8MB编译优化 strip 后Vue 3 构建产物JS/CSS/HTML/图片等约 900KB压缩包打包后4.7MB注意这个数字是 Linux 下用 xz 压缩后的 tar 包Windows 下的 NSIS 安装包会带上 WebView2 引导安装程序约 1-2MB最终 7MB 左右也比 Electron 小一个数量级。3.2 前端为什么可以是 VueVite Vue 3 构建出静态资源Tauri 对前端框架没有限制Vue、React、Svelte 都行。它的原理是在本地起一个静态资源服务生产环境可用 tauri://localhost 协议加载内嵌资源WebView 加载的是你构建出的 HTML/JS/CSS。所以我的前端技术栈基本原封不动地从 Electron 搬了过来Vue 3 Vue Router PiniaVite 作为构建工具Element Plus 组件库ECharts 画报表图表迁移成本主要是适配数据请求方式原来用 axios 直接发 HTTP现在可以通过 Tauri 的 Rust 后端来发请求也可以继续在 WebView 里发看业务需要。3.3 Rust 后端的定位把系统级能力从 Node.js 手里接过来Electron 里我们用 Node.js 做文件系统操作、对话框、通知、自动更新。Tauri 里这些由 Rust 端提供文件读写用 Rust 标准库 std::fs或者用 tauri-plugin-fs系统对话框tauri-plugin-dialog系统通知tauri-plugin-notification打开外部链接tauri-plugin-opener定时任务Rust 的 tokio 或 std::thread一开始团队担心 Rust 学不会但实际接触下来写几个 command 函数的门槛并不高函数加了 #[tauri::command] 宏前端用 invoke 调用跟 Electron 的 ipcMain.handle 长得几乎一模一样只是换了一套语法。3.4 4.7MB 是怎么优化出来的Cargo 配置里的关键参数不是装完 Tauri 就有 4.7MB需要做针对性优化。核心在 Cargo.toml 的 release profile 里[profile.release] panic abort codegen-units 1 lto true opt-level z strip true逐项解释一下opt-level z优先优化体积而不是运行速度对工具类应用来说性能足够。lto true开启链接时优化能让 Rust 的泛型代码在链接阶段进一步去重体积能再降百分之十几。codegen-units 1让编译器在单线程下生成代码虽然编译时间变长但能配合 LTO 做更多优化。panic abort发生 panic 时直接终止而不是展开栈减少编译产物里的 unwinding 代码。strip true去掉符号表编译产物里不带调试信息。前端侧也要配合关闭 Vite 的 sourcemap去掉没用到的组件依赖图片资源做压缩一个很直观的对比同样一个 “Hello World” 级别应用默认配置编译出来大约 8MB做完上面的优化能压到 4MB 左右再把前端资源压缩进去4.7MB 是很正常的水平。4. 迁移实操把一个 Electron Vue 2 工程改造成 Tauri Vue 34.1 环境准备与项目初始化我是在一台 Ubuntu 22.04 上先做验证然后退回 Windows 10 开发主力机。Tauri 在这两个平台都能良好工作但要注意 Linux 下的系统依赖sudo apt install libwebkit2gtk-4.1-dev \ build-essential \ curl \ wget \ file \ libxdo-dev \ libssl-dev \ libayatana-appindicator3-dev \ librsvg2-devWindows 上主要是装 Rust 工具链rustup和 VS Build Tools。Node.js 建议直接用 18 LTS 以上版本Tauri 2.x 对 Node 18/20 都支持良好。初始化用官方脚手架npm create tauri-applatest模板选择时我建议选 “Vue TypeScript”因为 Tauri 的命令行工具和 API 对 TS 类型支持已经很完善能减少很多低级错误。生成出来的目录结构大概是src/Vue 前端代码src-tauri/Rust 后端代码src-tauri/tauri.conf.json核心配置src-tauri/Cargo.tomlRust 依赖4.2 把 Vue 2 工程升级到 Vue 3 Vite这是整个迁移中最耗时的一部分。老工程用的是 Vue 2 vue-cliTauri 的官方模板默认是 Vite所以不能直接把旧代码拷贝进去得先升级。升级路径我走的是用vue upgrade工具把 Vue 2 组件过渡到 Vue 3 语法。把 vue-cli 的public/目录迁移成 Vite 的public/和src/assets/。检查所有生命周期钩子Vue 2 的beforeDestroy改成onBeforeUnmountfilters 语法移除。用 Vite 的server.host配置保证 Tauri dev 模式下能访问前端开发服务器。这里有个经验先只迁移核心页面所有非核心功能比如设置页、意见反馈放到后期再处理避免一次性把工程搞崩。4.3 配置 tauri.conf.jsonidentifier 和 devUrl 是高频踩坑点Tauri 2.x 的配置都在 src-tauri/tauri.conf.json 里最关键的是这段{ $schema: https://schema.tauri.app/config/2, productName: data-report, version: 1.0.0, identifier: com.example.datareport, build: { beforeDevCommand: npm run dev, devUrl: http://localhost:5173, beforeBuildCommand: npm run build, frontendDist: ../dist }, app: { windows: [ { title: 数据报表, width: 1280, height: 800, minWidth: 1024, minHeight: 700 } ], security: { csp: null } }, bundle: { active: true, targets: [deb, appimage, msi, nsis] } }特别提醒identifier这个字段Tauri 2.x 要求 bundle identifier 不能使用默认的com.tauri.dev否则打包时直接报错。另外devUrl必须和 Vite 的开发端口一致否则开发模式白屏这个我折腾了半天才反应过来。4.4 把 Electron 的 IPC 调用改造成 Tauri 的 invokeElectron 是ipcRenderer.invoke(export-excel, data)ipcMain.handle(export-excel, ...)的组合。Tauri 换成这样前端src/api/export.tsimport { invoke } from tauri-apps/api/core; export async function exportExcel(data: Recordstring, unknown) { return await invoke(export_excel, { data }); }Rust 后端src-tauri/src/lib.rsuse tauri::State; #[tauri::command] fn export_excel(data: serde_json::Value) - ResultString, String { // 在这里写文件导出逻辑 match crate::excel::export(data) { Ok(path) Ok(path), Err(e) Err(e.to_string()), } } #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![export_excel]) .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_opener::init()) .run(tauri::generate_context!()) .expect(error while running tauri application); }注意invoke的参数命名Rust 函数参数是data前端 invoke 传{ data }Tauri 会自动做 camelCase 转 snake_case 的映射不需要手动处理。但如果你 Rust 侧用#[tauri::command(rename_all camelCase)]也是可以的看项目风格。4.5 打包构建与体积验证开发调试没问题后打包就一条命令npm run tauri build首次编译很慢Rust 要拉取并编译几百个 crate项目依赖比较多的情况下 5 到 10 分钟很正常第二次开始有缓存会快很多。打包完成后看产物尺寸ls -lh src-tauri/target/release/bundle/appimage/data-report_1.0.0_amd64.AppImage实测第一次打包默认配置是 8.6MB按照 Cargo.toml 里的 release profile 优化配置重新编译后最终 AppImage 稳定在 5.1MBtar.xz 压缩包 4.7MB。Windows 的 NSIS 安装包 7.2MB比 Linux 稍大原因是 NSIS 安装器本身和 WebView2 引导程序加起来约 2MB。5. 迁移路上最典型的五个坑以及对应的排查思路5.1 Linux 打包时 fpm 报错不是代码问题是缺依赖这是热词里出现频率很高的一个问题我也踩了。打 deb 包时 Tauri 内部会调用 fpm 工具做打包报错信息很抽象error: failed to bundle project: error running fpm failed to read output排查过程先确认 fpm 是否安装然后看 Tauri 的系统依赖清单发现少了ruby和gem。Tauri 2.x 在 Linux 下打 deb 包依赖 fpm而 fpm 是 Ruby 生态的工具链。装上之后就好了sudo apt install ruby-full sudo gem install fpm这个问题本质上不是 Tauri 的 bug而是 Linux 环境差异大、打包工具链不统一。我后来在 CI 里都会提前装上这些工具避免每次打新包都有环境问题。5.2 Windows 精简系统里 WebView2 缺失安装包需要带引导器项目用户里有不少人的 Windows 10 是精简过的WebView2 运行时被裁剪掉了。Tauri 应用一启动就是白屏没有任何提示。解决方案有两个在 bundle 配置里启用 WebView2 引导安装Windows 安装包默认会携带。在应用启动时检测 WebView2 是否可用不可用则弹出提示并跳转官方安装地址。检测方式可以写一个简单的 Rust command#[tauri::command] fn check_webview2() - bool { // 检查注册表或系统目录 // Windows 上可通过查询 WebView2 Runtime 的注册表键值判断 true // 简化示意 }这个坑在 Electron 时代完全不存在因为 Electron 自带 Chromium。用 Tauri 后目标机器的 WebView2 是个外部运行时不能被默认依赖。我当时的处理是在安装说明里明确加一句“需要 WebView2 运行时”同时安装包选择带引导器的模式。5.3 localStorage 和 IndexedDB 在 WebView 里“不持久”权限上下文问题Tauri 的 WebView 默认允许使用 localStorage 和 IndexedDB但有一个容易被忽略的点WebView 的持久化存储是绑定应用标识的重新编译时如果改了 identifier存储分区会变看起来数据就丢了。我在迁移时把 identifier 从默认值改了结果开发调试阶段登录态天天掉排查了很久才发现是分区变化。另外macOS 上的 WKWebView 对 localStorage 的清理策略和 Windows 不太一样某些系统版本升级后也会清空 WebView 存储。如果你的应用对本地缓存有强需求建议不要依赖 WebView 的 localStorage而是通过 Rust 后端写文件到应用数据目录。这样对数据控制力更强也不受 WebView 清理策略影响。5.4 Node 生态的原生依赖没有 Rust 替代品先列依赖清单再动手Electron 工程里用了一些 Node 原生模块比如用于解析 Excel 的 node-xlsx、用于压缩的 adm-zip。迁移到 Tauri 后这些 JS 库不能在 Rust 端直接跑。处理路径有两种前端继续用 JS 库如果只是纯解析/生成文件可以把逻辑保留在前端用 WebView 的 JS 能力完成只是不能调用 Node API。Rust 端找对应 crate解析 Excel 用rust_xlsxwriter或calamine压缩用zipcrate。我的建议是先画一张依赖清单把 Electron 里所有require(node:*)的地方列出来逐项确认用什么方案替代。这步别看简单漏一项就会在打包后的某个功能上突然报错排查成本更高。5.5 文件下载和默认保存目录的差异不能简单写死路径Electron 时代app.getPath(downloads)可以拿到系统下载目录。Tauri 里这个能力由 Rust 端提供更好的是用tauri-plugin-dialog让用户选择保存位置而不是默认下载到固定目录。我遇到的实际问题是WebView 里点击链接会直接在当前窗口打开而不是下载因为 Tauri 默认不接管下载行为。要拦截下载通常是在前端用锚点拦截 Rust 后端处理#[tauri::command] fn download_file(url: String, save_dir: String) - ResultString, String { // 用 reqwest 下载到 save_dir Ok(download finished.into()) }前端拿到文件后再通过dialog插件让用户选择保存位置这样比 Electron 的webContents.downloadURL更明确也不会出现下载了一半用户找不到文件的情况。6. 方案选型不是选最好而是选边界六种方案的适用场景6.1 什么时候继续用 Electron别为了换而换做完这次迁移我对 Electron 的态度反而更客观了。如果你遇到下面几类情况继续留在 Electron 完全没问题重度依赖 Chrome 特性比如要用 WebRTC、Service Worker、PWA 能力这类 WebView 支持可能不完整。追求生态和排错效率Electron 的文档、社区排错方案、第三方库丰富程度短期还是 Tauri 比不上的。团队没有 Rust 人才储备且无法接受学习成本服务端接口已经很成熟前端写桌面端Electron 上手效率确实快。说白了体积和内存不是衡量桌面框架的唯一标准Electron 的可靠性已经被大量生产环境验证过了。6.2 什么时候选 Tauri这轮横评后的明确判断Tauri 适合这几类场景体积敏感型产品个人工具、企业内部工具、需快速分发的应用4-10MB 和 200MB 的体验差异是质的。已有 Web 前端资产团队有 Vue/React 工程师想用现有代码降低成本。需要低内存占用Rust 后端 系统 WebView 的常驻内存比 Chromium 内核低一半以上我的工具实测从 420MB 降到 180MB。有系统能力需求文件操作、系统托盘、自定义协议、开机自启等Rust 都能搞定。6.3 其他四个方案各自的主场这轮对比后我给同事的建议是团队是 Go 背景优先看 Wails思路完全一致只是后端语言换成了 Go体积略比 Tauri 大点但也在可接受范围。需要统一移动端和桌面端代码且团队熟悉 Dart考虑 Flutter Desktop。要做高性能图形处理、工业控制这类桌面软件老老实实用 Qt。只想给 Python 脚本套个壳内部工具完全够用用 pywebview。6.4 决策框架一张评估清单最后给你一张我在选型时用的评估清单照着打一遍分数就清楚了维度权重建议ElectronTauriWailsQtFlutter Desktoppywebview安装包体积20%2108655运行时内存15%399866开发效率25%967568生态成熟度20%1076965团队匹配度20%1067568权重按自己项目的情况调整比如你团队全是前端、没有原生经验那 Tauri 的团队匹配度分数可能要再压低一点如果极客导向、员工学习意愿强Tauri 的分数还能再往上提。7. 附加价值Vue 生态在 Tauri 桌面端的流媒体播放场景7.1 从 m3u8 播放到桌面端播放器的轻量方案这次迁移还顺带验证了一个场景用 Vue Tauri 做桌面端播放器。网上搜“vue 播放 m3u8”的开发者不少说明视频点播、直播在桌面端需求很大常见的 m3u8 播放方案就是 hls.js 或 video.js 加 hls 插件。这类方案在 Web 页面里能用在 Tauri 里也完全能复用。我自己做了一个小验证把原来的浏览器播放页面直接嵌进 Tauri播放一个 m3u8 测试流表现很稳定首帧加载速度比 Chrome 里还快一点因为 Tauri 的 WebView 少了一层浏览器扩展和其他标签页抢占资源的过程。前端部分的核心代码大致是video idplayer controls autoplay/videoimport Hls from hls.js; const video document.getElementById(player) as HTMLVideoElement; if (Hls.isSupported()) { const hls new Hls(); hls.loadSource(https://example.com/live/stream.m3u8); hls.attachMedia(video); } else if (video.canPlayType(application/vnd.apple.mpegurl)) { video.src https://example.com/live/stream.m3u8; }这段逻辑在 Chrome、Edge、Tauri 里都能跑。唯一要注意的是跨域问题如果是私有流地址可以像我们在 Electron 时代做代理一样用 Rust 侧写一个本地代理来转发请求绕开 WebView 的 CORS 限制。Rust 的reqwest库写个几十行的代理很轻松。7.2 桌面端播放器的额外收益结合 Rust 做本地缓存和切片预取桌面端和浏览器端最大的区别在于桌面端可以更方便地管理本地文件。播放器场景下可以用 Rust 把远端 m3u8 切片预取到本地缓存目录下次播放同一个视频时直接读本地文件卡顿率大幅下降。我做了个简单的缓存逻辑#[tauri::command] async fn prefetch_segments(playlist_url: String, cache_dir: String) - ResultString, String { // 解析 m3u8, 获取 segment 列表 // 逐个下载到 cache_dir // 返回缓存后播放地址 Ok(format!(cached playlist: {}, playlist_url)) }这个能力在 Electron 里也能做但 Node.js 的并发下载控制、文件锁、缓存清理做起来不如 Rust 顺手而且 Rust 写出来的代码更不容易让 CPU 和内存飙升。如果你的目标平台是低配 Windows 机器这个优势会被放大。7.3 一个结论Vue 生态不会因为换掉 Electron 而废掉很多人担心从 Electron 迁到 Tauri 等于把前端生态推倒重来。实际迁移完你会发现Vue 组件、状态管理、路由、UI 组件库、图表库全部照常使用只是“跨语言 IPC”从 Node.js 换成了 Rust。前端团队的核心技能没有贬值只是多了一个可以触达系统底层能力的“外挂”。这个观察也让我更坚定地把工具类的桌面应用优先考虑 Tauri而不是把代码硬塞进一个看起来很全能的 Chromium 里。最后再分享两个小技巧第一个是用cargo bloat分析 Rust 二进制的体积构成。虽然我们做的是应用不是库但在压体积时能清晰看到哪些 crate 占了大量空间针对性地精简依赖比盲目改编译参数有效得多。第二个是把strip和opt-level的改动单独放在一个 profile 里不要影响 debug 构建。比如用[profile.release]单独配置生产构建开发时用dev模式快速编译就能兼顾开发体验和发布体积。另外如果你也准备迁一个老 Electron 项目我的建议是别一次性做全平台迁移先在 Linux 或 Windows 上跑通主流程确认体积和功能符合预期再扩展到 macOS。这样容错率高团队心理压力也小。迁移本身不困难难的是那颗“感觉要学一门新语言”的恐惧其实写两周你会发现Rust 在这里只是给 Vue 打工的工具人根本没你想象的那么深刻。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →