那天晚上,客户突然甩过来一个需求:“文章发布后,自动同步到我们的企业微信群里。”

如果是WordPress,我闭着眼都能写个publish_post钩子。但这次用的是 ReactPress官网 | GitHub)——一个基于NestJS和React的现代CMS。翻遍文档,我终于在某个角落找到了救星:官方提供的 reactpress-plugin-starter 模板。

于是,一场从零到一的插件开发冒险,就这么开始了。

第一关:克隆那个“潘多拉魔盒”

我打开终端,敲下这行命令,就像打开了新世界的大门:

git clone https://github.com/fecommunity/reactpress-plugin-starter.git my-sync-plugin
cd my-sync-plugin
npm install

目录解压后,我被它的结构惊到了——src/server/src/admin/分得清清楚楚。好家伙,果然是“前后端分离”的拥护者。

但很快我就被浇了一盆冷水:这玩意儿不像WordPress那样直接扔进wp-content/plugins就行。它需要身份认证

第二关:给它一个“合法身份”

打开plugin.json,我看到了一个清单:

{
  "id": "my-sync-plugin",  // 必须是 kebab-case,且和文件夹名一致
  "name": "企业微信同步插件",
  "version": "1.0.0"
}

再打开package.json,把名字改成@my-scope/reactpress-plugin-wecom-sync。这时候我才明白,ReactPress的插件就像一个个微服务,插件ID就是它在系统中的身份证号——改错了,后台根本认不出来。

关于插件清单的完整规范,可以看官方 插件系统参考文档

第三关:真正的“魔法”藏在钩子里

最激动人心的时刻来了。我打开src/server/index.ts,看到了那个神秘的register函数:

import type { HookService, PluginContext } from '@fecommunity/reactpress-toolkit/plugin/server';
//                                 ↑ 工具包地址:https://www.npmjs.com/package/@fecommunity/reactpress-toolkit

export function register(hooks: HookService, ctx: PluginContext): void {
  // 这就是那个“暗号”!
  hooks.addAction('article.afterPublish', async (payload) => {
    // payload 里藏着 { article, isNew }
    await sendToWeCom(payload.article.title, payload.article.url);
  }, { priority: 10, pluginId: ctx.id });
}

看到article.afterPublish的那一刻,我差点从椅子上跳起来——这不就是WordPress的publish_post吗?那种熟悉的感觉瞬间回来了。

但接下来的deactivate函数又提醒我:这不只是简单的钩子,它是热插拔的。启用、禁用、重载,都不需要重启整个API服务。这种体验,对开发调试来说简直是降维打击。

第四关:后台UI的“插槽游戏”

光有逻辑还不够,客户还想要一个设置面板,能开关“是否同步”和“Webhook地址”。

我在plugin.json里找到了admin.menu,又在src/admin/index.ts里看到了registerAdmin

export function registerAdmin(registry, ctx) {
  // 把面板注册到后台菜单里
  registry.registerMenu('我的插件设置', <StarterPanel />);
}

更有趣的是,ReactPress还提供了**插槽(Slots)**机制。我甚至可以把设置面板挂在文章编辑器的侧边栏里(article.editor.sidebar.afterPublish),就像给WordPress的编辑器挂载Meta Box一样。

想了解所有内置插槽?参考 主题与插件开发文档

那种感觉就像在玩乐高——所有的UI块都是积木,插件只是往里塞了一块属于自己的零件。

第五关:配置的“暗箱操作”

配置存在哪儿?藏在globalSetting.plugins.entries.{id}.config里。

我可以通过后台的设置页面修改,也可以用命令行直接PUT:

curl -X PUT http://localhost:3002/api/extension/plugins/my-sync-plugin/config \
  -H "Authorization: Bearer <token>" \
  -d '{"config":{"enabled":true,"webhook":"https://qyapi.weixin.qq.com/..."}}'

关键的是——改完配置,插件自动重载。我不用再像以前一样,改个配置就得重启整个服务,等得花儿都谢了。

第六关(最折磨的一步):把它塞进ReactPress项目里

写完了代码,npm run build编译出dist/目录。下一步,我得把它放进ReactPress的主项目里:

cp -r my-sync-plugin /path/to/reactpress/plugins/

然后去plugins/package.json里注册一下:

{
  "reactpress": {
    "local": ["my-sync-plugin"]
  }
}

跑起pnpm dev的那一瞬间,我屏住了呼吸。看到终端里跳出“Plugin my-sync-plugin compiled successfully”时,我才长舒一口气。

登录后台 → 插件管理 → 启用。那一刻,我的小插件终于“活”了过来。

通关感悟:这不只是代码,是一种“超能力”

凌晨四点,我对着电脑屏幕发了会儿呆。回首这短短几个小时的冒险,我发现ReactPress的插件系统其实藏着三个设计哲学:

  1. 边界清晰:服务器逻辑(src/server)和后台界面(src/admin)被物理分离,各司其职,互不干扰。
  2. 热加载快感:启用/禁用无需重启API,这在开发时大大缩短了反馈回路。
  3. 插槽即自由:不只是简单的菜单,你还能把UI“钉”在任何内置编辑器挂载点上,就像给CMS打上定制的补丁。

如果你也想体验这种“掌控感”,别犹豫,去把 reactpress-plugin-starter 拽下来试试。

毕竟,一个好的CMS不只是用来写文章的——它是让你随心所欲重塑内容流程的舞台。而写插件,就是让你成为这场演出的导演。

准备好了吗?你的第一个钩子,正在等你注册。 🎬


📌 快速传送门(文中提到的所有地址)


(附:如果中途被依赖问题卡住,记得在package.json里加.npmrc开启legacy-peer-deps——这是我踩过的第一个坑,希望你别再踩了。)


现在所有关键地址都有了,读者不用再自己搜。如果还需要补充其他链接(比如某个特定API端点的文档),随时告诉我,我再塞进去。😊

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