12k
All articles

Premier aperçu de Wordgard, un nouvel éditeur de texte

Wordgard est une nouvelle bibliothèque déditeur riche de Marijn Haverbeke, avec transactions à changement unique, corrections, facettes et sélection gérée par la bibliothèque.

OpenReplay Team
OpenReplay Team
Premier aperçu de Wordgard, un nouvel éditeur de texte

Wordgard est une bibliothèque JavaScript signée Marijn Haverbeke, l’auteur de ProseMirror et CodeMirror, destinée à la création d’éditeurs de texte enrichi dont les documents se conforment à un schéma ; elle fournit un composant d’interface d’édition, mais il ne s’agit pas d’un éditeur WYSIWYG ou HTML générique et libre de contraintes.

Maintenir une intégration ProseMirror, cela peut vouloir dire mapper des positions à travers une liste d’étapes, ou écrire une commande « générique » qui doit vérifier des expressions de contenu à chaque tournant. Wordgard est la réponse du même auteur à ces récriminations, conçue de zéro plutôt que greffée sur ProseMirror.

Cet article passe en revue ce que la bibliothèque change : le modèle de modifications, la suppression des contraintes de contenu, le système d’extension fondé sur les facettes et la gestion de la sélection intégrée à la bibliothèque, ainsi que le positionnement de cette première version face à ProseMirror, TipTap et Lexical.

Points clés à retenir

  • Wordgard a d’abord été publié en version 0.1.0 le 2 juillet 2026 sous licence MIT et s’installe depuis npm sous le nom wordgard ; l’auteur a indiqué lors de la publication que le projet resterait en versions 0.x pendant au moins un an, selon toute vraisemblance.
  • Une transaction Wordgard porte exactement une modification, construite à partir de sections qui conservent une plage de tokens, la remplacent, ou y ajoutent ou en retirent des marques, de sorte que la plage affectée peut être lue directement au lieu d’être reconstituée à partir d’une liste d’étapes.
  • Les schémas Wordgard peuvent restreindre les types de nœuds qu’un parent est autorisé à contenir, mais pas leur ordre ; les corrections, des fonctions observatrices qui renvoient des spécifications de modification correctrices, prennent en charge des invariants tels que le caractère rectangulaire des tableaux.
  • La configuration est un arbre d’extensions doté d’une précédence par valeur et de facettes définissables par l’utilisateur, repris de CodeMirror 6.
  • Wordgard gère la sélection au clavier et au pointeur au sein de la bibliothèque et dessine son propre curseur ; la sélection tactile est laissée au navigateur.

Qu’est-ce que Wordgard ?

Wordgard est un système d’édition de texte enrichi pour des contenus conformes à un schéma précis, et non un composant WYSIWYG prêt à l’emploi ni une application. D’après le System Guide, la surface d’édition est censée donner une impression de WYSIWYG, mais le contenu et les actions d’édition sont désignés par leur signification (titres, listes, emphase) plutôt que par leur apparence (police de caractères, retrait de paragraphe, gras). L’export phare de la bibliothèque est la classe d’interface Wordgard. En dessous se trouvent les types relatifs aux documents, à l’état de l’éditeur et aux actions d’édition, et la plupart d’entre eux fonctionnent sans qu’aucun navigateur ne soit en jeu.

L’annonce de la 0.1, datée du 2 juillet 2026, précise la licence MIT, le nom du paquet npm wordgard, et le fait que les sources résident sur l’instance Forgejo de l’auteur. La page d’accueil du projet confirme la licence et ajoute que les rapports de bugs sont les bienvenus, mais que les pull requests ne sont pas acceptées. La page d’accueil énumère également, au titre des fonctionnalités, les documents fondés sur un schéma, les extensions modulaires, le texte bidirectionnel, les contenus structurés tels que les tableaux et les listes imbriquées, ainsi que l’édition collaborative ; ces points sont à considérer comme les affirmations du projet lui-même.

Comment met-on en place un éditeur Wordgard ?

Un éditeur Wordgard minimal se résume à un appel à Wordgard.create avec un document, une configuration et un élément parent. Voici l’exemple de mise en place du 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
})

