Skip to content
Tauri 中文网

插件开发

插件能够挂接到 Tauri 生命周期,公开依赖于 Web 视图 API 的 Rust 代码,使用 Rust、Kotlin 或 Swift 代码处理命令等等。

¥Plugins are able to hook into the Tauri lifecycle, expose Rust code that relies on the web view APIs, handle commands with Rust, Kotlin or Swift code, and much more.

Tauri 提供了一个具有 Web 视图功能的窗口系统、一种在 Rust 进程和 Web 视图之间发送消息的方法,以及一个事件系统和几个工具来增强开发体验。根据设计,Tauri 核心不包含每个人都不需要的功能。相反,它提供了一种将外部功能添加到 Tauri 应用中的机制,称为插件。

¥Tauri offers a windowing system with web view functionality, a way to send messages between the Rust process and the web view, and an event system along with several tools to enhance the development experience. By design, the Tauri core does not contain features not needed by everyone. Instead it offers a mechanism to add external functionalities into a Tauri application called plugins.

Tauri 插件由 Cargo 包和可选的 NPM 包组成,后者为其命令和事件提供 API 绑定。此外,插件项目可以包括 Android 库项目和适用于 iOS 的 Swift 包。你可以在 移动插件开发指南 中了解有关为 Android 和 iOS 开发插件的更多信息。

¥A Tauri plugin is composed of a Cargo crate and an optional NPM package that provides API bindings for its commands and events. Additionally, a plugin project can include an Android library project and a Swift package for iOS. You can learn more about developing plugins for Android and iOS in the Mobile Plugin Development guide.

命名约定

¥Naming Convention

Tauri 插件有一个前缀,后跟插件名称。插件名称在 tauri.conf.json > plugins 下的插件配置中指定。

¥Tauri plugins have a prefix followed by the plugin name. The plugin name is specified on the plugin configuration under tauri.conf.json > plugins.

默认情况下,Tauri 会为你的插件包添加 tauri-plugin- 前缀。这有助于你的插件被 Tauri 社区发现并与 Tauri CLI 一起使用。初始化新插件项目时,你必须提供其名称。生成的 crate 名称将是 tauri-plugin-{plugin-name},JavaScript NPM 包名称将是 tauri-plugin-{plugin-name}-api(尽管我们建议尽可能使用 NPM 范围)。Tauri 的 NPM 包命名约定是 @scope-name/plugin-{plugin-name}

¥By default Tauri prefixes your plugin crate with tauri-plugin-. This helps your plugin to be discovered by the Tauri community and to be used with the Tauri CLI. When initializing a new plugin project, you must provide its name. The generated crate name will be tauri-plugin-{plugin-name} and the JavaScript NPM package name will be tauri-plugin-{plugin-name}-api (although we recommend using an NPM scope if possible). The Tauri naming convention for NPM packages is @scope-name/plugin-{plugin-name}.

初始化插件项目

¥Initialize Plugin Project

要引导新的插件项目,请运行 plugin new。如果你不需要 NPM 包,请使用 --no-api CLI 标志。如果你想使用 Android 和/或 iOS 支持初始化插件,请使用 --android 和/或 --ios 标志。

¥To bootstrap a new plugin project, run plugin new. If you do not need the NPM package, use the --no-api CLI flag. If you want to initialize the plugin with Android and/or iOS support, use the --android and/or --ios flags.

安装后,你可以运行以下命令来创建插件项目:

¥After installing, you can run the following to create a plugin project:

npx @tauri-apps/cli plugin new [name]

这将在目录 tauri-plugin-[name] 中初始化插件,并且根据使用的 CLI 标志,生成的项目将如下所示:

¥This will initialize the plugin at the directory tauri-plugin-[name] and, depending on the used CLI flags, the resulting project will look like this:

. tauri-plugin-[name]/
├── src/ - Rust code
│ ├── commands.rs - Defines the commands the webview can use
| ├── desktop.rs - Desktop implementation
| ├── error.rs - Default error type to use in returned results
│ ├── lib.rs - Re-exports appropriate implementation, setup state...
│ ├── mobile.rs - Mobile implementation
│ └── models.rs - Shared structs
├── permissions/ - This will host (generated) permission files for commands
├── android - Android library
├── ios - Swift package
├── guest-js - Source code of the JavaScript API bindings
├── dist-js - Transpiled assets from guest-js
├── Cargo.toml - Cargo crate metadata
└── package.json - NPM package metadata

