📚 全栈开发学习系列
第 31 篇 · 阶段五:扩展与部署
✅ 阶段一:编程基础(1-8篇) 已完成
✅ 阶段二:Web 后端(9-16篇) 已完成
✅ 阶段三:前端深化(17-25篇) 已完成
✅ 阶段四:跨平台 App(26-30篇) 已完成
📌 阶段五:扩展与部署 进行中
✓ 31 Tauri 桌面应用入门 32 Docker 容器化 33 Nginx 反向代理 34 CI/CD 自动化部署
读完本篇你将能:
✓ 理解 Tauri 的架构原理与 Electron 的本质区别
✓ 使用 Tauri 2.x 脚手架创建第一个桌面应用
✓ 通过 Command 系统实现前端调用 Rust 后端
✓ 掌握事件系统实现后端向前端推送消息
✓ 了解 Tauri 插件生态与打包发布流程

Tauri 桌面应用入门:Rust + Web 轻量开发

全栈开发学习系列 · 第 31 篇 · 阶段五扩展层
📑 本篇目录
一、为什么选择 Tauri:与 Electron 的架构对比
二、环境准备:Rust + Node.js 双栈安装
三、项目初始化:Tauri 2.x 脚手架实战
四、Command 系统:前端调用 Rust 函数
五、事件系统:后端向前端推送消息
六、插件生态与系统能力调用
七、常见错误与三层练习
八、知识回顾与下一篇预告

一、为什么选择 Tauri:与 Electron 的架构对比

如果你用过 VS Code、Discord、Figma 这些桌面应用,大概率接触过 Electron——把 Chromium 浏览器和 Node.js 打包进应用,用 Web 技术写桌面软件。但 Electron 有个老毛病:一个简单的记事本应用动辄 100MB+ 起步,内存占用几百 MB,就像为了煮一杯茶搬来一整座锅炉房。

Tauri 走了另一条路:不打包浏览器,直接用操作系统自带的 WebView。Windows 用 WebView2,macOS 用 WKWebView,Linux 用 WebKitGTK。后端用 Rust 编写,通过 IPC 桥与前端通信。这个设计带来的直接好处是:包体积只有 Electron 的 1/10 ~ 1/20,内存占用降低 50% 以上。

对比维度 Electron Tauri 2.x
渲染引擎 打包 Chromium(~80MB) 系统 WebView(0 额外体积)
后端语言 Node.js(JavaScript) Rust
典型包体积 80 ~ 200 MB 3 ~ 15 MB
内存占用(空应用) ~150 MB ~30 MB
跨平台 Win / macOS / Linux Win / macOS / Linux + iOS / Android
学习曲线 前端开发者友好 需要 Rust 基础
💡 小贴士:Tauri 2.x 的重大变化
Tauri 1.x 只支持桌面端,2.x 起加入了 iOS 和 Android 支持,一套代码可以同时覆盖桌面和移动端。但本篇聚焦桌面开发,移动端能力作为拓展了解即可。

Tauri 的核心架构可以理解为三层:前端 WebView 层负责 UI 渲染,Rust 后端层负责业务逻辑和系统调用,中间通过IPC 桥进行安全通信。所有跨语言调用都经过安全沙箱校验,前端无法直接访问文件系统或网络,必须通过显式声明的 Command 或插件接口,这也是 Tauri 安全性更高的原因。

二、环境准备:Rust + Node.js 双栈安装

Tauri 需要两套环境:Rust 工具链用于编译后端,Node.js用于前端开发和构建。两个都装好了才能跑通完整流程。

2.1 Rust 安装

官方推荐用 rustup 管理 Rust 版本,一条命令搞定:

Bash - 安装 Rust 工具链
1
2
3
4
5
# macOS / Linux
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# 验证安装
rustc --version # rustc 1.8x.x

Windows 用户直接去 rust-lang.org 下载 rustup-init.exe 运行即可。安装完成后重启终端,确保 rustc 和 cargo 命令可用。

2.2 系统依赖

不同操作系统还需要额外的 WebView 编译依赖:

Bash - 各平台系统依赖
1
2
3
4
5
6
7
8
9
# macOS:安装 Xcode 命令行工具
xcode-select --install
# Ubuntu / Debian
sudo apt install libwebkit2gtk-4.1-dev build-essential \
  curl wget file libssl-dev libgtk-3-dev libayatana-appindicator3-dev
