Nuclear 插件开发指南:从目录结构、Manifest 到 Provider 注册与插件商店发布
2026/9/13 17:18:26 网站建设 项目流程

Nuclear 插件开发指南:从目录结构、Manifest 到 Provider 注册与插件商店发布

【免费下载链接】nuclearStreaming music player that finds free music for you项目地址: https://gitcode.com/GitHub_Trending/nu/nuclear

本篇技术文章基于 Nuclear 仓库内的插件编写技能文档 .agents/skills/writing-plugins/SKILL.md,结合plugin-sdk包与播放器端插件加载器(PluginLoader、esbuild-wasm 编译器)的真实源码,完整讲解 Nuclear 插件的运行模型、目录结构、package.jsonManifest 规范、Streaming/Metadata 两种 Provider 的注册方式、全部可用 API,以及打包发布到插件商店的完整流程。读完后你可以独立搭建一个可被 Nuclear 在浏览器内编译加载的插件,并完成从 GitHub Release 到plugin-registry提交的发布闭环。

一、执行模型:插件是独立仓库,在浏览器内经 esbuild-wasm 编译

Nuclear 插件是独立的仓库(standalone repos),不内置于主程序,且在浏览器(Tauri WebView)内通过 esbuild-wasm 编译。理解这一模型是理解后续所有结构约定的前提。编译器实现在 pluginCompiler.ts 中,源码注释与实现透露了几个关键设计决策:

  • 为什么用 esbuild-wasm:Tauri WebView 里没有 Node 的fs,也没有原生 esbuild 二进制,因此插件即使写成 TypeScript,也能在浏览器上下文中被编译后执行(见 pluginCompiler.ts 顶部注释 L1-L21)。
  • 虚拟文件系统:编译器通过自定义的tauri-fsesbuild 插件,用 Tauri 的readTextFile读取插件目录下的相对导入,全程不接触 Node fs(pluginCompiler.ts)。
  • 纯 JS 文件跳过编译compilePlugin只对.ts/.tsx入口做编译,.js入口直接读取文本执行(pluginCompiler.ts)。
  • SDK 被标记为 external@nuclearplayer/plugin-sdk不在插件 bundle 中打包,而是运行时由宿主注入,避免插件意外捆绑宿主依赖(pluginCompiler.ts)。
  • 输出为 CJS:编译产物format: 'cjs'jsx: 'automatic'、内联 sourcemap,目标 ES2022,因此插件默认导出必须兼容module.exports.default的 CommonJS 环境。
  • 编译缓存:以入口路径为 key,记录参与上一次构建的所有文件的内容哈希;任何一个被导入文件发生变化都会触发重新编译,保证编辑后 reload 拿到的是新代码(pluginCompiler.ts)。

加载与沙箱化逻辑集中在 PluginLoader.ts:插件代码最终通过new Function('exports', 'module', 'require', code)在受控环境中求值,require是一个白名单 shim,只允许四个模块——@nuclearplayer/plugin-sdk@nuclearplayer/uireactreact/jsx-runtime,任何其它模块名都会抛出Module not found(PluginLoader.ts)。插件必须导出一个默认对象,否则加载失败。

二、插件目录结构与入口点

按 SKILL.md 的最小结构,一个插件仓库长这样:

my-plugin/ package.json # 带 nuclear 元数据的 Manifest src/ index.ts # 入口点,默认导出 NuclearPlugin

如果插件使用本地构建工具(如 tsup)预先打包,结构可以扩展为带dist/的形态(参见 plugin-sdk README)。

生命周期钩子

入口文件默认导出一个NuclearPlugin对象。SDK 的类型定义在 types.ts:

export type NuclearPlugin = { onLoad?(api: NuclearPluginAPI): void | Promise<void>; onUnload?(api: NuclearPluginAPI): void | Promise<void>; onEnable?(api: NuclearPluginAPI): void | Promise<void>; onDisable?(api: NuclearPluginAPI): void | Promise<void>; };

四个钩子全部可选,按 PluginLoader.ts 的逻辑,load()在解析完代码后若检测到onLoad会立即调用(并await)。SKILL.md 给出的标准骨架:

