你的第一个原生插件

本页内容

原生插件是 EmDash 导入到与 Astro 站点同一进程中的 npm 包。本教程创建一个记录内容保存的插件,将其安装到站点,并在 astro.config.mjs 中注册。

当插件需要进程内功能(如 React 管理组件、Astro 渲染组件或受信任的页面片段)时,使用原生格式。若钩子、路由、存储与 Block Kit 已覆盖该功能,请从沙箱插件开始。选择插件格式 比较了两种格式。

前提条件

从使用 pnpm 且能运行开发服务器的 EmDash 站点开始。站点必须已依赖 emdash,它提供下文使用的 emdash 命令。

命令将站点目录称为 my-emdash-site,并在其旁创建 plugin-activity。将 my-emdash-site 替换为你的站点目录名。

创建并注册包

  1. 在站点旁搭建原生包。

    pnpm exec emdash plugin init --native --name @example/plugin-activity --dir ../plugin-activity

    命令在创建插件 ID 时会移除 npm 作用域。包名为 @example/plugin-activity,插件 ID 为 plugin-activity。

  2. 安装包依赖。

    cd ../plugin-activity
    pnpm install
  3. 将生成的 src/index.ts 替换为内容保存钩子。

    import { definePlugin } from "emdash";
    import type { PluginDescriptor } from "emdash";
    
    export interface ActivityPluginOptions {
        logUpdates?: boolean;
    }
    
    export function activityPlugin(
        options: ActivityPluginOptions = {},
    ): PluginDescriptor<ActivityPluginOptions> {
        return {
            id: "plugin-activity",
            version: "0.1.0",
            format: "native",
            entrypoint: "@example/plugin-activity",
            options,
        };
    }
    
    export function createPlugin(options: ActivityPluginOptions = {}) {
        return definePlugin({
            id: "plugin-activity",
            version: "0.1.0",
            capabilities: ["content:read"],
            hooks: {
                "content:afterSave": async (event, ctx) => {
                    if (!event.isNew && options.logUpdates === false) return;
    
                    ctx.log.info("Content saved", {
                        collection: event.collection,
                        contentId: event.content.id,
                        isNew: event.isNew,
                    });
                },
            },
        });
    }
    
    export default createPlugin;

    content:afterSave 需要 content:read 能力。缺少该能力时 EmDash 会跳过该钩子。

  4. 构建包。

    pnpm build
  5. 将本地包安装到站点。

    cd ../my-emdash-site
    pnpm add ../plugin-activity
  6. 在 EmDash 集成中注册描述符工厂。

    import { defineConfig } from "astro/config";
    import emdash from "emdash/astro";
    import { activityPlugin } from "@example/plugin-activity";
    
    export default defineConfig({
        integrations: [
            emdash({
                plugins: [activityPlugin({ logUpdates: true })],
            }),
        ],
    });

    原生描述符属于 plugins,不属于 sandboxed。EmDash 会拒绝 sandboxed 数组中的原生描述符。

  7. 启动站点并在管理面板中保存一条条目。

    pnpm dev

    服务器日志包含带有集合、内容 ID 以及条目是否新建的 Content saved。

描述符与运行时边界

包导出有两项工作。EmDash 在不同阶段使用各自:

  • 描述符工厂 activityPlugin() 在 Astro 评估其配置时运行。它返回可序列化的构建时元数据:id、version、format、entrypoint 与 options。React 与 Astro 入口也属于此描述符。
  • 具名 createPlugin() 导出在 EmDash 初始化时运行。EmDash 从 entrypoint 导入它,传入序列化的 options,并期望从 definePlugin() 得到已解析的插件。

具名 createPlugin 导出是必需的。默认导出可能对包使用者有用,但 EmDash 的原生加载器按名称导入 createPlugin。

在描述符与 definePlugin() 中保持 id 与 version 相同。使用无作用域、kebab-case 的插件 ID,如 plugin-activity;将 npm 作用域保留在包名与 entrypoint 中。这样 ID 可作为 API 路由 URL 中的单一插件段使用。

插件标识与版本 列出可接受的 ID 与版本形式。

运行时行为属于 definePlugin():

  • capabilities 与 allowedHosts
  • storage
  • hooks 与 routes
  • admin 设置、页面、小组件与 Portable Text 声明

描述符携带 Astro 必须在构建时导入或暴露的静态条目。专题指南说明哪些管理字段需要匹配的描述符与运行时声明。

原生路由处理程序

原生路由处理程序接收一个 RouteContext。它将已验证输入与请求数据与常规 PluginContext 合并:

routes: {
	status: {
		permission: "plugins:read",
		handler: async (ctx) => ({
			pluginId: ctx.plugin.id,
			callerId: ctx.user?.id ?? null,
		}),
	},
},

等效的沙箱处理程序以两个参数接收 (routeCtx, ctx)。身份验证、权限、输入 schema 与路由 URL 在其他方面遵循共享的 API 路由 约定。

当原生路由声明 request.body 时,用 definePluginRoute() 包装它;该辅助函数从 body 模式推断 ctx.input。带 response: "raw" 的原生路由返回 pluginResponse()。 从 emdash 导入这两个辅助函数。共享 API 路由指南列出 body 模式、限制、响应策略与兼容性默认值。

添加另一个表面