# Windows:安装 WebView2 Runtime(Win11 自带)
# 下载 Microsoft Edge WebView2 Runtime 安装包

2.3 Node.js 安装

前端脚手架需要 Node.js 18+,推荐用 fnm 或 nvm 管理版本:

Bash - 安装 Node.js 20 LTS
1
2
3
4
5
# 使用 fnm 安装(推荐,跨平台)
fnm install 20
fnm default 20
node --version # v20.x.x
npm --version  # 10.x.x

三、项目初始化:Tauri 2.x 脚手架实战

环境准备好后,用 Tauri 官方脚手架 create-tauri-app 一键生成项目。它支持多种前端框架模板:Vanilla、Vue、React、Svelte 等,本系列以 React + TypeScript 为主,就用这个组合。

Bash - 创建 Tauri 项目
1
2
3
4
5
6
# 交互式创建项目
npm create tauri-app@latest my-desktop-app
# 按提示选择:
# ? 前端框架:React
# ? 语言:TypeScript

3.1 项目结构解析

创建完成后,目录结构长这样:

Text - Tauri 项目结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
my-desktop-app/
├── src/                // 前端源码(React)
│  ├── App.tsx         // 主组件
│  ├── main.tsx        // 前端入口
│  └── ...
├── src-tauri/          // Rust 后端
│  ├── Cargo.toml      // Rust 依赖配置
│  ├── tauri.conf.json // Tauri 配置文件
│  ├── icons/          // 应用图标
│  ├── capabilities/  // 权限配置(2.x 新增)
│  └── src/
│    ├── lib.rs      // Tauri 插件注册
│    └── main.rs      // 应用入口
├── package.json        // 前端依赖
├── vite.config.ts      // Vite 配置
└── tsconfig.json       // TS 配置

关键文件有三个:tauri.conf.json 控制窗口大小、应用名称、权限等;src-tauri/src/main.rs 是 Rust 入口;src/App.tsx 是前端主组件。

3.2 启动开发模式

Tauri 的开发模式会同时启动 Vite 开发服务器和 Rust 编译,第一次启动因为要下载和编译 Rust 依赖会比较慢,耐心等几分钟:

Bash - 启动开发模式
1
2
3
4
cd my-desktop-app
npm install
npm run tauri dev

编译完成后会自动弹出一个桌面窗口,显示 Vite 的欢迎页面。恭喜你,第一个 Tauri 应用跑起来了!接下来我们看看前后端怎么通信。

四、Command 系统:前端调用 Rust 函数

Tauri 的前后端通信走 IPC 桥,但你不需要手动写底层协议。Tauri 封装了 Command 系统:在 Rust 端用 #[tauri::command] 宏标记函数,在前端用 invoke() 调用,参数和返回值自动序列化。

打个比方:Command 就像餐厅的菜单——后厨(Rust)把能做的菜(函数)列在菜单上(注册 command),顾客(前端)只能点菜单上有的菜,不能自己跑进厨房操作。这种显式声明的机制也是 Tauri 安全模型的核心。

4.1 定义 Rust Command

先在 Rust 端定义一个简单的加法函数,用宏标记为 command:

Rust - src-tauri/src/main.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 1. 定义 Command 函数
#[tauri::command]
fn add(a: i32, b: i32) -> i32 {
    a + b
}
// 2. 定义带错误处理的 Command
#[tauri::command]
fn greet(name: String) -> Result<String, String> {
    if name.trim().is_empty() {
        return Err("名字不能为空".to_string());
    }
    Ok(format!("你好,{}!来自 Rust 的问候", name))
}
// 3. 在 tauri::Builder 中注册
fn main() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![add, greet])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

三步流程:加宏 → 写函数 → generate_handler 注册。返回 Result 时,Err 会在前端抛出异常,天然适配 try/catch。

4.2 前端调用 invoke

前端使用 @tauri-apps/api 包的 invoke 函数,传入 command 名称和参数对象:

TypeScript - src/App.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { invoke } from '@tauri-apps/api/core'
import { useState } from 'react'
function App() {
  const [message, setMessage] = useState('')
  const handleGreet = async () => {
    try {
      const result = await invoke<string>(
        'greet',
        { name: 'Tauri 学习者' }
      )
      setMessage(result)
    } catch (err) {
      console.error('调用失败:', err)
    }
  }
  return <button onClick={handleGreet}>问候</button>
}
💡 小贴士:Tauri 2.x 的 API 路径变了
2.x 版本中,@tauri-apps/api 拆分成了子模块。核心 API 从 @tauri-apps/api/core 导入,而不是 1.x 的 @tauri-apps/api/tauri。迁移时注意替换路径。

五、事件系统:后端向前端推送消息

Command 是前端主动调用后端的请求-响应模式。但有些场景需要后端主动推送——比如文件下载进度、系统通知、长任务状态更新。这时候用事件系统(Event System)。

事件系统的工作方式类似广播电台:后端(电台)在某个频道(事件名)上发射信号,前端(收音机)调到这个频道就能收到消息。一个事件可以有多个监听器,也可以只发给特定窗口。

5.1 Rust 端发送事件

Rust - 后端发送事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use tauri::{AppHandle, Manager};
#[tauri::command]
async fn long_task(app: AppHandle) -> Result<(), String> {
    for i in 1..=100 {
        // 模拟耗时操作
        tokio::time::sleep(std::time::Duration::from_millis(50)).await;
        
        // 发送进度事件
        app
          .emit("task-progress", i)
          .map_err(|e| e.to_string())?;
    }
    
    // 发送完成事件
    app.emit("task-complete", "任务完成!")
      .map_err(|e| e.to_string())?;
    
    Ok(())
}

5.2 前端监听事件

TypeScript - 前端监听事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { listen } from '@tauri-apps/api/event'
import { useEffect, useState } from 'react'
function ProgressBar() {
  const [progress, setProgress] = useState(0)
  useEffect(() => {
    let unlisten: Promise<Fn> | null = null
    
    const setupListener = async () => {
      unlisten = await listen<number>(
        'task-progress',
        (event) => setProgress(event.payload)
      )
    }
    setupListener()
    
    return () => { unlisten?.() }
  }, [])
  return <div>进度: {progress}%</div>
}

注意 listen 返回一个取消监听的函数,组件卸载时一定要调用,否则会造成内存泄漏。这和前端的 addEventListener / removeEventListener 是一个道理。

六、插件生态与系统能力调用

Tauri 核心只提供最基础的窗口和 IPC 能力,文件系统、通知、对话框这些系统能力都以插件(Plugin)形式提供。这种设计保持了核心的轻量,也让你按需引入功能。

6.1 常用官方插件

插件 功能 典型场景
@tauri-apps/plugin-fs 文件系统操作 读写文件、目录遍历
@tauri-apps/plugin-dialog 原生对话框 文件选择、消息提示
@tauri-apps/plugin-notification 系统通知 桌面推送通知
@tauri-apps/plugin-shell Shell 命令执行 调用外部程序
@tauri-apps/plugin-http HTTP 请求 绕过 CORS 的网络请求
@tauri-apps/plugin-store 持久化存储 配置文件保存
@tauri-apps/plugin-updater 自动更新 应用版本升级

6.2 插件使用三步曲

以 dialog 插件为例,使用一个插件需要三步:Cargo 加依赖、Rust 注册、前端调用。

Bash - 安装 dialog 插件
1
2
3
4
5
6
# 1. 前端安装 npm 包
npm install @tauri-apps/plugin-dialog
# 2. Rust 添加依赖(在 src-tauri/Cargo.toml)
# 或在 src-tauri 目录下执行:
cd src-tauri && cargo add tauri-plugin-dialog
Rust - main.rs 中注册插件
1
2
3
4
5
6
7
fn main() {
  tauri::Builder::default()
    .plugin(tauri_plugin_dialog::init())
    .invoke_handler(tauri::generate_handler![...])
    .run(tauri::generate_context!())
    .expect("error while running tauri application");
}
TypeScript - 前端打开文件对话框
1
2
3
4
5
6
7
8
9
10
11
12
import { open } from '@tauri-apps/plugin-dialog'
const selectFile = async () => {
  const selected = await open({
    multiple: false,
    filters: [{
      name: '文本文件',
      extensions: ['txt', 'md']
    }]
  })
  console.log('选中的文件:', selected)
}
⚠️ 注意:2.x 的权限系统(Capabilities)
Tauri 2.x 引入了 Capabilities 权限系统,默认禁止一切前端调用。安装插件后还需要在 src-tauri/capabilities/ 目录下的 JSON 文件中声明允许的权限,否则前端调用会被拒绝。这是 2.x 相比 1.x 最大的安全增强。

