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 是主打隐私保护的开源笔记应用,其插件机制让开发者能够通过官方 API 扩展编辑器、内容脚本、Webview 面板等能力。本指南以 Joplin 仓库内置的 Yeoman 生成器generator-joplin(对应生成器文档 packages/generator-joplin/generators/app/templates/GENERATOR_DOC.md)为核心,完整讲解"环境安装 → 脚手架生成 → 项目结构 → 构建打包 → 版本管理 → 发布上架 → 框架升级 → 外部脚本编译"的插件全生命周期,并结合仓库源码逐层剖析npm run dist、plugin.config.json、webpack.config.js等关键文件的底层实现,帮助你从零开始产出可分发、可发布的 Joplin 插件。
环境准备与项目脚手架生成
1. 安装 Yeoman 与 generator-joplin
生成器依赖 Node.js 与 npm(官方文档假定你已预先安装)。先全局安装 Yeoman 与生成器本体:
npm install -g yo@4.3.1 npm install -g generator-joplin生成器模板文档中给出的版本为yo@4.3.1,这是与当前生成器(generator-joplin3.7.2,见 packages/generator-joplin/package.json)配套验证过的版本。生成器本身依赖yeoman-generator5.10.0,并额外使用chalk、slugify、yosay三个库完成交互提示与包名处理。
2. 生成一个新插件项目
安装完成后,在希望创建插件的目录下运行:
yo --node-package-manager npm joplin与早期写法yo joplin相比,--node-package-manager npm显式指定包管理器为 npm,避免 Yeoman 因环境差异推断出错。
运行后会进入交互式问答流程。对照生成器源码 packages/generator-joplin/generators/app/index.js 中的prompting()方法,需要依次填写以下信息:
| 提问字段 | 含义 | 示例 |
|---|---|---|
pluginId | 插件唯一 ID,必须是全局唯一值,如反向域名或 UUID | com.example.MyPlugin |
pluginName | 插件名称,将显示在 UI 中 | My TOC Plugin |
pluginDescription | 插件描述 | Adds a table of contents |
pluginAuthor | 作者名称 | Your Name |
pluginRepositoryUrl | 源码仓库地址 | https://example.com/repo |
pluginHomepageUrl | 插件主页地址 | https://example.com |
packageName | npm 包名 | 默认由插件名推导,见下文 |
其中packageName不会直接询问,而是由生成器根据pluginName自动推导出默认值后再让你确认。推导逻辑位于 packages/generator-joplin/generators/app/utils.js 的packageNameFromPluginName():
- 将
*+~.()'"!:@[]等特殊字符替换为-; - 用
slugify(..., { lower: true })将非字母字符转为小写字母; - 去掉首尾多余的
-; - 统一加上
joplin-plugin-前缀; - 截断到 214 字符以内(npm 包名长度上限)。
例如插件名My TOC会被推导为joplin-plugin-my-toc。这一步很重要:包名是否符合joplin-plugin-前缀,直接决定插件能否被官方插件仓库收录(详见下文"发布插件"小节)。
生成器在writing()阶段会通过copyTpl把所有模板文件渲染到目标目录,其中.gitignore与package.json因 npm 的历史 bug(npm/npm#3763)在模板中命名为.gitignore_TEMPLATE、package_TEMPLATE.json,安装时再重命名还原。
生成后的项目结构解析
新生成的项目中,最关键的两个文件是:
src/index.ts:插件源码入口。生成器给出的默认实现只有几行——通过api别名导入 Joplin 插件 API,并注册一个在启动时打印日志的插件:
import joplin from 'api'; joplin.plugins.register({ onStart: async function() { // eslint-disable-next-line no-console console.info('Hello world. Test plugin started!'); }, });src/manifest.json:插件清单文件,声明插件 ID、版本、名称、描述、作者、主页、仓库地址、分类与截图等元信息。生成器模板(packages/generator-joplin/generators/app/templates/src/manifest.json)默认结构如下:
{ "manifest_version": 1, "id": "com.example.MyPlugin", "app_min_version": "3.7", "version": "1.0.0", "name": "My TOC Plugin", "description": "Adds a table of contents", "author": "Your Name", "homepage_url": "https://example.com", "repository_url": "https://example.com/repo", "keywords": [], "categories": [], "screenshots": [], "icons": {}, "promo_tile": {} }其中id即前面填写的pluginId,version与package.json中的版本号保持一致(updateVersion脚本负责同步,见后文)。app_min_version为生成器当前支持的 Joplin 最低版本 3.7。模板同时会复制整套api/目录(Joplin.d.ts、JoplinViews.d.ts、JoplinSettings.d.ts等 TypeScript 类型声明,覆盖编辑器、视图面板、菜单、工具栏、对话框、数据访问等全部 API 面),以及script/publish/发布辅助脚本。
此外plugin.config.json值得注意——它默认只包含一个空的extraScripts数组,用于声明需要额外编译的外部脚本(内容脚本、Webview 脚本),详见"外部脚本文件"一节。
构建插件:npm run dist的完整工作流
1. 三条 Webpack 配置链
生成器模板的package.json(packages/generator-joplin/generators/app/templates/package_TEMPLATE.json)把构建命令定义为一串依次执行的三阶段 Webpack 调用:
"dist": "webpack --env joplin-plugin-config=buildMain && webpack --env joplin-plugin-config=buildExtraScripts && webpack --env joplin-plugin-config=createArchive"之所以要拆成三次串行调用而不是并行,原因写在 webpack.config.js 的注释里:各阶段存在先后依赖,Webpack 并行运行会破坏顺序,因此通过--env joplin-plugin-config参数切换配置。具体三个阶段是:
buildMain:编译主入口src/index.ts,并通过copy-webpack-plugin把src/下其余资源(非.ts/.tsx文件)原样复制到dist/。此阶段开始时还会清空dist/与publish/目录并重建publish/。buildExtraScripts:按plugin.config.json中的extraScripts列表逐个编译外部脚本,编译产物会覆盖第一阶段复制过来的同名 JS 文件——这是有意为之的设计:不需要编译的 JS 直接复制,需要编译的则被替换为编译结果。createArchive:以dist/index.js为占位入口触发onBuildCompleted钩子,用tar把整个dist/打成 JPL 压缩包,并生成配套的插件信息 JSON。
构建完成后你会得到两类产物:
dist/:编译后的代码目录;publish/<插件ID>.jpl:JPL 插件压缩包(可直接在 Joplin 中安装分发),以及publish/<插件ID>.json:插件信息文件。
2. JPL 打包与校验的底层逻辑
打包与校验逻辑全部集中在 webpack.config.js 中:
createPluginArchive()用glob枚举dist/下所有文件(dist为空会直接报错),然后通过tar.create以portable: true、strict: true模式打成.jpl;createPluginInfo()会读取manifest.json,追加_publish_hash(JPL 文件的 sha256 哈希,格式为sha256:...)与_publish_commit(当前 git 分支与 commit,若不在 git 仓库中则为空字符串)后写入publish/<ID>.json;validatePackageJson()对package.json做发布前校验:包名必须以joplin-plugin-开头、keywords 必须包含joplin-plugin,同时警告不要使用postinstall脚本(建议改用prepare,确保发布前一定执行构建);readManifest()校验 manifest 中id必须存在,categories不得重复且必须属于allPossibleCategories(appearance、developer tools、productivity、themes、integrations、viewer、search、tags、editor、files、personal knowledge management 之一,且必须为小写),screenshots的src类型必须属于 jpg/jpeg/png/gif/webp 且本地截图文件不超过 1MB。
3. 关于 TypeScript 与 Webpack 配置
模板项目默认使用 TypeScript(ts-loader处理.ts/.tsx),但文档明确说明你可以改配置改用纯 JavaScript。Webpack 配置中还有一个细节:由于插件运行在 Electron 的 Node 环境中,模板把 Node 内建模块的fallback全部设为false,避免 Webpack 5 因不再默认 polyfill 而弹出警告。
版本号管理:npm run updateVersion
插件开发中经常需要递增版本号。直接手工同步package.json与manifest.json容易出错,因此生成器内置了updateVersion脚本:
"updateVersion": "webpack --env joplin-plugin-config=updateVersion"其实现updateVersion()(同样在 webpack.config.js 中)会把package.json与manifest.json中的版本号patch 位 +1(如 1.0.3 → 1.0.4),保持两者同步;若发现两者版本不一致会打印警告提示手工对齐。发布新版本前先跑这个命令,可以避免"插件版本号没变导致插件仓库不更新"的常见问题。
发布插件到 Joplin 插件仓库
1. 通过 npm publish 发布
构建完成后,把插件发布到 npmjs.com:
npm publish之后 Joplin 的插件仓库脚本会自动拾取你的插件并收录,前提是包满足以下全部条件:
package.json的name以joplin-plugin-开头,例如joplin-plugin-toc;package.json的keywords包含joplin-plugin;publish/目录下同时存在.jpl与.json两个文件(它们由npm run dist自动生成)。
正常情况下,生成器已自动设置好包名与 keywords,并把正确的文件放进publish/。如果插件迟迟没有出现在插件仓库中,优先复查上述三个条件——这是文档特别强调的排错路径,对应validatePackageJson()中的警告逻辑。
2. 生成器自带的 submit 一键发布流程
除了手工npm publish,新版生成器还随模板附赠了一套script/publish/自动化发布脚本,通过npm run submit触发。入口 script/publish/index.ts 将发布拆成四个阶段:
- verifyBuild:校验构建产物与元数据;
- verifyGitState:校验 git 状态(确保基于干净的提交发布);
- authenticate:GitHub OAuth 设备流认证(依赖
@octokit/auth-oauth-device); - submitPayload:向 Joplin 插件仓库提交 payload。
整套流程对版本号同步、构建产物存在性、git 干净度做了前置把关,适合希望把发布固化为标准流水线的开发者。
升级插件框架:npm run update
Joplin 插件 API 会随版本演进,生成器提供了框架升级命令:
"update": "npm install -g generator-joplin && yo joplin --node-package-manager npm --update --force"执行前它会先全局更新generator-joplin,然后以--update模式重新运行生成器。对照 generators/app/index.js 的writing()实现,更新模式下的行为要点如下:
- 不会动你的业务代码:
src/index.ts、src/manifest.json、README.md在更新时被跳过,保证已有插件逻辑不受影响; - package.json 走智能合并:调用
mergePackageKey()(见 utils.js)——以你现有的package.json为基础,框架新出现的键会被补进来;keywords强制确保包含joplin-plugin;devDependencies一律以框架版本为准覆盖;scripts中的dist、prepare、update三个键也强制采用框架版本(否则插件可能构建失败);其余键尽量保留你的自定义值; - .gitignore/.npmignore 走行级合并:
mergeIgnoreFile()将框架模板与你现有文件的规则按行去重合并,而不是整体覆盖; - plugin.config.json 保留原内容:更新时不改动,避免丢失你配置的
extraScripts; - 其余配置文件(如 webpack.config.js)会被覆盖。
正因为webpack.config.js每次升级都会被覆盖,文档给出的最佳实践是:不要直接改 webpack.config.js,而是另建一个独立 JS 文件,在 webpack.config.js 中用一行require引入。这样升级后只需恢复那一行引入语句,你的自定义配置就能原样回归。
外部脚本文件:内容脚本与 Webview 脚本的编译
1. 何时需要"额外编译"
默认情况下,Webpack 只编译src/index.ts及其 import 的模块,其余文件只是被原样复制进插件包。这对简单插件已经够用,但遇到以下两类脚本时就必须编译:
- TypeScript 脚本:
.ts无法直接被 Joplin 执行,必须编译为 JavaScript; - 依赖了 package.json 中第三方模块的脚本:无论 JS 还是 TS,都必须编译,把依赖打包进 JPL 文件,否则运行时找不到模块。
典型场景就是文档中提到的content scripts(内容脚本,用于在笔记编辑器内注入自定义行为)与webview scripts(面板 Webview 中运行的脚本)。
2. 通过 extraScripts 声明并引用
要让某个外部脚本参与编译,把它加入plugin.config.json的extraScripts数组,路径相对于src/目录。例如源码位于src/webviews/index.ts,则配置为:
{ "extraScripts": ["webviews/index.ts"] }编译后,脚本永远以.js扩展名输出(类型后缀会被剥离),上例最终产物为插件包内的webviews/index.js——引用脚本时(例如通过joplin.views.panels.addScript())必须使用这个编译后的路径,而不是原始.ts路径。
底层实现见webpack.config.js的resolveExtraScriptPath()与buildExtraScriptConfigs():每个 extra script 都会生成一个独立的 Webpack 配置,入口为./src/<name>,输出文件名去掉扩展名后补.js,并以commonjs库模式导出默认值。同时模板还为 extra script 预置了一组 CodeMirror 相关库(@codemirror/*、@lezer/*)的externals声明——如果你的内容脚本通过require()或joplin.require()引用这些库,它们不会被重复打进 JPL,而是直接复用 Joplin 运行时自带的版本。
总结:插件开发的完整命令流
| 阶段 | 命令 | 作用 |
|---|---|---|
| 初始化 | yo --node-package-manager npm joplin | 交互式生成插件项目 |
| 开发 | 编辑src/index.ts与src/manifest.json | 编写插件逻辑与元信息 |
| 构建 | npm run dist | 编译代码、打出dist/与publish/*.jpl、publish/*.json |
| 升级版本 | npm run updateVersion | 同步递增 package.json 与 manifest.json 的 patch 版本 |
| 发布 | npm publish(或npm run submit) | 发布到 npm,等待官方仓库自动收录 |
| 框架升级 | npm run update | 合并更新框架文件,保留业务代码 |
围绕这条流程,本文涉及的模板与实现均可在仓库中继续深挖:生成器模板目录、生成器交互与文件写入逻辑、包名推导与合并工具、Webpack 构建配置 以及 生成器自身说明。从脚手架到发布,generator-joplin把模板生成、构建打包、校验、发布、升级各环节串成了一条自动化流水线,开发者只需专注于src/下的业务代码即可。
【免费下载链接】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),仅供参考