Tauri 2 启动链路拆解与打包发布全流程避坑指南
发布时间:2026/10/1 16:45:26来源:尧图网络
1. 把 Tauri 的启动链路彻底拆开看1.1 Tauri 的启动到底在启动些什么很多人第一次接触 tauri 的启动过程会本能地拿它跟 Electron 对比。这个对比是对的但要先搞清楚一个关键差异Electron 打包出来的是「一个自带的 Chromium 加一份 Node 运行时」而 Tauri 打包出来的是「一个 Rust 编译的原生可执行文件加上对系统自带 WebView 的调用」。这个差异几乎决定了后面所有启动、运行、打包环节的技术细节也是很多人在启动阶段翻车、在打包阶段被体积和兼容性折腾的根本原因。说人话就是Electron 是把整个浏览器引擎塞进你的安装包谁装都得背着这一坨Tauri 是借宿主系统已经装好的渲染引擎来用Windows 上借 WebView2Edge 的内核macOS 上借 WKWebViewSafari 的内核Linux 上借 webkit2gtk。所以 Tauri 的启动过程本质上被拆成了两半一半是 Rust 侧的进程初始化一半是 WebView 的创建与前端资源加载。这两半谁先谁后、谁来等谁是整个启动链路的命门。在你敲下tauri dev或者双击安装完的应用图标之后实际发生的事情大致是这样一条链路CLI 或可执行文件读取配置、解析窗口定义、初始化 Rust 运行时、创建 WebView 实例、把前端资源本地 dev server 地址或打包进二进制的静态文件塞进 WebView、然后前端 JS 通过 IPC 跟 Rust 侧建立通信通道。任何一环慢了、错了表现出来都是「窗口没出来」或者「窗口出来了但是白屏」。这两个症状的排查方向完全不同后面我会分开讲。我先给一个判断标准如果你的项目在开发机上能跑打包之后在别人机器上白屏那九成是资源路径或者 WebView2 缺失的问题如果开发阶段tauri dev卡住不动那基本是前端 dev server 或者 Rust 编译卡在了某个环节。把这两个场景区分清楚能省掉大量瞎试的时间。1.2 环境依赖清单少装一个都跑不起来Tauri 的环境准备是新手最容易踩坑的地方因为它的依赖横跨前端和系统层报错信息还经常指向不明确。我按平台把必须装的东西列一遍这是基于 Tauri 2.x 的实际情况。三个通用依赖必须齐活Rust 工具链通过 rustup 安装 stable 版本Tauri 2 建议 1.77 以上、Node 环境其实只要能跑你选的前端构建工具即可用 pnpm、npm、yarn 都行甚至可以不要 Node 直接写纯静态页、以及系统 WebView 运行时。系统 WebView 这块是分平台的Windows 10/11 自带 WebView2 的情况不统一Win11 一般预装Win10 很多机器没有需要在安装包里带引导macOS 用系统 WKWebView不需要额外装Linux 需要libwebkit2gtk-4.1-dev注意 Tauri 2 用的是 4.1Tauri 1 用的是 4.0这个版本号差一点点就会导致编译报找不到 pkg-config 的错误。Linux 上还有一串经常被漏掉的依赖我按 Debian/Ubuntu 系的写法列出来sudo apt update sudo apt install -y libwebkit2gtk-4.1-dev \ build-essential \ curl wget file \ libxdo-dev libssl-dev \ libayatana-appindicator3-dev \ librsvg2-dev这里libxdo-dev是给模拟输入用的librsvg2-dev是图标处理依赖libayatana-appindicator3-dev是托盘图标依赖。我见过有人只装了 webkit 就开始编译结果在链接阶段报一堆 undefined reference然后开始怀疑 Rust 环境坏了其实只是少了两个 -dev 包。Windows 上需要 MSVC 构建工具Visual Studio Build Tools 里勾选「使用 C 的桌面开发」或者退而求其次用 MinGW 的 GNU 工具链。我的建议是直接上 MSVC因为 WebView2 相关的原生链接在 MSVC 下最省事。macOS 上装 Xcode Command Line Tools 即可xcode-select --install一条命令解决。注意不要在同一台机器上混用多种 Rust target 工具链去编译同一个项目尤其是 Windows 上 MSVC 和 GNU 混用产出的二进制在链接 WebView2 加载器时行为不一致会出现本地能跑、换台机器就崩的情况。1.3 从命令行到窗口弹出一次 tauri dev 的完整轨迹先用脚手架起一个项目看清楚它生成了什么pnpm create tauri-app my-app cd my-app pnpm install pnpm tauri devcreate-tauri-app会问你前端框架、包管理器、UI 模板选完之后生成的目录结构大致是这样my-app/ ├── src/ # 前端源码 ├── index.html ├── package.json ├── vite.config.ts └── src-tauri/ ├── Cargo.toml ├── tauri.conf.json ├── build.rs ├── capabilities/ │ └── default.json ├── icons/ └── src/ ├── main.rs └── lib.rs关键在src-tauri这一层它是整个原生侧的根。main.rs在 Tauri 2 里被拆得极薄真正的入口逻辑挪到了lib.rs这是为了兼容移动端编译移动端没有 main 函数这个概念// src-tauri/src/main.rs #![cfg_attr(not(debug_assertions), windows_subsystem windows)] fn main() { app_lib::run(); }// src-tauri/src/lib.rs #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_opener::init()) .invoke_handler(tauri::generate_handler![greet]) .run(tauri::generate_context!()) .expect(error while running tauri application); }注意main.rs第一行那句#![cfg_attr(not(debug_assertions), windows_subsystem windows)]它的作用是让 release 构建在 Windows 上不弹出那个黑乎乎的控制台窗口而 debug 构建保留控制台方便你看到println!的输出。这个细节看似小但很多人第一次打包完发现多了个控制台窗口就是不知道去改这里。tauri dev执行后的完整顺序是CLI 读取tauri.conf.json先执行build.beforeDevCommand指定的命令拉起前端 dev server然后轮询探测build.devUrl是否可访问探测通过之后再调用cargo run编译 Rust 侧编译完成后启动二进制二进制内部通过tauri::generate_context!()宏把编译期注入的配置和前端资源读出来创建窗口加载 devUrl。整个过程是串行的任何一个环节卡住后面的都不会开始。1.4 启动时的进程模型谁在管谁理解进程模型对排查问题特别有用。Tauri 应用运行时在操作系统看来就是一个原生进程它内部持有 WebView 实例。WebView 在 Windows 上会拉起若干msedgewebview2.exe子进程渲染进程、GPU 进程等在 macOS 上 WebView 是进程内的在 Linux 上 webkit2gtk 也会 fork 出 WebKitWebProcess 和 WebKitNetworkProcess。这意味着两件事。第一任务管理器里看到的 Tauri 应用内存占用要把子进程一起算别只看主进程那几兆然后说 Tauri 比 Electron 省一半——省是真的省但省的是安装包体积和基础内存占用渲染复杂度上去了 WebView 一样吃内存。第二调试的时候如果你发现主进程活着但界面死了去看 WebView 子进程是不是被系统回收了或者崩了Windows 上可以用事件查看器查msedgewebview2的异常。另外 Rust 侧的异步运行时默认用的是 tokioTauri 内部把命令调度、事件循环、插件生命周期都挂在上面。所以你在#[tauri::command]里写async fn是天然支持的不需要自己手动搭 runtime。但要注意如果在命令里做阻塞式的重 IO比如同步读大文件、跑一个长时间的循环会占住执行线程表现就是界面还能动但所有 IPC 调用都卡住。这种情况用tauri::async_runtime::spawn_blocking包一层。2. 开发模式运行前端热更新和 Rust 重编译怎么配合2.1 tauri.conf.json 里决定启动行为的几个关键字段配置文件是整个启动过程的总控Tauri 2 的结构跟 1.x 有较大调整我把最影响启动行为的字段挑出来讲。很多人是从 1.x 教程迁移过来的直接照抄distDir和devPath结果报字段未识别就是版本对不上。{ $schema: https://schema.tauri.app/config/2, productName: my-app, version: 0.1.0, identifier: com.example.myapp, build: { beforeDevCommand: pnpm dev, devUrl: http://localhost:1420, beforeBuildCommand: pnpm build, frontendDist: ../dist }, app: { windows: [ { title: my-app, width: 1000, height: 700, resizable: true, visible: false } ], security: { csp: null } }, bundle: { active: true, targets: all, icon: [ icons/32x32.png, icons/128x128.png, icons/icon.icns, icons/icon.ico ] } }几个字段的含义和易错点值得单独说字段作用常见坑build.devUrl开发模式下 WebView 加载的地址端口和 Vite 配置不一致导致一直轮询超时build.frontendDist打包时前端产物的相对路径相对的是src-tauri目录不是项目根少写一层../就白屏build.beforeDevCommand启动前拉起前端服务命令写成前台阻塞模式会导致 CLI 一直等identifier应用唯一标识改动后会影响签名、安装包升级、配置目录路径app.windows[].visible窗口初始可见性设成 false 后必须在前端 ready 时手动 show否则永远看不到窗口identifier这一项我必须强调一下它是应用的「身份证号」。在 macOS 上它决定签名时的 bundle id在 Windows 上影响注册表写入路径和单实例判断在 Linux 上影响配置和数据目录的位置。项目一旦发布这个值就不要再改了改了之后老版本用户的自动更新会失败因为系统认为这是两个不同的应用。visible: false这个技巧用得好能显著提升体验。做法是窗口先隐藏前端在合适的时机比如首屏数据加载完成通过getCurrentWindow().show()显示出来这样用户看到的是直接渲染好的界面而不是一段白屏闪烁。这就是所谓的「无闪烁启动」实际用起来观感差别挺明显的。2.2 前端 dev server 与 Rust 侧热重载的实操配置开发体验的核心是两套热更新各管一摊前端改动由 Vite或你用的构建工具负责改 JS/CSS 秒级刷新走的是 WebView 内部的模块热替换Rust 侧改动由 Tauri CLI 负责它监听src-tauri目录的文件变化触发 cargo 增量编译并重启整个进程。前端这边的重载要能正常работатьVite 配置得配合上 Tauri 的端口。用create-tauri-app生成的模板里已经写好了// vite.config.ts import { defineConfig } from vite; export default defineConfig({ clearScreen: false, server: { port: 1420, strictPort: true, watch: { ignored: [**/src-tauri/**], }, }, });strictPort: true是关键端口被占时直接报错而不是自动换端口。因为tauri.conf.json里的devUrl写死了 1420如果 Vite 偷偷换到 1421CLI 就会一直轮询 1420表现就是「命令跑起来了但窗口永远不出现」。这个坑我踩过一次找了大半天最后发现是另一个项目占着 1420 端口。watch.ignored里排除src-tauri也很重要否则前端 watcher 会去监听 Rust 编译产物Rust 一重建就触发前端全量刷新两边互相打架机器风扇狂转。Rust 侧的重编译默认是开启的CLI 会监听src-tauri/src下的.rs文件。但有个细节tauri.conf.json本身的改动不会自动重启改完配置要手动 CtrlC 重新tauri dev。这一点跟热词里常搜的「运行错误」高度相关很多人改了窗口尺寸、改了权限配置发现没生效以为配置写错了其实只是没重启。如果你的 Rust 编译特别慢依赖多的时候增量编译也要几十秒可以在Cargo.toml里给 dev profile 做点优化把依赖的优化级别调低、把自身代码的调试信息保留[profile.dev] incremental true opt-level 0 [profile.dev.package.*] opt-level 2这么配的意思是第三方依赖编译一次之后基本不变给它们开opt-level 2换来更快的运行速度你自己的代码经常改保持opt-level 0换来最快的编译速度。实测下来首次编译会慢一些但之后的增量编译明显更快是笔划算的买卖。2.3 启动卡住时的排查顺序我把启动阶段的问题整理成一个固定的排查顺序按这个顺序走基本不会漏。第一步看前端 dev server 有没有起来。另开一个终端手动 curl 一下devUrl能返回 HTML 说明前端没问题问题在 Rust 侧返回连接拒绝说明前端根本没起来去看beforeDevCommand的输出。第二步看 Rust 编译有没有报错。cargo 的报错一般很明确缺依赖、版本冲突、target 不匹配都会指出来。如果卡在Compiling不动多半是网络拉 crates 慢配个国内镜像源就能解决。第三步看窗口进程起来没。Windows 上任务管理器搜你的productNamemacOS 用ps aux | grep找。进程在但窗口不在就是前端加载失败右键窗口区域打开开发者工具的快捷键Windows/Linux 是CtrlShiftImacOS 是CmdOptionI看控制台报错。第四步看 IPC 通道。如果界面出来了但功能不响应打开控制台看有没有invoke相关的报错。Tauri 2 的权限模型比 1.x 严格得多命令没在 capabilities 里声明会被直接拒绝报错信息里有明确的权限缺失提示。3. tauri build打包全流程拆解3.1 打包前的四项准备工作tauri build不是一条命令就能完事的前面有几项准备不做产出的安装包要么装不上要么装上了有各种奇怪问题。第一项是图标。Tauri 要求一套多尺寸图标手动做很麻烦用 CLI 直接生成pnpm tauri icon ./app-icon.png给它一张 1024x1024 的 png它会在src-tauri/icons下生成 Windows 的.ico、macOS 的.icns、Linux 和通用场景的各个尺寸 png。原始图片必须是正方形且带透明通道否则生成的图标边缘会有黑边。我见过有人拿一张带白色背景的 jpg 转成 png 直接丢进去结果 macOS 的 Dock 图标是一坨白色方块找半天原因。第二项是版本号。tauri.conf.json里的version字段同时被用于安装包元数据、自动更新比对、Windows 注册表项。改版本号就改这一处不要去Cargo.toml或者package.json里改那两个跟安装包版本没有强绑定关系。如果想要三处同步写个小脚本在构建前跑一下。第三项是 identifier前面说过了发布前定好之后别动。第四项是bundle.targets。默认all会在当前平台上生成所有它能生成的格式。Windows 上是.msi和.exemacOS 上是.app和.dmgLinux 上是.deb、.rpm和.AppImage。想指定的话可以写成数组比如[nsis]只要 Windows 的 NSIS 安装包。3.2 Windows 打包NSIS 和 WiX 到底选哪个Windows 上 Tauri 提供两种安装包格式选择依据是分发场景。对比项NSIS.exeWiX.msi体积更小压缩率高相对大一些安装模式支持当前用户/全局/两者都选主要面向全局安装中文路径兼容性好偶有问题企业分发需要额外处理原生支持组策略、SCCM静默安装/S/quiet自定义界面需要改 nsi 脚本改 wxs学习成本高我的实际选择是面向普通用户的桌面应用一律用 NSIS因为它体积小、安装模式灵活、中文兼容好如果是给企业内部批量部署走 WiX 的 msi 更省事。Tauri 2 里 NSIS 的安装模式改成在配置里写{ bundle: { windows: { nsis: { installMode: both, languages: [SimpChinese, English], displayLanguageSelector: true } } } }installMode三个取值currentUser只装到当前用户目录不需要管理员权限perMachine装到 Program Files需要提权both让用户在安装时自己选。给普通用户的产品我建议用currentUser省掉 UAC 弹窗用户体验顺畅很多。还有个必须处理的点WebView2 运行时的分发策略。配置项是bundle.windows.webviewInstallMode四个取值各有取舍downloadBootstrapper安装包很小安装时联网下载。省体积但要求联网适合大部分场景。embedBootstrapper把引导程序打进安装包体积增加约 1.5MB仍然需要联网下载运行时本体。offlineInstaller把完整的 WebView2 运行时打进安装包体积直接涨一百多兆换来的完全离线可用。skip不管假设目标机器已装。适合内网环境自己统一部署过运行时的场景。Win10 用户基数还很大我一般用downloadBootstrapper同时在应用启动时检测 WebView2 是否可用不可用就引导用户去装。3.3 macOS 与 Linux 打包的现实约束macOS 这边有个硬约束必须先说清楚macOS 的安装包只能在 macOS 机器上打。这跟 Windows 打包可以在 Linux 上借助交叉编译工具链不同苹果的签名和打包工具链不开放。所以如果你只有一台 Windows 机器是没法产出 dmg 的这不是 Tauri 的限制是苹果生态的限制。macOS 上如果要同时支持 Intel 和 Apple Silicon用通用二进制pnpm tauri build --target universal-apple-darwin这个命令会把两个架构的产物合并成一个体积大约翻倍但一份安装包通吃。如果你的用户群集中在新机器上也可以只打aarch64-apple-darwin体积能省一半。Linux 上的情况更碎一些。deb 和 rpm 分别对应 Debian 系和 Red Hat 系AppImage 是通用格式双击就能跑不需要安装。AppImage 的构建依赖linuxdeploy有时候会因为缺少fuse相关组件而失败这是热词里fpm报错那一类问题的常见来源。遇到打包报错先看是不是缺系统工具而不是去改项目配置。关于跨平台打包我的建议很明确不要指望在一台机器上打出所有平台的包。Windows 打包最好在 Windows 上做macOS 必须用 macOSLinux 建议用对应发行版的容器。真正靠谱的做法是上 CI用三个平台的 runner 分别构建这块我在第 5 章会展开。3.4 体积优化把安装包从几十兆压到个位数Tauri 相对 Electron 的体积优势是它的核心卖点但默认配置下并不一定拿到最优结果。我做过一次对比同一个应用默认配置打出来 macOS 上是 4.5MB做了一轮优化之后降到 2.8MBWindows 的 NSIS 安装包从 3.2MB 降到 2.1MB。具体做法是给 release profile 加配置[profile.release] panic abort codegen-units 1 lto true opt-level s strip true逐条解释一下为什么这么写。lto true开启链接时优化让编译器跨 crate 做内联和死代码消除效果最明显但编译时间会明显变长codegen-units 1放弃并行代码生成把整个 crate 当一个单元优化同样是用编译时间换体积opt-level s面向体积优化而不是速度比opt-level 3小一截性能损失在大多数界面类应用里感知不到strip true去掉符号表能省掉不少panic abort让 panic 直接终止进程而不展开栈省掉展开相关的代码。注意panic abort会让catch_unwind失效如果你用了某些依赖这个机制的库会编译不过或者运行异常。遇到这种情况单独把它去掉其他几项保留。另外 GDB 调试 release 包的时候没有符号表会很痛苦需要调试时临时关掉strip。前端侧也有优化空间。Vite 默认的产物已经比较干净但要确认没有把 sourcemap 一起打进去build.sourcemap保持默认的 false。如果有大量图标和字体考虑做子集化和按需加载。另外排查体积构成可以用cargo bloat看 Rust 侧哪些 crate 占了大头有时候会发现某个功能只用了一个函数却引入了一个巨型依赖。4. 签名、更新与权限上线前必须过的坎4.1 代码签名的实际操作没签名的应用在现代操作系统上会遭到各种拦截。macOS 上未签名的应用默认打不开用户得去系统设置里手动放行这个体验在正式产品里不可接受。Windows 上未签名的安装包会触发 SmartScreen 警告用户要点「仍要运行」才能装。macOS 的签名和公证可以在 CI 里通过环境变量自动完成Tauri CLI 会读取这些变量export APPLE_CERTIFICATEbase64编码的p12证书 export APPLE_CERTIFICATE_PASSWORD证书密码 export APPLE_SIGNING_IDENTITYDeveloper ID Application: xxx (TEAMID) export APPLE_ID你的开发者账号 export APPLE_PASSWORD应用专用密码 export APPLE_TEAM_ID团队ID这里APPLE_PASSWORD必须是应用专用密码而不是账号登录密码这一点容易搞混。证书从钥匙串导出成 p12 之后再 base64 编码编码时不要带换行否则 CI 里解码会失败。Windows 的签名相对简单一些用signtool配合证书文件即可Tauri 也支持通过配置自动调用。如果没有购买代码签名证书至少把安装包的元数据填完整公司名、产品名、版本可以减少一些杀软误报的概率。4.2 自动更新的配置与踩坑Tauri 2 的自动更新由tauri-plugin-updater提供工作方式是应用启动时去配置的 endpoint 拉一个 JSON 清单比对版本号发现新版就下载更新包、校验签名、然后安装。签名校验用的是一对密钥先生成pnpm tauri signer generate -w ~/.tauri/myapp.key它会输出公钥和私钥。公钥填进tauri.conf.json的plugins.updater.pubkey私钥用于给更新包签名在 CI 里通过TAURI_SIGNING_PRIVATE_KEY和TAURI_SIGNING_PRIVATE_KEY_PASSWORD两个环境变量传给构建过程。私钥绝对不能进代码仓库这是底线。配置大致长这样{ bundle: { createUpdaterArtifacts: true }, plugins: { updater: { endpoints: [ https://your-server.com/updates/{{target}}/{{arch}}/{{current_version}} ], pubkey: 公钥内容 } } }createUpdaterArtifacts打开之后构建会额外产出.sig签名文件这个文件必须跟更新包一起传到服务器上否则客户端校验不过会拒绝更新。实际踩过的坑有两个。一个是 endpoint 返回的 JSON 格式必须是 Tauri 规定的结构字段名写错了客户端会静默失败日志里只有一行级别很低的提示得开 debug 日志才能看到。另一个是更新包的下载地址在 JSON 里是绝对路径本地测试时写相对路径是不会被解析的。4.3 capabilities 权限模型导致的运行报错Tauri 2 相比 1.x 最大的变化之一就是权限系统。所有从前端调用的能力包括内置 API 和插件都必须在src-tauri/capabilities/下的配置文件里显式声明否则调用会被拒绝。{ $schema: ../gen/schemas/desktop-schema.json, identifier: default, description: 默认权限集, windows: [main], permissions: [ core:default, opener:default, dialog:default, fs:allow-read-text-file ] }这个设计的好处是安全边界清晰坏处是迁移和调试时容易漏。最常见的症状是开发的某个功能在 1.x 里能跑升到 2.x 之后报not allowed去控制台看会明确告诉你是哪个权限缺失把对应的权限加进去就行。但有些报错不会这么直白比如文件系统相关的操作可能会表现成「读到了空内容」而不是抛异常这时候要去检查fs相关的 scope 配置不只是加个fs:default就完事具体路径还需要在 scope 里放行。5. 常见问题速查与排查思路5.1 启动与运行阶段的典型问题我把这些年遇到过的启动类问题整理成一张速查表症状和原因一一对应症状大概率原因处理方向tauri dev卡住不弹窗devUrl 端口被占用或前端没起检查端口手动 curl devUrl窗口弹出但白屏frontendDist 路径错误或 CSP 拦截打开控制台看报错检查相对路径报找不到 pkg-configLinux 缺 webkit2gtk-4.1-dev装齐系统依赖调用命令报 not allowedcapabilities 缺权限声明补权限配置后重启界面能显示但点击无反应IPC 通道未建立或 JS 报错看控制台检查 invoke 参数中文显示成方框系统缺少字体或前端未指定字族显式指定字体栈第五行那个问题值得多说两句。Tauri 的invoke参数名默认会被转换成 camelCaseRust 侧如果是 snake_case 的参数需要加#[tauri::command(rename_all snake_case)]或者在配置里改全局约定。不少人遇到的是「参数传过去了但 Rust 收到的是 None」查半天以为是序列化问题其实就是命名约定没对齐。5.2 打包与分发阶段的坑打包阶段的问题集中在三块依赖缺失、配置错误、平台差异。fpm相关的报错基本都出在 Linux 打 deb/rpm 上检查是不是缺了ruby或者fpm本身没装Tauri 会尝试自动装但有时候因为权限问题装不上手动装一下更稳。Windows 打包报找不到light.exe或者candle.exe是 WiX 工具链没装好。Tauri 会在首次构建时自动下载网络不好的话会失败可以手动装 WiX 3.x 并加到 PATH。macOS 打包报签名失败先确认证书在钥匙串里的名称跟APPLE_SIGNING_IDENTITY完全一致包括括号里的团队 ID。名称对不上会报一个很含糊的错误。另外有个经常被问到的问题能不能在 Windows 上打 Linux 包。技术上部分可行用 Docker 加交叉编译工具链但 glibc 版本差异会导致打出来的包在老发行版上跑不起来。我的建议是用 Docker 跑对应发行版的容器来构建比如用ubuntu:20.04的容器打 deb兼容性覆盖面会好很多。5.3 用 CI 流水线自动打包手动在三台机器上打包是低效且容易出错的正规做法是上 CI。GitHub Actions 是最省事的方案因为它的 runner 天然覆盖三个平台。核心思路是写一个矩阵任务每个平台跑自己的构建命令。关键的处理点有这么几个Rust 缓存要配上否则每次构建都要重新编译所有依赖一次要十几二十分钟密钥类的东西全部走仓库 secrets包括 macOS 的证书、Windows 的签名证书、更新用的私钥构建产物用actions/upload-artifact收集然后在发布时统一上传。还有个细节是 macOS 的 runner 架构GitHub 提供了 arm64 和 x64 两种要打通用二进制的话需要两个架构都跑一遍或者用支持 universal 的构建方式。另外构建 macOS 应用时要注意 runner 上的 Xcode 版本版本太低会导致某些系统 API 链接失败。最后分享一个我自己用的小技巧在beforeBuildCommand里挂一个脚本自动把 git 短哈希和构建时间写进前端的一个常量文件打包出来的应用在「关于」页面显示这两个信息。这样用户反馈问题时你一眼就能知道对方装的是哪个构建产物省掉一轮来回确认版本的沟通。这个做法在打包频繁迭代的阶段特别好用。
网站建设高端定制企业官网