Un plugin nativo es un paquete de npm que EmDash importa en el mismo proceso que el sitio de Astro. Este tutorial crea un plugin que registra los guardados de contenido, lo instala en un sitio y lo registra en astro.config.mjs.
Usa el formato nativo cuando el plugin necesite una función dentro del proceso, como componentes de administración en React, componentes de renderizado de Astro o fragmentos de página de confianza. Si los hooks, las rutas, el almacenamiento y Block Kit cubren la función, empieza con un plugin sandboxed. Elegir un formato de plugin compara los formatos.
Requisitos previos
Empieza con un sitio EmDash que use pnpm y pueda ejecutar su servidor de desarrollo.
Los comandos llaman my-emdash-site al directorio del sitio y crean plugin-activity junto a él. Sustituye my-emdash-site por el nombre del directorio de tu sitio.
Crear y registrar el paquete
-
Crea un directorio de paquete nativo junto al sitio.
mkdir -p ../plugin-activity/src cd ../plugin-activity -
Añade los metadatos del paquete y los scripts de compilación.
{ "name": "@example/plugin-activity", "version": "0.1.0", "type": "module", "main": "./dist/index.mjs", "exports": { ".": { "types": "./dist/index.d.mts", "import": "./dist/index.mjs" } }, "files": ["dist"], "scripts": { "build": "tsdown src/index.ts --format esm --dts --clean", "dev": "tsdown src/index.ts --format esm --dts --watch", "typecheck": "tsc --noEmit" }, "peerDependencies": { "emdash": "*" }, "devDependencies": { "emdash": "*", "tsdown": "^0.20.0", "typescript": "^5.9.0" } }El nombre del paquete de npm es
@example/plugin-activity. El código de ejecución que aparece más abajo usa el ID de plugin sin ámbitoplugin-activitypara que quepa en las URL de las rutas de la API del plugin. -
Añade la configuración de TypeScript.
{ "compilerOptions": { "target": "ES2022", "module": "preserve", "moduleResolution": "bundler", "strict": true, "esModuleInterop": true, "declaration": true, "outDir": "./dist", "rootDir": "./src" }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] } -
Instala las dependencias del paquete.
pnpm install -
Crea
src/index.tscon un hook de guardado de contenido.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:afterSaverequiere la capacidadcontent:read. EmDash omite el hook cuando falta esa capacidad. -
Compila el paquete.
pnpm build -
Instala el paquete local en el sitio.
cd ../my-emdash-site pnpm add ../plugin-activity -
Registra la factoría del descriptor en la integración de 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 })], }), ], });Los descriptores nativos van en
plugins, no ensandboxed. EmDash rechaza un descriptor nativo en el arraysandboxed. -
Inicia el sitio y guarda una entrada en el panel de administración.
pnpm devEl registro del servidor incluye
Content savedcon la colección, el ID del contenido y si la entrada se creó.
Límite entre el descriptor y el entorno de ejecución
La exportación del paquete tiene dos funciones. EmDash usa cada una en una etapa distinta:
- La factoría del descriptor,
activityPlugin(), se ejecuta mientras Astro evalúa su configuración. Devuelve metadatos serializables de tiempo de compilación:id,version,format,entrypointyoptions. Los puntos de entrada de React y Astro también pertenecen a este descriptor. - La exportación con nombre
createPlugin()se ejecuta cuando EmDash se inicializa. EmDash la importa desdeentrypoint, le pasa lasoptionsserializadas y espera un plugin resuelto dedefinePlugin().
La exportación con nombre createPlugin es obligatoria. Una exportación por defecto puede resultar útil a quienes consumen el paquete, pero el cargador nativo de EmDash importa createPlugin por nombre.
Mantén id y version idénticos en el descriptor y en definePlugin(). Usa un ID de plugin sin ámbito y en kebab-case, como plugin-activity; conserva el ámbito de npm en el nombre del paquete y en entrypoint. Así el ID sigue siendo utilizable como único segmento de plugin en las URL de las rutas de la API.
Identidad y versionado del plugin enumera las formas de ID y de versión aceptadas.
El comportamiento en tiempo de ejecución pertenece a definePlugin():
capabilitiesyallowedHostsstoragehooksyroutes- declaraciones de
admin: ajustes, páginas, widgets y Portable Text
El descriptor lleva las entradas estáticas que Astro debe importar o exponer en tiempo de compilación. Las guías específicas muestran qué campos de administración necesitan declaraciones coincidentes en el descriptor y en el entorno de ejecución.
Manejadores de rutas nativos
Los manejadores de rutas nativos reciben un único RouteContext. Combina la entrada validada y los datos de la solicitud con el PluginContext habitual:
routes: {
status: {
permission: "plugins:read",
handler: async (ctx) => ({
pluginId: ctx.plugin.id,
callerId: ctx.user?.id ?? null,
}),
},
},
El manejador sandboxed equivalente recibe (routeCtx, ctx) como dos argumentos. La autenticación, los permisos, los esquemas de entrada y las URL de las rutas siguen, por lo demás, el contrato compartido de las rutas de API.
Envuelve una ruta nativa en definePluginRoute() cuando declare request.body; el helper infiere
ctx.input a partir del modo del cuerpo. Una ruta nativa con response: "raw" devuelve pluginResponse().
Importa ambos helpers desde emdash. La guía compartida de rutas de API enumera los modos de cuerpo, los límites, la política
de respuesta y los valores predeterminados de compatibilidad.
Leer secretos y bindings
Los manejadores de hooks y de rutas de un plugin nativo reciben el contexto del plugin, no el contexto de Astro, por lo que Astro.locals no está disponible en ellos. Lee los secretos y los bindings de la plataforma desde el entorno de ejecución.
Lee los secretos de despliegue desde process.env. En Node.js, los proporciona el gestor de secretos de la plataforma de alojamiento. En Cloudflare Workers, los secretos establecidos con wrangler secret put llegan a process.env mediante el flag nodejs_compat con una fecha de compatibilidad de 2025-04-01 o posterior, como se describe en Secretos del Worker.
Para una credencial que introduce un administrador del sitio, declara en su lugar un campo secret en el formulario de ajustes generado. EmDash cifra el valor con EMDASH_ENCRYPTION_KEY antes de almacenarlo, así que define esa clave antes de que un administrador guarde el campo. El plugin lee el valor con ctx.settings.get().
En Cloudflare Workers, los bindings como una cola, un bucket de R2 o send_email son objetos de env en cloudflare:workers. Astro carga astro.config.mjs en Node.js, y ese archivo importa el paquete del plugin para la factoría del descriptor. Node.js no puede cargar cloudflare:workers, así que impórtalo dentro del manejador en lugar de al principio del módulo. El siguiente hook envía el ID de cada entrada guardada a una cola enlazada como ACTIVITY_QUEUE:
hooks: {
"content:afterSave": async (event) => {
const { env } = await import("cloudflare:workers");
await env.ACTIVITY_QUEUE.send({ contentId: event.content.id });
},
},
El módulo cloudflare:workers solo se resuelve en el entorno de ejecución de Workers, por lo que un plugin que lo importa solo funciona en sitios que usan el adaptador de Cloudflare.
Para comprobar los tipos de la importación, añade @cloudflare/workers-types a las devDependencies del paquete y a compilerOptions.types en tsconfig.json. Después declara cada binding que lee el plugin en la interfaz Cloudflare.Env. La siguiente declaración tipa la cola del ejemplo anterior:
declare namespace Cloudflare {
interface Env {
ACTIVITY_QUEUE: Queue;
}
}
Añadir otra superficie
- Páginas y widgets de administración en React cubre los ajustes, las páginas personalizadas, los widgets del panel, los widgets de campo, los paneles del editor y las columnas de lista.
- Componentes de renderizado de Portable Text registra componentes de Astro para los bloques del plugin.
- Fragmentos de página añade scripts o HTML de confianza a las páginas públicas.
- Distribuir plugins nativos empaqueta los puntos de entrada de compilación y de código fuente para npm.