Portieren Sie ein WordPress-Plugin, indem Sie Inhaltsverhalten, gespeicherte Daten, HTTP-Routen und Benutzeroberfläche trennen. Wählen Sie anschließend das EmDash-Plugin-Format, das diese Anforderungen unterstützt.
Prüfen, ob das Plugin zu EmDash passt
Gute Kandidaten implementieren Verhalten, das unabhängig vom WordPress-Core ist, etwa Inhaltsvalidierung, externe API-Aufrufe, Hintergrundverarbeitung, benutzerdefinierte gespeicherte Datensätze, Einstellungen oder ein Admin-Tool.
Portieren Sie kein Plugin, dessen einziger Zweck darin besteht, eine WordPress-Funktion abzubilden, die Astro oder EmDash bereits ersetzt. Beispiele sind PHP-Seiten-Caching, WordPress-Rewrite-Regeln, Theme-Vorlagenauswahl oder Änderungen an WordPress-Core-Globals.
Definiert ein Plugin nur einen benutzerdefinierten Beitragstyp oder Felder, hat aber wenig Laufzeitverhalten, legen Sie stattdessen eine EmDash-Collection und Seed-Datei an.
Sandboxed oder Native wählen
Beginnen Sie mit Ein Plugin-Format wählen. Beide Formate teilen Hook-Namen und die PluginContext-APIs, aber ihre Quellpakete unterscheiden sich.
| Anforderung | Sandboxed | Native |
|---|---|---|
| Registry-Installation | Ja | Nein |
| Isolierte Laufzeit | Ja, mit konfiguriertem Runner | Nein |
| Hooks, Routen, KV, strukturierter Speicher | Ja | Ja |
| Block-Kit-Adminseiten | Ja | Ja |
| Benutzerdefinierte React-Admin-Komponenten | Nein | Ja |
| Astro-Komponenten für öffentliches Rendering | Nein | Ja |
| Rohe Seitenfragmente | Nein | Ja |
Wählen Sie Native nur, wenn der Port eine ausschließlich native Build-Time- oder Benutzeroberfläche benötigt.
Sandboxed-Paketformat
emdash-plugin init erzeugt das aktuelle Sandboxed-Format:
my-plugin/
├── emdash-plugin.jsonc
├── src/
│ └── plugin.ts
├── tests/
│ └── plugin.test.ts
├── package.json
└── tsconfig.json
Das Manifest enthält Identität, Publisher, Capabilities, erlaubte Hosts und Speicherdeklarationen. Die Version kommt normalerweise aus package.json.
Das folgende Manifest deklariert eine indizierte Speicher-Collection und die für content:afterSave erforderliche Capability:
{
"$schema": "./node_modules/@emdash-cms/plugin-cli/schemas/emdash-plugin.schema.json",
"slug": "read-time",
"publisher": "did:plc:abc123def456",
"license": "MIT",
"author": { "name": "Example Author" },
"security": { "email": "security@example.com" },
"capabilities": ["content:read"],
"allowedHosts": [],
"storage": {
"calculations": { "indexes": ["contentId", "updatedAt"] }
}
}
src/plugin.ts exportiert standardmäßig ein plain object mit dem Typ SandboxedPlugin. Sandboxed-Hook-Handler verwenden { handler }; Sandboxed-Routen-Handler erhalten (routeCtx, ctx):
import type { SandboxedPlugin } from "emdash/plugin";
export default {
hooks: {
"content:afterSave": {
handler: async (event, ctx) => {
await ctx.storage.calculations.put(event.content.id, {
contentId: event.content.id,
updatedAt: new Date().toISOString(),
});
},
},
},
routes: {
recent: {
handler: async (_routeCtx, ctx) => {
const result = await ctx.storage.calculations.query({
orderBy: { updatedAt: "desc" },
limit: 10,
});
return { items: result.items };
},
},
},
} satisfies SandboxedPlugin;
Die Route ist unter /_emdash/api/plugins/read-time/recent erreichbar. Ein Speicherfeld muss als Index deklariert sein, bevor eine Abfrage danach filtern oder sortieren kann.
Erstellen Sie das Paket mit emdash-plugin build; fügen Sie diesem Format keinen handgeschriebenen src/index.ts-Deskriptor hinzu. Lesen Sie Ihr erstes Sandboxed-Plugin für das generierte package.json, Build-Ausgabe und Site-Registrierung.
Native-Paketformat
Ein Native-Paket exportiert sowohl eine Deskriptor-Factory für astro.config.mjs als auch eine Laufzeit-Factory mit definePlugin(). Optionale Admin- und Astro-Einstiegspunkte sind separate Paketexporte.
my-native-plugin/
├── src/
│ ├── index.ts
│ ├── admin.tsx
│ └── astro/
│ └── index.ts
├── package.json
└── tsconfig.json
Der folgende reduzierte Native-Einstiegspunkt zeigt die beiden erforderlichen Teile:
import { definePlugin } from "emdash";
import type { PluginDescriptor } from "emdash";
export interface ReadTimeOptions {
wordsPerMinute?: number;
}
export function readTimePlugin(options: ReadTimeOptions = {}): PluginDescriptor {
return {
id: "read-time",
version: "0.1.0",
format: "native",
entrypoint: "@example/plugin-read-time",
capabilities: ["content:read"],
options,
};
}
export function createPlugin(options: ReadTimeOptions = {}) {
return definePlugin({
id: "read-time",
version: "0.1.0",
capabilities: ["content:read"],
admin: {
settingsSchema: {
wordsPerMinute: {
type: "number",
label: "Words per minute",
default: options.wordsPerMinute ?? 200,
min: 1,
},
},
},
hooks: {
"content:afterSave": async (event, ctx) => {
ctx.log.info("Content saved", { id: event.content.id });
},
},
});
}
export default createPlugin;
Native-Hook-Handler können direkt Funktionen sein. Native-Routen-Handler erhalten ein kombiniertes Kontextargument. Halten Sie die Deskriptor- und Laufzeitkopien von id, version, Capabilities und Entrypoints synchron.
Lesen Sie Ihr erstes Native-Plugin, bevor Sie React-Adminseiten, Portable-Text-Renderer oder Seitenfragmente hinzufügen.
WordPress-Verhalten abbilden
Hooks
Ordnen Sie die Absicht einer WordPress-Action oder eines Filters zu, nicht nur den Namen:
| WordPress | EmDash |
|---|---|
register_activation_hook() | plugin:install bei Erstinstallation oder plugin:activate bei Aktivierung |
register_uninstall_hook() | plugin:uninstall |
wp_insert_post_data | content:beforeSave |
save_post | content:afterSave |
before_delete_post | content:beforeDelete |
deleted_post | content:afterDelete |
wp_handle_upload_prefilter | media:beforeUpload |
add_attachment | media:afterUpload |
Hook-Events haben eigene typisierte Formen. Prüfen Sie die Hook-Referenz, bevor Sie WordPress-Callback-Argumente übersetzen.
Content-Hooks, die Eintragsdaten erhalten, erfordern content:read. Fügen Sie Capabilities entsprechend der APIs hinzu, die der Port aufruft:
| Capability | Verfüg gemachte API |
|---|---|
content:read | Inhalte lesen und Content-Hooks registrieren, die Eintragsdaten bereitstellen |
content:write | Inhalte erstellen, aktualisieren, veröffentlichen oder löschen; impliziert auch Lesezugriff |
media:read | Mediadatensätze lesen |
media:write | Medien erstellen oder aktualisieren; impliziert auch Lesezugriff |
network:request | ctx.http für in allowedHosts gelistete Hosts verwenden |
Optionen und benutzerdefinierte Tabellen
Verwenden Sie ctx.settings für Benutzerkonfiguration und ctx.kv für kleine interne Werte. Beide Stores sind pro Plugin isoliert. Deklarieren Sie Zugangsdaten als secret-Felder in admin.settingsSchema, damit EmDash sie verschlüsselt.
Verwenden Sie deklarierte ctx.storage.<collection>-Collections für abfragbare Plugin-Datensätze. Die Speicherdeklaration gehört in emdash-plugin.jsonc für Sandboxed und in definePlugin() für Native. Öffnen Sie nicht die EmDash-Datenbank und interpolieren Sie kein SQL aus Plugin-Code.
Der folgende Vergleich portiert einen Optionswert, ohne WordPress-Globals dem neuen Plugin preiszugeben:
WordPress
$api_key = get_option('read_time_api_key', '');
update_option('read_time_api_key', $new_api_key); EmDash
import type { PluginContext } from "emdash/plugin";
export async function saveApiKey(ctx: PluginContext, newApiKey: string) {
await ctx.settings.set("apiKey", newApiKey);
}
export async function readApiKey(ctx: PluginContext) {
return await ctx.settings.get<string>("apiKey") ?? "";
} Identifizieren Sie bei einer WordPress-Custom-Tabelle die Felder für Filter und Sortierung, bevor Sie Speicher deklarieren. Das folgende Sandboxed-Manifest-Fragment indiziert beide von der Abfrage verwendeten Felder:
"storage": {
"jobs": { "indexes": ["status", "createdAt"] }
}
Die Laufzeit kann dann Job-Datensätze speichern und abfragen:
await ctx.storage.jobs.put("job-123", {
status: "pending",
createdAt: new Date().toISOString(),
});
const pending = await ctx.storage.jobs.query({
where: { status: "pending" },
orderBy: { createdAt: "asc" },
limit: 50,
});
Deklarieren Sie sowohl status als auch createdAt als Indizes im Manifest oder in der Native-Speicherdefinition, bevor Sie diese Abfrage ausführen.
REST-Endpunkte
Ordnen Sie eine WordPress-REST-Route einer Plugin-Route zu. EmDash mountet sie unter /_emdash/api/plugins/<plugin-id>/<route-name>. Definieren Sie ein inputSchema, wenn die Route Eingaben akzeptiert, und geben Sie JSON-serialisierbare Daten zurück.
Einstellungen und Adminseiten
Sandboxed-Plugins beschreiben Adminseiten mit Block Kit und lesen oder schreiben Werte über Routen und KV. Sie liefern kein React in die Admin-Anwendung.
Native-Plugins können admin.settingsSchema für ein generiertes Formular verwenden. Nutzen Sie einen adminEntry-Paketexport für benutzerdefinierte React-Seiten, Widgets, Feld-Widgets oder Listenspalten.
Dateien und Medien
Verwenden Sie die Medien-APIs für hochgeladene oder generierte Dateien. Sandboxed-Plugins haben keinen Dateisystemzugriff. Native-Plugins teilen den Host-Prozess, aber das Schreiben deployment-lokaler Dateien ist keine portable Speicherstrategie.
Plugin portieren
-
Erfassen Sie WordPress-Hooks, Optionen, Custom-Tabellen, Cron-Jobs, REST-Routen, Adminseiten, Blöcke, Shortcodes und externe Hosts.
-
Entfernen Sie Verhalten, das zu Astro-Routing, dem EmDash-Inhaltsmodell oder der Deployment-Plattform gehört.
-
Wählen Sie das Sandboxed- oder Native-Paketformat. Notieren Sie jede Capability und jeden erlaubten Host, den das verbleibende Verhalten benötigt.
-
Definieren Sie KV-Schlüssel und strukturierte Speicher-Collections. Fügen Sie Indizes für jedes Feld hinzu, das in
whereoderorderByverwendet wird. -
Portieren Sie ein beobachtbares Verhalten nach dem anderen. Testen Sie Hook oder Route mit repräsentativen Inhalten und Fehlerfällen.
-
Fügen Sie Block Kit oder Native-Admin-UI erst hinzu, wenn die zugrunde liegenden Routen und Speicher funktionieren.
-
Testen Sie Installation, Upgrade, Aktivierung, Deaktivierung, Deinstallation mit und ohne Datenlöschung sowie Capability-Änderungen.
Nächste Schritte
- Sandboxed-Plugin-Manifest für Vertrauensvertrag und Paketmetadaten.
- Capabilities für Zugriff auf Inhalte, Medien, Netzwerk-Hosts und Hooks.
- Storage für KV und indizierte Collections.
- React-Adminseiten für ausschließlich native UI.