服务端插件
当插件需要在服务端(Node.js 环境)执行逻辑时,可以使用服务端插件功能。服务端代码在本地 Node 侧载(src-node)中通过 Worker 线程 执行,与 Koa 主进程隔离,单个插件未捕获异常不会拖垮整个 Node 服务。
在插件 server 入口中如需使用与主应用一致的 API,请从 @public-tauri/api/node 引入:与 localhost:2345 上已有能力一致的(如 utils 中走 invokeServerUtils 的路径)在 Node 内直接完成;仅当需要 Tauri、WebView 或 window 事件时,才经主窗体上的 Socket 回桥执行。主应用启动后会自动建立 __public_tauri_host__ 的 Socket 连接用于该回桥。
pnpm --filter src-node run build 会生成 dist/public-plugin-worker.cjs,主进程用 new Worker(fileUrl) 加载。协议字段见 src-node/src/plugin/worker-protocol.ts(kind: m2w:* / w2m:* 前缀,避免与旧版单字母字段混淆)。
配置
在 package.json 的 publicPlugin 字段中添加 server 指向服务端入口文件:
{
"publicPlugin": {
"server": "dist/server.js",
"commands": [...]
}
}服务端入口文件
服务端入口文件是一个标准的 Node.js ESM 模块。你可以导出函数供前端通过 channel.invoke 调用:
// src/server.ts
import fs from 'node:fs'
import path from 'node:path'
export async function readFile(filePath: string) {
const content = await fs.promises.readFile(filePath, 'utf-8')
return content
}
export async function listFiles(dirPath: string) {
const entries = await fs.promises.readdir(dirPath, { withFileTypes: true })
return entries.map(entry => ({
name: entry.name,
isDirectory: entry.isDirectory(),
}))
}
export async function runScript(script: string) {
// 执行系统命令等操作
const { exec } = await import('node:child_process')
return new Promise((resolve, reject) => {
exec(script, (error, stdout, stderr) => {
if (error) reject(error)
else resolve({ stdout, stderr })
})
})
}前端调用
view 模式
在 view 模式下,使用 channel.invoke 调用服务端方法,并通过 channel.on 接收 server 事件:
import { channel } from '@public-tauri/api'
// 调用服务端方法
const content = await channel.invoke<string>('readFile', '/path/to/file.txt')
const files = await channel.invoke<Array<{name: string, isDirectory: boolean}>>('listFiles', '/path/to/dir')
// 监听服务端事件
channel.on('file-changed', (data) => {
console.log('文件变化:', data)
})main 模式
在 main 模式下同样使用 channel。插件名称由宿主注入,插件代码不需要也不应该直接接触 createPluginChannel、invokePluginServerMethod 等宿主内部接口:
import { channel } from '@public-tauri/api'
const result = await channel.invoke('readFile', '/path/to/file.txt')
channel.on('my-event', (data) => {
console.log('收到事件:', data)
})服务端通信
服务端可以从 @public-tauri/api/node 导入 channel,与前端使用同一组方法:
import { channel } from '@public-tauri/api/node'
channel.emit('file-changed', { path: '/tmp/example.txt' })
channel.on('frontend-event', (payload) => {
console.log('收到前端事件:', payload)
})
channel.handle('serverMethod', async (input) => {
return `server handled: ${input}`
})
const value = await channel.invoke('frontendMethod', 'from server')服务端模块的命名函数导出会自动注册为 handler;如果同名 channel.handle() 被调用,则以后注册的 handler 会覆盖之前的同名 handler。
静态资源
插件目录会自动注册为静态资源服务。你可以通过 HTTP 访问插件目录下的文件:
http://<plugin-name>.plugin.localhost:2345/dist/index.html对于 npm scope 包(如 @scope/my-plugin):
http://my-plugin.scope.plugin.localhost:2345/dist/index.html这使得 view 模式的插件可以直接通过 HTTP URL 加载。
构建服务端代码
服务端代码需要构建为 Node.js 可执行的 ESM 格式。推荐使用 tsdown:
tsdown 配置
// tsdown.config.ts
import { defineConfig } from 'tsdown'
export default defineConfig([
// 前端入口
{
entry: './src/main.ts',
format: 'esm',
platform: 'browser',
target: 'es2022',
outExtensions: () => ({ js: '.js' }),
},
// 服务端入口
{
entry: './src/server.ts',
format: 'esm',
platform: 'node',
target: 'es2022',
outExtensions: () => ({ js: '.js' }),
deps: {
neverBundle: ['node:*'],
},
},
])在 server 中使用 @public-tauri/api
构建 server 产物时,将 @public-tauri/api 解析到 Node 实现,例如 tsdown alias:@public-tauri/api → @public-tauri/api/node(或在你的构建里将 server 入口单独配置 alias)。在 server 源码中:
import { utils, fetch, dialog, channel } from '@public-tauri/api/node'mainWindow.on 等需要回调的 API 无法经回桥序列化,请在 server 中避免使用;需要时改由前端完成。 channel.invoke 是双向 RPC:前端可调用 server 导出函数或 channel.handle,server 也可调用前端 channel.handle。createPluginStorage、createPluginChannel、invokePluginServerMethod 是宿主内部能力,不从 @public-tauri/api 暴露给插件。
注意事项
- 服务端模块可选:如果不需要服务端能力,无需配置
server字段。 - 安全性:服务端代码拥有完整的系统访问权限,请注意安全。
- 生命周期:服务端模块在插件注册时加载,在插件卸载时销毁。
- 调用约定:
channel.invoke的第一个参数是对端 handler 名称,后续参数依次传递。 - ListView / preload:
command.preload等由 HTTP 静态资源在 WebView 中加载;本页所述为publicPlugin.server指向的 Node 模块。