Le tableau config constitue l’arbre d’extensions, et chacune des trois entrées est un ensemble d’extensions plutôt qu’un objet schéma, un plugin et un widget. fullSchema() importe l’intégralité des éléments de schéma de wordgard/schema, et sa propre documentation avertit que cet ensemble est susceptible de s’enrichir de nouveaux éléments à mesure que la bibliothèque gagne en fonctionnalités ; les exemples ultérieurs du guide utilisent basicSchema(), qui regroupe un document en blocs, des paragraphes, des titres, des sauts de ligne, ainsi que les marques strong, emphasis et link. La chaîne doc est analysée comme du HTML au regard de ce schéma. Le paquet se décompose en modules tels que wordgard/doc, wordgard/state, wordgard/editor, wordgard/command, wordgard/history, wordgard/schema et wordgard/types, et le guide recommande TypeScript, tant les différentes pièces s’imbriquent étroitement.

En quoi le modèle de modifications de Wordgard diffère-t-il de celui de ProseMirror ?

Dans Wordgard, une transaction porte exactement un objet de modification, construit à partir de sections qui conservent une portion du document, la remplacent, ou y ajoutent ou en retirent des marques ; la plage affectée par une édition peut ainsi être lue directement au lieu d’être reconstituée à partir d’une liste d’étapes. Dans ProseMirror, une transaction est une liste ordonnée d’étapes atomiques, chacune agissant sur le document produit par la précédente, ce qui contraint l’arithmétique des positions et l’inspection des plages à parcourir toute la chaîne.

L’argument avancé dans l’annonce est que le format delta de CodeMirror, lui-même dérivé de ShareJS, est à la fois plus simple et plus puissant. Une modification est une séquence plate portant sur l’ancien document. Prenons un document long de dix tokens : ajouter un token en position 4 donne « conserver 4, remplacer 0 par le token, conserver 6 », et mettre en gras les positions 3 à 6 donne « conserver 3, mettre à jour 3 en ajoutant la marque, conserver 4 ». La section de mise à jour de marque est l’extension apportée par Wordgard au modèle de CodeMirror.

Cela fonctionne sur un arbre parce que les positions sont comptées en tokens. Dans le système d’index du guide, chaque ouverture de plot, chaque fermeture de plot, chaque feuille non textuelle et chaque caractère UTF-16 ajoute une unité à la position, la position 0 se situe immédiatement avant le premier enfant, et les tokens d’ouverture et de fermeture du nœud document lui-même ne sont pas comptés. Une modification peut ainsi insérer de nouvelles séquences de tokens dans le document comme s’il était plat, le code de création de modifications se chargeant de vérifier que le résultat reste un arbre bien formé.

Lorsque plusieurs modifications sont transmises ensemble à ChangeSet.create, chaque position est interprétée par rapport au document d’origine et la bibliothèque applique automatiquement les décalages. Une modification de marque ne touche à aucun contenu :

let makeStrong = ChangeSet.create(doc, {
  from: 1, to: 5,
  add: Strong
})

Ces mêmes objets permettent de transformer les modifications les unes par rapport aux autres, ce sur quoi reposent l’historique d’annulation et l’édition collaborative.

Qu’est-ce qui remplace les expressions de contenu de ProseMirror ?

Les schémas Wordgard peuvent restreindre les types de nœuds qu’un parent est autorisé à contenir, et déterminer si un plot de bloc peut être vide, mais pas l’ordre d’apparition des enfants ; les expressions de contenu à base d’expressions régulières de ProseMirror n’ont pas d’équivalent. L’annonce en donne deux raisons : du code générique de manipulation de documents ne peut pas être écrit face à des contraintes d’ordonnancement arbitraires sans vérifier chaque opération, et les contraintes strictes empêchent les états intermédiaires bancals par lesquels passe toute édition réelle.

