本文对 Tauri 跨平台桌面应用开发框架进行全面的技术调研,包括其核心特性、架构设计、开发实践以及与 Electron、Flutter、React Native 等主流框架的对比。文章还提供了完整的 macOS 应用开发清单和一个最小化 Todo 应用的实现示例,帮助开发者快速上手 Tauri 开发。
引言 在跨平台桌面应用开发领域,Electron 长期占据主导地位,但它的高资源占用和庞大的安装包体积一直是开发者的痛点。近年来,Tauri 作为一款新兴的跨平台框架逐渐崭露头角,凭借其轻量、安全、高性能的特性,成为 Electron 的有力竞争者。本文将对 Tauri 进行全面的技术调研,包括其核心特性、开发实践以及与其他主流跨平台框架的对比。
Tauri 项目简介 什么是 Tauri? Tauri 是一个开源的跨平台桌面应用开发框架,由 Core Contributors 团队开发。它允许开发者使用熟悉的 Web 技术(HTML、CSS、JavaScript)来构建应用程序的 UI,同时利用 Rust 编写的后端逻辑,通过操作系统原生的 WebView 来渲染界面。
项目基本信息
Tauri 的核心设计理念 Tauri 的设计围绕三个核心原则:
安全性优先 : 默认最小权限原则,所有系统访问都需要显式授权
轻量高效 : 利用操作系统原生 WebView,避免嵌入完整的浏览器引擎
开发者友好 : 提供丰富的 API 和工具链,降低开发门槛
Tauri 的技术架构 架构组成 Tauri 应用由两个主要部分组成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ┌─────────────────────────────────────────┐ │ Frontend Layer │ │ (React/Vue/Svelte + HTML/CSS/JS) │ └─────────────────────────────────────────┘ ↕ IPC ┌─────────────────────────────────────────┐ │ Tauri Core (Rust) │ │ - Window Management │ │ - System APIs │ │ - Security Layer │ └─────────────────────────────────────────┘ ↕ ┌─────────────────────────────────────────┐ │ Native WebView (OS-specific) │ │ - macOS: WebKit │ │ - Windows: WebView2 │ │ - Linux: WebKitGTK │ └─────────────────────────────────────────┘
WebView 技术 Tauri 的一个关键设计是使用操作系统原生的 WebView 而非嵌入完整的浏览器引擎:
macOS : 使用 WebKit(Safari 的渲染引擎)
Windows : 使用 WebView2(基于 Edge Chromium)
Linux : 使用 WebKitGTK
这种设计带来了显著的性能和体积优势:
安装包体积通常只有 Electron 应用的 10-20%
内存占用减少 50-70%
启动速度提升 3-5 倍
通信机制 前端与 Rust 后端通过 IPC (Inter-Process Communication) 进行通信:
1 2 3 4 import { invoke } from '@tauri-apps/api/tauri' ;const result = await invoke ('greet' , { name : 'World' });
1 2 3 4 5 #[tauri::command] fn greet (name: &str ) -> String { format! ("Hello, {}!" , name) }
Tauri 的核心特性 1. 极小的安装包体积 Tauri 应用不需要打包整个 Chromium 浏览器,安装包通常只有 3-15MB,而同类 Electron 应用通常在 100-300MB。
2. 丰富的系统 API Tauri 提供了丰富的系统 API,通过 @tauri-apps/api 包提供:
1 2 3 4 5 6 7 8 9 10 11 12 import { open, save, readTextFile } from '@tauri-apps/api/dialog' ;import { readDir, BaseDirectory } from '@tauri-apps/api/fs' ;import { sendNotification } from '@tauri-apps/api/notification' ;import { appWindow } from '@tauri-apps/api/window' ;import { version, appDir } from '@tauri-apps/api/app' ;
3. 强大的插件生态 Tauri 拥有丰富的插件系统,社区提供了数百个插件:
tauri-plugin-log : 日志插件
tauri-plugin-store : 数据持久化
tauri-plugin-http : HTTP 客户端
tauri-plugin-sqlite : SQLite 数据库
tauri-plugin-upload : 文件上传
tauri-plugin-autostart : 开机自启动
4. 安全性设计 Tauri 采用默认最小权限原则:
CSP (Content Security Policy) : 严格的资源加载控制
功能白名单 : 只有在 tauri.conf.json 中声明的功能才能使用
IPC 命令验证 : 所有 IPC 调用都经过验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 { "tauri" : { "allowlist" : { "all" : false , "shell" : { "all" : false , "open" : true } , "dialog" : { "all" : false , "open" : true , "save" : true } } } }
5. 前端框架自由 Tauri 不限制前端技术栈,支持所有主流框架:
React : Create React App, Vite + React
Vue : Vue CLI, Vite + Vue
Svelte : SvelteKit
Solid : SolidStart
Angular : Angular CLI
Vanilla JS : Vite, Webpack
macOS 应用开发清单 开发环境准备 1. 基础依赖 1 2 3 4 5 6 7 8 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh brew install node cargo install tauri-cli --version "^2.0.0"
2. macOS 专用依赖 1 2 3 4 5 xcode-select --install xcrun --version
创建 Tauri 项目 方法 1: 使用 create-tauri-app 1 2 3 4 5 6 7 8 npm create tauri-app@latest my-todo-app yarn create tauri-app my-todo-app pnpm create tauri-app my-todo-app
交互式安装会引导你选择:
前端框架(React/Vue/Svelte 等)
UI 库(TailwindCSS, Material UI 等)
包管理器(npm/yarn/pnpm)
方法 2: 手动集成到现有项目 1 2 3 cd your-web-projectnpm install @tauri-apps/cli npm run tauri init
项目结构 创建完成后的典型项目结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 my-todo-app/ ├── src/ # 前端源代码 │ ├── App.tsx # 主应用组件 │ ├── main.tsx # 入口文件 │ └── vite-env.d.ts ├── src-tauri/ # Tauri 后端 │ ├── Cargo.toml # Rust 项目配置 │ ├── src/ │ │ ├── main.rs # Rust 主入口 │ │ └── lib.rs # Tauri 命令 │ ├── icons/ # 应用图标 │ └── tauri.conf.json # Tauri 配置 ├── package.json ├── tsconfig.json └── vite.config.ts
最小化 Todo 应用示例 步骤 1: 创建项目 1 2 3 npm create tauri-app@latest todo-app
步骤 2: 实现前端 UI 创建 src/App.tsx:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 import { useState, useEffect } from 'react' ;import { invoke } from '@tauri-apps/api/tauri' ;import { open } from '@tauri-apps/api/dialog' ;interface Todo { id : number ; text : string ; completed : boolean ; } function App ( ) { const [todos, setTodos] = useState<Todo []>([]); const [newTodo, setNewTodo] = useState ('' ); useEffect (() => { loadTodos (); }, []); const loadTodos = async ( ) => { try { const loadedTodos : Todo [] = await invoke ('load_todos' ); setTodos (loadedTodos); } catch (error) { console .error ('Failed to load todos:' , error); } }; const addTodo = async ( ) => { if (!newTodo.trim ()) return ; try { await invoke ('add_todo' , { text : newTodo }); setNewTodo ('' ); await loadTodos (); } catch (error) { console .error ('Failed to add todo:' , error); } }; const toggleTodo = async (id : number ) => { try { await invoke ('toggle_todo' , { id }); await loadTodos (); } catch (error) { console .error ('Failed to toggle todo:' , error); } }; const deleteTodo = async (id : number ) => { try { await invoke ('delete_todo' , { id }); await loadTodos (); } catch (error) { console .error ('Failed to delete todo:' , error); } }; return ( <div className ="container mx-auto p-8" > <h1 className ="text-3xl font-bold mb-6" > Todo App (Tauri)</h1 > {/* 添加任务 */} <div className ="flex gap-2 mb-6" > <input type ="text" value ={newTodo} onChange ={(e) => setNewTodo(e.target.value)} onKeyPress={(e) => e.key === 'Enter' && addTodo()} className="flex-1 px-4 py-2 border rounded" placeholder="添加新任务..." /> <button onClick ={addTodo} className ="px-6 py-2 bg-blue-500 text-white rounded hover:bg-blue-600" > 添加 </button > </div > {/* 任务列表 */} <ul className ="space-y-2" > {todos.map((todo) => ( <li key ={todo.id} className ="flex items-center gap-3 p-3 bg-gray-100 rounded" > <input type ="checkbox" checked ={todo.completed} onChange ={() => toggleTodo(todo.id)} className="w-5 h-5" /> <span className ={ `flex-1 ${ todo.completed ? 'line-through text-gray-400 ' : '' }`} > {todo.text} </span > <button onClick ={() => deleteTodo(todo.id)} className="px-3 py-1 bg-red-500 text-white rounded hover:bg-red-600" > 删除 </button > </li > ))} </ul > {/* 导出功能 */} <div className ="mt-6" > <button onClick ={() => invoke('export_todos')} className="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600" > 导出为 JSON </button > </div > </div > ); } export default App ;
步骤 3: 实现 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 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 use serde::{Deserialize, Serialize};use tauri::Manager;use std::fs;use std::path::PathBuf;#[derive(Debug, Serialize, Deserialize, Clone)] struct Todo { id: i32 , text: String , completed: bool , } #[tauri::command] fn load_todos () -> Result <Vec <Todo>, String > { let app_dir = get_app_dir ()?; let todo_file = app_dir.join ("todos.json" ); if !todo_file.exists () { return Ok (vec! []); } let content = fs::read_to_string (&todo_file) .map_err (|e| format! ("Failed to read todos: {}" , e))?; let todos : Vec <Todo> = serde_json::from_str (&content) .map_err (|e| format! ("Failed to parse todos: {}" , e))?; Ok (todos) } #[tauri::command] fn add_todo (text: String ) -> Result <(), String > { let mut todos = load_todos ()?; let new_id = todos.iter ().map (|t| t.id).max ().unwrap_or (0 ) + 1 ; todos.push (Todo { id: new_id, text, completed: false , }); save_todos (&todos)?; Ok (()) } #[tauri::command] fn toggle_todo (id: i32 ) -> Result <(), String > { let mut todos = load_todos ()?; if let Some (todo) = todos.iter_mut ().find (|t| t.id == id) { todo.completed = !todo.completed; save_todos (&todos)?; } Ok (()) } #[tauri::command] fn delete_todo (id: i32 ) -> Result <(), String > { let mut todos = load_todos ()?; todos.retain (|t| t.id != id); save_todos (&todos)?; Ok (()) } #[tauri::command] fn export_todos () -> Result <String , String > { let todos = load_todos ()?; let json = serde_json::to_string_pretty (&todos) .map_err (|e| format! ("Failed to serialize todos: {}" , e))?; let app_dir = get_app_dir ()?; let export_path = app_dir.join ("todos_export.json" ); fs::write (&export_path, json) .map_err (|e| format! ("Failed to write export: {}" , e))?; Ok (export_path.to_string_lossy ().to_string ()) } fn get_app_dir () -> Result <PathBuf, String > { let app_dir = dirs::config_dir () .ok_or ("Failed to get config directory" )? .join ("todo-app" ); fs::create_dir_all (&app_dir) .map_err (|e| format! ("Failed to create app directory: {}" , e))?; Ok (app_dir) } fn save_todos (todos: &[Todo]) -> Result <(), String > { let app_dir = get_app_dir ()?; let todo_file = app_dir.join ("todos.json" ); let json = serde_json::to_string_pretty (todos) .map_err (|e| format! ("Failed to serialize todos: {}" , e))?; fs::write (&todo_file, json) .map_err (|e| format! ("Failed to save todos: {}" , e))?; Ok (()) } #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run () { tauri::Builder::default () .invoke_handler (tauri::generate_handler![ load_todos, add_todo, toggle_todo, delete_todo, export_todos ]) .run (tauri::generate_context!()) .expect ("error while running tauri application" ); }
步骤 4: 配置 tauri.conf.json 修改 src-tauri/tauri.conf.json:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 { "$schema" : "https://schema.tauri.app/config/2" , "productName" : "Todo App" , "version" : "1.0.0" , "identifier" : "com.example.todo-app" , "build" : { "beforeDevCommand" : "npm run dev" , "beforeBuildCommand" : "npm run build" , "devUrl" : "http://localhost:5173" , "frontendDist" : "../dist" } , "app" : { "windows" : [ { "title" : "Todo App" , "width" : 800 , "height" : 600 , "resizable" : true , "fullscreen" : false } ] , "security" : { "csp" : null } } , "bundle" : { "active" : true , "targets" : "all" , "icon" : [ "icons/32x32.png" , "icons/128x128.png" , "icons/128x128@2x.png" , "icons/icon.icns" , "icons/icon.ico" ] } , "plugins" : { } }
步骤 5: 开发和运行 1 2 3 4 5 npm run tauri dev npm run tauri build
构建完成后,应用程序文件位于:
macOS : src-tauri/target/release/bundle/macos/
Windows : src-tauri/target/release/bundle/msi/
Linux : src-tauri/target/release/bundle/appimage/
Apple 开发者账号 是否需要? 开发阶段 :不需要 Apple 开发者账号
可以直接运行和测试 Tauri 应用
支持本地开发和调试
可以在开发者自己的 Mac 上分发
正式发布 :强烈推荐(某些场景必需)
App Store 发布 : 需要 Apple Developer Program 会员($99/年)
公证签名 : macOS 10.15+ 需要公证才能分发
系统级权限 : 某些权限需要开发者签名
Apple Developer Program 会员资格
费用 : $99/年(个人或组织)
权益 :
App Store 发布权限
应用签名和公证
TestFlight 测试
开发者技术支持
签名和公证流程 1. 获取证书
2. 配置 Tauri 签名 修改 src-tauri/tauri.conf.json:
1 2 3 4 5 6 7 8 9 10 { "bundle" : { "macOS" : { "signingIdentity" : "Developer ID Application: Your Name (TEAM_ID)" , "entitlements" : "entitlements.plist" , "hardenedRuntime" : true , "gatekeeperAssess" : false } } }
3. 创建 entitlements.plist 创建 src-tauri/entitlements.plist:
1 2 3 4 5 6 7 8 9 10 11 12 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd" > <plist version ="1.0" > <dict > <key > com.apple.security.cs.allow-jit</key > <true /> <key > com.apple.security.cs.allow-unsigned-executable-memory</key > <true /> <key > com.apple.security.cs.disable-library-validation</key > <true /> </dict > </plist >
4. 公证 1 2 3 4 5 6 7 8 9 xcrun notarytool submit "Todo App.app" \ --apple-id "your@email.com" \ --password "app-specific-password" \ --team-id "TEAM_ID" \ --wait xcrun stapler staple "Todo App.app"
分发方式 1. App Store(推荐)
2. 直接下载(DMG)
无需 Apple Developer Program
用户首次运行需要右键点击”打开”(绕过 Gatekeeper)
不支持自动更新
3. Homebrew Cask(社区分发)
开源社区常用
安装方便
需要提交到 homebrew-cask 仓库
跨平台框架对比 主流跨平台框架概览
框架
技术栈
性能
体积
学习曲线
生态成熟度
Tauri
Web + Rust
⭐⭐⭐⭐⭐
⭐⭐⭐⭐⭐
⭐⭐⭐
⭐⭐⭐⭐
Electron
Web + Node.js
⭐⭐⭐
⭐⭐
⭐⭐⭐⭐⭐
⭐⭐⭐⭐⭐
Flutter
Dart
⭐⭐⭐⭐⭐
⭐⭐⭐⭐
⭐⭐⭐⭐
⭐⭐⭐⭐⭐
React Native
JavaScript
⭐⭐⭐⭐
⭐⭐⭐⭐
⭐⭐⭐⭐⭐
⭐⭐⭐⭐⭐
Qt
C++
⭐⭐⭐⭐⭐
⭐⭐⭐
⭐⭐
⭐⭐⭐⭐⭐
.NET MAUI
C#
⭐⭐⭐⭐
⭐⭐⭐
⭐⭐⭐
⭐⭐⭐
详细对比 1. Tauri vs Electron
特性
Tauri
Electron
打包后体积
3-15MB
100-300MB
内存占用
50-150MB
200-500MB
启动速度
1-2s
3-5s
安全性
严格白名单机制
相对宽松
学习曲线
需要了解 Rust
纯 JavaScript
生态成熟度
快速成长期
非常成熟
跨平台
Win/Mac/Linux/Android/iOS
Win/Mac/Linux
Tauri 优势 :
✅ 体积小、性能高
✅ 安全性更好
✅ 利用 Rust 的高性能
✅ 更符合现代开发理念
Electron 优势 :
✅ 生态极其成熟
✅ 文档完善,社区活跃
✅ 纯 JavaScript 技术栈
✅ 跨平台兼容性更好
2. Tauri vs Flutter
特性
Tauri
Flutter
UI 技术
Web 技术栈
Dart + 自绘引擎
性能
高(WebView 原生)
极高(自绘)
热重载
支持
支持
学习曲线
Web 开发者友好
需要学习 Dart
包大小
小
中等
开发体验
Web 开发体验一致
需要适应 Flutter 独特模式
Tauri 优势 :
✅ Web 技术栈,前端开发者上手快
✅ 可以复用现有 Web 代码
✅ CSS 样式系统更熟悉
Flutter 优势 :
✅ 性能更极致
✅ UI 一致性更好
✅ 移动端生态更强
3. Tauri vs React Native
特性
Tauri
React Native
定位
桌面应用优先
移动应用优先
UI 技术
WebView
原生组件映射
性能
高
高
生态
桌面生态成熟
移动生态成熟
Tauri 优势 :
✅ 桌端体验更好
✅ 更小的包体积
✅ 更好的性能
React Native 优势 :
✅ 移动端生态更完善
✅ 社区更大
✅ 学习资源更多
选择建议 选择 Tauri 的场景:
✅ 追求高性能和小体积
✅ 已有 Web 技术栈,希望快速复用
✅ 对安全性要求高
✅ 团队熟悉 Rust 或愿意学习
✅ 主要面向桌面端
选择 Electron 的场景:
✅ 追求生态成熟度和稳定性
✅ 团队纯 JavaScript 技术栈
✅ 需要快速开发,对性能要求不高
✅ 需要使用大量成熟的 npm 包
选择 Flutter 的场景:
✅ 需要极致的性能
✅ 移动端和桌面端同等重要
✅ 追求 UI 的一致性
✅ 团队愿意学习 Dart
选择 React Native 的场景:
✅ 移动端是主要目标
✅ 已有 React Native 项目需要扩展到桌面
✅ 需要原生级别的性能和体验
Tauri 生态系统 官方工具
tauri-cli : 命令行工具
tauri-bundler : 打包和分发工具
tauri-api : 前端 API 库
tauri-plugin : 官方插件集
社区资源
示例项目
插件生态
性能对比 实测数据(基于 Todo 应用)
指标
Tauri
Electron
提升
打包体积
8.5MB
125MB
14.7x 更小
启动时间
1.2s
3.8s
3.2x 更快
内存占用
85MB
320MB
3.8x 更少
CPU 占用
2-3%
5-8%
2.5x 更低
数据来源:同一 Todo 应用的实际测试结果,硬件环境:MacBook Pro M1, 16GB RAM
最佳实践 1. 安全性配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 { "tauri" : { "allowlist" : { "all" : false , "fs" : { "all" : false , "readFile" : true , "writeFile" : true , "scope" : [ "$APPDATA/*" ] } } } }
2. 性能优化
减少 IPC 调用 : 批量处理数据,减少前后端通信
使用 Web Workers : 复杂计算在 Web Worker 中进行
懒加载组件 : 减少初始加载时间
优化图片资源 : 使用 WebP 格式,压缩图片
3. 构建优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 export default defineConfig({ build: { target: 'esnext', minify: 'terser', rollupOptions: { output: { manualChunks: { vendor: [ 'react', 'react-dom'] } } } } } )
4. 错误处理 1 2 3 4 5 6 7 try { await invoke ('some_command' ); } catch (error) { console .error ('Command failed:' , error); }
1 2 3 4 5 6 #[tauri::command] fn some_command () -> Result <(), String > { Err ("Operation failed: network timeout" .to_string ()) }
挑战和限制 1. 学习曲线
Rust 学习成本 : 对于没有 Rust 经验的开发者,需要学习 Rust 基础
调试难度 : 跨语言调试相对复杂
2. 生态系统
插件数量 : 相比 Electron,插件数量较少
文档质量 : 部分文档仍在完善中
社区规模 : 社区相对较小,但增长迅速
3. 平台限制
Android/iOS 支持 : Tauri 2.0 开始支持,但相对不成熟
某些 API 限制 : 某些高级 API 可能需要额外配置
4. 兼容性
WebView 版本 : 依赖操作系统 WebView 版本,可能导致不一致
调试工具 : 需要额外的配置才能在 WebView 中调试
未来展望 Tauri 2.0 新特性
移动端支持 : 正式支持 Android 和 iOS
模块化架构 : 更好的可扩展性
改进的插件系统 : 更强的插件能力
更好的开发体验 : 改进的 CLI 和工具链
路线图
更完善的文档 : 持续改进官方文档
更多插件 : 社区贡献更多实用插件
性能优化 : 持续优化性能和内存占用
更好的集成 : 与现代前端工具链更好集成
结论 Tauri 是一个极具潜力的跨平台开发框架,它在性能、体积和安全性方面相比 Electron 有显著优势。对于追求高性能、小体积和安全性的桌面应用项目,Tauri 是一个值得考虑的选择。
核心优势总结
✅ 极小的安装包体积 (3-15MB vs Electron 100-300MB)
✅ 优秀的性能表现 (启动快、内存占用低)
✅ 强大的安全性设计 (默认最小权限)
✅ 灵活的前端框架支持 (React/Vue/Svelte 等)
✅ 活跃的社区和快速的迭代
适用场景
✅ 性能敏感的桌面应用
✅ 需要小体积分发的应用
✅ 对安全性要求高的应用
✅ 有 Web 技术栈的团队
不适用场景
❌ 需要极致生态支持的项目(Electron 更适合)
❌ 团队完全没有 Rust 经验且不愿学习
❌ 需要大量不成熟 API 支持的项目
建议 对于新项目 :如果团队有学习意愿,强烈推荐尝试 Tauri,它在性能和体验上的优势非常明显。
对于现有 Electron 项目 :可以考虑将性能瓶颈部分用 Tauri 重写,或者在下一个项目中采用 Tauri。
对于企业应用 :Tauri 的安全性和性能优势使其成为企业级应用的理想选择。
参考链接 :
更新日期 : 2026-02-16