如果你有一个现有的插件,并想为其添加 Android 或 iOS 功能,你可以使用 plugin android addplugin ios add 来引导移动库项目并指导你完成所需的更改。

¥If you have an existing plugin and would like to add Android or iOS capabilities to it, you can use plugin android add and plugin ios add to bootstrap the mobile library projects and guide you through the changes needed.

移动插件开发

¥Mobile Plugin Development

插件可以运行用 Kotlin(或 Java)和 Swift 编写的原生移动代码。默认插件模板包括一个使用 Kotlin 的 Android 库项目和一个 Swift 包。它包括一个示例移动命令,展示如何从 Rust 代码触发其执行。

¥Plugins can run native mobile code written in Kotlin (or Java) and Swift. The default plugin template includes an Android library project using Kotlin and a Swift package. It includes an example mobile command showing how to trigger its execution from Rust code.

移动插件开发指南 中阅读有关为移动设备开发插件的更多信息。

¥Read more about developing plugins for mobile in the Mobile Plugin Development guide.

插件配置

¥Plugin Configuration

在使用插件的 Tauri 应用中,插件配置在 tauri.conf.json 上指定,其中 plugin-name 是插件的名称:

¥In the Tauri application where the plugin is used, the plugin configuration is specified on tauri.conf.json where plugin-name is the name of the plugin:

{
"build": { ... },
"tauri": { ... },
"plugins": {
"plugin-name": {
"timeout": 30
}
}
}

插件的配置在 Builder 上设置并在运行时进行解析。以下是用于指定插件配置的 Config 结构的示例:

¥The plugin’s configuration is set on the Builder and is parsed at runtime. Here is an example of the Config struct being used to specify the plugin configuration:

src/lib.rs
use tauri::plugin::{Builder, Runtime, TauriPlugin};
use serde::Deserialize;
// Define the plugin config
#[derive(Deserialize)]
struct Config {
timeout: usize,
}
pub fn init<R: Runtime>() -> TauriPlugin<R, Config> {
// Make the plugin config optional
// by using `Builder::<R, Option<Config>>` instead
Builder::<R, Config>::new("<plugin-name>")
.setup(|app, api| {
let timeout = api.config().timeout;
Ok(())
})
.build()
}

生命周期事件

¥Lifecycle Events

插件可以挂接到多个生命周期事件:

¥Plugins can hook into several lifecycle events:

还有其他 移动插件的生命周期事件

¥There are additional lifecycle events for mobile plugins.

setup

  • 时间:插件正在初始化

    ¥When: Plugin is being initialized

  • 原因:注册移动插件、管理状态、运行后台任务

    ¥Why: Register mobile plugins, manage state, run background tasks

src/lib.rs
use tauri::{Manager, plugin::Builder};
use std::{collections::HashMap, sync::Mutex, time::Duration};
struct DummyStore(Mutex<HashMap<String, String>>);
Builder::new("<plugin-name>")
.setup(|app, api| {
app.manage(DummyStore(Default::default()));
let app_ = app.clone();
std::thread::spawn(move || {
loop {
app_.emit("tick", ());
std::thread::sleep(Duration::from_secs(1));
}
});
Ok(())
})

on_navigation

  • 时间:Web 视图正在尝试执行导航

    ¥When: Web view is attempting to perform navigation

  • 原因:验证导航或跟踪 URL 更改

    ¥Why: Validate the navigation or track URL changes

返回 false 将取消导航。

¥Returning false cancels the navigation.

src/lib.rs
use tauri::plugin::Builder;
Builder::new("<plugin-name>")
.on_navigation(|window, url| {
println!("window {} is navigating to {}", window.label(), url);
// Cancels the navigation if forbidden
url.scheme() != "forbidden"
})

