Ihr erstes natives Plugin

Auf dieser Seite

Ein natives Plugin ist ein npm-Paket, das EmDash im selben Prozess wie die Astro-Website importiert. Dieses Tutorial erstellt ein Plugin, das Speichervorgänge von Inhalten protokolliert, installiert es in einer Website und registriert es in astro.config.mjs.

Verwenden Sie das native Format, wenn das Plugin eine In-Process-Funktion benötigt, etwa React-Admin-Komponenten, Astro-Rendering-Komponenten oder vertrauenswürdige Seitenfragmente. Wenn Hooks, Routen, Speicher und Block Kit die Funktion abdecken, beginnen Sie mit einem Sandbox-Plugin. Ein Plugin-Format wählen vergleicht die Formate.

Voraussetzungen

Beginnen Sie mit einer EmDash-Website, die pnpm verwendet und ihren Entwicklungsserver starten kann.

Die Befehle nennen das Website-Verzeichnis my-emdash-site und erstellen plugin-activity daneben. Ersetzen Sie my-emdash-site durch den Verzeichnisnamen Ihrer Website.

Das Paket erstellen und registrieren

  1. Erstellen Sie neben der Website ein Verzeichnis für das native Paket.

    mkdir -p ../plugin-activity/src
    cd ../plugin-activity
  2. Fügen Sie die Paket-Metadaten und Build-Skripte hinzu.

    {
        "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"
        }
    }

    Der npm-Paketname lautet @example/plugin-activity. Der folgende Laufzeitcode verwendet die Plugin-ID ohne Scope, plugin-activity, damit sie in die URLs der Plugin-API-Routen passt.

  3. Fügen Sie die TypeScript-Konfiguration hinzu.

    {
        "compilerOptions": {
            "target": "ES2022",
            "module": "preserve",
            "moduleResolution": "bundler",
            "strict": true,
            "esModuleInterop": true,
            "declaration": true,
            "outDir": "./dist",
            "rootDir": "./src"
        },
        "include": ["src/**/*"],
        "exclude": ["node_modules", "dist"]
    }
  4. Installieren Sie die Abhängigkeiten des Pakets.

    pnpm install
  5. Erstellen Sie src/index.ts mit einem Hook für das Speichern von Inhalten.

    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 erfordert die Capability content:read. EmDash überspringt den Hook, wenn diese Capability fehlt.

  6. Bauen Sie das Paket.

    pnpm build
  7. Installieren Sie das lokale Paket in der Website.

    cd ../my-emdash-site
    pnpm add ../plugin-activity
  8. Registrieren Sie die Deskriptor-Factory in der EmDash-Integration.

    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 })],
            }),
        ],
    });

    Native Deskriptoren gehören in plugins, nicht in sandboxed. EmDash lehnt einen nativen Deskriptor im Array sandboxed ab.

  9. Starten Sie die Website und speichern Sie einen Eintrag im Admin-Bereich.

    pnpm dev

    Das Server-Log enthält Content saved mit der Sammlung, der Inhalts-ID und der Angabe, ob der Eintrag neu erstellt wurde.

Grenze zwischen Deskriptor und Laufzeit

Der Paket-Export hat zwei Aufgaben. EmDash verwendet jede in einer anderen Phase:

  • Die Deskriptor-Factory activityPlugin() läuft, während Astro seine Konfiguration auswertet. Sie gibt serialisierbare Build-Time-Metadaten zurück: id, version, format, entrypoint und options. React- und Astro-Einstiegspunkte gehören ebenfalls in diesen Deskriptor.
  • Der benannte Export createPlugin() läuft, wenn EmDash initialisiert wird. EmDash importiert ihn aus entrypoint, übergibt die serialisierten options und erwartet von definePlugin() ein aufgelöstes Plugin.

Der benannte Export createPlugin ist erforderlich. Ein Default-Export kann für Paketnutzer nützlich sein, aber der native Loader von EmDash importiert createPlugin über den Namen.

Halten Sie id und version im Deskriptor und in definePlugin() identisch. Verwenden Sie eine Plugin-ID ohne Scope in Kebab-Case, etwa plugin-activity; behalten Sie den npm-Scope im Paketnamen und in entrypoint. So bleibt die ID als einzelnes Plugin-Segment in API-Routen-URLs verwendbar.