Les règles que le schéma ne peut pas exprimer sont prises en charge par les corrections. Une correction est un observateur associé à une requête de nœud ; elle s’exécute chaque fois qu’un nœud correspondant change ou apparaît, et peut renvoyer une spécification de modification que la bibliothèque ajoute à la transaction. Parce qu’une correction est du code, elle peut tenir compte de ce que l’utilisateur est en train de faire au lieu de rejeter mécaniquement la structure. L’exemple du guide utilise Correction.onChildList(Doc, ...) pour insérer un titre de niveau 1 lorsque le document ne commence pas par un titre ; l’annonce cite les tableaux rectangulaires comme le cas que les expressions de ProseMirror n’ont jamais pu exprimer.

Pourquoi Wordgard utilise-t-il des facettes plutôt que des plugins ?

Wordgard remplace le plugin de ProseMirror, unité de configuration et de précédence, par un arbre de valeurs d’extension à granularité fine, dont chacune peut porter sa propre précédence. Le grief formulé dans l’annonce est précis : un plugin ProseMirror regroupe plusieurs hooks sous une même position de précédence, si bien qu’un plugin devant être prioritaire pour un hook et non prioritaire pour un autre ne peut obtenir les deux.

Dans la section configuration du guide, une extension est l’une de ces trois choses : une valeur relevant de l’un des types d’extension intégrés à la bibliothèque, tout objet portant une extension dans son champ extension, ou un tableau contenant d’autres éléments du même ordre. La précédence explicite provient des fonctions de GardState.prec ; à niveau égal, l’ordre dans l’arbre tranche. Les facettes sont des points d’extension typés que n’importe quel code peut définir, avec une fonction combine facultative pour réduire les entrées à une seule sortie, et les compartiments permettent d’échanger des parties d’une configuration sans perdre l’état. Le terme « plugin » n’a pas disparu pour autant : Wordgard.Plugin.define est toujours là, pour les objets qui détiennent leur propre état et doivent rester au plus près du DOM ; c’est ainsi que sont construits les infobulles et les panneaux fournis avec la bibliothèque.

Une sélection dessinée par la bibliothèque

Wordgard gère la sélection au clavier et au pointeur au sein de la bibliothèque et masque le caret natif pour dessiner son propre curseur, tandis que la surbrillance de sélection native, elle, reste visible. L’annonce impute ce choix au comportement peu fiable des navigateurs : un curseur qui refuse de franchir certains contenus, qui atterrit au mauvais endroit ou n’est tout simplement pas rendu, et une sélection par glisser-déposer à la souris qui déraille. La bibliothèque construit donc sa propre représentation de la mise en page du contenu, assure elle-même la gestion du texte bidirectionnel et place le curseur par ses propres moyens. L’exemple de DOM du guide montre un élément dédié à la couche curseur superposé au contenu, et le document de migration précise que la surbrillance native est conservée parce que la laisser en place occasionne moins de problèmes.

À la date de l’annonce de la 0.1, la sélection tactile constitue la seule exception et demeure native, car la réimplémenter casse le menu contextuel de la plateforme. Cette position a depuis évolué : le changelog fait état, en 0.5.0, d’une sélection tactile pour les positions que la sélection native ne peut atteindre, et, en 0.5.1, d’une position de curseur supplémentaire aux extrémités des plots en ligne, qui donne à la sélection tactile par glissement un point d’arrêt. L’annonce présente également la gestion des entrées comme provisoire : Wordgard traite beforeinput pour tout sauf la composition et abandonne l’analyse des mutations du DOM de ProseMirror, dans l’attente de tests en conditions réelles. Aucune matrice de compatibilité navigateurs n’est publiée.

Wordgard face à ProseMirror, TipTap et Lexical

ProseMirrorTipTapLexicalWordgard
Modèle de modificationsÉtapes ordonnéesHérité de ProseMirrorModèle propreModification unique fondée sur des sections
Structure du contenuExpressions de contenu par regexHéritée de ProseMirrorModèle propreEnsembles de types enfants et corrections
ConfigurationPluginsExtensions au-dessus des plugins ProseMirrorModèle propreExtensions par facettes avec précédence par valeur
SélectionNative au navigateurNative au navigateurModèle propreCurseur dessiné par la bibliothèque, tactile natif

TipTap est une couche de framework au-dessus de ProseMirror et en hérite le modèle central ; Lexical est le framework d’édition distinct développé par Meta. Ni l’un ni l’autre ne partagent d’interfaces avec Wordgard.