七、常见错误与三层练习

7.1 常见错误

错误1:command not found
Invoke Error: command 'greet' not found
原因:Rust 函数加了 #[tauri::command],但忘了在 generate_handler! 宏里注册。
修复:在 tauri::generate_handler![add, greet] 中添加函数名。
错误2:权限被拒绝(2.x 特有)
scope not allowed: `dialog:allow-open` not allowed by any capability
原因:Tauri 2.x 的 Capability 系统默认禁止所有操作。
修复:在 src-tauri/capabilities/default.json 的 permissions 数组中添加 "dialog:allow-open"。
错误3:Linux 编译缺少 webkit2gtk
Package 'webkit2gtk-4.1' not found
原因:Linux 系统没有安装 WebView 开发库。
修复:Ubuntu 执行 sudo apt install libwebkit2gtk-4.1-dev,其他发行版参考官方文档。

7.2 三层练习

初 入门级:制作一个桌面版 Markdown 预览器
目标:左侧编辑 Markdown,右侧实时预览渲染结果
步骤:
1. 用 create-tauri-app 创建 React + TS 项目
2. 前端安装 marked 库做 Markdown 解析
3. 用 textarea 做编辑器,监听 input 事件实时渲染
4. 用 plugin-fs 实现"打开文件"和"保存文件"功能
验收:能打开 .md 文件、编辑后保存、左右分栏实时预览
中 进阶级:系统通知 + 倒计时应用
目标:桌面倒计时器,时间到了弹系统通知
步骤:
1. 前端设计倒计时输入界面(时/分/秒)
2. 用 setInterval 实现前端倒计时(简单版)
3. 进阶:倒计时逻辑移到 Rust 端,用事件推送进度到前端
4. 时间到调用 plugin-notification 发送系统通知
挑战:窗口最小化后倒计时仍然准确(提示:用 Rust 端计时避免浏览器节流)
高 挑战级:简易图片压缩工具
目标:批量压缩图片并保存到指定目录
步骤:
1. 用 plugin-dialog 选择多个图片文件
2. Rust 端引入 image crate 做图片压缩和格式转换
3. 用 Command 触发压缩任务,用 Event 实时推送每张的进度
4. 前端展示进度条、成功/失败列表
加分项:支持拖拽文件到窗口、支持质量参数调节、输出目录选择

八、知识回顾与下一篇预告

🏷️ Tauri 2.x 架构 🏷️ WebView vs Chromium 🏷️ #[tauri::command] 🏷️ invoke 调用 🏷️ emit / listen 事件 🏷️ Plugin 插件系统 🏷️ Capabilities 权限 🏷️ 包体积优化

本篇从 Tauri 的架构设计讲起,对比了它和 Electron 的本质区别:用系统 WebView 替代打包的 Chromium,用 Rust 替代 Node.js,换来 10 倍以上的体积缩减和更低的内存占用。我们学习了 Command 系统实现前端调用 Rust、事件系统实现后端主动推送,以及插件生态的使用方式。

Tauri 的设计哲学是"安全优先、按需加载"——每一个系统能力都需要显式声明权限,这增加了一点配置成本,但换来了更好的安全性和更小的包体积。对于追求轻量和性能的桌面应用,Tauri 是当前最值得关注的选择。

📌 下一篇预告
第 32 篇:Docker 容器化入门——应用打包与部署
完成了前端、App、桌面端的学习后,我们进入运维层。下一篇开始讲 Docker 容器化技术:如何把应用打包成镜像、如何用 Docker Compose 编排多服务、如何让你的应用在任何环境下一键启动。
觉得有用就点个❤️在看,分享给更多一起学习的朋友
— 全栈开发学习系列 · 第 31 篇 —