on_webview_ready

  • 时间:已创建新窗口

    ¥When: New window has been created

  • 原因:为每个窗口执行初始化脚本

    ¥Why: Execute an initialization script for every window

src/lib.rs
use tauri::plugin::Builder;
Builder::new("<plugin-name>")
.on_webview_ready(|window| {
window.listen("content-loaded", |event| {
println!("webview content has been loaded");
});
})

on_event

  • 时间:事件循环事件

    ¥When: Event loop events

  • 原因:处理核心事件,例如窗口事件、菜单事件和请求应用退出

    ¥Why: Handle core events such as window events, menu events and application exit requested

使用此生命周期钩子,你可以收到任何事件循环 events 的通知。

¥With this lifecycle hook you can be notified of any event loop events.

src/lib.rs
use std::{collections::HashMap, fs::write, sync::Mutex};
use tauri::{plugin::Builder, Manager, RunEvent};
struct DummyStore(Mutex<HashMap<String, String>>);
Builder::new("<plugin-name>")
.setup(|app, _api| {
app.manage(DummyStore(Default::default()));
Ok(())
})
.on_event(|app, event| {
match event {
RunEvent::ExitRequested { api, .. } => {
// user requested a window to be closed and there's no windows left
// we can prevent the app from exiting:
api.prevent_exit();
}
RunEvent::Exit => {
// app is going to exit, you can cleanup here
let store = app.state::<DummyStore>();
write(
app.path().app_local_data_dir().unwrap().join("store.json"),
serde_json::to_string(&*store.0.lock().unwrap()).unwrap(),
)
.unwrap();
}
_ => {}
}
})

on_drop

  • 时间:插件正在解构

    ¥When: Plugin is being deconstructed

  • 原因:插件被销毁时执行代码

    ¥Why: Execute code when the plugin has been destroyed

有关更多信息,请参阅 Drop

¥See Drop for more information.

src/lib.rs
use tauri::plugin::Builder;
Builder::new("<plugin-name>")
.on_drop(|app| {
// plugin has been destroyed...
})

公开 Rust API

¥Exposing Rust APIs

项目的 desktop.rsmobile.rs 中定义的插件 API 将作为与插件同名的结构导出给用户(pascal 格式)。当设置插件时,将创建此结构的实例并将其作为状态进行管理,以便用户可以通过插件中定义的扩展特性随时使用 Manager 实例(例如 AppHandleApp Window)检索它。

¥The plugin APIs defined in the project’s desktop.rs and mobile.rs are exported to the user as a struct with the same name as the plugin (in pascal case). When the plugin is setup, an instance of this struct is created and managed as a state so that users can retrieve it at any point in time with a Manager instance (such as AppHandle, App, or Window) through the extension trait defined in the plugin.

例如,global-shortcut plugin 定义了一个 GlobalShortcut 结构,可以使用 GlobalShortcutExt 特性的 global_shortcut 方法读取:

¥For example, the global-shortcut plugin defines a GlobalShortcut struct that can be read by using the global_shortcut method of the GlobalShortcutExt trait:

src-tauri/src/lib.rs
use tauri_plugin_global_shortcut::GlobalShortcutExt;
tauri::Builder::default()
.plugin(tauri_plugin_global_shortcut::init())
.setup(|app| {
app.global_shortcut().register(...);
Ok(())
})

添加命令

¥Adding Commands

命令在 commands.rs 文件中定义。它们是常规的 Tauri 应用命令。它们可以直接访问 AppHandle 和 Window 实例、访问状态并以与应用命令相同的方式获取输入。阅读 命令指南 以了解有关 Tauri 命令的更多详细信息。

¥Commands are defined in the commands.rs file. They are regular Tauri applications commands. They can access the AppHandle and Window instances directly, access state, and take input the same way as application commands. Read the Commands guide for more details on Tauri commands.

此命令显示如何通过依赖注入访问 AppHandleWindow 实例,并接受两个输入参数(on_progressurl):

¥This command shows how to get access to the AppHandle and Window instance via dependency injection, and takes two input parameters (on_progress and url):