Plugin-Identität und Versionierung listet die zulässigen ID- und Versionsformen auf.

Laufzeitverhalten gehört in definePlugin():

  • capabilities und allowedHosts
  • storage
  • hooks und routes
  • admin-Einstellungen, -Seiten, -Widgets und Portable-Text-Deklarationen

Der Deskriptor trägt die statischen Einträge, die Astro zur Build-Zeit importieren oder bereitstellen muss. Die themenbezogenen Leitfäden zeigen, welche Admin-Felder zueinander passende Deklarationen in Deskriptor und Laufzeit benötigen.

Native Routen-Handler

Native Routen-Handler erhalten einen einzigen RouteContext. Er kombiniert validierte Eingaben und Anfragedaten mit dem regulären PluginContext:

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

Der entsprechende Sandbox-Handler erhält (routeCtx, ctx) als zwei Argumente. Authentifizierung, Berechtigungen, Eingabeschemata und Routen-URLs folgen ansonsten dem gemeinsamen Vertrag der API-Routen.

Umhüllen Sie eine native Route mit definePluginRoute(), wenn sie request.body deklariert; der Helper leitet ctx.input aus dem Body-Modus ab. Eine native Route mit response: "raw" gibt pluginResponse() zurück. Importieren Sie beide Helper aus emdash. Der gemeinsame Leitfaden zu API-Routen listet die Body-Modi, Limits, die Antwort- Richtlinie und die Kompatibilitäts-Standardwerte auf.

Secrets und Bindings lesen

Hook- und Routen-Handler in einem nativen Plugin erhalten den Plugin-Kontext, nicht den Astro-Kontext, daher ist Astro.locals in ihnen nicht verfügbar. Lesen Sie Secrets und Plattform-Bindings aus der Laufzeitumgebung.

Lesen Sie Deployment-Secrets aus process.env. Unter Node.js stellt der Secret-Manager der Hosting-Plattform sie bereit. Unter Cloudflare Workers gelangen mit wrangler secret put gesetzte Secrets über das Flag nodejs_compat mit einem Kompatibilitätsdatum von 2025-04-01 oder später nach process.env, wie unter Worker-Secrets beschrieben.

Für eine Zugangsdaten-Eingabe durch einen Website-Administrator deklarieren Sie stattdessen ein Feld secret im generierten Einstellungsformular. EmDash verschlüsselt den Wert vor dem Speichern mit EMDASH_ENCRYPTION_KEY; setzen Sie diesen Schlüssel also, bevor ein Administrator das Feld speichert. Das Plugin liest den Wert mit ctx.settings.get().

Unter Cloudflare Workers sind Bindings wie eine Queue, ein R2-Bucket oder send_email Objekte auf env aus cloudflare:workers. Astro lädt astro.config.mjs in Node.js, und diese Datei importiert das Plugin-Paket für die Deskriptor-Factory. Node.js kann cloudflare:workers nicht laden, importieren Sie es daher innerhalb des Handlers und nicht am Anfang des Moduls. Der folgende Hook sendet die ID jedes gespeicherten Eintrags an eine als ACTIVITY_QUEUE gebundene Queue:

hooks: {
	"content:afterSave": async (event) => {
		const { env } = await import("cloudflare:workers");
		await env.ACTIVITY_QUEUE.send({ contentId: event.content.id });
	},
},

Das Modul cloudflare:workers lässt sich nur in der Workers-Laufzeit auflösen, daher funktioniert ein Plugin, das es importiert, nur auf Websites, die den Cloudflare-Adapter verwenden.

Um den Import auf Typen zu prüfen, fügen Sie @cloudflare/workers-types zu den devDependencies des Pakets und zu compilerOptions.types in tsconfig.json hinzu. Deklarieren Sie dann jedes Binding, das das Plugin liest, im Interface Cloudflare.Env. Die folgende Deklaration typisiert die Queue aus dem vorherigen Beispiel:

declare namespace Cloudflare {
	interface Env {
		ACTIVITY_QUEUE: Queue;
	}
}

Eine weitere Oberfläche hinzufügen