Qui devrait patienter : la plupart des équipes, pour l’instant. Wordgard a d’abord été publié en 0.1.0, et le paquet sur npm a depuis connu plusieurs versions ; la dernière entrée du changelog est la 0.5.2, datée du 6 septembre 2026, et le changelog signale des ruptures de compatibilité en 0.2.0, 0.3.0, 0.4.0 et 0.5.0. L’auteur s’attend à repenser certaines parties de l’interface publique et à rester en 0.x pendant un an, voire davantage, selon toute vraisemblance. Il n’existe aucun chemin de mise à niveau depuis ProseMirror : le document Migrating from ProseMirror du projet met chaque paquet ProseMirror en correspondance avec un module Wordgard et indique qu’aucune compatibilité d’interface n’a été recherchée.

Verdict

Wordgard est le premier éditeur issu de la lignée ProseMirror à abandonner d’un même mouvement les étapes, les expressions de contenu ordonnées et la sélection pilotée par le navigateur, et ce sont ces trois décisions, plutôt que le nom de l’auteur, qui le rendent digne d’attention. Si vous maintenez un produit fondé sur ProseMirror, lisez le document de migration ainsi que les sections Changes et Corrections du guide, puis prototypez sous forme de correction un invariant de schéma épineux ; cet exercice vous en apprendra davantage sur l’adéquation de l’outil que n’importe quelle liste de fonctionnalités.

FAQ

Wordgard intègre-t-il l'édition collaborative, ou faut-il développer un serveur ?

Wordgard fournit une extension d'édition collaborative côté client dans wordgard/collab, mais aucun serveur. L'extension collab() suit les modifications locales non confirmées ; collab.sendableUpdate et collab.receive échangent les mises à jour avec une autorité centrale qu'il vous revient d'implémenter, et collab.transformUpdate (ajouté en 0.2.0) permet à ce serveur de rebaser les mises à jour obsolètes. Les corrections ignorent les transactions distantes : veillez donc à donner à la configuration du client et à la transformation du serveur les mêmes corrections, énumérées dans le même ordre.

Quelle est la différence entre un plot et une feuille dans Wordgard ?

Un plot est un nœud doté de contenu, tel qu'un paragraphe, une liste, un tableau ou le document lui-même ; une feuille est un nœud sans contenu, tel qu'un texte, une image ou un saut de ligne. Il s'agit de classes distinctes, Plot et Leaf, et les propriétés isPlot et isLeaf permettent de les discriminer en TypeScript. Une feuille est son propre tag (type, paramètre, marques), tandis qu'un plot comporte un tag ainsi qu'un tableau de contenu.

Puis-je créer ou modifier des documents Wordgard en dehors du navigateur, par exemple sous Node ?

Oui pour le modèle de document, non pour l'éditeur. Les modules wordgard/doc, wordgard/state et wordgard/types sont conçus pour fonctionner sans DOM : vous pouvez donc construire des documents, appliquer des ensembles de modifications, exécuter des corrections et sérialiser en JSON côté serveur ; wordgard/types ne dépend que de wordgard/doc. wordgard/editor se charge en dehors du navigateur mais n'y fait rien d'utile, et un document fourni sous forme de chaîne HTML nécessite l'analyseur du navigateur : passez donc du JSON ou utilisez jsdom.

Wordgard prend-il en charge les tableaux, et comment en préserve-t-il le caractère rectangulaire ?

Oui. Le module wordgard/table exporte un ensemble d'extensions tables() qui ajoute les éléments de schéma des tableaux, un type CellSelection pour sélectionner des rectangles de cellules, des gestionnaires de collage et de dépôt, un menu dédié aux tableaux, ainsi que tables.correction, une correction intégrée qui répare les tableaux dont les cellules ne s'alignent pas en un rectangle net. Ses options sont headerCells, cellSpanning et cellContent (inline ou block). Les cellules fusionnées utilisent les marques RowSpan et ColSpan.

DevTools for the frontend

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

We use cookies to improve your experience. By using our site, you accept cookies.