src/commands.rs
use tauri::{command, ipc::Channel, AppHandle, Runtime, Window};
#[command]
async fn upload<R: Runtime>(app: AppHandle<R>, window: Window<R>, on_progress: Channel, url: String) {
// implement command logic here
on_progress.send(100).unwrap();
}

要将命令公开给 webview,你必须挂接到 lib.rs 中的 invoke_handler() 调用:

¥To expose the command to the webview, you must hook into the invoke_handler() call in lib.rs:

src/lib.rs
Builder::new("<plugin-name>")
.invoke_handler(tauri::generate_handler![commands::upload])

webview-src/index.ts 中定义一个绑定函数,以便插件用户可以轻松地在 JavaScript 中调用命令:

¥Define a binding function in webview-src/index.ts so that plugin users can easily call the command in JavaScript:

import { invoke, Channel } from '@tauri-apps/api/core'
export async function upload(url: string, onProgressHandler: (progress: number) => void): Promise<void> {
const onProgress = new Channel<number>()
onProgress.onmessage = onProgressHandler
await invoke('plugin:<plugin-name>|upload', { url, onProgress })
}

请务必在测试之前构建 TypeScript 代码。

¥Be sure to build the TypeScript code prior to testing it.

命令权限

¥Command Permissions

默认情况下,前端无法访问你的命令。如果你尝试执行其中一个,你将收到拒绝错误拒绝。要实际公开命令,你还需要定义允许每个命令的权限。

¥By default your commands are not accessible by the frontend. If you try to execute one of them, you will get a denied error rejection. To actually expose commands, you also need to define permissions that allow each command.

权限文件

¥Permission Files

权限定义为 permissions 目录中的 JSON 或 TOML 文件。每个文件都可以定义一个权限列表、一个权限集列表和插件的默认权限。

¥Permissions are defined as JSON or TOML files inside the permissions directory. Each file can define a list of permissions, a list of permission sets and your plugin’s default permission.

权限

¥Permissions

权限描述插件命令的权限。它可以允许或拒绝命令列表并关联特定于命令和全局范围。

¥A permission describes privileges of your plugin commands. It can allow or deny a list of commands and associate command-specific and global scopes.

permissions/start-server.toml
"$schema" = "schemas/schema.json"
[[permission]]
identifier = "allow-start-server"
description = "Enables the start_server command."
commands.allow = ["start_server"]
[[permission]]
identifier = "deny-start-server"
description = "Denies the start_server command."
commands.deny = ["start_server"]
范围

¥Scope

范围允许你的插件定义对单个命令的更深层次的限制。每个权限都可以定义一个范围对象列表,这些对象定义允许或拒绝特定于命令或全局插件的内容。

¥Scopes allow your plugin to define deeper restrictions to individual commands. Each permission can define a list of scope objects that define something to be allowed or denied either specific to a command or globally to the plugin.

让我们定义一个示例结构,它将保存 shell 插件允许生成的二进制文件列表的范围数据:

¥Let’s define an example struct that will hold scope data for a list of binaries a shell plugin is allowed to spawn:

src/scope.rs
#[derive(Debug, schemars::JsonSchema)]
pub struct Entry {
pub binary: String,
}
命令范围

¥Command Scope

你的插件消费者可以在其功能文件中定义特定命令的范围(参见 documentation)。你可以使用 tauri::ipc::CommandScope 结构读取特定于命令的范围:

¥Your plugin consumer can define a scope for a specific command in their capability file (see the documentation). You can read the command-specific scope with the tauri::ipc::CommandScope struct:

src/commands.rs
use tauri::ipc::CommandScope;
use crate::scope::Entry;
async fn spawn<R: tauri::Runtime>(app: tauri::AppHandle<R>, command_scope: CommandScope<'_, Entry>) -> Result<()> {
let allowed = command_scope.allows();
let denied = command_scope.denies();
todo!()
}
全球范围

¥Global Scope

当权限未定义任何要允许或拒绝的命令时,它被视为范围权限,并且它应仅为你的插件定义全局范围:

¥When a permission does not define any commands to be allowed or denied, it’s considered a scope permission and it should only define a global scope for your plugin:

permissions/spawn-node.toml
[[permission]]
identifier = "allow-spawn-node"
description = "This scope permits spawning the `node` binary."
[[permission.scope.allow]]
binary = "node"

