插件系统概述

本页内容

EmDash 的插件系统允许你在不修改核心代码的情况下扩展 CMS。插件可以钩入内容生命周期事件、存储自己的数据、向管理员暴露设置,以及向后台面板添加自定义 UI。

设计理念

EmDash 插件有两种类型:沙盒化原生。沙盒化插件在隔离的 V8 worker 中运行,可以从市场一键安装。原生插件在进程中运行,并在代码中配置。

优先使用沙盒化插件。 它们可以从后台 UI 安装、更新和删除,而无需触及代码或重新部署。仅在需要需要构建时集成的功能(React 后台页面、Portable Text 渲染组件或页面片段注入)时使用原生插件。

关键原则:

  • 沙盒优先 — 为沙盒设计;仅在需要时使用原生模式
  • 声明式 — 钩子、存储和路由在定义时声明,而不是动态注册
  • 类型安全 — 完全支持 TypeScript,具有类型化的上下文对象
  • 基于能力 — 插件声明它们需要什么;沙盒强制执行它

插件可以做什么

钩入事件

在内容保存、媒体上传和插件生命周期事件之前或之后运行代码。

存储数据

在索引集合中持久化插件特定的数据,无需编写数据库迁移。

暴露设置

声明设置 schema 并获得自动生成的后台 UI 以进行配置。

添加后台页面

使用 React 组件创建自定义后台页面和仪表板小部件。

创建 API 路由

为你的插件的后台 UI 或外部集成暴露端点。

发出 HTTP 请求

使用声明的主机限制调用外部 API 以确保安全。

插件架构

每个插件都使用 definePlugin() 创建:

import { definePlugin } from "emdash";

export default definePlugin({
	id: "my-plugin",
	version: "1.0.0",

	// 插件需要访问的 API
	capabilities: ["read:content", "network:fetch"],

	// 插件可以向其发出 HTTP 请求的主机
	allowedHosts: ["api.example.com"],

	// 持久存储集合
	storage: {
		entries: {
			indexes: ["userId", "createdAt"],
		},
	},

	// 事件处理器
	hooks: {
		"content:afterSave": async (event, ctx) => {
			ctx.log.info("Content saved", { id: event.content.id });
		},
	},

	// REST API 端点
	routes: {
		status: {
			handler: async (ctx) => ({ ok: true }),
		},
	},

	// 后台 UI 配置
	admin: {
		settingsSchema: {
			apiKey: { type: "secret", label: "API Key" },
		},
		pages: [{ path: "/dashboard", label: "Dashboard" }],
		widgets: [{ id: "status", size: "half" }],
	},
});

插件上下文

每个钩子和路由处理器都接收一个 PluginContext 对象,可以访问:

属性描述可用性
ctx.storage插件的文档集合始终(如果声明)
ctx.kv用于设置和状态的键值存储始终
ctx.content读/写站点内容使用 read:contentwrite:content
ctx.media读/写媒体文件使用 read:mediawrite:media
ctx.http用于外部请求的 HTTP 客户端使用 network:fetch
ctx.log结构化日志记录器(debug、info、warn、error)始终
ctx.plugin插件元数据(id、version)始终
ctx.site站点信息:nameurllocale始终
ctx.url()从路径生成绝对 URL始终
ctx.users读取用户信息:get()getByEmail()list()使用 read:users
ctx.cron调度任务:schedule()cancel()list()始终
ctx.email发送电子邮件:send()使用 email:send + 配置提供程序

上下文形状在所有钩子和路由中都是相同的。受能力限制的属性仅在插件声明所需能力时才存在。

能力

能力决定了插件上下文中可用的 API:

能力授予访问权限
read:contentctx.content.get()ctx.content.list()
write:contentctx.content.create()ctx.content.update()ctx.content.delete()
read:mediactx.media.get()ctx.media.list()
write:mediactx.media.getUploadUrl()ctx.media.upload()ctx.media.delete()
network:fetchctx.http.fetch()(限制为 allowedHosts
network:fetch:anyctx.http.fetch()(无限制 — 用于用户配置的 URL)
read:usersctx.users.get()ctx.users.getByEmail()ctx.users.list()
email:sendctx.email.send()(需要提供程序插件)
email:provide注册 email:deliver 独占钩子(传输提供程序)
email:intercept注册 email:beforeSend / email:afterSend 钩子
page:inject注册 page:metadata / page:fragments 钩子

注册

在你的 Astro 配置中注册插件:

import { defineConfig } from "astro/config";
import { emdash } from "emdash/astro";
import seoPlugin from "@emdash-cms/plugin-seo";
import auditLogPlugin from "@emdash-cms/plugin-audit-log";

export default defineConfig({
	integrations: [
		emdash({
			plugins: [seoPlugin({ generateSitemap: true }), auditLogPlugin({ retentionDays: 90 })],
		}),
	],
});

插件在构建时解析。对于具有相同优先级的钩子,顺序很重要 — 数组中较早的插件先运行。

执行模式

EmDash 支持两种插件执行模式:

模式描述平台
沙盒化具有强制限制的隔离 V8 worker仅 Cloudflare
原生进程中,具有完全访问权限任何

在沙盒模式下,能力在运行时级别强制执行 — 插件只能访问它们声明的内容。在原生模式下,能力是建议性的,插件具有完全的进程访问权限。

下一步