跳转到内容

入门

这一篇带你从空目录写出一个能构建、安装的插件。其余扩展点从能力和上下文开始读。

需要 Node 24 或更新版本。插件项目是 ESM,package.json 必须写 "type": "module"。SDK 提供类型和辅助函数,CLI 提供构建命令;两个包装同一个版本,包括 -rc.N 后缀。

终端窗口
npm init -y
npm pkg set type=module
npm install --save-exact @nextskin/plugin-sdk@rc
npm install --save-dev --save-exact @nextskin/plugin-cli@rc
npm install --save-dev @types/node

候选期使用 rc 标签;1.0.0 发布之后去掉 @rc。已有项目升级时把两个包一起换到同一个具体版本;版本和兼容解释如何兼容旧核心。

保留安装命令生成的依赖项,把项目的其余字段设成:

{
"name": "nextskin-plugin-hello",
"version": "1.0.0",
"type": "module",
"nextskin": { "id": "hello" },
"scripts": { "build": "nextskin-plugin build" }
}

name 是 npm 项目名,nextskin.id 才是站点识别插件的名字。id 匹配 ^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$,最多 64 字符,安装后不要改名。version 是插件自己的 semver 版本,不是 SDK 版本。不要占用官方插件的 id。

依赖别的插件时,在 nextskin 里写 "requires": { "other-plugin": "^1.0.0" };不能同时启用时写 "conflicts": ["incompatible-plugin"]。依赖的插件必须启用且版本满足范围。反向依赖也会检查:被其他启用插件依赖时不能随意停用、卸载或升级到不满足范围的版本。完整语法见版本和兼容。

创建 src/index.ts,默认导出 definePlugin 的返回值。下面是完整入口:

import { definePlugin } from '@nextskin/plugin-sdk'
export default definePlugin({
capabilities: [],
on: {
'user.registered': async ({ event, ctx }) => {
ctx.log.info(ctx.t('log.registered'), { uid: event.data.user.uid })
},
},
})

创建 locales/zh-CN.json:

{
"plugin": {
"name": "注册记录",
"description": "把新注册用户的编号写进站点日志。"
},
"log": { "registered": "新用户注册" }
}

再创建 locales/en.json:

{
"plugin": {
"name": "Registration log",
"description": "Write new user IDs to the site log."
},
"log": { "registered": "User registered" }
}

文案是嵌套 JSON 对象,键用点分隔,支持 ICU MessageFormat。先找请求语言,再找站点默认语言;都找不到就显示键名。后台名称取 plugin.name,缺少时显示 id;简介取 plugin.description。

这个插件不需要能力:事件已经带了公开用户资料。事件至少投递一次,所以日志可能重复;有外部副作用的处理函数要按事件一篇做幂等。

终端窗口
npm run build
npx nextskin-plugin build . --out dist/hello.tgz

命令格式是 nextskin-plugin build [目录] [--out 文件]。目录默认是当前目录;默认产物是插件目录下的 dist/<id>-<version>.tgz。显式 --out 相对于运行命令的当前目录解析。

入口固定是 src/index.ts。构建器打成单文件 server.mjs,执行入口模块来提取 manifest,打包 locales/ 下的 JSON,再校验整个包。入口及其依赖的顶层代码会在构建时执行,不要在那里联网、读生产机密或启动计时器;业务逻辑放在处理函数中。构建不会调用事件、路由、任务等 handler,也不会替你做 TypeScript 类型检查。

SDK 和 zod 在运行时由 host 提供;其他依赖应能打进服务端单文件。安装插件时不会运行 npm install,不要依赖本机的文件、原生扩展或未打包的模块。

构建不检查类型,要自己跑 TypeScript。装上它,在项目根目录放一个 tsconfig.json:

终端窗口
npm install --save-dev typescript
{
"compilerOptions": {
"target": "ES2023",
"lib": ["ES2023"],
"module": "preserve",
"moduleResolution": "bundler",
"strict": true,
"noEmit": true,
"types": ["node"]
},
"include": ["src"]
}

构建之前跑一遍 npx tsc。SDK 发布前在 TypeScript 6 和 7、bundler 和 nodenext 两种模块解析下都检查过,用 nodenext 时把 module 也改成 nodenext。

目前核心还没有发行包,测试站需要单独克隆 NextSkin 仓库、安装依赖,在那个仓库运行 pnpm dev。默认使用 SQLite,不需要外部数据库。空站先访问 /setup 建站,安装码在 apps/web/data/setup-code;安装前不能注册。

用超级管理员打开 /admin/plugins,上传 tgz(最多 10 MiB),核对能力、出站 origin、路由和表,确认安装。安装完成还没启用;进入详情页启用,核心先跑迁移,再切换 host。需要设置的插件在详情页填写设置。

也可以不经过后台,在那个仓库里用命令行:pnpm nextskin plugin install <tgz 的路径> --yes 安装或升级,pnpm nextskin plugin enable <插件 id> 启用,pnpm dev 不用停,几秒后就切换过去。正式站点上是发行目录里的 node nextskin.mjs plugin …。

注册一个测试用户,在运行 pnpm dev 的终端里查找 plugin/hello。核心还会在 core/plugins 下记录每次调用的调用点、耗时和结果。后台详情页能看到最近错误和死信,能重试或删除死信;普通日志看服务端输出。

更新代码后增加插件自己的 version,再重新构建、上传升级;新版本必须大于已安装版本,同版替换也会被拒绝。迁移只向前走,不支持降级。停用保留设置、数据和积压任务;卸载会删除这些数据,不能当成临时开关。

NextSkin v0.7.2插件 SDK v1.0.0-rc.7提交 e6ffcd2本站源码