本指南从零开始构建一个最小的沙箱插件。该插件记录每次内容保存并公开一个 API 路由。它在由配置的沙箱运行器提供的隔离运行时中执行。当站点操作员将其从 sandboxed: [] 移动到 plugins: [] 时(例如在没有沙箱运行器的平台上),相同的代码也会在进程内运行。
如果你还没有在沙箱和原生之间做出决定,请先阅读选择插件格式。
两个部分
一个沙箱插件包含:
emdash-plugin.jsonc— 手动编辑的清单:身份、信任契约(capabilities、hosts、storage)和配置文件字段。无代码。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)、配置文件字段和发布者固定。
以下示例显示了 hello 插件的完整清单:
{
"$schema": "./node_modules/@emdash-cms/plugin-cli/schemas/emdash-plugin.schema.json",
"slug": "plugin-hello",
"publisher": "did:plc:abc123def456", // your Atmosphere account DID
"license": "MIT",
"author": { "name": "Jane Doe", "url": "https://example.com" },
"security": { "email": "security@example.com" },
"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 # 首先检查清单架构
emdash-plugin build # 生成 dist/
对于编辑循环,运行 emdash-plugin dev(在保存时重建,在构建失败时保留最后的良好 dist/)。在站点中,安装或链接插件(pnpm add file:../plugin-hello 或工作区链接)并启动开发服务器。在管理员中保存一段内容,你应该在日志中看到 Content saved …;GET /_emdash/api/plugins/plugin-hello/recent 返回最后十个保存事件。
接下来阅读什么
- 清单 — 每个字段、信任契约、发布者固定
emdash-pluginCLI —build、dev、bundle- 钩子 — 完整的事件集
- API 路由 — 输入验证、公共路由、错误
- 存储和 KV — 查询选项、索引、批处理操作
- Capabilities 和安全性 — 内容访问、网络、主机允许列表
- 打包和发布 — 运送到市场