02Urteilskraft5 min

Was am Ende immer passiert, wenn ich etwas von Hand anfange

Ich habe viermal Editoren von Hand gebaut. Drei davon wurden mit einer Bibliothek neu geschrieben. Der vierte hielt keine zwei Tage.

Von Salva Castells

Titelbild: Globetrotter19, CC BY-SA 4.0.
In diesem Beitrag

Im September 2024 habe ich drei Wochen lang einen Block-Editor gebaut. Die Idee war die übliche: Jeder Absatz ist ein Block, du ziehst ihn, du sortierst ihn um, und jeder Block hat seinen eigenen KI-Knopf zum Umschreiben. Nichts Exotisches. Notion macht das seit Jahren.

Ich habe es mit dnd-kit für das Ziehen und einer Editor-Instanz pro Block gebaut. Die Komponente Block bekam id, index, content, onDelete, onContentChange, eine Funktion regenerateContent mit fünf Parametern, die komplette Blockliste für den Kontext und einen AbortController in einem Ref, um die Generierung mittendrin abzubrechen. Es funktionierte. Einer der Commits aus dieser Woche heißt wörtlich "never give up!!". Das ist, was davon übrig blieb.

Sechs Wochen später war das alles gelöscht und an seiner Stelle stand ein einziger Editor mit Extensions.

Was kaputtgeht, wenn du die Blöcke selbst orchestrierst

Das Problem war nicht das Ziehen. Das Ziehen lief gut. Das Problem war alles andere, und ich habe es in dieser Reihenfolge entdeckt:

  • Inhalt nach innen synchronisieren. Jeder Block hatte ein useEffect, das content !== editor.getHTML() verglich, um setContent aufzurufen. HTML als String zu vergleichen, um zu entscheiden, ob sich der Zustand geändert hat, ist genau die Sorte Sache, die aussieht, als würde sie funktionieren, bis sie es nicht mehr tut.
  • Auswahl über Blöcke hinweg. Gibt es nicht. Wenn jeder Block sein eigener Editor ist, kann der Nutzer nicht von der Mitte eines Absatzes bis in die Mitte des nächsten markieren. Es gibt keine halbe Lösung: Entweder es gibt ein Dokument oder es gibt N Dokumente.
  • Rückgängig machen. Jede Instanz hat ihren eigenen History-Stack. Strg+Z macht in dem Block rückgängig, in dem der Cursor steht, nicht das Letzte, was du getan hast. Das erwartet niemand.
  • Einfügen. Du fügst drei Absätze in einen Block ein und alle drei bleiben in einem Block. Sie aufzuteilen heißt, von Hand nachzubauen, was das Dokumentschema längst kann.

Keine dieser vier Sachen war mir im Kopf, als ich anfing. Alle vier tauchten genau in der Reihenfolge auf, in der ich sie aufgeschrieben habe.

Die Neuschreibung war kürzer als der Prototyp

Mitte Oktober lag neben dem Ordner block/ schon ein Ordner block2/ mit drei Dateien: der Editor, die Toolbar und ein Hook. Der komplette Hook waren vierzig Zeilen: StarterKit, TextStyle, FontFamily, TextAlign, Highlight, Placeholder. Sechs Extensions in einem Array deklariert, und damit kamen die vereinheitlichte History, die durchgehende Auswahl und das Einfügen mit Schema. Im November kamen Aufgabenlisten, Codeblöcke und Bilder per URL dazu: jeweils ein Import und ein Eintrag im Array.

Der Ordnername sagt alles. block2 ist kein Refactor. Es ist das Eingeständnis, dass das daneben nicht zu retten ist.

Dasselbe mit dem CSS dieser Seite. Ich hatte eine Datei mit eintausendsechshunderteinundachtzig Zeilen voller eigener Klassen. Beim Umstieg auf Komponenten überlebten nur die Farbtokens: hundertdreizehn Zeilen in globals.css. Der Rest war Nachbau.

Wo die Bibliothek immer gewinnt

Das ist kein "benutz Bibliotheken". Es gibt konkrete Kategorien, in denen das Problem größer ist, als es von außen aussieht, und es sind immer dieselben:

  • Rich-Text-Editoren. Auswahl, History, Einfügen und Schema sind ein einziges System. Man kann sich nicht drei von vier herauspicken.
  • Authentifizierung und Sessions. Niemand dankt dir für den Refresh-Token, den du selbst geschrieben hast.
  • Datum und Zeitzonen. Der seltene Fall erreicht dich in Produktion, nicht auf deiner Maschine.
  • Styles. Ein eigenes Klassensystem fängt sauberer an und endet als schlechter dokumentiertes Framework.

Das gemeinsame Signal ist dieses: Wenn der schwierige Teil nicht der erste Bildschirm ist, sondern das Zusammenspiel von Teilen, die du am Anfang nicht siehst, gewinnt die Bibliothek. Wenn der schwierige Teil deine Geschäftslogik ist, gibt es keine.

Wie ich heute entscheide

Ich habe die Reihenfolge umgedreht. Jetzt starte ich mit der Bibliothek und gehe nur davon weg, wenn sie mich blockiert.

Ein kollaborativer Editor, den ich letzten Sommer brauchte, lief so: von einem PlateJS-Template ausgehen und Yjs obendrauf setzen. Drei Tage nach dem ersten Commit waren die Cursor anderer Nutzer auf dem Bildschirm zu sehen. Monate vorher hätte ich mit dem Block und seinen fünf Props angefangen. Für ein Markdown-Notiztool letztes Jahr habe ich über den Editor gar nicht erst nachgedacht: CodeMirror, ein Import von highlight.js im CSS, und weiter zur eigentlichen Arbeit.

Und es gibt einen Fall, der noch schneller geht. Im April 2025 habe ich einen Agenten mit Tools geschrieben, eine agent.ts mit ihren Kontext-Tools. Am nächsten Tag gibt es einen Commit, der das entfernt und stattdessen direkt das Streaming des Anbieters nutzt. Weniger als vierundzwanzig Stunden zwischen Bauen und Löschen, und das ist das gute Ergebnis.

Die echten Kosten

Es von Hand zu bauen ist keine verschwendete Zeit: Ich weiß, was das Einfügen in einem Editor macht, weil ich es falsch nachgebaut habe. Dieses Wissen kaufst du dir nicht durch Dokumentationslesen.

Die Kosten liegen darin, wann du es merkst. Drei Wochen Blöcke von Hand und sechs weitere Wochen Koexistenz von block/ und block2/ sind zwei Monate. Die erste Seite einer 3D-Karte hielt zehn Tage, bevor ein Branch namens map-v2 sie komplett ersetzte; dort waren die Kosten zehn Tage, nicht zwei Monate, weil ich früher abgebrochen habe.

Der Unterschied zwischen beiden ist nicht, wie viel ich am Anfang wusste. Es ist, wie lange ich gebraucht habe zu akzeptieren, dass ich schon genug wusste.

Etwas von Hand zu bauen ist eine teure Art, die Dokumentation zu lesen. Manchmal lohnt es sich, den Preis zu zahlen; was sich nicht lohnt, ist ihn zweimal für dasselbe zu zahlen.