Un plugin natif est un paquet npm qu’EmDash importe dans le même processus que le site Astro. Ce tutoriel crée un plugin qui journalise les enregistrements de contenu, l’installe dans un site et l’enregistre dans astro.config.mjs.
Utilisez le format natif lorsque le plugin a besoin d’une fonctionnalité dans le même processus, comme des composants d’administration React, des composants de rendu Astro ou des fragments de page de confiance. Si les hooks, les routes, le stockage et Block Kit couvrent la fonctionnalité, commencez par un plugin sandboxed. Choisir un format de plugin compare les formats.
Prérequis
Partez d’un site EmDash qui utilise pnpm et peut exécuter son serveur de développement.
Les commandes appellent le répertoire du site my-emdash-site et créent plugin-activity à côté. Remplacez my-emdash-site par le nom du répertoire de votre site.
Créer et enregistrer le paquet
-
Créez un répertoire de paquet natif à côté du site.
mkdir -p ../plugin-activity/src cd ../plugin-activity -
Ajoutez les métadonnées du paquet et les scripts de build.
{ "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" } }Le nom du paquet npm est
@example/plugin-activity. Le code d’exécution ci-dessous utilise l’ID de plugin sans portéeplugin-activityafin qu’il tienne dans les URL des routes d’API du plugin. -
Ajoutez la configuration TypeScript.
{ "compilerOptions": { "target": "ES2022", "module": "preserve", "moduleResolution": "bundler", "strict": true, "esModuleInterop": true, "declaration": true, "outDir": "./dist", "rootDir": "./src" }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] } -
Installez les dépendances du paquet.
pnpm install -
Créez
src/index.tsavec un hook d’enregistrement de contenu.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:afterSavenécessite la capacitécontent:read. EmDash ignore le hook lorsque cette capacité est absente. -
Construisez le paquet.
pnpm build -
Installez le paquet local dans le site.
cd ../my-emdash-site pnpm add ../plugin-activity -
Enregistrez la fabrique de descripteur dans l’intégration 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 })], }), ], });Les descripteurs natifs vont dans
plugins, pas danssandboxed. EmDash rejette un descripteur natif placé dans le tableausandboxed. -
Démarrez le site et enregistrez une entrée dans le panneau d’administration.
pnpm devLe journal du serveur inclut
Content savedavec la collection, l’ID du contenu et l’indication de création de l’entrée.
Frontière entre le descripteur et l’exécution
L’export du paquet remplit deux rôles. EmDash utilise chacun à une étape différente :
- La fabrique de descripteur,
activityPlugin(), s’exécute pendant qu’Astro évalue sa configuration. Elle renvoie des métadonnées sérialisables de build :id,version,format,entrypointetoptions. Les points d’entrée React et Astro appartiennent aussi à ce descripteur. - L’export nommé
createPlugin()s’exécute lorsque EmDash s’initialise. EmDash l’importe depuisentrypoint, lui transmet lesoptionssérialisées et attend un plugin résolu dedefinePlugin().
L’export nommé createPlugin est obligatoire. Un export par défaut peut être utile aux consommateurs du paquet, mais le chargeur natif d’EmDash importe createPlugin par son nom.
Gardez id et version identiques dans le descripteur et dans definePlugin(). Utilisez un ID de plugin sans portée, en kebab-case, comme plugin-activity ; conservez la portée npm dans le nom du paquet et dans entrypoint. Cela permet d’utiliser l’ID comme unique segment de plugin dans les URL des routes d’API.
Identité et versionnage du plugin liste les formes d’ID et de version acceptées.
Le comportement d’exécution appartient à definePlugin() :
capabilitiesetallowedHostsstoragehooksetroutes- les déclarations
admin: paramètres, page, widget et Portable Text
Le descripteur porte les entrées statiques qu’Astro doit importer ou exposer au moment du build. Les guides spécialisés indiquent quels champs d’administration nécessitent des déclarations concordantes dans le descripteur et à l’exécution.
Gestionnaires de routes natifs
Les gestionnaires de routes natifs reçoivent un seul RouteContext. Il combine l’entrée validée et les données de la requête avec le PluginContext habituel :
routes: {
status: {
permission: "plugins:read",
handler: async (ctx) => ({
pluginId: ctx.plugin.id,
callerId: ctx.user?.id ?? null,
}),
},
},
Le gestionnaire sandboxed équivalent reçoit (routeCtx, ctx) comme deux arguments. L’authentification, les permissions, les schémas d’entrée et les URL de routes suivent par ailleurs le contrat commun des routes d’API.
Enveloppez une route native dans definePluginRoute() lorsqu’elle déclare request.body ; l’assistant déduit
ctx.input du mode du corps. Une route native avec response: "raw" renvoie pluginResponse().
Importez les deux assistants depuis emdash. Le guide commun des routes d’API liste les modes de corps, les limites, la politique
de réponse et les valeurs par défaut de compatibilité.
Lire les secrets et les bindings
Les gestionnaires de hooks et de routes d’un plugin natif reçoivent le contexte du plugin, pas le contexte Astro ; Astro.locals n’y est donc pas disponible. Lisez les secrets et les bindings de plateforme depuis l’environnement d’exécution.
Lisez les secrets de déploiement depuis process.env. Sur Node.js, le gestionnaire de secrets de la plateforme d’hébergement les fournit. Sur Cloudflare Workers, les secrets définis avec wrangler secret put atteignent process.env grâce au flag nodejs_compat avec une date de compatibilité du 2025-04-01 ou ultérieure, comme décrit dans Secrets du Worker.
Pour un identifiant saisi par un administrateur du site, déclarez plutôt un champ secret dans le formulaire de paramètres généré. EmDash chiffre la valeur avec EMDASH_ENCRYPTION_KEY avant de la stocker ; définissez donc cette clé avant qu’un administrateur n’enregistre le champ. Le plugin lit la valeur avec ctx.settings.get().
Sur Cloudflare Workers, les bindings tels qu’une file d’attente, un bucket R2 ou send_email sont des objets de env dans cloudflare:workers. Astro charge astro.config.mjs dans Node.js, et ce fichier importe le paquet du plugin pour la fabrique de descripteur. Node.js ne peut pas charger cloudflare:workers ; importez-le donc dans le gestionnaire plutôt qu’en haut du module. Le hook suivant envoie l’ID de chaque entrée enregistrée à une file d’attente liée sous le nom ACTIVITY_QUEUE :
hooks: {
"content:afterSave": async (event) => {
const { env } = await import("cloudflare:workers");
await env.ACTIVITY_QUEUE.send({ contentId: event.content.id });
},
},
Le module cloudflare:workers ne se résout que dans l’environnement d’exécution Workers ; un plugin qui l’importe ne fonctionne donc que sur les sites qui utilisent l’adaptateur Cloudflare.
Pour vérifier les types de l’import, ajoutez @cloudflare/workers-types aux devDependencies du paquet et à compilerOptions.types dans tsconfig.json. Déclarez ensuite chaque binding lu par le plugin dans l’interface Cloudflare.Env. La déclaration suivante type la file d’attente de l’exemple précédent :
declare namespace Cloudflare {
interface Env {
ACTIVITY_QUEUE: Queue;
}
}
Ajouter une autre surface
- Pages et widgets d’administration React couvre les paramètres, les pages personnalisées, les widgets du tableau de bord, les widgets de champ, les panneaux de l’éditeur et les colonnes de liste.
- Composants de rendu Portable Text enregistre des composants Astro pour les blocs de plugin.
- Fragments de page ajoute des scripts ou du HTML de confiance aux pages publiques.
- Distribuer des plugins natifs empaquette les points d’entrée de build et de source pour npm.