GUI.for.SingBox插件开发调试:工具与技巧分享
插件开发基础
插件系统架构概述
GUI.for.SingBox采用现代化插件架构,通过事件驱动机制实现功能扩展。插件系统基于Pinia状态管理构建,提供生命周期管理、事件触发和配置持久化能力。
核心接口定义
插件系统核心接口通过TypeScript类型定义实现:
// 插件配置项定义
export type PluginConfiguration = {
id: string
title: string
description: string
key: string
component: 'CheckBox' | 'Input' | 'Select' | ... // UI组件类型
value: any // 默认值
options: any[] // 下拉选择等组件的选项
}
// 插件元数据定义
export type PluginType = {
id: string // 唯一标识符
version: string // 语义化版本
name: string // 显示名称
description: string // 功能描述
type: 'Http' | 'File' // 加载方式
url: string // HTTP加载地址
path: string // 本地文件路径
triggers: PluginTrigger[] // 订阅事件列表
menus: Record<string, string> // 托盘菜单配置
context: { /* 上下文数据 */ } // 运行时上下文
configuration: PluginConfiguration[] // 配置项定义
disabled: boolean // 是否禁用
install: boolean // 是否需要安装
installed: boolean // 安装状态
status: number // 运行状态码
}
开发环境搭建
基础开发工具链
-
必备工具
- Node.js (v16+) 与 pnpm
- TypeScript 4.9+
- Git
- VS Code 及插件:
- Volar (Vue 3支持)
- ESLint
- TypeScript Vue Plugin
-
项目克隆与依赖安装
# 克隆仓库
git clone https://gitcode.com/gh_mirrors/gu/GUI.for.SingBox
# 进入前端目录
cd GUI.for.SingBox/frontend
# 安装依赖
pnpm install
- 开发模式启动
# 启动前端热重载开发服务器
pnpm dev
# 在另一个终端启动后端服务
cd ..
go run main.go
插件开发专用配置
在frontend/vite.config.ts中添加插件开发支持:
// vite.config.ts
export default defineConfig({
// ...其他配置
server: {
proxy: {
// 插件开发API代理
'/api/plugins': {
target: 'http://localhost:8083',
ws: true,
changeOrigin: true
}
}
},
define: {
// 启用插件开发模式标识
__PLUGIN_DEV__: JSON.stringify(true)
}
})
插件开发全流程
1. 项目结构创建
标准插件项目结构:
plugins/
├── my-first-plugin/
│ ├── src/
│ │ ├── index.ts # 插件主逻辑
│ │ ├── config.ts # 配置定义
│ │ └── utils.ts # 工具函数
│ ├── package.json # 依赖管理
│ ├── tsconfig.json # TypeScript配置
│ └── plugin.yaml # 插件元数据
2. 元数据定义
创建plugin.yaml定义插件基本信息:
id: "plugin-myfirst"
version: "v1.0.0"
name: "我的第一个插件"
description: "演示插件开发基础功能"
type: "File"
path: "plugins/my-first-plugin/dist/index.js"
triggers: ["OnManual", "OnSubscribe"]
menus: {
"menu.plugin.myfirst.title": "我的插件操作",
"menu.plugin.myfirst.action": "执行测试"
}
configuration:
- id: "showMessage"
title: "显示消息"
description: "是否在触发时显示提示消息"
key: "showMessage"
component: "CheckBox"
value: true
options: []
- id: "messageText"
title: "消息内容"
description: "触发时显示的消息文本"
key: "messageText"
component: "Input"
value: "插件执行成功"
options: []
3. 核心逻辑实现
创建src/index.ts实现插件功能:
// 导入类型定义
import type { PluginType, PluginConfiguration } from '@/stores/plugins'
// 声明全局变量获取插件元数据
declare global {
const Plugin: PluginType & Record<string, any>
}
/**
* 手动触发事件处理函数
* @returns 状态码 0=成功
*/
export async function OnManual(): Promise<number> {
// 获取配置值
const { showMessage, messageText } = Plugin
// 调用主应用API显示消息
if (showMessage) {
window.appApi.showMessage({
type: 'success',
title: '插件操作',
content: messageText
})
}
// 返回状态码
return 0
}
/**
* 订阅事件处理函数
* @param proxies 代理列表
* @param subscription 订阅配置
* @returns 处理后的代理列表
*/
export async function OnSubscribe(
proxies: Record<string, any>[],
subscription: any
): Promise<Record<string, any>[]> {
// 示例:添加自定义标签
return proxies.map(proxy => ({
...proxy,
tags: [...(proxy.tags || []), 'processed-by-my-plugin']
}))
}
// 导出所有事件处理函数
export default {
OnManual,
OnSubscribe
}
4. 构建配置
配置package.json和tsconfig.json:
// package.json
{
"name": "plugin-myfirst",
"version": "1.0.0",
"main": "src/index.ts",
"scripts": {
"build": "tsc && rollup -c",
"watch": "tsc --watch"
},
"dependencies": {
"@/stores": "workspace:*",
"@/utils": "workspace:*"
}
}
// tsconfig.json
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
"@/*": ["../../src/*"]
}
},
"include": ["src/**/*"]
}
调试技巧与工具
调试环境配置
- VS Code调试配置
创建.vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "插件开发调试",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}/frontend",
"sourceMaps": true,
"sourceMapPathOverrides": {
"webpack:///plugins/*": "${workspaceFolder}/frontend/plugins/*"
},
"preLaunchTask": "pnpm: dev"
}
]
}
- 插件热重载
创建plugins-dev-server.js实现热重载:
const chokidar = require('chokidar')
const { execSync } = require('child_process')
// 监听插件目录变化
const watcher = chokidar.watch('plugins/**/*.{ts,js,yaml}', {
persistent: true,
ignoreInitial: true
})
// 变化时执行构建并通知主应用
watcher.on('all', (event, path) => {
console.log(`[Plugin Dev] ${event}: ${path}`)
// 构建插件
execSync('pnpm run build:plugins', { stdio: 'inherit' })
// 通知主应用重新加载插件
execSync('curl http://localhost:3000/api/reload-plugins', { stdio: 'ignore' })
})
console.log('Plugin development watcher started...')
高级调试技术
- 使用应用日志系统
// 写入日志
window.appApi.log({
level: 'info',
module: 'plugin-myfirst',
message: '插件执行',
details: { timestamp: new Date().toISOString() }
})
- 性能分析
// 性能计时
const startTime = performance.now()
// 执行耗时操作
await processLargeData()
// 记录耗时
const duration = performance.now() - startTime
window.appApi.log({
level: 'debug',
module: 'plugin-myfirst',
message: '数据处理完成',
details: { duration: `${duration.toFixed(2)}ms` }
})
- 错误处理与调试
export async function OnManual(): Promise<number> {
try {
// 可能出错的代码
const result = await riskyOperation()
return 0
} catch (error) {
// 详细错误日志
window.appApi.log({
level: 'error',
module: 'plugin-myfirst',
message: '操作失败',
details: {
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
timestamp: new Date().toISOString()
}
})
// 显示错误消息
window.appApi.showMessage({
type: 'error',
title: '插件错误',
content: error instanceof Error ? error.message : '未知错误'
})
// 返回错误状态码
return 1
}
}
插件测试策略
单元测试实现
使用Jest编写单元测试:
// src/__tests__/index.test.ts
import { OnManual } from '../index'
// 模拟全局变量
declare global {
namespace NodeJS {
interface Global {
Plugin: any
appApi: any
}
}
}
// 测试用例
describe('OnManual', () => {
beforeEach(() => {
// 设置模拟数据
global.Plugin = {
showMessage: true,
messageText: '测试消息'
}
global.appApi = {
showMessage: jest.fn()
}
})
test('should show message when enabled', async () => {
// 执行测试函数
const result = await OnManual()
// 验证结果
expect(result).toBe(0)
expect(global.appApi.showMessage).toHaveBeenCalledWith({
type: 'success',
title: '插件操作',
content: '测试消息'
})
})
test('should not show message when disabled', async () => {
// 修改配置
global.Plugin.showMessage = false
// 执行测试函数
const result = await OnManual()
// 验证结果
expect(result).toBe(0)
expect(global.appApi.showMessage).not.toHaveBeenCalled()
})
})
集成测试流程
- 插件打包
# 构建插件
pnpm run build
# 创建插件包
cd dist
zip -r plugin-myfirst-v1.0.0.zip *
- 手动安装测试
- 通过应用界面: 设置 > 插件 > 安装插件 > 选择ZIP文件
- 通过API:
curl -X POST http://localhost:3000/api/plugins -F "file=@plugin-myfirst-v1.0.0.zip"
- 自动化测试脚本
#!/bin/bash
set -e
# 构建应用
pnpm run build
# 启动测试服务器
pnpm run test:server &
SERVER_PID=$!
# 等待服务器启动
sleep 5
# 运行集成测试
pnpm run test:e2e
# 停止服务器
kill $SERVER_PID
发布与分发
插件打包规范
- 目录结构要求
plugin-myfirst-v1.0.0.zip
├── index.js # 编译后的主文件
├── manifest.json # 插件元数据
├── config.d.ts # 配置类型定义
└── assets/ # 静态资源
├── icon.png
└── style.css
- 版本号规范
采用语义化版本(SEMVER):
- MAJOR: 不兼容的API变更 (1.0.0)
- MINOR: 向后兼容的功能新增 (0.1.0)
- PATCH: 向后兼容的问题修复 (0.0.1)
分发渠道
-
手动分发
- 通过论坛和社区分享ZIP包
- 个人网站提供下载
-
插件中心提交
- Fork Plugin-Hub仓库
- 添加插件元数据到
plugins/gfs.json - 创建Pull Request
常见问题与解决方案
开发常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 插件元数据不更新 | 缓存未清除 | 执行pnpm run clean:plugins |
| 类型定义错误 | TS配置问题 | 检查tsconfig.json中的paths配置 |
| 事件不触发 | 触发器未注册 | 确保triggers数组包含正确事件名 |
| API访问被拒绝 | 权限不足 | 在manifest中声明所需权限 |
| 构建失败 | 依赖冲突 | 使用pnpm why <package>检查依赖 |
性能优化指南
-
资源优化
- 最小化代码体积:
terser压缩JS - 延迟加载非关键资源
- 使用Tree-shaking移除未使用代码
- 最小化代码体积:
-
执行优化
- 避免同步阻塞UI线程
- 使用Web Worker处理耗时操作
- 缓存计算结果减少重复处理
-
内存管理
- 及时清理大对象引用
- 避免闭包中保留大对象
- 使用WeakMap存储临时数据
高级开发模式
插件间通信
// 发送消息
window.appApi.sendPluginMessage({
target: 'plugin-other',
action: 'data-updated',
payload: { id: 'data1', timestamp: Date.now() }
})
// 接收消息
window.appApi.onPluginMessage((message) => {
if (message.target === 'plugin-myfirst' && message.action === 'data-updated') {
console.log('Received data update:', message.payload)
refreshData()
}
})
使用应用状态管理
// 获取配置
const appSettings = await window.appApi.getAppSettings()
// 监听配置变化
window.appApi.onAppSettingsChange((newSettings) => {
if (newSettings.theme !== appSettings.theme) {
updateTheme(newSettings.theme)
appSettings.theme = newSettings.theme
}
})
自定义UI组件
- 注册自定义组件
// 注册Vue组件
window.appApi.registerComponent({
name: 'PluginCustomTable',
component: defineAsyncComponent(() => import('./components/CustomTable.vue'))
})
- 在配置界面使用
# plugin.yaml 中添加
configuration:
- id: "customView"
title: "高级配置"
description: "自定义表格视图"
key: "customView"
component: "PluginCustomTable" # 使用注册的组件名
value: { /* 初始数据 */ }
options: { /* 组件选项 */ }
总结与展望
开发经验分享
-
最佳实践
- 保持单一职责: 一个插件专注解决一个问题
- 提供详细文档: 使用JSDoc注释API
- 防御性编程: 处理所有可能的边界情况
- 向后兼容: 考虑旧版本应用的兼容性
-
资源推荐
未来发展方向
-
功能扩展
- WebAssembly插件支持
- 更丰富的UI组件库
- 实时协作功能
-
开发体验优化
- 插件开发脚手架工具
- 在线调试环境
- AI辅助开发工具
通过本指南,您应该已经掌握了GUI.for.SingBox插件开发的核心技术和最佳实践。社区欢迎您的贡献,无论是新插件、bug修复还是文档改进。
如果你觉得本指南有帮助,请点赞、收藏并关注项目更新!
下期预告: 《插件安全开发与审核指南》
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



