从 WPF 到 Tauri:桌面应用开发、调试、构建与发布完整入门
从 WPF 到 Tauri:桌面应用开发、调试、构建与发布完整入门
本文写给熟悉 WPF、C#、Visual Studio 和 MSBuild,但第一次接触 Tauri、Vue、TypeScript、Rust、Vite 与 GitHub Actions 的桌面开发者。
文中的项目名、仓库地址和业务代码均为通用示例,可以直接迁移到自己的项目中。
前言
刚接触 Tauri 项目时,WPF 开发者往往会遇到一连串困惑:
- 没有
.sln和.csproj,项目应该从哪里打开? - 应该使用 Visual Studio、VS Code,还是其他 IDE?
- Vue、TypeScript、Rust、Node.js 和 Tauri 分别负责什么?
- 为什么运行程序要输入命令,而不是直接按 Visual Studio 的绿色三角?
- CodeLLDB、
tasks.json和launch.json分别有什么用? - 为什么源码目录只有一点点代码,整个项目却占用几十 GB?
- 为什么 Tauri 安装包很小,又不需要用户安装 Node.js 和 Rust?
- GitHub 为什么能自动生成 Windows 和 macOS 安装包?每次推送都会发布吗?
这些问题的根源是:WPF 把编辑、构建、包管理、调试和发布高度集成在 Visual Studio 中,而 Tauri 把多个成熟生态组合在一起。理解每一层的职责后,它并不比 WPF 神秘,只是项目组织方式不同。
一、先建立整体模型
一个常见的 Tauri 桌面应用可以理解为:
Tauri 桌面应用
├─ 前端:HTML + CSS + JavaScript
│ └─ 通常使用 Vue、React、Svelte 等框架开发
│
├─ 原生端:Rust
│ ├─ 创建窗口
│ ├─ 操作系统托盘
│ ├─ 文件读写
│ ├─ 系统通知
│ ├─ 后台任务
│ └─ 调用操作系统 API
│
└─ Tauri
├─ 管理桌面窗口和 WebView
├─ 连接 JavaScript 与 Rust
├─ 提供权限和插件机制
└─ 生成 EXE、安装包、DMG 等产物
这里的“前端”和“后端”都运行在用户电脑上,不代表一定存在远程服务器。
可以先用 WPF 做一个职责对照:
| WPF/.NET | Tauri + Vue + Rust |
|---|---|
| WPF 应用框架 | Tauri |
| XAML | Vue 的 <template> |
| ResourceDictionary、Style | CSS |
| ViewModel、部分 Code-behind | Vue 的 <script setup lang="ts"> |
| Binding | v-model、:属性、{{ 文本 }} |
| Click、ICommand | @click 调用 TypeScript 函数 |
| C# Service | Rust command |
| C# 事件、消息总线 | Tauri event |
| NuGet | pnpm 和 Cargo |
| MSBuild | Vite、Cargo、Tauri CLI |
| Visual Studio 调试器 | 浏览器开发者工具、CodeLLDB 或 Windows Debugger |
这个对照只是帮助建立概念,并不表示两者实现机制完全一样。
二、Windows 和 macOS 上如何显示网页界面
Tauri 不会像 Electron 那样把一整套 Chromium 和 Node.js 打进每个应用,而是复用操作系统提供的 WebView。
| 操作系统 | Tauri 使用的 WebView | 浏览器内核 |
|---|---|---|
| Windows | Microsoft Edge WebView2 | Chromium |
| macOS | WKWebView | WebKit |
| Linux | WebKitGTK | WebKit |
因此:
- Windows 通常使用系统安装或更新的 WebView2。
- macOS 直接使用系统自带的 WKWebView。
- Linux 依赖系统中的 WebKitGTK。
这正是 Tauri 安装包通常比 Electron 小很多的重要原因。
同一套 HTML、CSS 和 JavaScript 在 WebView2 与 WKWebView 中可能存在少量差异,例如字体渲染、滚动条、日期输入框、新 CSS 特性和系统控件外观。因此,发布 macOS 版本前仍然需要在真实 Mac 或对应测试环境中验证。
调试前端界面时通常使用:
Windows:Ctrl + Shift + I
macOS:Command + Option + I
它们分别打开 WebView2 或 WKWebView 的开发者工具。
三、为什么 Tauri 不需要打包 Node.js 和 Rust 运行时
这是 WPF 开发者最关心的问题之一。
3.1 .NET 自包含为什么比较大
典型的 .NET Framework-dependent 发布依赖目标电脑已经安装匹配的 .NET Runtime。
如果使用 Self-contained 发布,则需要携带:
业务程序集
CoreCLR
.NET 基础类库
JIT 及相关原生组件
运行时依赖
因此产物体积明显增加。
3.2 Rust 为什么不需要额外运行时
Rust 使用提前编译:
Rust 源码
↓ rustc
Windows/macOS/Linux 原生机器码
Rust 程序通常不依赖一个类似 CoreCLR、JVM 或 Node.js 的大型通用运行时。编译器将程序需要的代码编译进最终二进制,同时继续调用操作系统提供的动态库和 API。
Rust 不是“一次编译,到处运行”。不同平台需要分别构建:
Windows x64 → Windows x64 EXE
macOS ARM64 → Apple Silicon 应用
macOS x64 → Intel Mac 应用
Linux x64 → Linux x64 可执行文件
3.3 Node.js 为什么不用进入安装包
Node.js 在常见的 Vue + Tauri 项目中主要用于开发和构建:
Vue/TypeScript 源码
↓ Node.js 运行 Vite
HTML + CSS + JavaScript 静态文件
正式运行时,JavaScript 由系统 WebView 执行,不由 Node.js 执行,所以安装包不需要携带 Node.js。
开发模式和正式模式的区别如下。
开发模式:
Node.js 启动 Vite 开发服务器
↓
提供 http://localhost:某端口
↓
Tauri 窗口中的 WebView 加载该地址
↓
支持热更新
正式构建:
Node.js 运行 Vite
↓
输出 dist 中的 HTML/CSS/JavaScript
↓
Tauri 将静态资源嵌入桌面应用
↓
用户电脑直接运行,不需要 Vite 和 Node.js
可以把这些工具分成两组:
| 只在开发和构建时使用 | 最终用户运行时使用 |
|---|---|
| Node.js | Rust 编译后的原生程序 |
| pnpm | 系统 WebView |
| Vite | 嵌入应用的 HTML/CSS/JavaScript |
| TypeScript 编译器 | 操作系统 API 和动态库 |
| Cargo | 业务数据和应用资源 |
| rustc |
3.4 为什么 Debug 目录很大,安装包却很小
Debug 构建会包含大量调试信息,并生成 PDB、增量编译缓存和依赖中间文件。Rust 的泛型实例化以及多个构建配置,也会使 target 目录快速增长到数 GB甚至数十 GB。
Release 构建则可以开启优化和符号裁剪:
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
panic = "abort"
strip = true
因此应区分:
项目构建缓存很大 ≠ 发给用户的安装包很大
四、Vue 和 Rust 分别负责什么
4.1 Vue/TypeScript 的职责
Vue 不只是“画界面”,通常还承担界面层和交互层职责:
- 页面布局和组件组织。
- 用户输入和双向绑定。
- 页面切换。
- 表单临时状态。
- 界面级输入校验。
- 列表排序和显示格式化。
- 主题、颜色、语言切换。
- 加载状态和进度显示。
- 点击、选择、展开、关闭等交互。
- 将用户操作转换为对 Rust 的调用。
一个简单组件可能写成:
<script setup lang="ts">
import { ref } from 'vue'
import { invoke } from '@tauri-apps/api/core'
const title = ref('')
const message = ref('')
async function save() {
await invoke('save_item', { title: title.value })
message.value = '保存成功'
}
</script>
<template>
<input v-model="title" />
<button @click="save">保存</button>
<span>{{ message }}</span>
</template>
和 WPF 对照:
v-model ≈ 双向 Binding
@click ≈ Click 或 ICommand
ref/reactive ≈ 可通知界面的状态属性
computed ≈ 根据其他状态计算的属性
4.2 Rust 的职责
Rust 更适合承担需要原生能力、后台可靠性和安全边界的工作,例如:
- 文件读写和配置存储。
- 数据校验和规范化。
- 数据库访问。
- 后台定时任务。
- 系统托盘。
- 原生窗口管理。
- 系统通知。
- 开机自启。
- 单实例运行。
- 调用操作系统 API。
- 启动外部进程。
- 下载文件和校验哈希。
- 自动更新。
- 权限敏感操作。
- CPU 密集型计算。
- 与已有 C/C++ 动态库进行 FFI 调用。
Rust 端可以暴露命令:
#[tauri::command]
fn save_item(title: String) -> Result<(), String> {
if title.trim().is_empty() {
return Err("标题不能为空".to_string());
}
// 保存数据……
Ok(())
}
并在 Tauri 初始化时注册:
.invoke_handler(tauri::generate_handler![save_item])
4.3 Vue 与 Rust 如何通信
Vue 调用 Rust 通常使用 invoke:
Vue/TypeScript
↓ invoke("save_item", 参数)
Tauri IPC
↓
Rust command
Rust 主动通知 Vue 通常使用事件:
Rust 后台任务检测到事件
↓ emit
Tauri IPC
↓ listen
Vue 更新界面
调用方向是双向的:
Vue → Rust:命令调用
Rust → Vue:事件通知
需要注意,Vue 对象与 Rust 对象不在同一块内存中。参数会被序列化后跨越 WebView 与原生端边界,因此 TypeScript interface 和 Rust struct 需要保持字段兼容。
4.4 为什么 Tauri 选择 Rust,而不是 C++
从架构上说,C++ 当然也能创建原生窗口、托管 WebView2、调用系统 API。但是 Tauri 的核心生态以 Rust 为中心。
Rust 的主要优势是:
- 编译成原生机器码。
- 没有 GC 暂停。
- 通过所有权和借用检查减少悬空指针、越界和 use-after-free。
- 对多线程共享数据有较强的编译期约束。
- Cargo 统一处理依赖、构建和测试。
- Tauri 的窗口、托盘、插件、权限和 IPC 能力可直接使用。
- 跨平台条件编译比较统一。
如果业务已经有成熟 C++ 库,可以通过 Rust FFI 调用 C++,也可以把 C++ 程序作为 sidecar 子进程。若完全不想使用 Rust,则通常需要选择其他桌面框架,而不是继续使用标准 Tauri 后端。
五、典型 Tauri + Vue 项目目录
一个常见项目大致如下:
desktop-app/
├─ .github/
│ └─ workflows/ GitHub Actions 工作流
├─ .vscode/ VS Code 项目配置
├─ src/ Vue/TypeScript 前端源码
│ ├─ assets/ 前端图片和资源
│ ├─ components/ Vue 组件
│ ├─ composables/ 可复用的 Vue 组合逻辑
│ ├─ App.vue 根组件
│ ├─ main.ts 前端入口
│ ├─ i18n.ts 项目自定义的多语言模块
│ ├─ types.ts TypeScript 类型
│ └─ style.css 样式
├─ src-tauri/ Tauri/Rust 原生项目
│ ├─ capabilities/ Tauri 权限声明
│ ├─ icons/ 应用及安装包图标
│ ├─ src/
│ │ ├─ main.rs Rust 可执行程序入口
│ │ └─ lib.rs Rust 库入口和主要逻辑
│ ├─ build.rs Cargo 构建脚本
│ ├─ Cargo.toml Rust 项目和依赖配置
│ ├─ Cargo.lock Rust 依赖锁定文件
│ └─ tauri.conf.json Tauri 窗口、构建和打包配置
├─ dist/ 前端构建产物
├─ node_modules/ JavaScript 依赖
├─ index.html Vite HTML 入口
├─ package.json 前端依赖和脚本
├─ pnpm-lock.yaml pnpm 依赖锁定文件
├─ tsconfig.json TypeScript 配置
└─ vite.config.ts Vite 配置
其中:
src/是 Vue/Vite 的常见约定,不是 Tauri 强制要求。src-tauri/是 Tauri 的典型默认目录。dist/是 Vite 常见的输出目录,可以通过配置修改。node_modules/和src-tauri/target/都是可重新生成的依赖或构建目录。
某些仓库还会有:
site/ 宣传网站
scripts/ 构建或发布辅助脚本
tests/ 测试
docs/ 文档
这些不是 Tauri 强制目录,而是项目自身的组织方式。site/ 通常表示与桌面程序放在同一仓库中的宣传网站,可以类比一个 Visual Studio 解决方案中同时包含桌面项目和网站项目。
六、哪些文件名固定,哪些只是约定
可以按三类理解。
6.1 工具要求或基本固定的名称
| 文件或目录 | 使用者 |
|---|---|
package.json |
Node.js 包管理生态 |
Cargo.toml |
Cargo |
Cargo.lock |
Cargo |
pnpm-lock.yaml |
pnpm |
.github/workflows/ |
GitHub Actions |
src-tauri/src/main.rs |
Cargo 默认二进制入口 |
src-tauri/src/lib.rs |
Cargo 默认库入口 |
src-tauri/build.rs |
Cargo 构建脚本 |
6.2 工具默认使用、但可以配置的名称
| 名称 | 说明 |
|---|---|
src/ |
常见前端源码目录 |
dist/ |
常见前端输出目录 |
index.html |
Vite 默认 HTML 入口 |
vite.config.ts |
Vite 配置 |
tsconfig.json |
TypeScript 配置 |
tauri.conf.json |
Tauri 默认配置文件 |
src-tauri/ |
Tauri 默认原生目录 |
这些通常可以通过参数或配置改变,但没有充分理由时不建议改,因为标准名称更容易被文档、扩展和其他开发者识别。
6.3 项目自己定义的名称
App.vue
types.ts
i18n.ts
error.ts
useUpdater.ts
这些文件都可以改名,但必须同步修改所有 import 和脚本引用。
例如:
import { translate } from './i18n'
如果把文件改成 translations.ts,引用也要改成:
import { translate } from './translations'
七、Vue 多语言为什么常见 i18n.ts
i18n 是 internationalization 的缩写,首尾字母之间有 18 个字母,所以行业中经常使用这个名称。
Vue 并不要求多语言文件必须叫 i18n.ts,也不要求必须采用某一种实现。
可选方案包括:
src/i18n.ts 自定义 TypeScript 翻译模块
src/locales/zh-CN.json JSON 语言资源
src/locales/en.json
src/resources/*.ts 按语言拆分 TS 模块
vue-i18n 第三方国际化框架
服务端或数据库读取 动态语言资源
直接写在组件中 适合无需切换语言的小项目
和 WPF 的对应关系大致是:
| WPF | Vue |
|---|---|
.resx |
JSON 或 TypeScript 语言资源 |
| ResourceDictionary | 语言对象或模块 |
| 自定义 XML | 自定义 JSON/XML 读取逻辑 |
| 翻译服务 | translate() 或 $t() |
| 第三方本地化库 | vue-i18n |
| XAML 中写死文字 | template 中写死文字 |
小型双语应用采用自定义 i18n.ts 的好处是依赖少、逻辑直观,并且可以通过 TypeScript 对翻译键做类型检查。大型应用则更适合采用 vue-i18n 和拆分后的语言资源文件。
八、开发环境由哪些工具组成
Windows 上常见的 Tauri 开发环境包括:
Node.js
pnpm/npm/yarn
Rust stable
Cargo
MSVC C++ 构建工具
WebView2
VS Code 或其他 IDE
macOS 上则通常需要:
Node.js
pnpm/npm/yarn
Rust stable
Cargo
Xcode Command Line Tools
WKWebView(系统自带)
VS Code、RustRover 等 IDE
查看本机环境可以使用:
node --version
pnpm --version
rustc --version
cargo --version
pnpm exec tauri --version
pnpm exec tauri info
如果某个 AI 开发工具或集成环境能够编译,而普通 PowerShell 不能执行 node 或 pnpm,通常说明该工具在自己的进程环境中注入了内部运行时路径。
内部运行时技术上可以临时借用,但不适合作为长期正式开发环境,因为:
- 缓存目录可能在升级后变化。
- 清理缓存后可能消失。
- 版本由工具管理,不由项目管理。
- VS Code、Git hooks 和其他程序不一定能找到它。
长期开发应单独安装项目需要的 Node.js、pnpm 和 Rust 工具链,并确保它们位于用户或系统 PATH 中。
九、常用开发工具如何选择
9.1 VS Code
这是 Vue + Tauri 项目中非常常见的组合:
VS Code
├─ Vue (Official)
├─ rust-analyzer
├─ CodeLLDB
└─ Tauri 扩展
各插件职责如下:
| 插件 | 职责 |
|---|---|
| Vue (Official) | Vue、模板和 TypeScript 提示 |
| rust-analyzer | Rust 补全、检查、跳转和重构 |
| CodeLLDB | Rust 原生程序断点调试 |
| Tauri | 配置提示和部分辅助命令 |
9.2 RustRover
RustRover 更接近传统完整 IDE,对 Cargo、Rust 代码和调试的集成更统一。但 Vue 前端和 Tauri 的双进程开发流程仍然需要运行配置,不能理解为安装后完全零配置。
9.3 Visual Studio
Visual Studio 在 Windows Tauri 开发中通常承担:
- 提供 MSVC 编译器和链接器。
- 提供 Windows SDK。
- 在需要时提供 Windows 原生调试器。
它通常不是 Vue + Tauri 项目的主要编辑器,但依然是 Windows 原生构建环境的重要组成部分。
十、编译器、任务和调试器不是同一件事
安装 CodeLLDB 后仍然可能需要 tasks.json 和 launch.json,因为它们职责不同:
Cargo/rustc → 编译 Rust
Vite/vue-tsc → 检查并构建前端
Tauri CLI → 协调前端与 Rust,打包桌面应用
tasks.json → 告诉 VS Code 先执行哪些构建命令
launch.json → 告诉 VS Code 启动哪个程序、使用哪个调试器
CodeLLDB → 程序启动后负责断点、单步、变量和调用栈
这和 Visual Studio 中由项目系统隐式完成的工作类似,只是 VS Code 将配置显式写在 JSON 文件中。
10.1 日常热更新入口
.vscode/launch.json 可以配置:
{
"version": "0.2.0",
"configurations": [
{
"name": "桌面应用:开发运行(热更新)",
"type": "node-terminal",
"request": "launch",
"command": "pnpm tauri:dev",
"cwd": "${workspaceFolder}",
"autoAttachChildProcesses": false
}
]
}
这个入口适合日常改界面和观察效果:
- Vue、TypeScript、CSS 通常支持热更新。
- Rust 修改后 Tauri 会重新编译和重启。
- 前端使用 WebView 开发者工具调试。
- 默认不会自动附加 Rust 断点调试器。
10.2 Rust 断点调试入口
tasks.json 中先定义构建任务:
{
"version": "2.0.0",
"tasks": [
{
"label": "构建 Debug EXE",
"type": "shell",
"command": "pnpm",
"args": ["tauri:build", "--debug", "--no-bundle"],
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
然后在 launch.json 中使用 CodeLLDB:
{
"name": "桌面应用:Rust 断点调试",
"type": "lldb",
"request": "launch",
"program": "${workspaceFolder}/src-tauri/target/debug/app.exe",
"cwd": "${workspaceFolder}",
"preLaunchTask": "构建 Debug EXE",
"sourceLanguages": ["rust"],
"env": {
"RUST_BACKTRACE": "1"
}
}
此时流程是:
按 F5
→ 执行 preLaunchTask
→ 构建前端静态资源
→ 编译 Rust Debug EXE
→ CodeLLDB 启动 EXE
→ Rust 断点生效
这个模式通常没有前端热更新,修改后需要停止并重新调试。
10.3 为什么常把两种模式分开
| 模式 | 前端热更新 | Rust 断点 |
|---|---|---|
tauri dev |
有 | 默认无 |
| 构建 Debug EXE + CodeLLDB | 无 | 有 |
也可以配置为同时启动 Vite 和 CodeLLDB,但会增加端口管理、停止顺序、文件监听和重复启动的问题。初学阶段将二者分开更容易理解和排错。
十一、日常运行、检查与打包
11.1 首次安装依赖
pnpm install --frozen-lockfile
11.2 日常开发
pnpm tauri:dev
这相当于日常“运行查看效果”,不需要每次生成安装包。
11.3 前端检查
pnpm build
pnpm test
常见的前端构建脚本类似:
"build": "vue-tsc --noEmit && vite build"
含义是:
vue-tsc --noEmit → 检查 Vue/TypeScript 类型
vite build → 生成 dist 静态资源
11.4 Rust 检查和测试
cargo check --manifest-path .\src-tauri\Cargo.toml
cargo test --locked --manifest-path .\src-tauri\Cargo.toml
11.5 生成 Windows 安装包
pnpm tauri:build --bundles nsis
常见输出位置为:
src-tauri/target/release/app.exe
src-tauri/target/release/bundle/nsis/*-setup.exe
只有以下情况通常需要打包:
- 测试正式 Release 行为。
- 测试安装和升级。
- 给别人分发。
- 准备 GitHub Release。
普通修改和日常调试不需要反复生成安装包。
十二、为什么目录占用几十 GB,以及如何清理
最容易膨胀的是:
src-tauri/target/ Rust 编译缓存、依赖和调试符号
node_modules/ JavaScript 依赖
dist/ 前端构建产物
可以安全重新生成的目录包括:
| 目录 | 是否可删除 | 恢复方式 |
|---|---|---|
src-tauri/target/ |
可以 | 下次 Cargo/Tauri 构建重新生成 |
node_modules/ |
可以 | pnpm install --frozen-lockfile |
dist/ |
可以 | pnpm build |
src-tauri/gen/ |
一般可以 | Tauri 重新生成 |
不要删除:
src/
src-tauri/src/
package.json
pnpm-lock.yaml
Cargo.toml
Cargo.lock
tauri.conf.json
.git/
清理 Rust 构建缓存:
cargo clean --manifest-path .\src-tauri\Cargo.toml
清理 pnpm 无用的全局包缓存:
pnpm store prune
清理后第一次编译会变慢,这是正常现象。
十三、版本号与安装升级
Tauri 项目中版本号可能同时存在于:
package.json
src-tauri/Cargo.toml
src-tauri/tauri.conf.json
是否必须三个都一致,取决于项目和发布工作流。为了减少歧义,通常应保持一致:
package.json 0.9.0
Cargo.toml 0.9.0
tauri.conf.json 0.9.0
Git 标签 v0.9.0
使用三段式语义版本:
0.8.0 正确
0.9.0 正确
0.9 不建议,某些发布脚本会直接拒绝
NSIS 安装包能否覆盖旧版本,通常取决于:
- 应用 identifier 是否保持不变。
- 安装模式是否保持不变。
- 新版本号是否高于旧版本。
- 旧程序是否已经退出。
- 安装包和便携版是否被混淆。
便携版 EXE 没有标准安装升级过程,通常需要退出旧程序后手动替换文件。
NSIS 默认界面比较朴素,可以配置图标、顶部图片、侧边图片、语言、许可协议,也可以进一步使用自定义 NSIS 模板。完全自定义会提高构建和维护成本。
十四、GitHub Actions 如何自动构建和发布
GitHub 不会自动分析项目并猜测如何编译。它只会读取:
.github/workflows/*.yml
然后按照工作流中的 on: 条件决定何时执行。
14.1 普通推送不一定触发发布
一个典型桌面发布工作流可能写成:
name: Release
on:
push:
tags: ['v*']
workflow_dispatch:
含义是:
- 推送以
v开头的 Git 标签时执行。 - 也允许在 GitHub Actions 页面手动运行。
普通推送:
git push origin master
不会触发这个 Release 工作流。
仅仅把版本号从 0.8.0 改成 0.9.0 并推送,也不会触发。版本号是项目数据,真正的触发条件是标签事件。
14.2 正式发布的典型流程
git add .
git commit -m "发布 0.9.0"
git push origin master
git tag v0.9.0
git push origin v0.9.0
最后一条推送标签的命令匹配:
tags: ['v*']
随后 GitHub Actions 才开始构建。
发布脚本还可以校验:
package.json 版本 0.9.0
Cargo.toml 版本 0.9.0
tauri.conf.json 版本 0.9.0
Git 标签 v0.9.0
如果推送了 v0.9.0,而代码中的版本仍是 0.8.0,工作流会被触发,但应在校验阶段失败。
14.3 Windows 和 macOS 为什么能同时构建
工作流会明确指定 Runner 和目标:
strategy:
matrix:
include:
- os: windows-latest
target: x86_64-pc-windows-msvc
bundles: nsis
- os: macos-latest
target: aarch64-apple-darwin
bundles: app,dmg
这不是 GitHub 自动识别出来的,而是工作流作者明确告诉 GitHub:
Windows Runner
→ 安装 Node、pnpm、Rust
→ 测试
→ 构建 Windows EXE 和 NSIS
macOS Runner
→ 安装 Node、pnpm、Rust
→ 测试
→ 构建 Apple Silicon App 和 DMG
所有构建成功后,工作流可以上传附件、生成更新清单,并将 Release 草稿转为公开版本。
14.4 网站发布可以使用不同触发条件
网站工作流可能只在网站文件发生变化时运行:
on:
push:
branches: [master]
paths:
- 'site/**'
- '.github/workflows/pages.yml'
还可以在桌面 Release 成功后自动重建网站:
on:
workflow_run:
workflows: [Release]
types: [completed]
最终形成:
普通业务代码推送
→ 不发布桌面安装包
网站代码推送
→ 更新 GitHub Pages
推送 v0.9.0 标签
→ 发布 Windows/macOS 安装包
→ Release 成功
→ 网站重新读取最新正式版本
→ 更新下载页面
十五、一套适合 WPF 开发者的日常流程
开发前
node --version
pnpm --version
cargo --version
日常修改和查看效果
pnpm tauri:dev
或者在 VS Code 中选择“开发运行(热更新)”后按 F5。
调试前端
打开 WebView 开发者工具
→ 查看 Console
→ 检查 Elements 和 CSS
→ 在 Sources 中设置 JavaScript/TypeScript 断点
调试 Rust
在 .rs 文件中设置断点
→ 选择 Rust 断点调试配置
→ F5
→ CodeLLDB 停在断点
也可以临时输出:
println!("开始保存");
dbg!(&data);
开启 Rust panic 调用栈:
$env:RUST_BACKTRACE = '1'
pnpm tauri:dev
提交代码前
pnpm build
pnpm test
cargo test --locked --manifest-path .\src-tauri\Cargo.toml
git status --short
git diff
需要安装包时
pnpm tauri:build --bundles nsis
准备正式发布时
同步版本号
→ 执行前端与 Rust 测试
→ 提交并推送代码
→ 创建 vX.Y.Z 标签
→ 推送标签
→ GitHub Actions 自动构建和发布
十六、从 WPF 迁移思维时最容易踩的坑
1. 把 tauri dev 当成完整断点调试
tauri dev 主要负责开发运行和热更新。Rust 断点还需要调试器和正确的启动配置。
2. 认为 CodeLLDB 会自动完成构建
CodeLLDB 是调试器,不是 Tauri 构建系统。构建顺序应由 task、Cargo、Vite 和 Tauri CLI 管理。
3. 每次修改都生成安装包
日常开发使用 tauri dev。安装包只在测试安装行为或准备交付时生成。
4. 把 node_modules 当成 Node.js 运行时
node_modules 是项目依赖,不是 Node.js 本体。Node.js 需要单独安装或由版本管理器提供。
5. 把 src-tauri/target 当成项目源码
它是编译缓存和产物,可以清理。真正的 Rust 源码位于 src-tauri/src。
6. 认为修改版本号就会触发 GitHub 发布
是否触发取决于 workflow 的 on: 配置。常见发布条件是推送 v* 标签,而不是检测文件内容变化。
7. 忽略开发版和安装版的单实例或数据目录冲突
如果开发版和安装版使用相同 identifier、单实例配置和数据目录,已经运行的安装版可能接管新进程,调试数据也可能与正式数据共用。调试前应退出托盘中的正式版本,并备份重要数据。
结语
WPF 与 Tauri 的核心差异,不在于一个“高级”、另一个“低级”,而在于工具链的组织方式:
WPF
Visual Studio 将项目、编译、调试、设计器和发布集中起来
Tauri
Vue/Vite 负责前端
Cargo/rustc 负责原生代码
Tauri CLI 负责协调和打包
VS Code/RustRover 负责编辑与调试体验
GitHub Actions 负责跨平台自动构建和发布
一旦把“编辑器、编译器、构建任务、调试器、运行时、WebView 和发布工作流”拆开理解,Tauri 项目就不再是一堆陌生目录,而是一条清晰的流水线:
编写 Vue 界面和 Rust 原生逻辑
→ 开发模式热更新
→ 分别调试前端和 Rust
→ 构建静态网页与原生程序
→ 打包各平台安装文件
→ 通过 Git 标签触发自动发布
对于已经熟悉 WPF 的开发者,真正需要学习的不是如何重新做桌面开发,而是如何把已有的界面、绑定、服务、事件、系统集成和发布经验,映射到 Vue、Rust、Tauri 和现代自动化工具链中。

浙公网安备 33010602011771号