本指南从零开始构建一个最小的沙盒插件。该插件记录每次内容保存并暴露一个 API 路由。它运行在由配置的沙盒运行器提供的隔离运行时中。当站点运维人员将其从 sandboxed: [] 移到 plugins: [] 时,相同的代码也可在进程内运行,例如在没有沙盒运行器的平台上。
如果您还没有决定选择沙盒还是原生,请先阅读选择插件格式。
两个部分
一个沙盒插件由以下组成:
emdash-plugin.jsonc— 手动编辑的清单:身份、信任契约(能力、主机、存储)和配置文件字段。不含代码。src/plugin.ts— 运行时:钩子和路由。从emdash/plugin仅导入类型;无运行时emdash导入。
emdash-plugin build 读取两者并输出站点消费的 dist/ 制品。
以下示例展示了一个完整插件的文件布局:
my-plugin/
├── emdash-plugin.jsonc # 身份 + 信任契约 + 配置文件
├── src/
│ └── plugin.ts # 钩子、路由——在沙盒运行时中运行
├── package.json
└── tsconfig.json
设置包
-
创建目录和
package.json。构建命令是emdash-plugin build;不需要编写tsdown调用。{ "name": "@my-org/plugin-hello", "version": "0.1.0", "type": "module", "main": "dist/index.mjs", "exports": { ".": { "import": "./dist/index.mjs", "types": "./dist/index.d.mts" }, "./sandbox": "./dist/plugin.mjs" }, "files": ["dist", "emdash-plugin.jsonc"], "scripts": { "build": "emdash-plugin build", "dev": "emdash-plugin dev" }, "peerDependencies": { "emdash": ">=0.13.0" }, "devDependencies": { "@emdash-cms/plugin-cli": "0.2.0", "emdash": ">=0.13.0", "typescript": "^5.9.0" } }"."是站点导入的生成的描述符;"./sandbox"是构建后的运行时文件。emdash-plugin build两者都会生成。 -
添加
tsconfig.json:{ "compilerOptions": { "target": "ES2022", "module": "preserve", "moduleResolution": "bundler", "strict": true, "esModuleInterop": true, "verbatimModuleSyntax": true, "skipLibCheck": true, "types": [] }, "include": ["src/**/*"], "exclude": ["node_modules"] }
编写清单
emdash-plugin.jsonc 携带插件的身份(slug)、信任契约(capabilities、allowedHosts、storage)、配置文件字段和发布者固定。将 publisher 设置为您的 Atmosphere 账户的 DID。
以下示例展示了 hello 插件的完整清单:
{
"$schema": "./node_modules/@emdash-cms/plugin-cli/schemas/emdash-plugin.schema.json",
"slug": "plugin-hello",
"publisher": "did:plc:abc123def456", // 您的 Atmosphere 账户 DID
"license": "MIT",
"author": { "name": "Jane Doe", "url": "https://example.com" },
"security": { "email": "[email protected]" },
"capabilities": [],
"allowedHosts": [],
"storage": { "events": { "indexes": ["timestamp"] } }
}
关于此清单的说明:
slug是 URL 安全 ID,不是 npm 包名。/^[a-z][a-z0-9_-]*$/,最多 64 个字符。它是插件路由 URL 中的单一路径段(/_emdash/api/plugins/<slug>/...)和存储索引生成的 SQL 标识符的一部分,因此@、/、前导数字和大写字母都会失败。将无范围的slug(plugin-hello)与有范围的 npm 包名搭配使用。storage预先声明集合。ctx.storage.events在运行时能工作仅因为events在此处已声明。访问未声明的集合会抛出错误。- 省略了
version。 构建从package.json读取它,因此只有一个真实来源。参见清单参考。 - 信任契约就是授权。 后续更改
capabilities、allowedHosts或storage需要递增版本号——已安装的站点同意的是旧契约。
编写运行时
src/plugin.ts 默认导出一个用 satisfies SandboxedPlugin 标注的裸对象。emdash/plugin 仅提供类型,因此沙盒插件不依赖 emdash 的运行时。
以下示例将每次内容保存记录到插件存储中,并暴露一个 recent 路由返回最近十次保存:
import type { SandboxedPlugin } from "emdash/plugin";
export default {
hooks: {
"content:afterSave": {
handler: async (event, ctx) => {
ctx.log.info("Content saved", {
collection: event.collection,
id: event.content.id,
});
await ctx.storage.events.put(`save-${Date.now()}`, {
timestamp: new Date().toISOString(),
collection: event.collection,
contentId: event.content.id,
});
},
},
},
routes: {
recent: {
handler: async (_routeCtx, ctx) => {
const result = await ctx.storage.events.query({ limit: 10 });
return { events: result.items };
},
},
},
} satisfies SandboxedPlugin;
关于运行时文件的说明:
satisfies SandboxedPlugin为所有内容提供类型。 它从钩子名称推断event(使用完整的规范事件类型)并将ctx推断为PluginContext,因此处理程序不需要参数标注。拼错的钩子键如"content:afterSav"是编译错误。- 钩子处理程序接受
(event, ctx)。 事件形式取决于钩子名称;参见钩子指南。 - 路由处理程序接受
(routeCtx, ctx)— 两个参数。routeCtx是{ input, request, requestMeta? };ctx是同一个PluginContext。路由可通过/_emdash/api/plugins/<slug>/<route-name>访问。 ctx.storage.events能工作是因为events在清单中已声明。ctx.kv始终可用 — 每个插件的键值存储,支持get、set、delete、list(prefix)。
注册插件
在站点的 astro.config.mjs 中,导入插件的默认导出并传入。沙盒插件放在 sandboxed: [] 中;进程内插件放在 plugins: [] 中。沙盒插件在两者中都能工作。以下示例使用 sandboxed::
import { defineConfig } from "astro/config";
import emdash from "emdash/astro";
import { sandbox } from "@emdash-cms/cloudflare";
import hello from "@my-org/plugin-hello";
export default defineConfig({
integrations: [
emdash({
sandboxed: [hello],
sandboxRunner: sandbox(),
}),
],
});
sandboxRunner 是可插拔的部分。示例使用来自 @emdash-cms/cloudflare 的 sandbox(),这是目前大多数站点使用的运行器。如果未配置运行器(或配置的运行器在当前平台不可用),sandboxed: [] 中的插件会在启动时被跳过——将插件移到 plugins: [] 中以在进程内运行。
构建和运行
从插件目录:
emdash-plugin validate # 先进行 schema 检查清单
emdash-plugin build # 输出 dist/
在编辑循环中,运行 emdash-plugin dev(保存时重新构建,构建失败时保留上一个正常的 dist/)。在站点中,安装或链接插件(pnpm add file:../plugin-hello 或工作区链接)并启动开发服务器。在管理面板中保存一段内容,您应该在日志中看到 Content saved …;GET /_emdash/api/plugins/plugin-hello/recent 返回最近十次保存事件。