Am 28. September 2026 wurde EmDash 1.0, das native Astro CMS von Cloudflare, offiziell freigegeben. Danach folgten am 2. Oktober die Version 1.1 und am 7. Oktober die Version 1.2 – in weniger als zwei Wochen gab es drei Aktualisierungen hintereinander.
Wie wir unser internes Blog "ゆるろぐ" von 0.22 auf 1.2 aktualisiert haben, habe ich hier dokumentiert. Diesmal möchte ich die größte Veränderung in 1.0 – die Plugin-Architektur – genauer beleuchten.
Für einen Überblick über EmDash als CMS schau dir den ersten Artikel an.
Kurz gesagt ist es WordPress, das mit Astro und Cloudflare neu gebaut wurde.
Wie haben sich Plugins in 1.0 verändert?
Plugins der 0.x-Version waren vereinfacht gesagt "npm-Pakete, die in der Astro-Konfigurationsdatei geschrieben wurden". Da sie im selben Prozess wie die Website liefen, hatten Plugins die gleichen Berechtigungen wie der Website-Code und konnten wie WordPress auf die Datenbank und Umgebungsvariablen zugreifen.
In 1.0 wurden hier Sandbox und verteilte Registrierung hinzugefügt.
Das ist eine sehr bedeutende Verbesserung.
- Plugins laufen standardmäßig in einer isolierten Umgebung und können nicht auf Website-Inhalte, Medien, Benutzer, Geheimnisse oder das Netzwerk zugreifen.
- Erforderliche Berechtigungen (Capabilities) werden vom Plugin deklariert und vom Site-Administrator bei der Installation genehmigt.
- Die genehmigten Berechtigungen sind nicht nur dokumentiert, sondern werden von der Runtime auch tatsächlich erzwungen.
- Plugins können mit einem Klick aus dem Admin-Dashboard installiert werden. Die Verteilungsquelle ist eine dezentralisierte Registry auf dem AT Protocol.
Im Blog von Cloudflare wird dies im Kontrast zu Wordpresss Modell erklärt, bei dem "alle Plugins denselben Prozess teilen".
Je mehr man über WordPress-Plugin-verursachte Incidents weiß, desto treffender ist diese Erklärung.
Plugins haben zwei verschiedene "Ausführungsmodi".
Das Wichtigste zu verstehen ist, dass derselbe Plugin-Code zwei verschiedene Ausführungsmodi hat: Trusted (vertraut) und Sandboxed (isoliert).
Trusted | Sandboxed | |
|---|---|---|
Registrierungsort |
|
|
Ausführungsumgebung | Derselbe Prozess wie die Website | Isolierter V8 Isolate |
Capability | Nur Deklaration (nicht erzwungen) | Runtime erzwingt es |
Netzwerk | Keine Einschränkung | Blockiert. Nur Hosts, die in |
Datenzugriff | Vollständiger Datenbankzugriff | Nur eigener Storage / KV und genehmigte Bereiche |
Node.js API | Verfügbar | Nicht verfügbar (nur Web API) |
Ressourcenlimit | Keine | CPU 50ms, Unteranfragen 10, 30 Sekunden, ca. 128MB |
Geeignete Anwendungsfälle | Eigener Code, überprüfte npm-Pakete | Drittanbieter, Registry-Verteilung |
Isolation bedeutet, dass für jedes Plugin eine dedizierte kleine Ausführungsumgebung bereitgestellt wird, die separat vom eigentlichen Standort läuft. Auf Cloudflare Workers werden Plugin-Worker
Dynamic Workers genannt und bei jeder Anfrage gestartet. Ab Version 1.0 wird auch bei der Ausführung auf Node.js das gleiche Laufzeitsystem (workerd) als separater Prozess gestartet und mit dem Plugin beladen, sodass
Sandbox nicht mehr auf Cloudflare beschränkt ist.
Das Wichtigste ist, dass der Plugin-Code in beiden Modi identisch ist, daher ist es üblich, lokal mit Trusted zu entwickeln und in der Produktion in die Sandbox zu verschieben.
Plugin-Struktur: Dateien zur Build-Zeit und Laufzeit trennen
EmDash-Plugins sind in zwei Teile unterteilt, die zu unterschiedlichen Zeitpunkten ausgeführt werden.
- Descriptor (Beschreiber): Metadaten wie
id,version, erforderliche Capabilities und Storage-Definitionen, die ausastro.config.mjsimportiert werden und daher zur Build-Zeit in Vite ausgeführt werden. Darf keine Nebenwirkungen haben. - Definition (定義): Der Inhalt von Hooks und Routes. Wird auf dem Server bei Anfragen ausgeführt
my-plugin/
├── src/
│ ├── index.ts # descriptor。astro.config.mjs から import される
│ └── sandbox-entry.ts # definePlugin({ hooks, routes })。実行時にロードされる
└── package.json # "." と "./sandbox" を exports で分ける
// src/index.ts
import type { PluginDescriptor } from "emdash";
export function myPlugin(): PluginDescriptor {
return {
id: "my-plugin",
version: "1.0.0",
format: "standard",
entrypoint: "@my-org/my-plugin/sandbox",
capabilities: ["content:read", "network:request"],
allowedHosts: ["api.example.com"],
options: {},
};
}
// src/sandbox-entry.ts
import { definePlugin, type PluginContext } from "emdash";
export default definePlugin({
hooks: {
"content:afterSave": async (event, ctx: PluginContext) => {
ctx.log.info(`saved ${event.collection}/${event.content.id}`);
},
},
});
Auf der Descriptor-Seite wird der Moduldpfad der Definition mit entrypoint geschrieben, und EmDash lädt ihn zur Laufzeit. Es ist verlockend, alles in einer Datei zusammenzufassen, aber da Vites Build-Prozess Laufzeit-Code einschließt, ist es richtig, sie zu trennen.
Standard und Native
Es gibt noch eine weitere Achse: Plugins haben zwei Formate, Standard und Native.
- Standard: Mit der oben beschriebenen Struktur. Die Admin-Oberfläche wird mit Block Kit (JSON) beschrieben. Funktioniert sowohl in Trusted als auch in Sandboxed und kann in der Registry veröffentlicht werden.
- Native: Kann eine mit React entwickelte Admin-Oberfläche, benutzerdefinierte Blöcke des Portable Text (Text-Editors) und Astro-Komponenten zum Rendern auf der Website enthalten. Im Gegensatz dazu funktioniert es nur im Trusted-Modus und kann nicht in der Registry veröffentlicht werden.
Die grundlegende Regel ist, Standard zu verwenden und nur dann auf Native zu wechseln, wenn Sie "benutzerdefinierte Blöcke zum Editor hinzufügen" oder "Komponenten in die Site-HTML einfügen" möchten.
Yuru Log verwendet Native-Plugins, um benutzerdefinierte Amazon-Produktkarten und Video-Einbettungen zu erstellen. Beide fügen dem Editor einen speziellen Block hinzu und rendern ihn mit einer Astro-Komponente. Dies ist im Sandbox-Modus aus grundlegenden Gründen unmöglich (Plugins, die zur Laufzeit installiert werden, können keine Astro-Komponenten mitbringen, die zur Build-Zeit erforderlich sind).
Capability: Das Vokabular der Berechtigung
Der Kern der Sandbox ist Capability. Ein Plugin deklariert, "worauf es zugreifen möchte" mit einer Zeichenkette, und der Administrator bestätigt und genehmigt dies in einem Dialog beim Installieren.
In Version 1.x wurde das Vokabular **in die Form : organisiert**. Hier sind einige häufig verwendete Beispiele.
Capability | Möglichkeiten |
|---|---|
| Artikel lesen / erstellen, aktualisieren und löschen |
| Veröffentlichen, verbergen, zeitlich planen |
| Medien lesen / hochladen und löschen |
| HTTP-Anfragen nach außen. Nur Hosts, die in |
| Ungespeicherte Inhalte während der Bearbeitung lesen / Änderungen vorschlagen |
| Inhalte überprüfen und Veröffentlichung blockieren |
Daneben gibt es noch Taxonomien, Kommentare, Weiterleitungen, Benutzer, E-Mail-Versand und weitere – insgesamt 27 verschiedene Arten.
storage und KV erfordern keine Deklaration und sind immer verfügbar. Sie sind pro Plugin ausgegrenzt, sodass Daten anderer Plugins nicht sichtbar sind.
Die Namen aus Version 0.x (read:content, network:fetch etc.) sind veraltet, funktionieren aber weiterhin als Aliase. Die Runtime normalisiert sie auf die aktuellen Namen, sodass bestehende Plugins unverändert funktionieren, und beim Bundling wird eine Warnung ausgegeben.
Hooks: Worauf können sie reagieren?
In der Runtime von Version 1.2.0 sind 30 verschiedene Hooks definiert. Sie umfassen das Speichern, Veröffentlichen, Planen und Löschen von Inhalten, das Hochladen von Medien, das Versenden von E-Mails, das Posten und Genehmigen von Kommentaren, die Installation und Aktivierung von Plugins sowie periodische Ausführungen über Cron. Im Vergleich zu 0.x ist das eine erhebliche Steigerung. Besonders bedeutsam ist, dass vor und nach der Veröffentlichung ((content:beforePublish / afterPublish)) nun separate Hooks vorhanden sind.
Durch throw in content:beforeSave können Sie den Speicherprozess abbrechen, daher werden Validierungs-Plugins hier geschrieben. Wenn Sie die Veröffentlichung selbst verhindern möchten, deklarieren Sie hooks.content-policy:register und verwenden den Hook vor der Veröffentlichung.
Ein wichtiges Detail der Version 1.2 ist, dass bei der ersten Ausführung nach dem Upgrade bestehende Plugins plugin:install und plugin:activate erneut ausgeführt werden. Plugins, die im install-Hook Initialdaten einspielen, müssen so geschrieben sein, dass sie nicht beschädigt werden, falls die Daten doppelt eingespielt werden.
Block Kit: Verwaltungsoberfläche ohne JavaScript
In der Sandbox können Sie das JavaScript des Plug-ins nicht im Browser ausführen. Daher wird die Admin-Schnittstelle in Block Kit, einem JSON-Format, beschrieben. Das Plug-in gibt ein Array von Blöcken wie „Header, Tabelle, Formular, Toggle" zurück, und EmDash rendert diese.
So funktioniert es: Wenn ein Benutzer die Plugin-Seite öffnet, sendet EmDash eine Anfrage an das Plugin mit der Nachricht „Seite geöffnet", und das Plugin gibt ein Array von Blöcken zurück. Wenn der Benutzer das Formular absendet, kommt eine neue Anfrage mit Eingabewerten an, das Plugin speichert diese und gibt ein neues Array von Blöcken zurück. Da die UI nur durch Request-Response funktioniert, ist dies ein Design, das sehr gut mit den Einschränkungen einer Sandbox kompatibel ist.
Neben den Admin-Seiten und Dashboard-Widgets können in 1.x auch die Seitenpanels neben dem Editor (editorPanels) mit Block Kit erstellt werden. Dies ist für Anwendungsfälle gedacht, bei denen artikelspezifische Status und Aktionen im Bearbeitungsbildschirm angezeigt werden. Beispiele sind die Anzeige des Übersetzungsstatus, SEO- und Textüberprüfung, Überprüfung auf fehlerhafte Links, Generierung von Auszügen aus dem Haupttext – alle Funktionen für den „aktuell bearbeiteten Artikel" passen hier hinein.
Verteilte Registry: Design ohne zentralen Marketplace
Das System zur Verteilung von Plugins hat sich ebenfalls in Version 1.0 geändert. Aus Sicht des Benutzers ist nur sichtbar, dass man Plugins über die Verwaltungsoberfläche suchen und mit einem Klick installieren kann, aber im Hintergrund ist alles auf das AT Protocol aufgebaut – der Grundlage von Bluesky – und hat eine etwas ungewöhnliche Struktur.
- Die Plugin-Informationen befinden sich im Konto des Autors. Der Autor meldet sich über ein Bluesky-Konto an, signiert die Informationen zu Paketen und Releases und speichert sie in seinem Kontodatenbereich. Sie werden nicht auf den Servern des Registry-Betreibers hinterlegt.
- Die Cloudflare-Registry ist nur "ein Katalog unter vielen".
plugins.emdashcms.comsammelt lediglich die Informationen, die von verschiedenen Autoren veröffentlicht wurden, um sie durchsuchbar zu machen. Jeder kann denselben Informationen sammeln und einen anderen Katalog erstellen. - Der Inhalt wird bei der Installation überprüft. EmDash vergewissert sich, dass das heruntergeladene Plugin "denselben Inhalt hat wie das vom Autor signierte und veröffentlichte Plugin". Der Hash der Datei, der Paketname und die Version sowie die angeforderten Capabilities werden mit den signierten Informationen verglichen. Falls das Plugin von GitHub Actions erstellt wurde, wird auch der Build-Datensatz überprüft. Plugins, die vom Katalog ersetzt oder manipuliert wurden, werden hier aussortiert.
- Moderation bedeutet nur "Ausblendung". Der Cloudflare-Katalog überprüft Beschreibungen, Links und Bilder mit einem KI-Modell (Clef). Bei Problemen werden diese nicht im Katalog angezeigt. Der Katalog-Betreiber kann jedoch weder den Plugin-Inhalt ändern noch den Autor seiner Eigentumsrechte berauben.
Mit anderen Worten: "Wenn der Registry-Betreiber verschwindet, kann die Verteilung weitergehen, solange die Informationen im Konto des Autors bleiben." "Der Betreiber kann den Katalog ausblenden, aber nicht manipulieren."
Das Besondere ist, dass es so konzipiert ist, dass es keinen zentralen Marketplace wie npm oder WordPress.org gibt.
Derzeit gibt es nur kostenlose Plugins. Für kostenpflichtige Plugins wird das Team das gleiche dezentrale Modell verfolgen, je nachdem wie sich AT Protocol entwickelt.
Die Übersetzungsmechanik lässt sich als Plugin umsetzen.
Bei Yurulog verwenden wir einen Mechanismus, um mit LLM englische Entwürfe aus japanischen Artikeln zu erstellen (wie man ein mehrsprachiges Blog mit EmDash erstellt). Derzeit liest ein Node-Skript das EmDash-API-Token und den Übersetzungs-API-Schlüssel aus .env, ruft japanische Artikel ab, übersetzt sie und erstellt englische Entwürfe mit translationOf. Das ist der grundsätzliche Ablauf.
Wenn man dies auf die 1.x-Plugin-Spezifikation anwendet, wird daraus fast direkt ein Standard-Plugin.
- Auslöser:
content:afterPublish-Hook. Wird ausgelöst, wenn ein japanischer Artikel veröffentlicht wird. - Artikel lesen und schreiben:
content:readundcontent:write. Übersetzungsergebnisse werden als Entwürfe erstellt - Aufrufe der Übersetzungs-API:
network:requestundallowedHostsmit einem Host der API - API-Schlüssel:
settingsSchema-Feldsecret. Eingeben über das Admin-Panel und verschlüsselt speichern..envist nicht mehr erforderlich, und auch die API-Token-Generierung entfällt. - Übersetzungsstatus: Speichern in storage, welche Revision des Originalartikels übersetzt wurde, und Anzeige im
editorPanelsder Artikelbearbeitungsseite von "Englische Version verfügbar / Originalartikel wurde aktualisiert, daher ist eine Neuübersetzung erforderlich"
Skripte, die bisher manuell ausgeführt wurden, funktionieren nun automatisch, wenn ein Administrator die Capability genehmigt. Darüber hinaus garantiert die Runtime, dass der Code zu keinem anderen Host als der Übersetzungs-API Zugriff hat. Das Beste an diesem System ist meiner Meinung nach, dass sich das Problem „Wo sollte ich Code mit API-Schlüsseln platzieren?" strukturell von selbst erledigt.
Ein wichtiger Punkt beim Design: Jedes Mal, wenn das Sandbox-Plugin den Host um etwas bittet (Einstellungen lesen, Artikel abrufen, in den Storage schreiben), wird dies als ein Subrequst gezählt, und das Standardlimit liegt bei 10. Für die Übersetzung eines einzelnen Artikels sollte das ausreichen, aber bei Vorgängen wie „mehrere unübersetzte Artikel auf einmal" stoßen Sie schnell an die Grenze, wenn Sie ctx.content.get() in einer Schleife aufrufen. Sie werden wahrscheinlich ein Design benötigen, das die Verarbeitung schrittweise über Cron-Hooks durchführt.
Außerdem können UI-Elemente wie der Sprachwechsel nicht aus der Sandbox heraus bereitgestellt werden, daher müssen diese im Theme selbst implementiert werden.
Fazit
- EmDash 1.0-Plugins folgen einem Sandbox-Modell. Sie deklarieren Funktionen, Administratoren genehmigen diese, und die Laufzeitumgebung setzt sie durch.
- Derselbe Code funktioniert sowohl in Trusted als auch in Sandboxed. Grundsätzlich: Entwicklung in Trusted, Produktion in Sandbox.
- Wenn Sie benutzerdefinierte Editor-Blöcke oder Astro-Komponenten benötigen, ist Native erforderlich (nur für Trusted-Inhaber)
- descriptor (zur Build-Zeit) und definition (zur Laufzeit) müssen sich immer in separaten Dateien befinden
- Der Name einer Capability wurde in 1.x in das Format
content:readgeändert. Der alte Name funktioniert als Alias, ist aber veraltet - Die Registry ist dezentralisiert auf AT Protocol. Kataloge können versteckt, aber nicht manipuliert werden
- In der Sandbox gilt: Ein Host-Aufruf = ein Teil-Request mit einem Limit von 10. Gestalten Sie Ihre Implementierung mit Bedacht für diese Einschränkung
Bei WordPress akzeptieren Sie mit jeder Plugin-Installation "Dieses Plugin hat vollständige Kontrolle über Ihre Website".
Ein einziges anfälliges Plugin gefährdet die gesamte Website.
EmDash 1.0 zeigt bei Sandbox-Plugins die verfügbaren Berechtigungen während der Installation an und die Laufzeit erlaubt nur die genehmigten Funktionen.
Für WordPress-Benutzer auf der Suche nach einer Alternative ist die Plugin-Sicherheit einer der Gründe, sich für EmDash zu entscheiden.
Es gibt noch nicht viele Plugins, und wir sind noch weit entfernt vom WordPress-Status "if you search, you'll find something", aber wir freuen uns auf ein Wachstum des Plugin-Ökosystems.
Da wir bereits Betriebserfahrung mit unserer eigenen Website gesammelt haben, planen wir, EmDash in unser Serviceangebot aufzunehmen.
Von DTP in die Web-Welt – und dann Markup, Frontend, Projektleitung und Accessibility alles gemeistert: ein "Technik-Weise". Seit den Anfangstagen von Liberogic vielseitig tätig und mittlerweile eine lebende Wissensquelle im Unternehmen. Derzeit fasziniert von der Frage "Können wir Accessibility-Umsetzung noch stärker mit KI unterstützen?" und erforscht Optimierungsmöglichkeiten durch gezieltes Prompt-Engineering. Technisch wie gedanklich immer noch in Entwicklung.
Ayumi Futamata
IAAP-zertifizierter Web Accessibility Specialist (WAS) / Markup Engineer / Frontend Engineer / Web Director