import type { NuclearPlugin, NuclearPluginAPI } from '@nuclearplayer/plugin-sdk'; const plugin: NuclearPlugin = { onLoad(api: NuclearPluginAPI) {}, onEnable(api: NuclearPluginAPI) { // 在这里注册 Provider }, onDisable() { // 在这里注销 Provider }, onUnload() {}, }; export default plugin;

各钩子语义(plugin-sdk README):

  • onLoad(api)— 插件代码加载完成、Manifest 解析后执行;
  • onEnable(api)— 用户在设置中启用插件时执行,注册 Provider 的正确时机
  • onDisable(api)— 用户禁用插件时执行,注销 Provider 的正确时机
  • onUnload(api)— 插件从内存移除前执行。

入口文件的解析顺序

Manifest 未声明main时,PluginLoader.ts 按以下候选顺序探测第一个存在的文件:index.jsindex.tsindex.tsxdist/index.jsdist/index.tsdist/index.tsx;全部找不到则抛出明确的错误信息。注意 Manifest 层面对缺失main只产生警告("will attempt fallback resolution"),不会阻断加载(pluginManifest.ts)。

三、Manifest:package.json 的字段规范

SKILL.md 中的完整 Manifest 示例:

{ "name": "nuclear-plugin-example", "version": "0.1.0", "description": "What this plugin does", "author": "Your Name", "license": "AGPL-3.0-only", "main": "src/index.ts", "type": "module", "nuclear": { "displayName": "Example Plugin", "category": "streaming", "icon": { "type": "link", "link": "https://example.com/icon.svg" } }, "dependencies": { "@nuclearplayer/plugin-sdk": "^1.1.0" } }

字段校验:zod Schema 的确切行为

宿主端使用 zod 对package.json做严格校验,实现在 pluginManifest.ts:

字段是否必填校验/归一化行为
name必填非空字符串,加载时 trim;作为插件唯一 id
version必填非空字符串,Semver 约定,trim
description必填非空字符串,trim
author必填非空字符串,trim
main可选缺省时给出警告并走候选文件回退解析
nuclear可选见下表

整个 Schema 使用.passthrough(),因此额外字段(如licensedependencies)不会导致校验失败。SDK 侧对应的类型定义在 types.ts:

nuclear子字段说明源码依据
displayName展示名,缺省回退到namePluginLoader.ts
category单分类(过渡字段)pluginManifest.ts,源码注释标注待迁移到categories后移除
categories分类数组,缺省由category派生为单元素数组pluginManifest.ts
icon仅支持{ "type": "link", "link": "url" },严格模式拒绝多余键pluginManifest.ts
permissions信息性声明;解析时自动 trim、去重(去重会写入警告)、按字母序排序pluginManifest.ts

另外,nuclear中出现displayName/category/categories/icon/permissions之外的未知键也会产生警告(pluginManifest.ts),是排查拼写错误的线索。

插件可用的categories取值:SKILL.md 面向 Provider 注册场景列出了streamingmetadatalyrics三类;面向商店提交的完整合法值更多,packages/docs的 publishing.md 给出的注册表合法分类为:streamingmetadatalyricsscrobblingdashboardplaylistsdiscoveryother,且注册表提交时必填。SDK 端的ProviderKind类型与此呼应,除了上述核心种类外还开放了任意字符串扩展(providers.ts)。

四、Provider 类型与注册:Streaming 和 Metadata

Streaming Provider:把曲目解析为可播放音频流

SDK 中流媒体 Provider 的完整类型定义在 types/streaming.ts:

export type StreamingProvider = ProviderDescriptor<'streaming'> & { searchForTrack: ( artist: string, title: string, album?: string, ) => Promise<StreamCandidate[]>; searchForTrackV2?: (track: Track) => Promise<StreamCandidate[]>; getStreamUrl: (candidateId: string) => Promise<Stream>; getStreamUrlV2?: (candidate: StreamCandidate) => Promise<Stream>; supportsLocalFiles?: boolean; };

SKILL.md 的最小实现示例:

const provider: StreamingProvider = { id: 'my-streaming', kind: 'streaming', name: 'My Streaming', async searchForTrack(artist, title, album?) { /* return StreamCandidate[] */ }, async getStreamUrl(candidateId) { /* return Stream */ }, }; api.Providers.register(provider); api.Providers.unregister('my-streaming');

两点源码层面的补充:

  1. 除了searchForTrack,还实现了searchForTrackV2(接收完整Track对象)与getStreamUrlV2(接收完整StreamCandidate),宿主会优先使用 V2 签名以便传递更完整的上下文——从类型定义的结构可以推断 V2 是新增能力的向后兼容扩展。
  2. id/kind/name来自通用描述符ProviderDescriptor(providers.ts),注册进宿主后由ProvidersHost统一管理:register返回 provider id,unregister返回是否成功,另外还提供list(kind?)getgetActivesetActivesubscribe等方法(providers.ts)。api.Providers门面封装在 api/providers.ts,直接透传ProvidersHost

Metadata Provider:搜索艺术家/专辑并拉取详情

SKILL.md 给出的元数据 Provider 形态:

const provider: MetadataProvider = { id: 'my-metadata', kind: 'metadata', name: 'My Metadata', searchCapabilities: ['artists', 'albums'], streamingProviderId: 'my-streaming', // 可选:将流媒体锁定到指定 Provider async searchArtists(params) { /* ... */ }, async searchAlbums(params) { /* ... */ }, async fetchArtistBio(id) { /* ... */ }, async fetchAlbumDetails(id) { /* ... */ }, };

其中searchCapabilities声明该 Provider 支持的搜索维度,streamingProviderId是可选字段:声明后,使用该元数据源检索出的曲目会锁定由对应 streaming Provider 解析流地址,保证"能搜到就能播"。

宿主侧暴露给插件反查元数据的能力,由MetadataHost类型定义(types/metadata.ts):searchfetchArtistBiofetchArtistSocialStatsfetchArtistAlbumsfetchArtistTopTracksfetchArtistPlaylistsfetchArtistRelatedArtistsfetchAlbumDetailsparams/Album/ArtistBio等模型类型统一来自@nuclearplayer/model包,并由 plugin-sdk 入口 再导出,插件直接import type { Album, Track, ArtistCredit } from '@nuclearplayer/plugin-sdk'即可使用。

五、可用 API 全览

SKILL.md 列出的核心 API:

  • api.Providers— 注册/注销 Provider
  • api.Settings— 插件设置存储
  • api.Http— fetch 封装
  • api.Ytdlp— yt-dlp 集成
  • api.Queue— 播放队列控制
  • api.Metadata— 搜索音乐元数据
  • api.Streaming— 解析流地址
  • api.Logger— 结构化日志(trace/debug/info/warn/error)

实际上 SDK 提供的NuclearPluginAPI(即NuclearAPI的子类)覆盖面更广,api/index.ts 中共暴露 15 个域 API:

API用途
api.Settings定义、读取、持久化插件设置,并可注册自定义设置控件(widget)
api.Providers注册/注销 Provider
api.Queue读取与操作播放队列
api.Streaming通过候选曲目解析音频流
api.Metadata搜索/拉取艺术家、专辑、曲目详情
api.Http从插件发起 HTTP 请求并绕过 CORS
api.Ytdlpyt-dlp 搜索与流信息
api.Favorites管理用户收藏曲目
api.Logger结构化日志
api.Dashboard获取仪表盘内容(热门曲目、新发行等)
api.Discovery从 Provider 获取曲目推荐
api.Playback控制播放、音量、随机、循环
api.Playlists创建、更新、删除播放列表
api.Events订阅播放器生命周期事件(如曲目播放结束)
api.Shell在系统浏览器中打开 URL

宿主为每个插件实例化这套 API 的接线代码在 createPluginAPI.ts:其中Settings使用按pluginId/displayName隔离的设置宿主(createPluginSettingsHost),Logger使用按插件 id 划分的日志宿主(createLoggerHost),其余为进程内共享的单例宿主。

api.Settings为例,api/settings.ts 提供了register(SettingDefinition[])声明式定义设置项、get/set读写插件私有设置、getGlobal/setGlobal读写全局设置、subscribe监听变更,以及registerWidget/unregisterWidget注册自定义 React 设置控件(控件渲染依赖编译时注入的@nuclearplayer/uireact白名单模块)。SDK 还导出useSettingReact hook(index.ts)供 TSX 插件消费。

六、本地开发与调试流程

SDK README 给出的快速起步:

mkdir my-plugin && cd my-plugin pnpm init -y pnpm add @nuclearplayer/plugin-sdk

创建src/index.ts并默认导出生命周期对象后,可以按需选择两条路径:

  1. 不预编译:直接把.ts源码打包进plugin.zip,Nuclear 会用 esbuild-wasm 即时编译;
  2. 预编译(推荐,加载更快):用任意能输出单文件的 bundler。SDK README 给出的 tsup 示例:
{ "devDependencies": { "tsup": "^8" }, "scripts": { "build": "tsup src/index.ts --dts --format cjs --minify --out-dir dist" } }

产物必须兼容 CommonJS 环境(module.exportsexports.default)。日常开发循环是:修改代码 → 重新构建 → 在 Nuclear 中重新加载插件。注意每次改动后都需要 reload 插件才能生效;编译缓存的失效逻辑(前文第二节)保证 reload 拿到的一定是重新编译后的代码。

七、发布:GitHub Release + 注册表提交

Nuclear 插件商店由一个静态注册表支撑:注册表只保存插件元数据(名称、仓库、分类),插件代码本体留在开发者自己的 GitHub 仓库中;用户安装时,Nuclear 从该仓库拉取最新 GitHub Release 的plugin.zip。发布共三步:

1. 打 tag 触发自动发版

按 SKILL.md,在插件仓库添加.github/workflows/release.yml

name: Release on: push: tags: ['v*'] jobs: release: runs-on: ubuntu-latest permissions: contents: write steps: - uses: actions/checkout@v4 - run: zip -r plugin.zip src package.json README.md - uses: softprops/action-gh-release@v2 with: files: plugin.zip generate_release_notes: true

然后打 tag 推送:git tag v0.1.0 && git push origin v0.1.0

2. plugin.zip 的内容约定

publishing.md 明确:Nuclear 只认最新 Release 中名为plugin.zip的资源,缺失即安装失败。zip 内文件必须位于根层级,不能套子目录:

plugin.zip ├── index.js # 入口(或 main 指向的文件) ├── package.json # 插件元数据 └── ... # 插件需要的其它文件

如果 zip 里是my-plugin/index.js这种带子目录的结构,插件将无法加载。由于 Nuclear 支持即时编译 TS,zip 里可以直接放.ts/.tsx源码,但预编译产物加载更快。

3. 向 plugin-registry 提交 PR

ForkNuclearPlayer/plugin-registry,把插件条目加入plugins.jsonplugins数组,并发起 PR。条目字段约束(publishing.md):

字段必填约束
id必须与package.jsonname一致;小写、连字符、2-64 字符
name展示名,1-64 字符
description10-200 字符
author1-64 字符
repoowner/repo-name格式
category必须与package.jsonnuclear.category一致
categories与插件 Provider 类型匹配的分类数组
tags至多 10 个,小写连字符、去重
version由 CI 自动填充
downloadUrl最新plugin.zip直链,由 CI 自动填充
addedAtISO 8601 时间戳

示例条目:

{ "id": "nuclear-plugin-discogs", "name": "Discogs", "description": "Fetch album and artist metadata from Discogs", "author": "nukeop", "repo": "NuclearPlayer/nuclear-plugin-discogs", "category": "metadata", "categories": ["metadata"], "tags": ["discogs", "metadata"], "version": "1.0.0", "downloadUrl": "https://github.com/NuclearPlayer/nuclear-plugin-discogs/releases/download/v1.0.0/plugin.zip", "addedAt": "2026-01-25T00:00:00Z" }

版本更新策略

发布新版本不需要改注册表:只需创建带新plugin.zip的 GitHub Release,用户下次安装或自动更新时即会拉到。Nuclear 在启动时检查插件更新,自动更新默认开启,用户可在设置的 Plugins 页关闭(见 publishing.md 及宿主侧自动更新实现 pluginAutoUpdate.ts)。只有变更描述、分类、标签等元数据时才需要再提注册表 PR。

参考路径汇总

  • 技能文档:.agents/skills/writing-plugins/SKILL.md
  • SDK 与入门:packages/plugin-sdk/README.md、packages/plugin-sdk/src/index.ts、packages/plugin-sdk/src/types.ts
  • Provider/流媒体/元数据类型:packages/plugin-sdk/src/types/providers.ts、packages/plugin-sdk/src/types/streaming.ts、packages/plugin-sdk/src/types/metadata.ts
  • 宿主加载链:PluginLoader.ts、pluginCompiler.ts、pluginManifest.ts、createPluginAPI.ts
  • 发布文档:packages/docs/plugins/publishing.md,以及 providers.md、streaming.md、plugin-system.md 等专题文档可进一步深入

【免费下载链接】nuclearStreaming music player that finds free music for you项目地址: https://gitcode.com/GitHub_Trending/nu/nuclear

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询