Joplin 插件开发实战:基于 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
本文以 Joplin 仓库中 GENERATOR_DOC.md 为主体,结合
generator-joplin的生成器实现与 Webpack 构建配置源码,系统讲解 Joplin 插件的创建、构建、版本管理与发布全流程。读完本文,你将掌握用 Yeoman 脚手架生成插件工程、理解/dist与.jpl产物的生成机制、配置extraScripts编译内容脚本/Webview 脚本,以及把插件成功提交到 Joplin 官方插件仓库的全部要点。
一、Joplin 插件开发总体框架
Joplin 是一款面向隐私保护的笔记应用,支持 Windows、macOS、Linux、Android 与 iOS,其插件系统允许开发者通过 JavaScript/TypeScript 扩展编辑器的行为与界面。插件开发依赖两部分基础设施:
- 生成器(generator-joplin):一个基于 Yeoman 的脚手架工具,位于 packages/generator-joplin,负责一键生成结构完整、可直接构建的插件工程;
- 插件运行时 API:Joplin 主程序在插件运行时注入的全局对象
joplin(类型声明位于 api/Joplin.d.ts),提供命令、数据、视图、设置、内容脚本等能力。
官方推荐的开发路径是:先用生成器创建工程 → 在/src中编写插件逻辑 → 用npm run dist构建出.jpl分发归档 → 通过npm publish提交到 npm 并最终进入 Joplin 插件仓库。
二、环境安装与工程生成
2.1 安装前置依赖
生成器运行在 Node.js 之上,因此在安装前请确认已经安装好 node.js 与 npm。随后通过 npm 全局安装 Yeoman 与生成器:
npm install -g yo@4.3.1 npm install -g generator-joplin注意这里对 Yeoman 的版本做了固定(yo@4.3.1),这是为了与生成器所依赖的 yeoman-generator@5.10.0 保持兼容,避免新版 Yeoman 行为差异导致脚手架失败。
2.2 生成新插件工程
yo --node-package-manager npm joplin执行后,生成器会通过交互式提示收集插件元信息。从 生成器源码 可以看到它会依次询问以下字段:
| 提示字段 | 含义 | 说明 |
|---|---|---|
pluginId | 插件 ID | 必须是全局唯一 ID,如com.example.MyPlugin或 UUID |
pluginName | 插件名称 | 用户友好字符串,会显示在 Joplin 界面中 |
pluginDescription | 插件描述 | 简要说明插件功能 |
pluginAuthor | 作者 | 作者名或组织名 |
pluginRepositoryUrl | 仓库地址 | 源码仓库 URL |
pluginHomepageUrl | 主页地址 | 插件主页 URL |
packageName | npm 包名 | 默认由插件名通过packageNameFromPluginName自动推导(utils.js),可直接回车采用默认值 |
生成的工程中,manifest.json的id、name、description、author、homepage_url、repository_url等字段会由这些回答直接填充(模板见 src/manifest.json)。
三、工程结构与核心文件
生成器产出的插件工程(模板目录见 templates)包含两类文件:一类是每次更新都会被覆盖的“框架文件”(webpack.config.js、plugin.config.json、tsconfig.json、package.json、.gitignore等),另一类是永远保留的“业务文件”(src/与README.md)。这种区分在后面的“更新插件框架”一节至关重要。
3.1/src/index.ts:插件入口
这是插件源码的入口点,Webpack 只会从这里开始编译。生成器提供的默认实现是一个最小可运行示例(src/index.ts):
import joplin from 'api'; joplin.plugins.register({ onStart: async function() { // eslint-disable-next-line no-console console.info('Hello world. Test plugin started!'); }, });关键点解读:
import joplin from 'api'中的api是 Webpack 在 webpack.config.js 里配置的路径别名,指向工程根目录的api/目录(即生成器复制进去的整套类型声明与运行时绑定);joplin.plugins.register()注册插件生命周期,onStart在插件加载时被调用,是编写业务逻辑的主入口;- 所有插件必须注册一次,未注册的插件不会生效。
3.2/src/manifest.json:插件清单
清单文件携带插件名称、版本、兼容的最低 Joplin 版本等关键信息,构建产物.jpl与插件仓库索引都依赖它。生成器默认清单(src/manifest.json):
{ "manifest_version": 1, "id": "<%= pluginId %>", "app_min_version": "3.7", "version": "1.0.0", "name": "<%= pluginName %>", "description": "<%= pluginDescription %>", "author": "<%= pluginAuthor %>", "homepage_url": "<%= pluginHomepageUrl %>", "repository_url": "<%= pluginRepositoryUrl %>", "keywords": [], "categories": [], "screenshots": [], "icons": {}, "promo_tile": {} }字段补充说明(依据 webpack.config.js 中的校验逻辑):
id:构建时若缺失会直接报错Manifest plugin ID is not set(见 readManifest 校验);categories:可选的插件分类,必须是预定义列表(appearance、developer tools、productivity、themes、integrations、viewer、search、tags、editor、files、personal knowledge management)中的值,且不允许重复,全部小写(见 validateCategories);screenshots:每个条目必须含src,本地文件仅支持 jpg/jpeg/png/gif/webp,且单个文件大小不得超过 1MB(见 validateScreenshots);以http://或https://开头的远程 URL 会被跳过校验。
3.3/plugin.config.json:外部脚本配置
当插件需要编译内容脚本(content scripts)或 Webview 脚本时,需要在此文件中声明。默认模板为空数组(plugin.config.json):
{ "extraScripts": [] }同时,该文件还支持一个webpackOverrides字段(构建配置在 加载 userConfig 后会展开...userConfig.webpackOverrides合并进基础配置),用于在不改动webpack.config.js的前提下追加自定义 Webpack 选项。
3.4 其余文件
api/:Joplin 插件 API 的 TypeScript 类型声明,覆盖joplin.commands、joplin.data、joplin.settings、joplin.views.*、joplin.contentScripts等全部能力,是开发时类型提示的来源;script/publish/:npm run submit发布流程使用的发布脚本(详见第五节);tsconfig.json:TypeScript 编译配置;工程默认使用 TypeScript,但你可以把配置改为纯 JavaScript 开发。
四、构建插件:npm run dist
4.1 构建产物
插件使用 Webpack 构建,产物落在/dist目录(编译后的index.js及从/src拷贝的其他资源),同时工程根目录的publish/下会生成.jpl归档文件与对应的.json信息文件,前者即用于分发与安装的插件包。
package.json中的构建脚本串联了三个 Webpack 阶段(见 package_TEMPLATE.json):
"dist": "webpack --env joplin-plugin-config=buildMain && webpack --env joplin-plugin-config=buildExtraScripts && webpack --env joplin-plugin-config=createArchive"4.2 三个构建阶段
从 webpack.config.js 的 main 函数 可以看到,三个配置阶段必须串行执行(Webpack 并行运行会导致目录被互相覆盖,源码注释明确说明了这一点):
- buildMain:编译
src/index.ts生成dist/index.js,并通过copy-webpack-plugin把/src下其余非 TS/TSX 文件(CSS、图片、JS 等)原样拷贝到/dist。此阶段开始时还会清理dist与publish目录并重建publish(见 清理逻辑); - buildExtraScripts:根据
plugin.config.json的extraScripts逐个编译外部脚本。由于输出路径相同,编译产物会覆盖第一阶段拷贝的同名 JS 文件——这是有意设计:不需要编译的 JS 直接拷贝,需要编译的则被编译版本替换(见 阶段注释); - createArchive:以
dist/index.js为入口执行一次空打包,在done钩子中触发 onBuildCompleted:先用 glob 收集dist下所有文件、用tar创建.jpl归档,再生成包含_publish_hash(sha256:前缀的 JPL 文件哈希)与_publish_commit(branch:commit格式的 Git 信息)的.json信息文件。
4.3 构建时的自动校验
每次构建归档时,validatePackageJson()都会对package.json输出警告或建议(见 validatePackageJson):
- 包名若不以
joplin-plugin-开头,会警告“无法发布”; keywords若不含joplin-plugin,会警告;- 若存在
postinstall脚本,会建议改用prepare(因为prepare在发布前执行,保证归档内容是最新构建产物)。
构建命令:
npm run dist五、版本号管理与发布
5.1 更新版本号
执行:
npm run updateVersion该命令(对应 Webpack 配置中的updateVersion分支,见 updateVersion 实现)会把版本号的patch 位加 1:例如1.0.3变为1.0.4。它会同时改写package.json与manifest.json两个文件中的版本号并保持同步;如果两处版本号最终不一致(例如手工改动过),会打印黄色警告提示你手工对齐。
5.2 发布到 npm 并进入 Joplin 插件仓库
插件通过 npm 分发。运行:
npm publish把插件提交到 npmjs.com 之后,Joplin 侧的后台脚本会自动把插件收录进官方插件仓库,前提是满足以下三个条件(这也是生成器默认就会做好的配置,可在 package_TEMPLATE.json 中核对):
package.json中的name以joplin-plugin-开头,例如joplin-plugin-toc;package.json中的keywords包含joplin-plugin;publish/目录下存在.jpl与.json文件(由npm run dist生成)。
如果插件发布后没有出现在官方仓库中,请优先核对上述三条。此外,package.json的files字段只包含publish,意味着 npm 包中只会上传发布产物目录。
5.3 一键提交发布(npm run submit)
较新的生成器还内置了submit脚本,把发布流程封装为四个串行阶段(script/publish/index.ts):
npm run submit其内部依次执行:
- Metadata & Build Verification(verifyBuild.ts):核对插件元信息并确认构建产物完备;
- Git State Validation(verifyGitState.ts):校验当前 Git 工作区状态,确保提交内容可追溯;
- GitHub Authentication(authenticate.ts):通过 OAuth 设备流完成 GitHub 认证;
- Submission(submitPayload.ts):把元数据、提交哈希与认证令牌一起提交到插件发布端点。
任一步骤失败都会输出错误并设置非零退出码,方便 CI 环境识别。
六、更新插件框架:npm run update
当 Joplin 插件框架发布新版本时,可以运行:
npm run update这条命令实际执行的是(见 package_TEMPLATE.json 的 update 脚本):
npm install -g generator-joplin && yo joplin --node-package-manager npm --update --force其行为在生成器源码中有明确实现(index.js 的 update 分支):
- 更新前会二次确认:明确告知“更新将覆盖配置文件,不会改动
/src或README.md”,并建议先把代码纳入版本控制以便事后查看 diff; package.json采用键级合并(mergePackageKey)而非整体覆盖,保留你已有的依赖与脚本;.gitignore、.npmignore采用行级合并(mergeIgnoreFile)追加内容;src/目录与README.md永远不被触碰;plugin.config.json在更新时保留你现有的内容(源码注释说明“或许以后再做合并”);- 唯一可能出问题的是
webpack.config.js,因为它会被整体覆盖。
针对webpack.config.js被覆盖的问题,官方建议:如果你需要定制构建,不要直接改webpack.config.js,而是新建一个独立的 JavaScript 文件,然后在webpack.config.js里用一行require引入它。这样每次更新后只需要恢复这一行引入代码,即可保留全部自定义逻辑(构建配置中...userConfig.webpackOverrides的合并机制也正是为此设计)。
七、外部脚本文件:内容脚本与 Webview 脚本的编译
7.1 为什么需要extraScripts
默认情况下,Webpack 只编译src/index.ts(及其 import 的依赖),其余文件会被原样拷贝进插件包。这对大多数插件已经够用,但以下两类场景必须借助extraScripts强制编译:
- 脚本是 TypeScript 文件:
.ts无法被 Joplin 运行时直接执行,必须先编译成 JavaScript; - 脚本依赖了你添加到
package.json的第三方模块:此时无论脚本是 JS 还是 TS,都必须编译,以便把依赖一并打包进.jpl文件,保证插件分发后脱离 node_modules 也能运行。
内容脚本与 Webview 脚本分别对应 Joplin API 中的joplin.contentScripts与joplin.views.panels.create()之后通过panel.addScript(path)添加的脚本。
7.2 配置方法
把脚本路径(相对于/src)加入plugin.config.json的extraScripts数组:
{ "extraScripts": ["webviews/index.ts"] }以上配置表示编译/src/webviews/index.ts。编译产物始终以.js扩展名输出——上例会生成webviews/index.js,之后在插件代码中引用时就要使用这个编译后的路径(而不是.ts路径)。
7.3 底层实现要点
从 buildExtraScriptConfigs 与 resolveExtraScriptPath 可以看到:
- 每个额外脚本都会生成一个独立的 Webpack 配置,入口指向
./src/<name>; - 输出文件名为“去扩展名的路径 +
.js”,例如webviews/index.ts→webviews/index.js,并设置library: 'default'、libraryTarget: 'commonjs',使脚本以 CommonJS 模块形式导出; - 脚本若引用了
@codemirror/*、@lezer/*等编辑器库,会被声明为externals(见 externalContentScriptLibraries),即这些库运行时由 Joplin 提供,不重复打包,减小.jpl体积; - 若
extraScripts为空数组,第二阶段直接返回空配置,不会产生任何额外编译(判断逻辑)。
八、常见问题排查清单
结合构建配置与发布校验逻辑,整理一份快速排查表:
| 现象 | 排查点 | 依据 |
|---|---|---|
| 构建报 “Manifest plugin ID is not set” | 检查src/manifest.json是否包含id | readManifest |
| 分类报错 “not a valid category” | 分类必须全小写且来自预定义列表,不能重复 | validateCategories |
| 截图校验失败 | 本地截图类型限 jpg/jpeg/png/gif/webp,单文件 ≤ 1MB | validateScreenshots |
| 构建报 “dist directory is empty” | dist目录为空导致归档失败,确认已成功执行 buildMain | createPluginArchive |
| 插件未出现在官方仓库 | 核对包名前缀、keywords、publish/产物三项条件 | 本文 5.2 节 |
| 报 “Could not find extra script” | extraScripts中的路径必须相对于/src且文件真实存在 | resolveExtraScriptPath |
update后自定义构建丢失 | 因为webpack.config.js被覆盖,自定义逻辑应放独立 JS 文件再引入 | 本文第六节 |
| 版本号不一致警告 | 检查package.json与manifest.json的 version 是否一致 | updateVersion |
九、小结
Joplin 插件开发的完整闭环可以概括为:yo joplin生成工程 → 在src/index.ts注册插件并编写逻辑 → 需要内容脚本/Webview 脚本时通过plugin.config.json的extraScripts声明 →npm run dist产出.jpl与信息文件 →npm run updateVersion同步版本号 →npm publish(或npm run submit)提交发布 → 定期npm run update同步框架更新(注意webpack.config.js会被覆盖)。
这套脚手架把构建、打包、校验与发布大量细节封装在模板配置中,理解 webpack.config.js 的三阶段构建与校验逻辑、src/manifest.json 的字段约束,以及 package_TEMPLATE.json 的脚本编排,就足以应对从入门到发布的绝大多数问题。生成器自身的实现(generators/app/index.js)和 API 类型声明(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
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考