Tauri 2.x 教程系列 (一):Hello Tauri --- 从零搭建第一个桌面应用
前言
Tauri 是一个轻量级的桌面应用框架,使用 Rust 作为后端,Web 技术(React/Vue/Svelte)作为前端。相比 Electron,Tauri 打包体积更小(通常 <10MB)、内存占用更低、安全性更好。
本教程使用 Tauri 2.x + React + TypeScript,从零开始搭建第一个桌面应用。
📦 本系列完整代码:https://gitee.com/futurelei/rs-tauri-demo
💡 本系列所有项目配置了 Cargo Workspace,8 个项目共享编译缓存,避免重复编译。
环境准备
1. 安装系统依赖 (Ubuntu)
bash
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
libsoup-3.0-dev \
libjavascriptcoregtk-4.1-dev \
build-essential \
curl wget file \
libxdo-dev libssl-dev \
libayatana-appindicator3-dev \
librsvg2-dev
2. 安装 Rust
bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
3. 安装 Tauri CLI
bash
cargo install tauri-cli --version "^2"
4. 验证安装
bash
rustc --version # rustc 1.xx.x
cargo tauri --version # tauri-cli 2.xx.x
创建项目
方式一:使用 create-tauri-app(推荐)
bash
npm create tauri-app@latest
# 选择: React + TypeScript + Vite
方式二:手动搭建(本教程采用)
我们手动创建以理解每个文件的作用:
01-hello-tauri/
├── index.html # Vite 入口 HTML
├── package.json # 前端依赖
├── vite.config.ts # Vite 配置
├── tsconfig.json # TypeScript 配置
├── src/
│ ├── main.tsx # React 入口
│ └── App.tsx # 主组件
├── src-tauri/
│ ├── Cargo.toml # Rust 依赖(Workspace member)
│ ├── build.rs # Tauri 构建脚本
│ ├── tauri.conf.json # Tauri 配置
│ ├── capabilities/ # 权限配置
│ │ └── default.json
│ └── src/
│ ├── main.rs # 程序入口
│ └── lib.rs # 应用核心
💡 本系列在根目录配置了 Cargo Workspace (
Cargo.toml),8 个项目的 Rust 代码共享一个target/编译目录,避免每个项目独立编译占用 3-4GB 磁盘。
编写代码
package.json
json
{
"name": "01-hello-tauri",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"tauri": "tauri"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1",
"@tauri-apps/api": "^2"
},
"devDependencies": {
"@tauri-apps/cli": "^2",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.4",
"typescript": "^5.6.3",
"vite": "^6.0.0"
}
}
Vite 配置
typescript
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
clearScreen: false,
server: {
port: 1420, // Tauri 固定端口
strictPort: true,
watch: {
ignored: ["**/src-tauri/**"], // 不监听 Rust 文件
},
},
});
Rust 后端 --- 核心逻辑 (lib.rs)
rust
// src-tauri/src/lib.rs
/// 获取问候语
fn greet(name: String) -> String {
let display_name = if name.trim().is_empty() {
"World".to_string()
} else {
name
};
format!("你好, {}! 欢迎来到 Tauri 2.x!", display_name)
}
/// Tauri 命令:根据 name 返回问候语
#[tauri::command]
fn greet_command(name: String) -> String {
greet(name)
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet_command])
.run(tauri::generate_context!())
.expect("启动应用时出错");
}
TDD: 先写测试
rust
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_greet_with_name() {
let result = greet("Alice".to_string());
assert_eq!(result, "你好, Alice! 欢迎来到 Tauri 2.x!");
}
#[test]
fn test_greet_with_empty_name_defaults_to_world() {
let result = greet("".to_string());
assert_eq!(result, "你好, World! 欢迎来到 Tauri 2.x!");
}
}
前端调用
tsx
// src/App.tsx
import { useState } from "react";
import { invoke } from "@tauri-apps/api/core";
function App() {
const [name, setName] = useState("");
const [greeting, setGreeting] = useState("");
const handleGreet = async () => {
const result = await invoke<string>("greet", { name: name || "World" });
setGreeting(result);
};
return (
<main>
<h1>👋 Hello Tauri 2.x</h1>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && handleGreet()}
/>
<button onClick={handleGreet}>问候</button>
{greeting && <p>✨ {greeting}</p>}
</main>
);
}
tauri.conf.json
json
{
"productName": "Hello Tauri",
"version": "0.1.0",
"identifier": "com.tauri-tutorial.hello-tauri",
"build": {
"frontendDist": "../dist",
"devUrl": "http://localhost:1420",
"beforeDevCommand": "npm run dev",
"beforeBuildCommand": "npm run build"
},
"app": {
"windows": [{
"title": "01 - Hello Tauri",
"width": 800,
"height": 600,
"center": true
}],
"security": { "csp": null }
}
}
运行项目
bash
cd 01-hello-tauri
npm install
npm run tauri dev # 启动开发模式(自动编译 Rust)
首次运行会编译 Rust 代码,耗时约 2-5 分钟。之后会看到 Tauri 窗口打开,显示我们的 React 应用。
运行测试
bash
# 方式一:从项目目录(推荐初学者)
cd 01-hello-tauri/src-tauri
cargo test
# 方式二:从根目录使用 workspace
cargo test -p hello-tauri
💡 Workspace 提示 :本系列所有项目共享编译缓存,从根目录执行
cargo test -p <package名>或从子目录执行cargo test都使用同一份target/。
Tauri 2.x 关键概念
1. 项目结构
| 目录/文件 | 作用 |
|---|---|
src/ |
React 前端代码 |
src-tauri/src/ |
Rust 后端代码 |
src-tauri/tauri.conf.json |
应用配置(窗口、权限、打包) |
src-tauri/capabilities/ |
权限声明(2.x 新增) |
src-tauri/Cargo.toml |
Rust 依赖管理 |
2. 命令系统 (IPC)
Tauri 的核心通信机制:前端通过 invoke() 调用 Rust 函数。
前端 invoke("greet", { name: "Alice" })
→ Rust #[tauri::command] fn greet_command(name: String) -> String
→ 返回结果到前端
3. 权限模型 (Capabilities)
Tauri 2.x 引入了声明式权限系统,在 capabilities/default.json 中声明:
json
{
"identifier": "default",
"windows": ["main"],
"permissions": ["core:default"]
}
常见问题
Q: 编译时 icon 文件缺失?
A: 在 src-tauri/icons/ 中放置 32x32.png、128x128.png 等图标文件。
Q: 端口被占用?
A: vite.config.ts 中的 port 改为其他值,同时更新 tauri.conf.json 的 devUrl。
Q: 8 个项目磁盘占用太大?
A: 根目录的 Cargo Workspace 已自动共享 target/ 目录,8 个项目共用一份编译产物。
总结
本节我们完成了:
- ✅ Tauri 2.x 环境搭建
- ✅ 理解项目结构
- ✅ 第一个 Tauri 命令 (greet)
- ✅ 前端调用后端
- ✅ TDD: Rust 单元测试
- ✅ 应用运行与调试
- ✅ Cargo Workspace 共享编译
下一节,我们将深入 React 组件化开发与 Tauri 命令系统进阶。
本系列文章:
- (一) Hello Tauri --- 从零搭建第一个桌面应用 👈
- (二) React 组件化与 Tauri 命令系统
- (三) 状态管理:Tauri State + Zustand + 事件
- (四) SQLite 本地数据库持久化
- (五) 路由导航与多页面架构
- (六) 测试金字塔与 TDD 实践
- (七) 大型项目工程化规范
- (八) 综合项目实战:Todo 应用