Skip to content

插件开发指南(基于 ESBuild + TSX)

本指南面向首次开发 Foxel 前端插件 App 的开发者,说明宿主提供的能力、插件对象规范、生命周期、清单上报机制,以及使用 TypeScript + React + esbuild 从 TSX 构建成自包含 JS 的方案。

快速检查清单

  • 已实现 window.FoxelRegister(plugin),仅导出一个 plugin 对象
  • mount(container, ctx) 内渲染,必要时实现 unmount
  • 文件读取统一使用 ctx.urls.downloadUrl
  • 仅操作 container,样式使用唯一前缀/ID
  • 设置合适的 supportedExtsnameiconversion
  • 产物为 IIFE 单文件:dist/plugin.js

工作原理与数据流

  • 加载方式:宿主以 <script src="..."> 注入远程插件 JS。插件脚本执行后必须调用 window.FoxelRegister(plugin) 注册自身。
  • 生命周期:
    • mount(container, ctx):宿主传入一个空容器节点与上下文 ctx;插件在该节点内渲染自己的 UI。
    • unmount(container)?:可选,宿主在卸载时调用,用于清理副作用(移除事件、销毁 React root 等)。
  • 应用选择:宿主根据插件的 supportedExts 与当前文件扩展名匹配决定是否显示为可用应用。未上报或为空数组时默认允许(便于首次运行)。
  • 清单上报:首次安装后,宿主会在成功加载插件并拿到 plugin 对象后,将其元数据上报至后端(非阻塞)。

读取文件

插件读取文件内容统一使用 ctx.urls.downloadUrl,无需自行调用后端 API。

DOM 范围

仅操作传入的 container 节点。不要修改宿主页面其他 DOM 或全局样式;如需注入样式,请使用唯一前缀/ID 以避免污染。

宿主接口(Host API)

  • 全局注册函数:window.FoxelRegister?: (plugin: RegisteredPlugin) => void
  • 插件对象 RegisteredPlugin 字段(均为可选,除 mount 外):
    • mount(container: HTMLElement, ctx: PluginMountCtx): void | Promise<void>
    • unmount?(container: HTMLElement): void | Promise<void>
    • key?: string:唯一键,建议稳定可读(如 org.demo.image-viewer)。
    • name?: string:展示名称。
    • version?: string:版本号(如 1.0.0)。
    • supportedExts?: string[]:支持的文件扩展名(小写、无点,如 ['jpg','png'])。
    • defaultBounds?: { x?: number; y?: number; width?: number; height?: number }:默认窗口位置与尺寸。
    • defaultMaximized?: boolean:是否默认最大化。
    • icon?: string:图标 URL(建议绝对 URL 或 data URI)。
    • description?/author?/website?/github?:元信息,用于“应用”页展示。
  • mount 上下文 PluginMountCtx
    • filePath: string:当前打开的文件完整路径(虚拟文件系统路径)。
    • entry: { name: string; is_dir: boolean; size: number; mtime: number; type?: string; is_image?: boolean }
    • urls: { downloadUrl: string }:无需鉴权即可读取当前文件内容的临时 URL。
    • host: { close(): void }:请求宿主关闭当前应用视图。

说明:

  • 插件不需要自己调用后端 API。读取文件内容统一使用 ctx.urls.downloadUrl
  • 插件只能操作传入的 container 节点,不要修改宿主页面其他 DOM 或全局样式(如需注入样式,请设置唯一 ID,避免污染)。

清单上报与后端字段映射

宿主会将插件对象字段映射到后端(POST /api/plugins/{id}/metadata):

  • 命名映射兼容驼峰/下划线:supportedExts -> supported_extsdefaultBounds -> default_boundsdefaultMaximized -> default_maximized
  • 存储模型见:models/database.py:Plugin
  • 上报失败不影响插件运行(宿主忽略错误)。

开发与构建(TSX -> IIFE JS,自包含)

  • 目标:输出单文件 dist/plugin.js,格式为 IIFE,可直接通过 <script> 加载执行。
  • 依赖:reactreact-domtypescriptesbuild(打包时将 React 一并内联,避免外部运行时依赖)。
  • 构建命令(示例):
