Joplin 插件开发实战:解读 events 示例插件的构建流程与事件监听 API
【免费下载链接】joplinJoplin - the privacy-focused note taking app with sync capabilities for Windows, macOS, Linux, Android and iOS.项目地址: https://gitcode.com/GitHub_Trending/jo/joplin
本文以 Joplin 仓库内置的 events 演示插件为切入点,系统讲解 Joplin 插件工程的标准结构、npm run dist构建与 JPL 打包流程,并逐行解析joplin.workspace事件监听 API(闹钟触发、同步开始/完成等)的实际用法。读完本文,你将掌握从零搭建、编译、分发一个 Joplin 插件,以及利用事件钩子实现自动化功能的完整套路。
背景:events 插件是什么
events是 Joplin 官方为插件开发者准备的示例插件之一,位于 packages/app-cli/tests/support/plugins/events。它的manifest.json中的描述写得很直白——"Demonstrate how to listen to various events"(演示如何监听各类事件)。也就是说,这个插件本身不提供任何界面功能,它的全部价值在于示范"如何注册插件、如何挂接工作区事件回调",是学习 Joplin 插件 API 的最佳入门样例。
它同时也是一个由 Yeoman 生成器generator-joplin生成的标准插件模板:目录下的 README.md 即模板自带的工程说明文档,描述了所有 Joplin 插件通用的结构、构建与更新方式。因此,本文将以该 README 为骨架,以 events 示例源码为血肉,展开讲解。
插件项目的目录结构
README 明确指出,一个 Joplin 插件项目里最值得关注的是两个文件:
/src/index.ts:插件的入口文件,所有插件逻辑的起点;/src/manifest.json:插件清单,声明插件名称、版本、最低兼容的应用版本等信息。
以 events 示例为例,实际目录结构如下(位于仓库 packages/app-cli/tests/support/plugins/events):
events/ ├── src/ │ ├── index.ts # 插件入口:注册插件并挂接事件 │ └── manifest.json # 插件清单 ├── api/ # 插件 API 的 TypeScript 类型声明 │ ├── Joplin.d.ts │ ├── JoplinData.d.ts │ ├── JoplinWorkspace.d.ts │ └── ... ├── webpack.config.js # 构建配置(模板自动生成) ├── plugin.config.json # 插件附加配置(如 extraScripts) ├── package.json # npm 脚本与依赖 ├── tsconfig.json # TypeScript 编译配置 └── README.md # 模板工程说明其中api/目录是模板自动附带、由 Joplin 插件 API 类型定义生成的声明文件集合,开发时 IDE 会基于它们提供完整的自动补全与类型检查;项目根目录下的api/index.ts导出一个全局的joplin单例对象,插件代码通过import joplin from 'api'引入它。
入口文件 src/index.ts
events 插件的 src/index.ts 完整代码如下:
import joplin from 'api'; joplin.plugins.register({ onStart: async function() { joplin.workspace.onNoteAlarmTrigger(async (event:any) => { const note = await joplin.data.get(['notes', event.noteId]); console.info('Alarm was triggered for note: ', note); }); joplin.workspace.onSyncStart(async (event:any) => { console.info('Sync has started...'); }); joplin.workspace.onSyncComplete(async (event:any) => { console.info('Sync has completed'); console.info('With errors:', event.withErrors); }); }, });这段代码展示了一个 Joplin 插件的"最小完整形态":
- 通过
joplin.plugins.register({...})注册插件; - 在
onStart生命周期回调中初始化业务逻辑; - 通过
joplin.workspace.xxx系列方法订阅工作区事件。
插件清单 manifest.json
src/manifest.json 的内容:
{ "id": "org.joplinapp.plugins.EventsDemo", "manifest_version": 1, "app_min_version": "1.4", "name": "Event demo", "description": "Demonstrate how to listen to various events", "version": "1.0.0", "author": "Laurent Cozic", "homepage_url": "https://joplinapp.org" }各字段含义如下:
| 字段 | 说明 | 示例值 |
|---|---|---|
id | 插件的全局唯一标识,通常采用反向域名风格;它同时决定了 JPL 安装包的命名 | org.joplinapp.plugins.EventsDemo |
manifest_version | 清单格式版本,目前为1 | 1 |
app_min_version | 运行该插件所需的最低 Joplin 版本,低于此版本的应用不会加载插件 | 1.4 |
name | 插件显示名称 | Event demo |
description | 插件描述 | Demonstrate how to listen to various events |
version | 插件版本号(语义化版本) | 1.0.0 |
author | 作者名 | Laurent Cozic |
homepage_url | 插件主页 | https://joplinapp.org |
从构建脚本 webpack.config.js 可以看到,manifest.json中的id会被读取并用于生成publish/{id}.jpl与publish/{id}.json两个分发产物;同时readManifest还会校验categories字段是否属于预定义的分类集合(如productivity、themes、editor等),如果类别重复或非法会直接抛出构建错误。
事件监听 API 详解
events 插件示范了三个最常用的工作区事件,它们的定义与完整事件族可以在 api/JoplinWorkspace.d.ts 中查到。
onNoteAlarmTrigger:待办闹钟触发
joplin.workspace.onNoteAlarmTrigger(async (event:any) => { const note = await joplin.data.get(['notes', event.noteId]); console.info('Alarm was triggered for note: ', note); });当与某个待办事项(to-do)关联的闹钟被触发时,Joplin 会调用该回调。事件对象event携带noteId字段(类型定义见 JoplinWorkspace.d.ts),随后示例通过数据 API 的joplin.data.get(['notes', noteId])按 ID 拉取完整的笔记对象——这演示了事件回调与数据 API 的组合用法:事件只给最小上下文,具体数据需要用joplin.data主动获取。
joplin.data的方法签名与 REST 语义一一对应(GET/POST/PUT/DELETE),其path参数是一个数组,形如["notes", ":id", "link"],详见 api/JoplinData.d.ts。由于插件运行在 Joplin 应用进程内部,调用该 API无需鉴权 token。
onSyncStart / onSyncComplete:同步生命周期
joplin.workspace.onSyncStart(async (event:any) => { console.info('Sync has started...'); }); joplin.workspace.onSyncComplete(async (event:any) => { console.info('Sync has completed'); console.info('With errors:', event.withErrors); });onSyncStart:同步过程开始时触发,回调不带事件参数(类型定义为SyncStartHandler = () => void);onSyncComplete:同步结束后触发,事件对象带有withErrors: boolean字段(见 JoplinWorkspace.d.ts),用于判断本次同步是否出现错误。
这两个钩子非常适合做同步状态的日志记录、桌面通知,或是在同步完成后再触发自定义的数据刷新逻辑。
更多可用事件
api/JoplinWorkspace.d.ts 还定义了 events 示例之外的一批常用事件,均可在插件中直接使用:
| API | 触发时机 | 事件参数 |
|---|---|---|
onNoteSelectionChange | 当前选中的笔记发生变化 | value: string[](笔记 ID 数组) |
onNoteChange | 笔记内容或任意属性变化(推荐,取代已废弃的onNoteContentChange) | { id, event },event取值为Create/Update/Delete |
onResourceChange | 资源(附件)被修改(新增/删除不触发) | { id } |
onNoteAlarmTrigger | 待办闹钟触发 | { noteId } |
onSyncStart | 同步开始 | 无 |
onSyncComplete | 同步完成 | { withErrors: boolean } |
filterEditorContextMenu | 编辑器右键菜单弹出前(仅桌面端),允许向菜单注入条目 | 菜单过滤对象 |
值得留意的是,onNoteSelectionChange、onNoteChange、onNoteAlarmTrigger、onSyncStart、onSyncComplete等订阅方法的返回类型是Promise<Disposable>——订阅后返回一个可销毁句柄,插件卸载时用于反注册回调,避免内存泄漏。
构建插件:npm run dist
README 明确指出构建命令只有一条:
npm run dist该命令来自 package.json 中的scripts配置,实际展开是三个依次执行的 Webpack 构建步骤:
"dist": "webpack --joplin-plugin-config buildMain && webpack --joplin-plugin-config buildExtraScripts && webpack --joplin-plugin-config createArchive"对应的三阶段逻辑定义在 webpack.config.js:
- buildMain:编译入口
./src/index.ts(用ts-loader将 TypeScript 编译为 CommonJS 模块,target 为 ES2015,配置见 tsconfig.json),并把src/下其余非 TS 资源(CSS、JSON 等)复制到dist/。此阶段开始时还会先清空dist/与publish/目录; - buildExtraScripts:按
plugin.config.json中的extraScripts列表逐一编译附加脚本(默认events示例为空数组); - createArchive:把
dist/目录内的全部文件打包成.jpl归档,并生成配套的.json信息文件。
构建产物
构建完成后,项目根目录会生成两个目录/产物:
dist/:编译后的中间产物(插件主代码index.js及复制过来的静态资源);publish/{pluginId}.jpl:JPL 插件安装包,是最终用于分发的文件;publish/{pluginId}.json则记录了插件元信息,并在打包时自动追加_publish_hash(JPL 文件的 SHA-256 校验值)与_publish_commit(git 分支与提交号),见 webpack.config.js。
把生成的.jpl文件放进 Joplin 的插件目录,或在应用内通过"工具 → 选项 → 插件"界面手动安装即可加载。
类型与语言选择
README 还特别说明:模板默认使用 TypeScript,但你也可以把工程改成纯 JavaScript——只要保证webpack.config.js中ts-loader的规则与入口文件匹配即可。tsconfig.json中的allowJs: true也表明混用 JS 是被允许的。
更新插件框架:yo joplin --update
当 Joplin 插件框架升级后,模板工程可以通过生成器一键更新:
yo joplin --update(等价地,package.json 中预置了npm run update脚本:先全局安装generator-joplin,再执行yo joplin --update。)
README 给出了两条非常重要的使用提醒:
src/目录内的源码不会被覆盖,被覆盖的只是框架相关文件(如package.json、.gitignore、webpack.config.js等);但若你曾修改过这些框架文件,务必先提交到版本控制,以便更新后比对 diff 并重新应用你的改动;- 尽量避免改动框架文件。如果必须改,例如要扩展 Webpack 配置,正确做法是新建一个独立的 JS 文件,然后在
webpack.config.js中用一行require引入它——这样升级框架时只需要恢复这一行,自定义逻辑不会丢失。
这条"少改动、外置文件"的策略与 GENERATOR_DOC.md 的说明一致:更新命令会尽量合并package.json与.gitignore的差异,且保留src/与README.md不动,唯独webpack.config.js会被整体覆盖,因此它是最需要隔离自定义改动的文件。
进阶:附加脚本(extraScripts)与插件发布
虽然 events 示例本身没有用到,但模板配套的 GENERATOR_DOC.md 与 plugin.config.json 还覆盖了两个实用主题,一并说明以保证文档完整性。
编译附加脚本
默认情况下 Webpack 只编译src/index.ts及其 import 链,其余文件仅被复制。但在两种场景下你需要把其他脚本也纳入编译:
- 脚本是 TypeScript 文件(必须编译成 JS 才能运行);
- 脚本
require了package.json中新增的第三方模块(必须打包进 JPL 才能随插件分发)。
做法是在 plugin.config.json 的extraScripts数组中加入相对src/的路径,例如文件位于src/webviews/index.ts就写"webviews/index.ts"。编译后的产物固定使用.js扩展名,插件代码中应引用编译后的路径(如webviews/index.js),详见 webpack.config.js 中resolveExtraScriptPath的实现。
发布到插件仓库
模板的发布流程也很清晰:先构建,再执行npm publish把插件发布到 npm。后续 Joplin 的自动脚本会将其收录进官方插件仓库,前提是满足三个条件(GENERATOR_DOC.md):
package.json的name以joplin-plugin-开头(例如joplin-plugin-toc);package.json的keywords包含joplin-plugin;publish/目录下存在构建生成的.jpl与.json文件。
webpack.config.js中的validatePackageJson函数会在每次打包时自动检查前两条并给出黄色警告(webpack.config.js),帮你在发布前及早发现问题。
小结
以 events 示例插件为镜,可以梳理出 Joplin 插件开发的完整心智模型:
- 工程骨架:
src/index.ts(入口)+src/manifest.json(清单)+api/(类型声明)三件套; - 生命周期:在
joplin.plugins.register的onStart中完成一切初始化; - 事件驱动:通过
joplin.workspace.onXxx订阅闹钟、同步、笔记选择等事件,配合joplin.data数据 API 实现自动化; - 构建分发:
npm run dist走完"编译主入口 → 编译附加脚本 → 打包 JPL"三阶段,产物落在publish/; - 框架升级:
yo joplin --update,把自定义 Webpack 逻辑外置为独立文件以最小化升级冲突。
对于想要深入的同学,仓库里还提供了大量同类示例插件(位于 packages/app-cli/tests/support/plugins 目录,如settings、register_command、dialog等),以及完整的插件 API 类型定义可供查阅;Yeoman 生成器源码则在 packages/generator-joplin 中,可以对照模板了解每个文件是如何生成的。
【免费下载链接】joplinJoplin - the privacy-focused note taking app with sync capabilities for Windows, macOS, Linux, Android and iOS.项目地址: https://gitcode.com/GitHub_Trending/jo/joplin
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考