GUI.for.SingBox插件开发调试:工具与技巧分享

GUI.for.SingBox插件开发调试:工具与技巧分享

插件开发基础

插件系统架构概述

GUI.for.SingBox采用现代化插件架构,通过事件驱动机制实现功能扩展。插件系统基于Pinia状态管理构建,提供生命周期管理、事件触发和配置持久化能力。

mermaid

核心接口定义

插件系统核心接口通过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 // 运行状态码
}

开发环境搭建

基础开发工具链

  1. 必备工具

    • Node.js (v16+) 与 pnpm
    • TypeScript 4.9+
    • Git
    • VS Code 及插件:
      • Volar (Vue 3支持)
      • ESLint
      • TypeScript Vue Plugin
  2. 项目克隆与依赖安装

# 克隆仓库
git clone https://gitcode.com/gh_mirrors/gu/GUI.for.SingBox

# 进入前端目录
cd GUI.for.SingBox/frontend

# 安装依赖
pnpm install
  1. 开发模式启动
# 启动前端热重载开发服务器
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.jsontsconfig.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/**/*"]
}

调试技巧与工具

调试环境配置

  1. 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"
    }
  ]
}
  1. 插件热重载

创建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...')

高级调试技术

  1. 使用应用日志系统
// 写入日志
window.appApi.log({
  level: 'info',
  module: 'plugin-myfirst',
  message: '插件执行',
  details: { timestamp: new Date().toISOString() }
})
  1. 性能分析
// 性能计时
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` }
})
  1. 错误处理与调试
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()
  })
})

集成测试流程

  1. 插件打包
# 构建插件
pnpm run build

# 创建插件包
cd dist
zip -r plugin-myfirst-v1.0.0.zip *
  1. 手动安装测试
  • 通过应用界面: 设置 > 插件 > 安装插件 > 选择ZIP文件
  • 通过API: curl -X POST http://localhost:3000/api/plugins -F "file=@plugin-myfirst-v1.0.0.zip"
  1. 自动化测试脚本
#!/bin/bash
set -e

# 构建应用
pnpm run build

# 启动测试服务器
pnpm run test:server &
SERVER_PID=$!

# 等待服务器启动
sleep 5

# 运行集成测试
pnpm run test:e2e

# 停止服务器
kill $SERVER_PID

发布与分发

插件打包规范

  1. 目录结构要求
plugin-myfirst-v1.0.0.zip
├── index.js         # 编译后的主文件
├── manifest.json    # 插件元数据
├── config.d.ts      # 配置类型定义
└── assets/          # 静态资源
    ├── icon.png
    └── style.css
  1. 版本号规范

采用语义化版本(SEMVER):

  • MAJOR: 不兼容的API变更 (1.0.0)
  • MINOR: 向后兼容的功能新增 (0.1.0)
  • PATCH: 向后兼容的问题修复 (0.0.1)

分发渠道

  1. 手动分发

    • 通过论坛和社区分享ZIP包
    • 个人网站提供下载
  2. 插件中心提交

    • Fork Plugin-Hub仓库
    • 添加插件元数据到plugins/gfs.json
    • 创建Pull Request

常见问题与解决方案

开发常见问题

问题原因解决方案
插件元数据不更新缓存未清除执行pnpm run clean:plugins
类型定义错误TS配置问题检查tsconfig.json中的paths配置
事件不触发触发器未注册确保triggers数组包含正确事件名
API访问被拒绝权限不足在manifest中声明所需权限
构建失败依赖冲突使用pnpm why <package>检查依赖

性能优化指南

  1. 资源优化

    • 最小化代码体积: terser压缩JS
    • 延迟加载非关键资源
    • 使用Tree-shaking移除未使用代码
  2. 执行优化

    • 避免同步阻塞UI线程
    • 使用Web Worker处理耗时操作
    • 缓存计算结果减少重复处理
  3. 内存管理

    • 及时清理大对象引用
    • 避免闭包中保留大对象
    • 使用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组件

  1. 注册自定义组件
// 注册Vue组件
window.appApi.registerComponent({
  name: 'PluginCustomTable',
  component: defineAsyncComponent(() => import('./components/CustomTable.vue'))
})
  1. 在配置界面使用
# plugin.yaml 中添加
configuration:
  - id: "customView"
    title: "高级配置"
    description: "自定义表格视图"
    key: "customView"
    component: "PluginCustomTable"  # 使用注册的组件名
    value: { /* 初始数据 */ }
    options: { /* 组件选项 */ }

总结与展望

开发经验分享

  1. 最佳实践

    • 保持单一职责: 一个插件专注解决一个问题
    • 提供详细文档: 使用JSDoc注释API
    • 防御性编程: 处理所有可能的边界情况
    • 向后兼容: 考虑旧版本应用的兼容性
  2. 资源推荐

未来发展方向

  1. 功能扩展

    • WebAssembly插件支持
    • 更丰富的UI组件库
    • 实时协作功能
  2. 开发体验优化

    • 插件开发脚手架工具
    • 在线调试环境
    • AI辅助开发工具

通过本指南,您应该已经掌握了GUI.for.SingBox插件开发的核心技术和最佳实践。社区欢迎您的贡献,无论是新插件、bug修复还是文档改进。


如果你觉得本指南有帮助,请点赞、收藏并关注项目更新!
下期预告: 《插件安全开发与审核指南》

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值