bash
esbuild src/index.tsx \
  --bundle \
  --format=iife \
  --platform=browser \
  --target=es2019 \
  --jsx=automatic \
  --minify \
  --outfile=dist/plugin.js
  • 体积优化:--define:process.env.NODE_ENV="production"

文件结构建议:

  • package.json:含 build 指令。
  • tsconfig.json:开启严格模式,jsx 设为 react-jsx
  • foxel.d.ts:声明宿主期望的类型,便于 TS 校验(只声明类型,不引入宿主代码)。
  • src/App.tsx:React 组件。
  • src/index.tsx:构造 plugin 对象并 window.FoxelRegister(plugin)

示例:文本查看器(TSX)

位置:https://github.com/DrizzleTime/foxel-text-viewer

  • 功能:加载 downloadUrl 文本;工具栏提供“换行/复制/关闭”。
  • 支持扩展名:['txt','md','log','json','yaml','yml','sh','py','js','ts','css','html']
  • 关键点:
    • mount 内创建 React root 渲染;在 unmount 中销毁 root。
    • 仅操作 container;样式以唯一 ID 注入。

构建:npm i && npm run build(生成 dist/plugin.js)。

本地调试与安装

  1. 后端与前端开发环境:
bash
# 后端
uv venv && source .venv/bin/activate && uv sync && \
uvicorn main:app --reload --host 0.0.0.0 --port 8000

# 前端(Vite 默认端口 5173)
cd web && bun install && bun run dev
  1. 提供插件 JS 的可访问 URL:
  • 方式 A:将示例构建产物复制到本仓库 web/public/plugins/,例如 web/public/plugins/my-text-viewer.js,则开发时 URL 为 http://127.0.0.1:5173/plugins/my-text-viewer.js
  • 方式 B:使用任意静态服务器(如 nginx、Vercel、GitHub Pages)托管 dist/plugin.js,使用其公网 URL。
  1. 在“应用”页安装:
  • 打开“应用”页 -> “安装应用” -> 粘贴插件 JS URL -> 安装。
  • 首次安装完成后宿主会更新清单信息;在文件管理页面选择对应文件即可找到插件应用。

最佳实践

  • 仅操作 container;通过唯一 ID 注入样式,避免污染宿主全局。
  • icon 使用绝对 URL 或 data URI;避免相对路径误指宿主站点。
  • supportedExts 使用小写、无点后缀;与宿主匹配逻辑一致。
  • 使用 ctx.host.close() 触发关闭;不要直接移除 container
  • 如需并发请求,注意 unmount 时取消(AbortController)。

清单上报

上报失败不会中断插件运行;宿主忽略该错误以保证体验。

常见问题

  • 未显示为可用应用:确认后缀匹配 supportedExts;或先清空 supportedExts 以验证。
  • 未上报清单:确认脚本已调用 window.FoxelRegister(plugin)
  • 图标不显示:确保 URL 可访问或使用 data URI。
  • 脚本加载失败:检查 URL 是否可访问;<script> 不受 CORS 限制,但服务端可能有防盗链限制或 CSP 限制。

附录:类型声明(摘自宿主实现)

查看类型声明
ts
// foxel.d.ts 建议拷贝使用(简化版)
export interface RegisteredPlugin {
  mount: (container: HTMLElement, ctx: {
    filePath: string;
    entry: {
      name: string;
      is_dir: boolean;
      size: number;
      mtime: number;
      type?: string;
      is_image?: boolean;
    };
    urls: { downloadUrl: string };
    host: { close: () => void };
  }) => void | Promise<void>;
  unmount?: (container: HTMLElement) => void | Promise<void>;
  key?: string; name?: string; version?: string;
  supportedExts?: string[];
  defaultBounds?: { x?: number; y?: number; width?: number; height?: number };
  defaultMaximized?: boolean;
  icon?: string; description?: string; author?: string; website?: string; github?: string;
}
declare global { interface Window { FoxelRegister?: (plugin: RegisteredPlugin) => void } }

按各示例 package.jsonbuild 指令构建后,得到 dist/plugin.js,用于在“应用”页安装。