人人都会AI编程

插件结构、Rust 核心实现、前端 API 封装

更新时间:2026-07-11

Tauri 的插件体系是功能复用的核心。一个插件通常包含 Rust 端的命令实现和前端侧的 JavaScript/TypeScript 封装。它的结构清晰,开发流程固定,非常适合在多个应用中共享同一套原生能力。


1. 插件结构

一个标准的 Tauri 插件(比如 tauri-plugin-fs)在源码仓库中通常有以下布局:

plugin-name/
├── src/                # Rust 源码
│   ├── lib.rs          # 插件入口,注册命令、配置权限
│   ├── commands.rs     # 所有命令的具体实现
│   └── error.rs        # 自定义错误类型(可选)
├── guest-js/           # 前端 NPM 包(提供给 WebView 调用的 API)
│   ├── src/
│   │   └── index.ts    # 封装的对 Rust 命令的调用
│   ├── package.json
│   └── tsconfig.json
├── permissions/        # 插件权限定义(Tauri v2 新增)
│   ├── default.toml    # 默认权限列表
│   └── schema.json     # 权限配置的 JSON Schema
├── build.rs            # 编译构建脚本
└── Cargo.toml          # Rust 包配置,声明为 tauri 插件类型

这种结构将 后端逻辑前端调用 完全分离,但共用同一个发布周期。最终用户只需要在 Cargo.toml 和前端 package.json 中分别引入即可。


2. Rust 核心实现

Rust 侧的核心任务是:定义可调用的命令,并通过 tauri::Builder 将它们注册到 IPC 系统中

一个典型的插件 lib.rs 长这样:

use tauri::plugin::{Builder, TauriPlugin};
use tauri::{Runtime, command};

// 定义命令:这里读取一个文本文件
#[command]
fn read_file(path: String) -> Result<String, String> {
    std::fs::read_to_string(path).map_err(|e| e.to_string())
}

// 定义插件的构建函数
pub fn init<R: Runtime>() -> TauriPlugin<R> {
    Builder::new("my-plugin")
        .invoke_handler(tauri::generate_handler![read_file])
        .build()
}

如果你想带状态(例如数据库连接),可以利用 Buildersetup 方法:

Builder::new("db")
    .setup(|app, _api| {
        let pool = init_db_pool();
        app.manage(pool); // 将状态注入 Tauri 全局管理
        Ok(())
    })
    .invoke_handler(tauri::generate_handler![query_db])

所有命令都必须通过 invoke_handler 显式注册,未经注册的 Rust 函数前端无法调用,从架构层面隔绝了未授权访问

对于 Tauri v2,你还需要在插件中声明 权限。例如在 permissions/default.toml 中定义哪些命令是公开的:

[default]
description = "允许读取用户桌面上的文本文件"
permissions = ["allow-read-file"]

然后在应用级配置中开启该插件的权限。这为命令调用增加了细粒度的访问控制。


3. 前端 API 封装

前端侧通过 @tauri-apps/api 提供的 invoke 函数与后端通信,插件的 JS/TS 封装就是在原始调用之上加一层语义化包装。

import { invoke } from '@tauri-apps/api/core';

/**
 * 读取指定路径的文本文件
 * @param path 文件路径
 * @returns 文件内容
 */
export async function readFile(path: string): Promise<string> {
  return invoke('plugin:my-plugin|read_file', { path });
}

// 或者对于 v2 更标准的写法
export function readFile(path: string): Promise<string> {
  return invoke('plugin:my-plugin|read_file', { path });
}

在 Tauri v2 中,插件的命令会带上 plugin: 前缀,如 plugin:my-plugin|read_file,这是为了区分应用自身命令和插件命令。前端封装时会把这个细节隐藏,用户直接 import { readFile } from 'tauri-plugin-my-plugin' 即可。

如果要暴露事件或者可取消的操作,可以结合 Channel 或其他通信模式。一般而言,插件作者会提供完整的 TypeScript 类型定义,保证调用时类型安全。


真实开发流程

假设你为 Tauri 应用写一个“获取系统空闲内存”的插件,步骤如下:

  1. 创建 Rust 插件项目(可以使用 cargo 模板或直接改造一个 crate);
  2. commands.rs 中用 Rust 系统调用(如 sysinfo 库)获取空闲内存,并定义 get_free_memory 命令;
  3. lib.rs 中注册该命令,同时定义权限;
  4. guest-js/src/index.ts 中用 invoke 封装一个 getFreeMemory() 异步函数并导出;
  5. 发布两个包:Rust crate 和前端 NPM 包;
  6. 应用开发者在其 Cargo.toml 和前端项目中分别添加依赖,配置权限,直接调用。

这种三层的清晰分工,使得社区可以放心地共享插件,任何一位前端开发者都能通过 TypeScript 调用 Rust 实现的高性能原生功能,而无需关注底层的 IPC 细节和安全性。