Tauri 2.0 正式版终于发布了。从 beta 一路跟过来,2.0 的变化相当大:新的权限系统、重写的 IPC 安全模型、移动端支持。这篇文章梳理核心变更,并从零搭建一个系统信息查看工具来感受 2.0 的开发体验。
2.0 核心变更
权限系统(Capabilities)
这是 2.0 最大的架构级变更。1.x 时代的 allowlist 被全新的 capabilities 系统取代。
1.x 的做法是在 tauri.conf.json 里配一个白名单:
{
"tauri": {
"allowlist": {
"fs": { "readFile": true, "writeFile": false },
"shell": { "open": true }
}
}
}
2.0 改为基于 capability 文件的声明式权限。每个窗口(或 webview)可以分配不同的能力:
// src-tauri/capabilities/main-window.json
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "main-window-capability",
"description": "主窗口的权限",
"windows": ["main"],
"permissions": [
"core:default",
"fs:allow-read-text-file",
"fs:deny-write-file",
"shell:allow-open",
"os:allow-info"
]
}
这意味着不同窗口可以有不同的权限边界。比如设置窗口只需要读配置的权限,而主窗口可以访问更多 API。
IPC 安全模型
2.0 重新设计了前端到后端的 IPC 通信。核心变化:
- 命令默认需要权限声明才能被前端调用
- origin 校验:可以限制只有特定 origin 的 webview 才能调用某个命令
- scope 细化:文件系统操作可以限制到具体路径
// 定义命令时声明权限
#[tauri::command]
async fn get_system_info() -> Result<SystemInfo, String> {
// ...
}
// plugin 权限定义
// permissions/default.toml
// [[permission]]
// identifier = "allow-get-system-info"
// description = "允许获取系统信息"
// commands.allow = ["get_system_info"]
移动端支持
2.0 原生支持 iOS 和 Android。不是套壳 —— 它通过 WKWebView (iOS) 和 Android WebView 实现,Rust 代码通过 JNI (Android) 和 C FFI (iOS) 桥接。
项目初始化时可以选择目标平台:
# 创建项目时选择移动端支持
cargo tauri init
# 或
npm create tauri-app@latest
# 添加移动端
cargo tauri android init
cargo tauri ios init
# 开发
cargo tauri android dev
cargo tauri ios dev
移动端有些限制:不支持多窗口、系统托盘,部分桌面端 API 不可用。但文件系统、HTTP、通知等核心能力都可以跨平台使用。
其他重要变更
- 插件系统重构:官方功能拆成独立插件(fs、shell、dialog 等),按需引入
- 多 webview 支持:一个窗口里可以有多个 webview
- 事件系统增强:支持 webview 级别的事件隔离
- 构建产物优化:更小的二进制体积
从 1.x 迁移
Tauri 提供了迁移工具:
# 自动迁移
npx @tauri-apps/cli migrate
不过自动迁移只能处理配置层面的变更。代码层面需要手动调整的地方:
- allowlist → capabilities:最大的改动,需要重写权限配置
- API 导入路径变更:
@tauri-apps/api的模块结构变了 - 插件化:之前内置的功能现在要单独安装插件
# 安装需要的官方插件
cargo add tauri-plugin-fs tauri-plugin-shell tauri-plugin-os
前端 API 变更示例:
// 1.x
import { readTextFile } from '@tauri-apps/api/fs';
// 2.0
import { readTextFile } from '@tauri-apps/plugin-fs';
实战:系统信息工具
做一个简单但完整的工具,展示 CPU、内存、磁盘、网络等系统信息。
项目初始化
npm create tauri-app@latest sysinfo-tool -- --template react-ts
cd sysinfo-tool
cargo add serde serde_json sysinfo --manifest-path src-tauri/Cargo.toml
cargo add tauri-plugin-os --manifest-path src-tauri/Cargo.toml
Rust 后端:采集系统信息
// src-tauri/src/lib.rs
use serde::Serialize;
use sysinfo::{System, Disks, Networks};
#[derive(Serialize)]
struct CpuInfo {
name: String,
cores: usize,
usage: f32,
frequency: u64,
}
#[derive(Serialize)]
struct MemoryInfo {
total: u64,
used: u64,
available: u64,
usage_percent: f64,
}
#[derive(Serialize)]
struct DiskInfo {
name: String,
mount_point: String,
total: u64,
available: u64,
fs_type: String,
}
#[derive(Serialize)]
struct SystemInfo {
hostname: String,
os_name: String,
os_version: String,
kernel_version: String,
cpu: CpuInfo,
memory: MemoryInfo,
disks: Vec<DiskInfo>,
uptime: u64,
}
#[tauri::command]
fn get_system_info() -> SystemInfo {
let mut sys = System::new_all();
sys.refresh_all();
std::thread::sleep(std::time::Duration::from_millis(200));
sys.refresh_cpu_usage();
let cpu = sys.cpus().first().map(|c| CpuInfo {
name: c.brand().to_string(),
cores: sys.cpus().len(),
usage: sys.global_cpu_usage(),
frequency: c.frequency(),
}).unwrap_or(CpuInfo {
name: "Unknown".into(), cores: 0, usage: 0.0, frequency: 0,
});
let memory = MemoryInfo {
total: sys.total_memory(),
used: sys.used_memory(),
available: sys.available_memory(),
usage_percent: sys.used_memory() as f64 / sys.total_memory() as f64 * 100.0,
};
let disk_list = Disks::new_with_refreshed_list();
let disks = disk_list.iter().map(|d| DiskInfo {
name: d.name().to_string_lossy().to_string(),
mount_point: d.mount_point().to_string_lossy().to_string(),
total: d.total_space(),
available: d.available_space(),
fs_type: String::from_utf8_lossy(d.file_system()).to_string(),
}).collect();
SystemInfo {
hostname: System::host_name().unwrap_or_default(),
os_name: System::name().unwrap_or_default(),
os_version: System::os_version().unwrap_or_default(),
kernel_version: System::kernel_version().unwrap_or_default(),
cpu,
memory,
disks,
uptime: System::uptime(),
}
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_os::init())
.invoke_handler(tauri::generate_handler![get_system_info])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
权限配置
// src-tauri/capabilities/default.json
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "默认权限",
"windows": ["main"],
"permissions": [
"core:default",
"os:default"
]
}
自定义命令的权限需要在插件配置中声明。对于应用内命令,2.0 默认允许调用(除非你显式限制)。
前端展示
// src/App.tsx
import { useState, useEffect } from "react";
import { invoke } from "@tauri-apps/api/core";
interface SystemInfo {
hostname: string;
os_name: string;
os_version: string;
kernel_version: string;
cpu: { name: string; cores: number; usage: number; frequency: number };
memory: { total: number; used: number; available: number; usage_percent: number };
disks: { name: string; mount_point: string; total: number; available: number; fs_type: string }[];
uptime: number;
}
function formatBytes(bytes: number): string {
const gb = bytes / (1024 * 1024 * 1024);
return gb >= 1 ? `${gb.toFixed(1)} GB` : `${(bytes / (1024 * 1024)).toFixed(0)} MB`;
}
function formatUptime(seconds: number): string {
const days = Math.floor(seconds / 86400);
const hours = Math.floor((seconds % 86400) / 3600);
const mins = Math.floor((seconds % 3600) / 60);
return `${days}天 ${hours}小时 ${mins}分钟`;
}
export default function App() {
const [info, setInfo] = useState<SystemInfo | null>(null);
const refresh = async () => {
const data = await invoke<SystemInfo>("get_system_info");
setInfo(data);
};
useEffect(() => { refresh(); }, []);
if (!info) return <div>加载中...</div>;
return (
<div className="container">
<h1>系统信息</h1>
<section>
<h2>操作系统</h2>
<p>{info.os_name} {info.os_version} ({info.kernel_version})</p>
<p>主机名: {info.hostname} | 运行时间: {formatUptime(info.uptime)}</p>
</section>
<section>
<h2>CPU</h2>
<p>{info.cpu.name}</p>
<p>{info.cpu.cores} 核 | {info.cpu.frequency} MHz | 使用率 {info.cpu.usage.toFixed(1)}%</p>
<div className="progress-bar">
<div style={{ width: `${info.cpu.usage}%` }} />
</div>
</section>
<section>
<h2>内存</h2>
<p>{formatBytes(info.memory.used)} / {formatBytes(info.memory.total)}</p>
<div className="progress-bar">
<div style={{ width: `${info.memory.usage_percent}%` }} />
</div>
</section>
<section>
<h2>磁盘</h2>
{info.disks.map((d, i) => (
<div key={i} className="disk-item">
<p>{d.mount_point} ({d.fs_type})</p>
<p>{formatBytes(d.total - d.available)} / {formatBytes(d.total)}</p>
</div>
))}
</section>
<button onClick={refresh}>刷新</button>
</div>
);
}
构建
# 开发
cargo tauri dev
# 构建
cargo tauri build
2.0 的构建产物依然很小。这个系统信息工具在 Linux 下约 3MB,Windows 下约 5MB(包含 WebView2 引导程序)。
2.0 开发体验总结
优势:
- 权限系统更安全,细粒度控制好
- 插件系统干净,不用的功能不打包
- 移动端支持是杀手级特性
- Rust 后端的性能和安全性依然是最大卖点
需要注意的:
- 迁移成本不低,权限系统的概念需要时间理解
- 移动端支持还在完善,部分插件尚未适配
- 生态规模比 Electron 小,第三方库少
总的来说,如果你在意应用体积和资源占用,Tauri 2.0 值得认真考虑。