Ein erster Blick auf Wordgard, einen neuen Texteditor
Wordgard ist eine neue Rich-Text-Editor-Bibliothek von Marijn Haverbeke mit Einzeländerungen, Korrekturen, Facetten und eigener Auswahlsteuerung.
Wordgard ist eine JavaScript-Bibliothek von Marijn Haverbeke, dem Autor von ProseMirror und CodeMirror, zum Erstellen von Rich-Text-Editoren, deren Dokumente einem Schema entsprechen; sie bringt eine Editor-UI-Komponente mit, ist aber kein generischer, formfreier WYSIWYG- oder HTML-Editor.
Die Pflege einer ProseMirror-Integration kann bedeuten, Positionen durch eine Liste von Steps zu mappen oder ein „generisches“ Command zu schreiben, das bei jedem Schritt Content Expressions prüfen muss. Wordgard ist die Antwort desselben Autors auf diese Beschwerden – von Grund auf neu gebaut, statt an ProseMirror angeflanscht.
Dieser Artikel behandelt, was die Bibliothek verändert: das Änderungsmodell, den Verzicht auf Content-Beschränkungen, das Facet-basierte Erweiterungssystem und die bibliotheksseitige Selektion – und außerdem, wie sich ein erstes Release dieses Autors neben ProseMirror, TipTap und Lexical einordnet.
Die wichtigsten Erkenntnisse
- Wordgard wurde erstmals am 2. Juli 2026 als Version 0.1.0 unter der MIT-Lizenz veröffentlicht und lässt sich über npm als
wordgardinstallieren; der Autor erklärte zum Release, dass das Projekt wahrscheinlich mindestens ein Jahr lang bei 0.x-Versionen bleiben wird. - Eine Wordgard-Transaktion trägt genau eine Änderung, die aus Abschnitten aufgebaut ist, die einen Token-Bereich beibehalten, ersetzen oder Marks darauf hinzufügen bzw. entfernen. Der betroffene Bereich kann daher direkt gelesen werden, statt ihn aus einer Step-Liste zu rekonstruieren.
- Wordgard-Schemas können einschränken, welche Node-Typen ein Parent enthalten darf, nicht aber deren Reihenfolge; Corrections – Observer-Funktionen, die Korrektur-Change-Specs zurückgeben – übernehmen Invarianten wie rechteckige Tabellen.
- Die Konfiguration ist ein Baum aus Erweiterungen mit Präzedenz pro Wert und benutzerdefinierbaren Facets, übernommen von CodeMirror 6.
- Wordgard verarbeitet Tastatur- und Pointer-Selektion in der Bibliothek und zeichnet seinen eigenen Cursor; die Touch-Selektion bleibt dem Browser überlassen.
Was ist Wordgard?
Wordgard ist ein Rich-Text-Editor-System für Inhalte, die einem bestimmten Schema entsprechen – keine sofort einsetzbare WYSIWYG-Komponente und keine Anwendung. Laut dem System Guide soll sich die Bearbeitungsoberfläche wie WYSIWYG anfühlen, aber Inhalte und Bearbeitungsaktionen werden nach ihrer Bedeutung benannt (Überschriften, Listen, Hervorhebung) und nicht nach ihrem Aussehen (Schriftfamilie, Absatzeinzug, Fettschrift). Der prominenteste Export der Bibliothek ist die UI-Klasse Wordgard. Darunter liegen die Typen für Dokumente, Editor-State und Bearbeitungsaktionen, und die meisten davon funktionieren ganz ohne Browser.
Die 0.1-Ankündigung vom 2. Juli 2026 nennt die MIT-Lizenz, den npm-Paketnamen wordgard und dass der Quellcode auf der Forgejo-Instanz des Autors liegt. Die Projekt-Homepage bestätigt die Lizenz und ergänzt, dass Bug-Reports willkommen sind, Pull Requests aber nicht angenommen werden. Die Homepage listet außerdem schemabasierte Dokumente, modulare Erweiterungen, bidirektionalen Text, strukturierte Inhalte wie Tabellen und verschachtelte Listen sowie kollaboratives Editieren als Features auf; diese Punkte sind als Eigenaussagen des Projekts zu verstehen.
Wie richtet man einen Wordgard-Editor ein?
Ein minimaler Wordgard-Editor ist ein Aufruf von Wordgard.create mit einem Dokument, einer Konfiguration und einem Parent-Element. Dies ist das Setup-Beispiel aus dem Guide:
import {Wordgard, menuBar} from "wordgard/editor"
import {fullSchema} from "wordgard/schema"
import {history} from "wordgard/history"
let editor = Wordgard.create({
doc: `<p>Starting content</p>`,
config: [
fullSchema(), // A predefined document schema
history(), // Enable the undo history
menuBar() // Show a menu
],
parent: document.body
})
Das config-Array ist der Erweiterungsbaum, und jeder der drei Einträge ist ein Bündel von Erweiterungen und nicht ein Schema-Objekt, ein Plugin und ein Widget. fullSchema() zieht den gesamten Satz an Schema-Elementen aus wordgard/schema herein, und die zugehörige Dokumentation warnt, dass dieser Satz weitere Elemente aufnehmen kann, sobald die Bibliothek um Funktionen wächst; die späteren Beispiele im Guide verwenden basicSchema(), das ein Block-Dokument, Absätze, Überschriften, Zeilenumbrüche sowie die Marks Strong, Emphasis und Link bündelt. Der doc-String wird als HTML gegen dieses Schema geparst. Das Paket ist in Module wie wordgard/doc, wordgard/state, wordgard/editor, wordgard/command, wordgard/history, wordgard/schema und wordgard/types aufgeteilt, und der Guide empfiehlt TypeScript, weil die Teile so eng ineinandergreifen.
Wie unterscheidet sich das Änderungsmodell von Wordgard von dem von ProseMirror?
In Wordgard trägt eine Transaktion genau ein Change-Objekt, das aus Abschnitten aufgebaut ist, die einen Dokumentbereich beibehalten, ersetzen oder Marks darauf hinzufügen bzw. entfernen. Der von einer Bearbeitung betroffene Bereich kann daher direkt gelesen werden, statt aus einer Liste von Steps rekonstruiert zu werden. In ProseMirror ist eine Transaktion eine geordnete Liste atomarer Steps, von denen jeder auf dem Dokument operiert, das der vorherige erzeugt hat – was Positionsarithmetik und Bereichsprüfung dazu zwingt, die gesamte Kette abzulaufen.
Die Begründung in der Ankündigung lautet, dass das Delta-Format von CodeMirror, selbst abgeleitet von ShareJS, sowohl einfacher als auch leistungsfähiger ist. Eine Änderung ist eine flache Sequenz über das alte Dokument. Nehmen wir ein Dokument mit zehn Tokens: Das Einfügen eines Tokens an Position 4 ergibt „keep 4, replace 0 with the token, keep 6“, und die Positionen 3 bis 6 fett zu setzen ergibt „keep 3, update 3 adding the mark, keep 4“. Der Mark-Update-Abschnitt ist Wordgards Erweiterung des CodeMirror-Modells.
Das funktioniert auf einem Baum, weil Positionen in Tokens gezählt werden. Im Index-System des Guides erhöht jedes Plot-Open, jedes Plot-Close, jedes Non-Text-Leaf und jedes UTF-16-Zeichen die Position um eins, Position 0 liegt direkt vor dem ersten Kind, und die Open- und Close-Tokens des Dokument-Nodes selbst werden nicht gezählt. Dadurch kann eine Änderung neue Token-Sequenzen in das Dokument spleißen, als wäre es flach, wobei der Code zur Change-Erzeugung die Aufgabe übernimmt, zu prüfen, ob das Ergebnis noch ein wohlgeformter Baum ist.
Wenn mehrere Änderungen gemeinsam an ChangeSet.create übergeben werden, wird jede Position gegen das Originaldokument interpretiert, und die Bibliothek verschiebt sie automatisch. Eine Mark-Änderung berührt den Inhalt überhaupt nicht:
let makeStrong = ChangeSet.create(doc, {
from: 1, to: 5,
add: Strong
})
Dieselben Objekte unterstützen das Transformieren von Änderungen gegeneinander – die Grundlage für die Undo-History und das kollaborative Editieren.
Was ersetzt ProseMirrors Content Expressions?
Wordgard-Schemas können einschränken, welche Node-Typen ein Parent enthalten darf und ob ein Block-Plot leer sein darf – nicht aber die Reihenfolge, in der Kinder auftreten; für ProseMirrors reguläre-Ausdruck-artige Content Expressions gibt es kein Äquivalent. Die Ankündigung nennt zwei Gründe: Generischer Code zur Dokumentmanipulation lässt sich nicht gegen beliebige Reihenfolgebedingungen schreiben, ohne jede Operation zu prüfen, und harte Beschränkungen blockieren die unordentlichen Zwischenzustände, durch die echtes Editieren hindurchgeht.
Regeln, die das Schema nicht ausdrücken kann, werden über Corrections behandelt. Eine Correction ist ein Watcher, der an eine Node-Query gebunden ist; sie läuft, wann immer ein passender Node sich ändert oder auftaucht, und kann eine Change-Spec zurückgeben, die die Bibliothek der Transaktion hinzufügt. Weil eine Correction Code ist, kann sie berücksichtigen, was der Nutzer gerade mitten im Tun ist, statt die Struktur mechanisch abzulehnen. Das Beispiel im Guide verwendet Correction.onChildList(Doc, ...), um eine Überschrift der Ebene 1 einzufügen, wenn das Dokument nicht mit einer solchen beginnt; die Ankündigung nennt rechteckige Tabellen als den Fall, den ProseMirrors Expressions niemals ausdrücken konnten.
Warum verwendet Wordgard Facets anstelle von Plugins?
Wordgard ersetzt ProseMirrors Plugin als Einheit von Konfiguration und Präzedenz durch einen Baum feingranularer Erweiterungswerte, von denen jeder seine eigene Präzedenz tragen kann. Die Kritik in der Ankündigung ist präzise: Ein ProseMirror-Plugin bündelt mehrere Hooks unter einer einzigen Präzedenzposition, sodass ein Plugin, das für einen Hook hohe und für einen anderen niedrige Priorität benötigt, nicht beides bekommen kann.
Im Konfigurationsabschnitt des Guides ist eine Erweiterung eines von drei Dingen: ein Wert eines der eingebauten Erweiterungstypen der Bibliothek, ein beliebiges Objekt, das in seinem Feld extension eine Erweiterung trägt, oder ein Array, das weitere davon enthält. Explizite Präzedenz kommt von den Funktionen in GardState.prec; innerhalb einer Ebene entscheidet die Baumreihenfolge. Facets sind typisierte Erweiterungspunkte, die beliebiger Code definieren kann, mit einer optionalen combine-Funktion, um Eingaben auf eine Ausgabe zu reduzieren, und Compartments erlauben es, Teile einer Konfiguration auszutauschen, ohne State zu verwerfen. Das Wort „Plugin“ ist nicht verschwunden: Wordgard.Plugin.define gibt es weiterhin – für Objekte, die eigenen State halten und nahe am DOM sitzen müssen; auf diese Weise sind die mitgelieferten Tooltips und Panels gebaut.
Von der Bibliothek gezeichnete Selektion
Wordgard verarbeitet Tastatur- und Pointer-Selektion in der Bibliothek und versteckt den nativen Caret, um seinen eigenen Cursor zu zeichnen, während das native Selektions-Highlight selbst sichtbar bleibt. Die Ankündigung führt dies auf unzuverlässiges Browserverhalten zurück: ein Cursor, der sich nicht über bestimmte Inhalte hinausbewegen lässt, der an der falschen Stelle landet oder überhaupt nicht gezeichnet wird, sowie Mausziehselektion, die fehlschlägt. Deshalb baut die Bibliothek ihr eigenes Bild davon auf, wie Inhalte angeordnet sind, erledigt die Behandlung bidirektionalen Texts selbst und platziert den Cursor eigenständig. Das DOM-Beispiel im Guide zeigt ein dediziertes Cursor-Layer-Element, das den Inhalt überlagert, und das Migrationsdokument erklärt, dass das native Highlight bleibt, weil es weniger Probleme verursacht, es unangetastet zu lassen.
Zum Zeitpunkt der 0.1-Ankündigung ist die Touch-Selektion die eine Ausnahme und bleibt nativ, weil eine Neuimplementierung das Kontextmenü der Plattform kaputt macht. Diese Grenze hat sich inzwischen verschoben: Das Changelog vermerkt in 0.5.0 Touch-Selektion für Positionen, die die native Selektion nicht erreichen kann, und in 0.5.1 eine zusätzliche Cursor-Position an den Rändern von Inline-Plots, wodurch die Touch-Ziehselektion einen Haltepunkt bekommt. Die Ankündigung stellt außerdem die Eingabeverarbeitung als vorläufig dar: Wordgard behandelt beforeinput für alles außer Composition und verzichtet auf ProseMirrors DOM-Mutation-Parsing – vorbehaltlich Tests in der Praxis. Eine Browser-Support-Matrix wird nicht veröffentlicht.
Wordgard neben ProseMirror, TipTap und Lexical
| ProseMirror | TipTap | Lexical | Wordgard | |
|---|---|---|---|---|
| Änderungsmodell | Geordnete Steps | Erbt von ProseMirror | Eigenes Modell | Einzelne abschnittsbasierte Änderung |
| Inhaltsstruktur | Regex-Content-Expressions | Erbt von ProseMirror | Eigenes Modell | Mengen erlaubter Kindtypen plus Corrections |
| Konfiguration | Plugins | Extensions über ProseMirror-Plugins | Eigenes Modell | Facet-Erweiterungen mit Präzedenz pro Wert |
| Selektion | Browser-nativ | Browser-nativ | Eigenes Modell | Von der Bibliothek gezeichneter Cursor, native Touch-Selektion |
TipTap ist eine Framework-Schicht über ProseMirror und erbt dessen Kernmodell; Lexical ist Metas eigenständiges Editor-Framework. Keines von beiden teilt Schnittstellen mit Wordgard.
Wer warten sollte: die meisten Teams, zumindest vorerst. Wordgard wurde erstmals als 0.1.0 veröffentlicht, und das Paket auf npm hat seitdem mehrere Releases durchlaufen; der neueste Eintrag im Changelog ist 0.5.2 vom 6. September 2026, und das Changelog vermerkt Breaking Changes in 0.2.0, 0.3.0, 0.4.0 und 0.5.0. Der Autor rechnet damit, Teile der öffentlichen Schnittstelle zu überdenken, und geht davon aus, wahrscheinlich ein Jahr oder länger bei 0.x zu bleiben. Es gibt keinen Upgrade-Pfad von ProseMirror: Das Dokument Migrating from ProseMirror ordnet jedem ProseMirror-Paket ein Wordgard-Modul zu und stellt fest, dass keine Schnittstellenkompatibilität angestrebt wurde.
Fazit
Wordgard ist der erste Editor aus der ProseMirror-Linie, der Steps, geordnete Content Expressions und browsergesteuerte Selektion in einem Entwurf verwirft – und diese drei Entscheidungen machen ihn beachtenswert, nicht der Name des Autors. Wenn Sie ein ProseMirror-basiertes Produkt betreuen, lesen Sie das Migrationsdokument sowie die Abschnitte Changes und Corrections des Guides und prototypisieren Sie dann eine unbequeme Schema-Invariante als Correction; diese Übung wird Ihnen mehr über die Passung verraten als jede Feature-Liste.
FAQs
Enthält Wordgard kollaboratives Editieren, oder muss ich einen Server bauen?
Wordgard liefert eine clientseitige Erweiterung für kollaboratives Editieren in wordgard/collab mit, aber keinen Server. Die Erweiterung collab() verfolgt unbestätigte lokale Änderungen; collab.sendableUpdate und collab.receive tauschen Updates mit einer zentralen Instanz aus, die Sie selbst implementieren, und collab.transformUpdate (hinzugefügt in 0.2.0) erlaubt es diesem Server, veraltete Updates zu rebasen. Corrections überspringen Remote-Transaktionen, geben Sie der Client-Konfiguration und der Server-Transformation deshalb dieselben Corrections in derselben Reihenfolge.
Was ist der Unterschied zwischen einem Plot und einem Leaf in Wordgard?
Ein Plot ist ein Node mit Inhalt, etwa ein Absatz, eine Liste, eine Tabelle oder das Dokument; ein Leaf ist ein Node ohne Inhalt, etwa Text, ein Bild oder ein Zeilenumbruch. Es handelt sich um getrennte Klassen, Plot und Leaf, und die Eigenschaften isPlot und isLeaf grenzen sie in TypeScript voneinander ab. Ein Leaf ist sein eigener Tag (Typ, Parameter, Marks), während ein Plot einen Tag plus ein Content-Array hält.
Kann ich Wordgard-Dokumente außerhalb des Browsers erzeugen oder verändern, zum Beispiel in Node?
Für das Dokumentmodell ja, für den Editor nein. Die Module wordgard/doc, wordgard/state und wordgard/types sind darauf ausgelegt, ohne DOM zu laufen, sodass Sie serverseitig Dokumente aufbauen, Change Sets anwenden, Corrections ausführen und nach JSON serialisieren können; wordgard/types hängt nur von wordgard/doc ab. wordgard/editor lädt außerhalb des Browsers, tut aber nichts Sinnvolles, und ein Dokument als HTML-String benötigt den Browser-Parser, übergeben Sie also JSON oder verwenden Sie jsdom.
Unterstützt Wordgard Tabellen, und wie hält es sie rechteckig?
Ja. Das Modul wordgard/table exportiert ein Erweiterungsbündel tables(), das die Tabellen-Schema-Elemente, einen Typ CellSelection zum Auswählen rechteckiger Zellbereiche, Paste- und Drop-Handler, ein Tabellenmenü sowie tables.correction hinzufügt – eine eingebaute Correction, die Tabellen reparieren, deren Zellen sich nicht zu einem saubereren Rechteck ausrichten. Ihre Optionen sind headerCells, cellSpanning und cellContent (inline oder block). Verbundene Zellen verwenden die Marks RowSpan und ColSpan.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.
Star on GitHub12k