前言
Tauri 项目也做了一段时间了,也有用别人开发的插件。自己也想知道如何开发 Tauri 插件。
Tauri 插件介绍
插件可以挂载到 Tauri 的生命周期中 ,暴露需求 webview API 的 Rust 代码,使用 Rust、Kotlin 或 Swift 代码处理命令并能处理更多需求。
Tauri 提供了一个基于 webview 功能的视窗系统,一个在 Rust 进程和 webview 之间发送信息的方式,一个事件系统,以及一些增强开发体验的工具。 设计上 Tauri Core 只包含所有人都需要的功能。相对地,它提供了一种将外部功能添加到 Tauri 应用程序中的机制,被称为插件。
一个 Tauri 插件由一个 Cargo 包和一个可选的 NPM 包(用于提供命令和事件 API 绑定)构成。
何时需要开发 Tauri 插件?
- 需要访问系统原生 API(文件、硬件、网络底层)
- 性能关键型任务(大数据处理、实时计算)
- 代码复用与团队协作(跨项目共享功能模块)
- 安全敏感场景(加密、认证、许可证管理)
通过 Tauri 插件,开发者可以 兼顾 Web 的开发效率与 Native 的性能能力,构建真正高性能、跨平台的桌面应用。
Tauri 插件核心概念
插件作用:封装可复用的跨平台功能模块(如硬件访问、加密算法),通过 Rust 后端 + 前端 API 暴露能力。
技术栈:
- Rust:核心逻辑实现
- TypeScript/JavaScript:前端 API 接口
- WASM(可选):浏览器端直接调用
创建基础插件模板
1. 命名规范
Tauri 插件具有一个前缀(Rust 包使用 tauri-plugin-
前缀,NPM 包使用 @tauri-apps/plugin-
前缀),随后是插件名称。 插件名称由插件配置中的 tauri.conf.json > plugin
和许可列表中的配置确定。
默认情况下,Tauri 会在你的插件 crate 前面加上 tauri-plugin-
。这有助于你的插件被 Tauri 社区发现,但不是必须的。初始化新插件项目时,必须提供其名称。生成包名称将为 tauri-plugin-{plugin-name}
,JavaScript NPM 包名称为 tauri-plugin-{plugin-name}-api
(尽管我们建议使用 NPM 范围如果可能的话)。NPM 包的 Tauri 命名约定是 @scope-name/plugin-{plugin-name}
。
2. 初始化 Rust 插件库
使用引导创建一个新的插件项目,请运行 plugin new
。如果不需要相应的 NPM 程序包,请使用 --no-api
命令行标志。
bash
pnpm tauri plugin new [name]
cargo new tauri-plugin-example --lib
cd tauri-plugin-example
这会在 tauri-plugin-[name]
目录下初始化插件,取决于初始化插件时所选择的命令行标志,项目将具有以下结构:
css
. tauri-plugin-[name]/
├── src/ - Rust 代码
│ ├── commands.rs - 定义 webview 可用的命令
| ├── desktop.rs - 桌面实现
| ├── error.rs - 用于返回 results 的默认的错误类型
│ ├── lib.rs - 重新导出适当的实现、设置状态......
│ ├── mobile.rs - 移动端实现
│ └── models.rs - 公共的结构体
├── permissions/ - 这将托管(生成的)命令的权限文件
├── android - 安卓库
├── ios - Swift 包
├── guest-js - JavaScript API 绑定的源代码
├── dist-js - 从 guest-js 转译的资源
├── Cargo.toml - Cargo 包元数据
└── package.json - NPM 包元数据
3. 编辑 Cargo.toml
toml
[package]
name = "tauri-plugin-example"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"] # 编译为动态库
[dependencies]
tauri = { version = "2.0", features = ["plugin"] }
serde = { version = "1.0", features = ["derive"] } # 序列化支持
4. 实现插件结构
rust
// src/lib.rs
use tauri::{
plugin::{Builder, TauriPlugin},
Runtime, AppHandle,
};
// 定义插件配置(可选)
#[derive(serde::Deserialize)]
pub struct Config {
api_key: String,
}
// 核心插件实现
pub fn init<R: Runtime>() -> TauriPlugin<R, Config> {
Builder::new("example")
.setup(|app, config| {
println!("插件初始化,配置密钥: {}", config.api_key);
Ok(())
})
.invoke_handler(|app, invoke| {
// 处理前端调用
})
.build()
}
5. 生命周期事件
插件可以挂载到如下生命周期事件中:
setup
: 插件初始化on_navigation
: Webview 将要触发导航时on_webview_ready
: 新窗口创建时on_event
: 事件循环on_drop
: 插件被析构时
实现前后端通信
1. 定义 Rust 命令
rust
// 添加命令处理
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
// 更新插件初始化
pub fn init<R: Runtime>() -> TauriPlugin<R, Config> {
Builder::new("example")
.setup(|app, config| { /* ... */ })
.invoke_handler(tauri::generate_handler![greet]) // 注册命令
.build()
}
2. 前端调用插件
typescript
// 前端集成 (JavaScript/TypeScript)
import { invoke } from "@tauri-apps/api";
async function callPlugin() {
const response = await invoke("plugin:example|greet", { name: "World" });
console.log(response); // 输出 "Hello, World!"
}
高级功能扩展
1. 事件系统
rust
// Rust 端触发事件
app.emit_all("plugin-event", "数据载荷").unwrap();
// 前端监听
import { listen } from "@tauri-apps/api/event";
listen("plugin-event", (event) => {
console.log("收到事件:", event.payload);
});
2. 状态管理
rust
// 定义共享状态
struct AppState {
counter: Mutex<i32>,
}
// 注册状态
app.manage(AppState { counter: Mutex::new(0) });
// 在命令中使用状态
#[tauri::command]
fn increment(state: State<AppState>) -> i32 {
let mut counter = state.counter.lock().unwrap();
*counter += 1;
*counter
}
插件集成到 Tauri 应用
1. 本地插件引用
toml
# 主应用 Cargo.toml
[dependencies]
tauri-plugin-example = { path = "../tauri-plugin-example" }
2. 初始化插件
rust
// main.rs
fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_example::init())
.run(tauri::generate_context!())
.expect("运行失败");
}
发布插件
1. 打包发布到 crates.io
bash
cargo publish --allow-dirty
2. 前端用户安装
bash
npm install tauri-plugin-example
调试技巧
-
Rust 日志输出:
rustlog::info!("调试信息: {:?}", data);
启动应用时加环境变量:
bashRUST_LOG=info cargo tauri dev
-
前端 DevTools:
rust.setup(|app| { #[cfg(debug_assertions)] app.get_window("main").unwrap().open_devtools(); Ok(()) })