你可以使用 tauri::ipc::GlobalScope 结构读取全局范围:

¥You can read the global scope with the tauri::ipc::GlobalScope struct:

src/commands.rs
use tauri::ipc::GlobalScope;
use crate::scope::Entry;
async fn spawn<R: tauri::Runtime>(app: tauri::AppHandle<R>, scope: GlobalScope<'_, Entry>) -> Result<()> {
let allowed = scope.allows();
let denied = scope.denies();
todo!()
}

:::note 注意

我们建议检查全局和命令范围以获得灵活性

¥We recommend checking both global and command scopes for flexibility

:::

架构

¥Schema

范围条目需要 schemars 依赖来生成 JSON 模式,以便插件使用者知道范围的格式并在其 IDE 中自动补齐。

¥The scope entry requires the schemars dependency to generate a JSON schema so the plugin consumers know the format of the scope and have autocomplete in their IDEs.

要定义模式,首先将依赖添加到你的 Cargo.toml 文件中:

¥To define the schema, first add the dependency to your Cargo.toml file:

# we need to add schemars to both dependencies and build-dependencies because the scope.rs module is shared between the app code and build script
[dependencies]
schemars = "0.8"
[build-dependencies]
schemars = "0.8"

在你的构建脚本中,添加以下代码:

¥In your build script, add the following code:

build.rs
#[path = "src/scope.rs"]
mod scope;
const COMMANDS: &[&str] = &[];
fn main() {
tauri_plugin::Builder::new(COMMANDS)
.global_scope_schema(schemars::schema_for!(scope::Entry))
.build();
}
权限集

¥Permission Sets

权限集是单个权限的组,可帮助用户以更高的抽象级别管理你的插件。例如,如果单个 API 使用多个命令,或者命令集合之间存在逻辑连接,则应定义一个包含它们的集合:

¥Permission sets are groups of individual permissions that helps users manage your plugin with a higher level of abstraction. For instance if a single API uses multiple commands or if there’s a logical connection between a collection of commands, you should define a set containing them:

permissions/websocket.toml
"$schema" = "schemas/schema.json"
[[set]]
identifier = "allow-websocket"
description = "Allows connecting and sending messages through a WebSocket"
permissions = ["allow-connect", "allow-send"]
默认权限

¥Default Permission

默认权限是具有标识符 default 的特殊权限集。建议你默认启用所需的命令。例如,如果不允许 request 命令,http 插件将毫无用处:

¥The default permission is a special permission set with identifier default. It’s recommended that you enable required commands by default. For instance the http plugin is useless without the request command allowed:

permissions/default.toml
"$schema" = "schemas/schema.json"
[default]
description = "Allows making HTTP requests"
permissions = ["allow-request"]

自动生成的权限

¥Autogenerated Permissions

为每个命令定义权限的最简单方法是使用在 build.rs 文件中定义的插件构建脚本中定义的自动生成选项。在 COMMANDS const 中,以 snake_case 定义命令列表(应与命令函数名称匹配),Tauri 将自动生成 allow-$commandnamedeny-$commandname 权限。

¥The easiest way to define permissions for each of your commands is to use the autogeneration option defined in your plugin’s build script defined in the build.rs file. Inside the COMMANDS const, define the list of commands in snake_case (should match the command function name) and Tauri will automatically generate an allow-$commandname and a deny-$commandname permissions.

以下示例生成 allow-uploaddeny-upload 权限:

¥The following example generates the allow-upload and deny-upload permissions:

src/commands.rs
const COMMANDS: &[&str] = &["upload"];
fn main() {
tauri_plugin::Builder::new(COMMANDS).build();
}

有关更多信息,请参阅 权限概述 文档。

¥See the Permissions Overview documentation for more information.

管理状态

¥Managing State

插件可以像 Tauri 应用一样管理状态。阅读 状态管理指南 以了解更多信息。

¥A plugin can manage state in the same way a Tauri application does. Read the State Management guide for more information.


Tauri v2.3 中文网 - 粤ICP备13048890号
Nodejs.cn 旗下网站