<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
<title>Peter Kröner • Webtechnologie • Weblog</title>
<link>http://feeds2.feedburner.com/kroener</link>
<description><![CDATA[Weblog von Peter Kröner, HTML5-Trainer, Webentwickler und Autor]]></description>
<language>de</language>
<ttl>120</ttl>
<atom:link href="http://feeds2.feedburner.com/kroener" rel="self" type="application/rss+xml" />
<item>
<title><![CDATA[Öffentliche Erklärbär-Termine 1. Halbjahr 2025]]></title>
<link>https://www.peterkroener.de/oeffentliche-erklaerbaer-termine-1-halbjahr-2025/</link>
<description><![CDATA[<p>
Willkommen im Jahr 2025! Wie immer reist der rasende Erklärbär durch die Gegend und verbreitet Neues und Kontroverses zum Thema Frontend-Entwicklung. Ihr könnt entweder bei einem der folgenden Termine dabei sein oder mich <a href="/kontakt/">direkt zu euch in die Firma einladen</a> – Workshops, Code-Reviews, Beratung, ihr kennt das Programm. Für die Öffentlichkeit fest eingeplant ist für das 1. Halbjahr schon folgendes:
</p>

<ul>

  <li><strong>6. Februar, <a href="https://www.oop-konferenz.de/de">OOP Konferenz</a> (München): Talk „Legacy-Refactoring im Web-Frontend – Standards to the&#8230;</strong></li></ul>]]></description>
<pubDate>Thu, 23 Jan 2025 15:34:00 +0100</pubDate>
<guid isPermaLink="false">https://www.peterkroener.de/oeffentliche-erklaerbaer-termine-1-halbjahr-2025/</guid>
<dc:creator>Peter Kröner</dc:creator>
<content:encoded><![CDATA[<p>
Willkommen im Jahr 2025! Wie immer reist der rasende Erklärbär durch die Gegend und verbreitet Neues und Kontroverses zum Thema Frontend-Entwicklung. Ihr könnt entweder bei einem der folgenden Termine dabei sein oder mich <a href="/kontakt/">direkt zu euch in die Firma einladen</a>&nbsp;&ndash; Workshops, Code-Reviews, Beratung, ihr kennt das Programm. Für die Öffentlichkeit fest eingeplant ist für das 1. Halbjahr schon folgendes:
</p>

<ul>

  <li><strong>6. Februar, <a href="https://www.oop-konferenz.de/de">OOP Konferenz</a> (München): Talk „Legacy-Refactoring im Web-Frontend – Standards to the Rescue!“</strong>&nbsp;&ndash; ein Überblick über die Chancen, die Webstandard für Legacy-Refactoring bieten, plus ein paar Horrorstories aus der Praxis.</li>

  <li><strong>6. März, <a href="https://basta.net/frankfurt/">BASTA! Spring 2025</a> (Frankfurt a. Main):</strong>
    <ul>
      <li>Talk <a href="https://basta.net/web-development/browser-webstandards-frontend/">„Mein Browser kann WAS!? Neue, unterschätzte und unbekannte Frontend-Webstandards“</a>&nbsp;&ndash; Überblick über Neuheiten aus der Frontend-Welt, von HTML und Browser-APIs bis hin zu CSS und JS.</li>
      <li>Talk <a href="https://basta.net/web-development/worst-javascript-features/">„Best of the Worst – die Top 5 der schlimmsten JavaScript-Features“</a>&nbsp;&ndash; Mischung aus Comedy-Programm und Wissenswertem aus den obskureren Ecken des ECMAScript-Standards.</li>
    </ul>
  </li>

  <li><strong>17. - 19. März, <a href="https://javascript-days.de/muenchen/">JavaScript &amp; Angular Days</a> (Unterhaching):</strong>
    <ul>
      <li>Ganztags-Workshop <a href="https://javascript-days.de/javascript/javascript-upgrade-fuer-2025-frontend-entwicklerinnen-teil-1/">„JavaScript-Upgrade für 2025 für Frontend-Entwickler:innen“</a>&nbsp;&ndash; alles, was es über JavaScript des Jahres 2025 zu wissen gibt, mit den neuesten Features, neuen Use Cases für alten Features und wilden Spekulationen über die Zukunft.</li>
      <li>Halbtags-Workshop <a href="https://javascript-days.de/javascript/go-vanilla-or-go-home-natives-dom-hacking-fuer-das-jahr-2025/">„Go Vanilla or go Home! Natives-DOM-Hacking für das Jahr 2025“</a>&nbsp;&ndash; Vanilla-Webdev-Rundumschlag, der mit einem Basics-DOM-Refresher beginnt und diverse neue APIs demonstriert, um dann bei Web Components und DIY-Eventsystemen zu Enden</li>
      <li>Halbtags-Workshop <a href="https://javascript-days.de/javascript/async-await-2-0-native-streams-und-asychrone-iterables-fuers-frontend/">„Async/Await 2.0: Native Streams und asychrone Iterables für's Frontend“</a>&nbsp;&ndash; Deep Dive in asynchrone Iteration, asynchrone Generators und Stream-APIs im Frontend. Eine Hirn-Kernschmelze für alle JS-Entwickler:innen, die sonst schon alles wissen.</li>
      <li>Ganztags-Workshop <strong>„Web Components für Designsysteme“</strong>: eine Co-Produktion mit Tobias Struckmeier, die die Chancen (und Hürden) für den Einsatz von Web Components im Kontext von Designsystemen von A bis Z durchdiskutiert.</li>
    </ul>
  </li>

</li>

<li><strong>25. März, Online: <a href="https://entwickler.de/live-events/break-free-from-frameworks-with-vanilla-js/">Live-Event „Break Free from Frameworks with Vanilla JS“</a></strong>&nbsp;&ndash; ein halbtägiger Vanilla-Webdev-Rundumschlag, der mit einem Basics-DOM-Refresher beginnt und diverse neue APIs demonstriert, um dann bei Web Components und DIY-Eventsystemen zu Enden (auf Englisch)</li>

<li><strong>9. - 11. + 14. - 16. April, Online: <a href="https://workshops.de/seminare-schulungen-kurse/javascript">„JavaScript
Intensiv-Schulung“</a></strong>&nbsp;&ndash; 6 halbe Tage klassische JavaScript-Druckbetankung für Ein- und Umsteiger:innen.</li>

<li><strong>KW 19, <a href="https://jax.de/mainz/">JAX 2025</a> (Mainz):</strong> der Titel meines Talks <a href="https://jax.de/web-development-javascript/javascript-funktion-schreiben-verstehen/">„Auf der dunklen Seite der Macht: die schlimmste JavaScript-Funktion der Welt schreiben und verstehen“</a> sagt eigentlich alles. Statt am guten Beispiel lernen wir aus der übelsten Gammelfunktion, die ich zu schreiben imstande war, einiges Neues und Unbekanntes über JavaScript.</li>

<li><strong>8. Mai, <a href="https://enterjs.de/">enterJS 2025</a> (Mannheim):</strong> im Talk <a href="https://enterjs.de/veranstaltung-82750-0-crimes-against-the-browser-finsterste-frontend-verbrechen.html">„Crimes Against the Browser: Finsterste Frontend-Verbrechen“</a> nehme ich eine grauslige Sammlung an Hacks und faulen Tricks zum Anlass, ein großes Spektrum an Browser-APIs und Webstandards-Detritus zu beleuchten.</li>

<li><strong>5. und 6. Juni, <a href="https://webinale.de/">Webinale</a> (Berlin):</strong>
  <ul>
    <li>5. Juni: Talk <a href="https://webinale.de/web-design-development/anti-features-in-javascript/">„Best of the Worst – The Most Awful Anti-Features in JavaScript, Ranked!“</a>&nbsp;&ndash; Mischung aus Comedy-Programm und Wissenswertem aus den obskureren Ecken des ECMAScript-Standards (auf Englisch)</li>
    <li>6. Juni: Workshop <a href="https://webinale.de/web-design-development/workshop-native-dom-programming/">„Go Vanilla Or Go Home! Native-DOM-Programming in 2025“</a>&nbsp;&ndash; ein ganztägiger Vanilla-Webdev-Rundumschlag, der mit einem Basics-DOM-Refresher beginnt und diverse neue APIs demonstriert, um dann bei Web Components und DIY-Eventsystemen zu Enden (auf Englisch)</li>
  </ul>
</li>

<li><strong>12. - 16. Mai, <a href="">International JavaScript Conference</a> (London):</strong>
  <ul>
    <li>12. Mai: Ganztages-Event <a href="https://javascript-conference.com/architecture-performance/fullstack-day-javascript-tools/">„iJS Fullstack Day: Level Up Your Full Stack Performance“</a>&nbsp;&ndash; ein ganzer Tag rund um Performance im weitesten Sinne, mit Talks, Diskussionen und es gibt sogar etwas zu gewinnen (auf Englisch)</li>
    <li>13. Mai: Talk <a href="https://javascript-conference.com/javascript-practices-tools/awful-anti-features-javascript">„Best of the Worst – The Most Awful Anti-Features in JavaScript, Ranked!“</a>&nbsp;&ndash; Mischung aus Comedy-Programm und Wissenswertem aus den obskureren Ecken des ECMAScript-Standards (auf Englisch)</li>
    <li>15. Mai: Ganztags-Workshop <a href="https://javascript-conference.com/javascript-practices-tools/async-await-2-workshop-native-streams-asychronous-iterables/">„Async/Await 2.0: Native Streams and Asychronous Iterables“</a>&nbsp;&ndash; Deep Dive in asynchrone Iteration, asynchrone Generators und Stream-APIs im Frontend. Eine Hirn-Kernschmelze für alle JS-Entwickler:innen, die sonst schon alles wissen (auf Englisch)</li>
  </ul>
</li>

</ul>

<p>
Termine unpassend, Orte alle zu weit weg und Programme nicht genehm? Ich komme auch gerne mit einem maßgeschneiderten Workshop vorbei – <a href="/kontakt/">mich kann man ganz einfach mieten!</a> Auch für Code-Reviews und Frontend-Feuerwehreinsätze stehe ich gerne zur Verfügung. Einfach anschreiben!
</p>]]></content:encoded>
</item>
<item>
<title><![CDATA[Öffentliche Erklärbär-Termine Herbst 2024]]></title>
<link>https://www.peterkroener.de/oeffentliche-erklaerbaer-termine-herbst-2024/</link>
<description><![CDATA[<p>
Es wird mal wieder Zeit für eine Liste öffentlicher Erklärbär-Termine! Neben dem traditionellen Konferenz-Programm im Herbst könnt ihr mich natürlich <a href="/profil">wie gehabt direkt mieten</a>, um in eurer Firma die frohe Kunde von Zero-Dependency Web Components, neuen JavaScript-Features oder besserem TypeScript zu verkünden.
</p>

<ul>

<li><strong>11. - 13 und 16 - 18. September online: <a href="https://workshops.de/seminare-schulungen-kurse/javascript">JavaScript Intensiv-Schulung</a> bei Workshops.de:</strong> einmal alles über JavaScript im Druckbetankungsverfahren! Die Schulung&#8230;</li></ul>]]></description>
<pubDate>Mon, 26 Aug 2024 10:07:29 +0200</pubDate>
<guid isPermaLink="false">https://www.peterkroener.de/oeffentliche-erklaerbaer-termine-herbst-2024/</guid>
<dc:creator>Peter Kröner</dc:creator>
<content:encoded><![CDATA[<p>
Es wird mal wieder Zeit für eine Liste öffentlicher Erklärbär-Termine! Neben dem traditionellen Konferenz-Programm im Herbst könnt ihr mich natürlich <a href="/profil">wie gehabt direkt mieten</a>, um in eurer Firma die frohe Kunde von Zero-Dependency Web Components, neuen JavaScript-Features oder besserem TypeScript zu verkünden.
</p>

<ul>

<li><strong>11. - 13 und 16 - 18. September online: <a href="https://workshops.de/seminare-schulungen-kurse/javascript">JavaScript Intensiv-Schulung</a> bei Workshops.de:</strong> einmal alles über JavaScript im Druckbetankungsverfahren! Die Schulung verteilt den Inhalt von drei vollen Tagen über 6 halbe Tage, damit sie weniger anstrengend wird und die Teilnehmer:innen nicht komplett in Beschlag nimmt.</li>

<li><strong>21. - 24. Oktober auf den <a href="https://javascript-days.de/berlin/">JavaScript- und Angular-Days</a> (Berlin):</strong>
  <ul>
    <li><strong>21. Oktober:</strong> der Ganztages-Workshop <a href="https://javascript-days.de/fundamentals/java-script-upgrade-2024-frontend-entwickler-teil1/">„JavaScript-Upgrade für 2024 für Frontend-Entwickler:innen“</a> (in zwei Teilen) ist <em>das</em> Upgrade für alle, die nicht 24/7 den JavaScript-Nachrichtenzirkus verfolgen und informiert über das Neueste (und das wenig Beachtete) aus den letzten paar ECMAScript-Updates. Dieses Mal sind unter anderem <b>Resizable Array Buffers</b>, <b>Atomics.waitAsync</b> und <b>String.prototype.isWellFormed()</b> neu im Programm.</li>
    <li><strong>22. Oktober:</strong> im Workshop <a href="https://javascript-days.de/javascript/proxies-und-exotic-objects/">„Proxies! Exotic Objects für den JavaScript-Alltag“</a> erschlagen wir alles rund um das Konzept der <em>Exotic Objects</em> in JavaScript. Wir grenzen Exotic Objects von Ordinary Objects ab, entwickeln eigene Exotic Objects mit der Proxy-API und diskutieren erschöpfend die Möglichkeiten und Limitierungen von Proxies im Alltag.</li>
    <li><strong>22. Oktober:</strong> bei der Veranstaltung <a href="https://javascript-days.de/javascript/best-of-the-worst-javascript-features-aus-der-hoelle/">„Best of the Worst - JavaScript-Features aus der Hölle“</a> handelt es sich möglicherweise um ein als Workshop getarntes Comedy-Program, dessen Titel ich einer Youtube-Show entwendet habe.</li>
  </ul>
</li>

<li><strong>04. - 08. November auf der <a href="https://jax.de/">W-JAX</a> (München):</strong>
  <ul>
    <li><strong>05. November:</strong> wer um 19 Uhr noch Energie übrig hat, kann sich die Kurzfassung des Comedy-Programms <a href="https://javascript-days.de/fundamentals/java-script-upgrade-2024-frontend-entwickler-teil1/">„Best of the Worst – JavaScript-Features aus der Hölle“</a> geben.</li>
    <li><strong>06. November:</strong> die Session <a href="https://javascript-days.de/javascript/proxies-und-exotic-objects/">„Mein Browser kann WAS!? Neue, unterschätzte und unbekannte Webstandards für jedes Frontend“</a> bringt alle Teilnehmenden auf den neuesten Stand, was <em>nützliche</em> Frontend-APIs angeht, die in jedem Projekt hilfreich sein könnten – etwas weniger Web Serial API, dafür mehr Compression Streams und Co.</li>
  </ul>
</li>

<li><strong>11. - 15. November auf der <a href="https://javascript-conference.com/muenchen/">International JavaScript Conference</a> (München):</strong>
  <ul>
    <li><strong>11. November:</strong> der <a href="https://javascript-conference.com/general-web-development/javascript-fullstack-day-24/">„IJS Fullstack Day“</a> ist eine Mischung als Lightning Talks, Panel-Diskussion und AMA, zu dem ich meine Outsider-Perspektive auf moderne Webentwicklung beisteuern darf! Während es viel um fancy Tools und AI gehen wird, darf ich ein wenig über Minimalismus und das Weniger-ist-Mehr-Prinzip schwafeln.</li>
    <li><strong>14. November:</strong> mittlerweile wissen wir alle, was sich hinter dem Titel <a href="https://javascript-conference.com/javascript-practices-tools/worst-features-javascript">„Best of the Worst – The 5 Most Awful Anti-features in JavaScript“</a> verbirgt, oder?</li>
    <li><strong>15. November:</strong> im <strong>Ganztages-Workshop</strong> <a href="https://javascript-conference.com/general-web-development/workshop-web-formulare-web-components/">„Next Level Web-Formulare mit Web Components“</a> stelle ich den von mir entwickelten Ansatz für DIY-Formularelemente vor, den Social-Media-Follower vermutlich schon kennen.</li>
  </ul>
</li>

<li><strong>13. - 14. November auf der <a href="https://ctwebdev.de/">c&#39;t webdev</a> (Köln):</strong> wir kennen den Inhalt des <a href="https://ctwebdev.de/sessions24/best-of-the-worst-die-top-5-schlimmsten-javascript-features/">Talks „Best of the Worst“</a> <strong>am 13. November</strong>, also auf zum nächsten Termin...</li>

<li><strong>22. - 28. November auf der <a href="https://www.dotnet-developer-conference.de/en/">.NET Developer Conference</a> (Köln):</strong> der Ganztages-Workshop <strong>„From Zero to Frontend-Hero“</strong> am 22. November hat, wie der Name vermuten lässt, die Frontend-Aufschlauung von Backend-Entwickler:innen zum Thema. Wir besprechen, warum semantisches HTML wichtig ist, was CSS mittlerweile alles kann, warum JS so ist wie es ist, was es mit TypeScript auf sich hat und demystifizieren Konzepte wie Full-Stack-Frameworks und Server-Side-Rendering.</li>

</ul>

<p>
Termine unpassend, Orte alle zu weit weg und Programme nicht genehm? Ich komme auch gerne mit einem maßgeschneiderten Workshop vorbei – <a href="/profil">mich kann man ganz einfach mieten!</a> Auch für Code-Reviews und Frontend-Feuerwehreinsätze stehe ich gerne zur Verfügung.<img src="https://ssl-vg03.met.vgwort.de/na/da89307ccf364a87b33ded5531749ae6" width="1" height="1" alt="">
</p>]]></content:encoded>
</item>
<item>
<title><![CDATA[Frontend ohne FOMO: ein Erfahrungsbericht]]></title>
<link>https://www.peterkroener.de/frontend-ohne-fomo-ein-erfahrungsbericht/</link>
<description><![CDATA[<p>
Die erste Fassung der <a href="https://code.movie/">Code.Movie-Webseite</a>, damals noch mit dem Playground als SAAS, hatte ich vor ca. 2 Jahren gebaut und dabei alles zum Einsatz gebracht, was moderne Frontend-Entwicklung zu bieten hatte: Next.js und React sowie diverse Packages für React. Dazu zählten unter anderem <a href="https://react-redux.js.org/">Redux</a> und <a href="https://react-dnd.github.io/react-dnd/about">React DnD</a>, die freilich ihrerseits Ergänzungs-Packages nach sich zogen (u. a. <a href="https://redux-saga.js.org/">Redux-Saga</a> und <a href="https://www.npmjs.com/package/react-dnd-html5-backend">react-dnd-html5-backend</a>),&#8230;</p>]]></description>
<pubDate>Tue, 13 Aug 2024 10:05:10 +0200</pubDate>
<guid isPermaLink="false">https://www.peterkroener.de/frontend-ohne-fomo-ein-erfahrungsbericht/</guid>
<dc:creator>Peter Kröner</dc:creator>
<content:encoded><![CDATA[<p>
Die erste Fassung der <a href="https://code.movie/">Code.Movie-Webseite</a>, damals noch mit dem Playground als SAAS, hatte ich vor ca. 2 Jahren gebaut und dabei alles zum Einsatz gebracht, was moderne Frontend-Entwicklung zu bieten hatte: Next.js und React sowie diverse Packages für React. Dazu zählten unter anderem <a href="https://react-redux.js.org/">Redux</a> und <a href="https://react-dnd.github.io/react-dnd/about">React DnD</a>, die freilich ihrerseits Ergänzungs-Packages nach sich zogen (u.&nbsp;a. <a href="https://redux-saga.js.org/">Redux-Saga</a> und <a href="https://www.npmjs.com/package/react-dnd-html5-backend">react-dnd-html5-backend</a>), plus natürlich allerlei weiteres Zubehör wie Linter-Plugins für Next und React. Das Endergebnis war eine moderne Webapp nach allen Regeln der Kunst – SPA, SSR, jedes Feature war dabei. Wenn ich heute die <code>package.json</code> von damals aus dem Archiv hole, finde ich die Liste der Dependencies auch gar nicht mal so besonders lang. Die Packages konzentrierten sich auf das Wesentliche und statt für jede Kleinigkeit NPM heranzuziehen, habe ich viel mit eigenem Code gelöst.
</p>

<p>
Das Projekt litt trotz einer eher überschaubaren intrinsischen Komplexität und einem moderat dimensionierten Dependency Tree daran, dass ich permanent damit beschäftigt war, vorhandenen Code wieder und wieder neu zu schreiben, ohne viel sichtbaren Fortschritt am Projekt zu erzielen. Das könnte allein mein höchstpersönliches Versagen gewesen sein, <a href="https://polotek.net/posts/the-frontend-treadmill/">aber es drängt sich der Eindruck auf, dass nicht nur ich dieses Problem kenne.</a>
</p>

<h3>Code als Dauer-Baustelle: eine Ursachensuche</h3>

<p>
Am Dauer-Rewrite waren die Dependencies nicht unbeteiligt; von einem Tag auf den anderen wollte beispielsweise Next.js alle Pages statt in Folder A lieber in Folder B sehen und dort, wo die Dependencies wirklich gestapelt waren, gab es auch bei kleinen Updates immer wieder Kompatibilitätsprobleme. Das größere Problem saß aber in Kiel vor dem Bildschirm, denn mich hatte das Frontend-<a href="https://de.wikipedia.org/wiki/Fear_of_missing_out">FOMO</a> weit mehr im Griff, als mir damals klar war.
</p>

<p>
Neue Versionen von Software bedeuten in aller Regel Fortschritt. Klar, manchmal bricht eine API und manchmal wird etwas verschlimmbessert, aber in aller Regel geht es vorwärts – und wer möchte nicht gern vorn mit dabei sein? Das Problem ist nur, dass Fortschritt, wenn er auf bestehenden Code trifft, nicht ohne Arbeitseinsatz eingebaut werden kann&nbsp;… manchmal, ohne wahrnehmbare Verbesserungen zu erzielen. In meinem Fall passierte das mehrfach:
</p>

<ul>
<li>Dependency-Updates mit API-Änderungen (wie das angesprochene Next.js-Update) bringen Umbauten des betroffenen Codes mit sich. Diese können in sich eine Verbesserung darstellen, aber genauso gut einfach eine reine Umformulierung des Bestehenden ohne spürbaren Gewinn bedeuten. Dass am Ende von den eventuellen Verbesserungen etwas Wahrnehmbares bei den Nutzer:innen des Projekts ankommt, ist sehr unwahrscheinlich und war beim Next.js-Update am Ende auch nicht der Fall.</li>
<li>Eines Tages ließ ich mich überzeugen, <a href="https://www.npmjs.com/package/@reduxjs/toolkit">das Redux-Toolkit</a> einzubauen und im Zuge dessen das gesamte bisherige State-Management der App zu ersetzen. Viel Arbeit, keine Änderung an UI oder UX. Der neue Code war im Vergleich zu meinem vorher verwendeten DIY-Redux-Toolkit deutlich weniger idiosynkratisch, aber außerhalb des Codes gab es keine wahrnehmbare Änderung. Da mit dem Code nur Menschen in Kontakt kamen, die auch schon vorher wussten, wie das System funktioniert, war dieser Umbau nicht wirklich hilfreich.</li>
<li>Ich muss vermutlich nicht erklären, was mit dem frisch umgebauten State Management passiert ist, als ein Jahr später <a href="https://preactjs.com/blog/introducing-signals/">Signals</a> um die Ecke kamen. Verbesserungen an UI, UX und Feature-Set: keine.</li>
</ul>

<p>
Jetzt könnte man mir vorwerfen, dass ich hätte kommen sehen sollen, dass keiner dieser Umbauten mir weiterhelfen würde. Aber ich würde nicht sagen, dass mir nichts davon <em>geholfen</em> hat: das Next.js-Update mitzunehmen war nötig, um auch in Folge Updates mitgehen zu können und das mehrfache Neuschreiben hat das State Management ganz am Ende in einen hervorragenden Zustand gebracht! Letzteres lag vermutlich weniger an Redux-Toolkit oder Signals als eher daran, dass ich beim dritten Rewrite ein viel besseres Bild davon hatte, was ich zu erreichen versuchte&nbsp;… aber der Fortschritt an sich ist nicht wegzudiskutieren. Selbst wenn es keine wirklichen Verbesserungen gegeben hätte, so wäre es doch keine gute Idee gewesen, die Updates zu ignorieren, denn das frische Update von heute ist der etablierte Standard für die nächsten Updates der Dependencies von morgen.
</p>

<p>
Wir müssen aber auch festhalten, dass alle genannten Updates und Verbesserungen sich nur dort manifestiert haben, wo sie eine untergeordnete Rolle spielen: im Programmcode. Code ist nicht unwichtig, aber wenn wir Frontend-Web-Apps bauen, ist das Ziel ein anklickbares Produkt, in dem eine dritte Iteration von State Management nur mittelbar eine Rolle spielt. Das dazu nötige JavaScript, CSS und HTML kann sich dabei innerhalb eines relativ breiten Qualitätskorridors bewegen, ehe es zu einem Problem wird.
</p>

<p>
Nun bin ich, wie alle Hörer:innen von <a href="https://workingdraft.de/">Working Draft</a> wissen, weit entfernt davon, unkritisch jedem Hype hinterherzulaufen. Trotzdem war es mir, als ich mich in das React-Universum begeben hatte, nicht möglich, sich dem permanenten Neuschreiben und dem Dauer-Update von bereits Funktionierendem zu entziehen. Es gab jederzeit genug Neuerungen zum Einbau und genug Gründe (v.a. Kompatibilität aktueller und zukünftiger Dependencies untereinander) für all die Neuerungen. Am Ende hat mich das permanente Neuschreiben ausreichend genervt, um das betroffene Projekt eines Tages einfach abzubrechen.
</p>

<h3>Wo kommt das ständige Neuschreiben her?</h3>

<p>
Web-Frontends könnten eigentlich, wenn einmal gebaut, fast auf ewig unverändert weiter funktionieren. Browser sind gnadenlos abwärtskompatibel und wenn wir wollten, könnten wir auch heute noch Seiten aus <code>&lt;frameset&gt;</code> und <code>document.write()</code> zusammenbauen. Warum schreiben wir dann unsere Frontends (ganz oder in Teilen) ständig neu?
</p>

<p>
Neben den zweifellos nötigen Updates (neue Features im betroffenen Frontend oder globale Neuheiten, auf die regiert werden muss – z.&nbsp;B. Sicherheits-Features, Responsive Design oder Dark Mode) kommt meines Erachtens viel Rewrite daher, dass die meisten Web-Apps keine Berührungspunkte mehr mit der eigentlich so stabilen Web-Plattform haben. Das ist kein Zufall, denn der <em>Sinn und Zweck</em> von Tools wie React ist, die Plattform wegzuabstrahieren. Die Webstandards definieren allerlei unkomfortable APIs und statt eines konsistenten Big-Picture-Designs herrscht ein wildes Durcheinander von Paradigmen und Konventionen. Angesichts dessen ist eine komplette Neukonzeption natürlich sehr attraktiv. Chaos und OOP? Einfach React installieren und es herrschen ein klares Konzept und funktionale Programmierung!
</p>

<p>
Solche Abstraktionsschichten (und alle darauf aufbauenden Schichten, wie etwa clientseitige Router und UI-Toolkits) sind aber nicht, wie ein Webbrowser, der unbedingten Abwärtskompatibilität verpflichtet, wodurch sich ein Teil des permanenten Neuschreibens erklärt. Die Frontend-Frameworks dieser Welt haben aber auch ein ganzes Ökosystem um sich herum angesammelt, das nicht nur Software, sondern auch Ideen und Praktiken generiert – weit schneller, als die dem Ökosystem zugrundeliegenden Frameworks API-Änderungen produzieren. Abertausende Nerds finden jeden Tag neue Patterns und neue Best Practices und publizieren dazu Texte, Code und Videos ohne Unterlass. Wie es Software-Entwickler:innen angesichts dessen gelingen soll, nicht FOMO anheimzufallen (und andererseits nicht zum Fortschritts-Totalverweigerer zu werden), weiß ich nicht. Und mir ist es am Ende auch nicht besonders gut gelungen.
</p>

<p>
Zum Dauer-Rewrite im Frontend kommt meines Erachtens in drei Schritten:
</p>

<ol>
<li>Moderne Frameworks versuchen der Web-Plattform ein neues Paradigma überzustülpen, um ihre Nutzer:innen vor den Bugs und Inkonsistenzen der nativen APIs zu bewahren und die Implementierung von relevanten Patterns möglichst einfach zu machen.</li>
<li>Auf der frisch erschaffenen Plattform erwächst ein Ökosystem, das iterativ immer neue Tools und Patterns generiert, die die neu entstandene Plattform möglichst gut zu nutzen versucht. Es wird ausprobiert, verfeinert, verworfen, verbessert.</li>
<li>Entwickler:innen sehen, dass stets und ständig neue Tools und Patterns als aktuell gelten und verwenden Packages, die früher oder später diese neuen Tools und Patterns voraussetzen werden und versuchen, stets ihren gesamten Stack aktuell zu halten, statt (mögliche) technische Schulden anzuhäufen.</li>
</ol>

<p>
Keine der beteiligten Parteien macht bei irgendeinem dieser Schritte etwas wirklich falsch! Die Entwickler:innen des Frameworks wollen eine objektiv bessere Plattform bauen, die Entwickler:innen der Tools und Patterns versuchen, aus der neuen Plattform alles herauszuholen und die Entwickler:innen der Web-Apps tun verständlicherweise alles, um mit der Gesamtentwicklung Schritt zu halten. Das bedeutet aber auch, dass sich alle Beteiligten in einem Zustand des permanenten Experimentierens befinden und eine experimentelle Plattform ist keine stabile Plattform. Sie mag in mancherlei Hinsicht <em>besser</em> sein, aber diese Verbesserungen müssen jeden Tag neu erarbeitet und ausgebaut werden.
</p>

<p>
Es sei denn, man verwendet statt der besseren, aber weniger stabileren Plattform eine stabile Plattform und schluckt ein paar DX-Kröten.
</p>

<h3>Meine eigene, kleine Hütte im Wald</h3>

<p>
In der oberen Kreidezeit schreib’ ich einst, <a href="https://www.peterkroener.de/man-kann-nicht-kein-javascript-framework-verwenden/">dass man nicht kein JavaScript-Framework&nbsp;verwenden kann</a> und diese Aussage würde ich weiter so vertreten. Ein Framework besteht aus Entscheidungen, die durch Software in Form gegossen werden – und wenn man programmiert, trifft man Entscheidungen und schreibt Software. Früher oder später hat man ein Framework in der Hand, entweder ein im Vorfeld installiertes, ein am Anfang designtes oder ein im Laufe der Entwicklung passiertes. Um irgendeine Form von Struktur kommen wir nicht herum.
</p>

<p>
Wenn aber Entscheidungen und Software eigentlich genau das Metier von uns Softwareentwickler:innen sind, sollten wir eigentlich alle in der Lage sein, ein eigenes Frontend-Framework zu bauen. Diese Idee ist in einer Welt, in der Web Components existieren, gar nicht mal <em>so</em> absurd (denn immerhin müssen wir zumindest kein ganz neues Komponentenmodell erfinden) und eröffnet einige neue Möglichkeiten:
</p>

<ol>
<li>Wir können das Framework so gestalten, wie wir wollen und die Tradeoffs so wählen, wie wir möchten. Insbesondere können wir uns entscheiden, das Framework vergleichsweise <em>wenig</em> machen zu lassen und uns stattdessen mit den Macken der Web-Plattform arrangieren&nbsp;… und uns auf diese Weise Stabilität erkaufen. Falls uns gewisse Aspekte gar nicht interessieren (z.&nbsp;B. TypeScript oder Server-Side-Rendering), können wir diese komplett ignorieren und an sie kein einziges Byte und keinen einzigen Design-Kompromiss verschwenden.</li>
<li>Updates und Verbesserungen finden statt, wenn wir wollen, statt in Abhängigkeit von irgendwelchen Third-Party-Autoren von Frameworks oder Dependencies. Welche Teile des Frameworks wir selbst schreiben und welche Teile aus Drittpaketen stammen, entscheiden wir ebenfalls selbst.</li>
<li>Wir entkoppeln uns vom in anderen Framework-Ökosystemen grassierenden FOMO. Einerseits fehlt uns damit eine breite Auswahl an fertigen Plugins, die wir etwa im React-Universum vorfinden, und wir werden vieles mit mehr Aufwand als <code>npm install</code> lösen müssen. Andererseits werden wir uns auf diese Weise auch keinen permanent wackelnden Turm aus Dependencies aufbauen können, was sich langfristig auszahlen könnte.</li>
</ol>

<p>
Es versteht sich von selbst, dass es nicht für <em>jedes</em> Projekt und <em>jedes</em> Team möglich ist, ein eigenes Framework zu bauen, aber ich bin überzeugt, dass es für <em>viele</em> Teams und Projekte machbar und sinnvoll ist, sich ein pareto-optimales DIY-Framework zu leisten. Ein solches könnte die wildesten Tradeoffs machen, die sich keins der etablierten Frameworks je erlauben würde:
</p>

<ol>
<li>TypeScript komplett ignorieren und stattdessen originelle APIs bereitstellen, die TS nie verstehen würde? Kein Problem!</li>
<li>Spielt Performance eine untergeordnete Rolle? Warum dann nicht Dinge wie Templates oder JSX im Frontend kompilieren und sich den Compile-Schritt ersparen?</li>
<li>Neueste Web-Component-Standards mit Oldschool-Templating wie z.B. <a href="https://handlebarsjs.com/">Handlebars.js</a> kombinieren? Warum nicht!</li>
<li>Heutzutage unpopuläre Architekturen wie MVC oder Two-Way-Databinding aus der Gruft holen, weil sie gut auf das zu lösende Problem passen? Nur zu!</li>
</ol>

<p>
Das eigene kleine Framework muss nicht komplex oder vielseitig sein: <a href="https://gist.github.com/SirPepe/2d9a718b749a2ca6e68a634e2393491f">hier ist zum Beispiel der Code des Frameworks hinter Code.Movie</a>, alle 180 Zeilen inkl. Kommentaren und Whitespace. Das Ganze setzt sich wie folgt zuammen:
</p>

<ul>
<li>Dependency 1: <a href="https://sirpepe.github.io/use-ornament/">Ornament</a> für Web-Component-APIs,</li>
<li>Dependency 2: <a href="https://webreflection.github.io/uhtml/">uhtml</a> für Templating und DOM-Diffing,</li>
<li>Eine Basisklasse namens <code>BaseElement</code>, die <code>HTMLElement</code> erweitert, initialisiert das Shadow DOM und führt ein UI-Update aus, wenn sich auf der Klasse entsprechend dekorierte Accessors ändern,</li>
<li>Ein Decorator <code>@listen()</code> implementiert Event Delegation von Klicks und andere Events im Shadow DOM,</li>
<li>Der Ornament-Decorator <code>@define()</code> ist so erweitert, dass er Stylesheets laden und dem Shadow DOM bereitstellen kann</li>
</ul>

<p>
Das ganze „Framework“ besteht also aus einer Klasse, die zwei Dependencies zusammenknotet, drei Konventionen etabliert (UI lebt in Shadow DOM, lokaler State lebt in Accessors und das Templating ist in der Klassenmethode <code>render()</code> definiert) und den zwei Hilfsfunktionen bzw. Hilfs-Decorators <code>@listen()</code> und <code>@define()</code>. Die so entstehenden Web Components sehen wie folgt aus:
</p>

<pre class="prettyprint language-javascript">import {
  BaseElement,
  define,
  attr,
  literal,
  string,
} from &quot;../../framework/index.js&quot;;

@define(&quot;cm-page-layout&quot;, {
  sheets: [new URL(&quot;./styles.css&quot;, import.meta.url)],
})
export class CMPageLayout extends BaseElement {
  @attr(literal({ values: [&quot;page&quot;, &quot;docs&quot;], transform: string() }))
  accessor mode = &quot;page&quot;;

  render() {
    return this.html&#x60;&lt;div class=${this.mode}&gt;
  &lt;div class=&quot;sidebar&quot;&gt;&lt;slot name=&quot;sidebar&quot;&gt;&lt;/slot&gt;&lt;/div&gt;
  &lt;div class=&quot;main&quot;&gt;&lt;slot&gt;&lt;/slot&gt;&lt;/div&gt;
&lt;/div&gt;&#x60;;
  }
}</pre>

<p>
Das ist nicht so kompakt wie eine React-Komponente und (mangels TypeScript) nicht so typsicher wie es das Angular-Äquivalent wäre, aber auf jeden Fall ein relativ simples und einfach zu verstehendes Konstrukt. Die Syntax für das Einbinden von Stylesheets ist etwas umständlich und dass im Shadow DOM verwendete HTML-Tags manuell importiert werden müssen, nervt ein wenig. Es gibt kein SSR, keinen Router und nur einen sehr basalen Build-Schritt mit <a href="https://parceljs.org/">Parcel</a>, da das gesamte Projekt tendenziell eher eine MPA denn eine SPA ist. Komponenten enthalten diverse Ad-hoc-Lösungen für Probleme, die das Framework nicht berücksichtigt und es existiert ein nur mäßig gelungenes State-Management-System, das nochmals um die 290 Zeilen wiegt (dazu später mehr).
</p>

<p>
Diesen diversen Nachteilen steht gegenüber, dass das verwendete Framework nur 180 Zeilen und nur zwei austauschbare Mini-Dependencies zu Felde führt, auf Web Components und damit stabilen Webstandards basiert und einfach nur ein unspektakuläres Werkzeug ist&nbsp;… und daher mir bisher null Frontend-FOMO eingebracht hat! Seitdem das Framework vor ein paar Monaten ein Feature-Equilibrium gefunden hat, habe ich es nicht mehr angerührt. Ich kann damit Komponenten definieren, die einen Rahmen für UIs darstellen, aber auch flexibel genug sind, um Ad-hoc-Problemlösung zu ermöglichen. Ich will etwas machen, für das es keine Lösung aus der Dose gibt? Einfach den entsprechenden Code in die betroffene Klasse schreiben und fertig!
</p>

<h3>Vor- und Nachteile des DIY-Frameworks</h3>

<p>
Als ich mich dazu aufmachte, Code.Movie mit einem DIY-Framework neu zu schreiben, sahen meine Erwartungen wie folgt aus:
</p>

<ul>
<li>Weniger <code>npm update</code> durch weniger Dependencies und dadurch weniger permanentes Neuschreiben von vorhandener Funktionalität,</li>
<li>vergleichsweise wenig Entwicklungsaufwand am Framework, denn Web Components können ja <em>so</em> schwer nicht sein,</li>
<li>relativ viel Rad-Neu-Erfinden, denn es fehlt der vorher bestehende Zugriff auf das React-Ökosystem</li>
<li>etwas schlechtere Performance durch das Bevorzugen von einfachen Lösungen gegenüber besonders anspruchsvoll-optimierten Lösungen.</li>
</ul>

<p>
Diese Erwartungen wurden nur teilweise erfüllt. So hat sich etwa die <strong>Performance</strong> (v.a. Rendering und die reine JavaScript-Performance bei Interaktionen wie etwas Drag &amp; Drop) im Vergleich zum alten React-Projekt deutlich <em>verbessert.</em> Ich führe das darauf zurück, dass der neue Code im Vergleich einfach extrem stark reduziert ist. Kein Code ist so schnell wie kein Code, egal wie fleißig und trickreich die Performanceoptimierung auch ist. Die Ladeperformance ist im Vergleich zum Next.js-Projekt mit all seinem SSR und all seinen Optimierern auch ungefähr gleich geblieben. Weniger ist einfach mehr.
</p>

<p>
Überraschenderweise würde ich auch behaupten, dass ich nicht häufiger das <strong>Rad neu erfinden</strong> musste als mit der React-App. Viele Web-APIs wie Drag &amp; Drop, für die man in einem React-Projekt eigene Kompatibilitätsmodule benötigt, konnte ich einfach direkt benutzen! Einen Adapter für <a href="https://codemirror.net/">CodeMirror</a> musste ich tatsächlich selbst schreiben, aber das war dank origineller Anforderungen auch im React-Projekt der Fall.
</p>

<p>
Der <strong>Entwicklungsaufwand am Framework</strong> war etwas höher als erwartet, was primär daran lag, dass ich offenbar meinte, unbedingt einen besonders ausgebufften State Manager entwickeln zu müssen. Dieser hat eine API auf Basis von Decorators und verwendet unter der Haube proxybasiertes Change Tracking&nbsp;…
</p>

<pre class="prettyprint language-javascript">import { BaseElement, define, listen } from &quot;../../../framework/index.js&quot;;
import { PlaygroundState, languages } from &quot;../../../data/index.js&quot;;

@define(&quot;cm-project-panel&quot;, {
  sheets: [new URL(&quot;index.css&quot;, import.meta.url)],
})
export class CMProjectPanel extends BaseElement {

  // Trackt eine State-Variable im accessor #open
  @PlaygroundState.use(&quot;ui.projectPanel.open&quot;)
  accessor #open;

  // Event Listener für toggles auf &lt;details&gt; im Shadow DOM
  @listen(&quot;toggle&quot;, &quot;details&quot;)
  #handleToggle(evt) {
    // Neuer state? Einfach accessor updaten!
    // Löst Re-Render der betroffenen Komponenten aus usw.
    this.#open = evt.target.open;
  }
  
  // ... Render-Methode und viel mehr
}</pre>

<p>
&hellip; was eine ganz brauchbare Developer Experience bietet, aber auch ziemlich aufwendig war (ich ungebildeter Informatik-in-der-Realschule-Abwähler <a href="https://github.com/SirPepe/shed/blob/main/src/TrieMap.ts">musste eine eigene Trie-Implementierung entwickeln!</a>) und 290 LOC wiegt. Eine simplere Lösung um den Preis einer etwas umständlicheren API oder der Rückgriff auf einen de-facto-Standard wie Signals hätte mir in der Gesamtschau viel Zeit erspart.
</p>

<p>
Wenig überraschend gibt es im Projekt jetzt weniger <strong>Dependency-Updates</strong>, aber auch nicht null, denn ein Buildsystem inkl. Dev-Server gibt es weiterhin. Das Ende des permanenten Neuschreibens hat sich vielmehr dadurch eingestellt, dass ich nicht mehr Teil des Next.js/React-Ökosytems bin und mich daher in viel geringerem Maße von FOMO betroffen fühle. Ich koche mein eigenes Süppchen und radikale Umwälzungen wie der <a href="https://react.dev/learn/react-compiler">React-Compiler</a> und <a href="https://react.dev/reference/rsc/server-components">React Server Components</a> betreffen mich nicht mehr. Dieser Effekt ist <em>ausgesprochen</em> befreiend und ist meines Erachtens der eigentliche Grund, warum ich bei Code.Movie dieser Tage mehr an Features statt an Rewrites arbeiten kann.
</p>

<h3>Fazit: baut eure eigenen Frameworks!</h3>

<p>
Ich bin überzeugt, dass es für viele Projekte von Vorteil wäre, wenn sie sich aus dem Dauer-Rewrite-Zyklus der modernen Frontend-Entwicklung befreien könnten – auch für Projekte, die größer sind, als meine 1-Personen-MPA. Klar, React und Co bieten viel, aber sie kosten auch viel: viele Kilobytes, viel zu lernen und viel Aufwand bei entweder dem andauernden Neuscheiben vorhandener Features oder dem Umgang mit Frontend-FOMO. Viele Gründe, die normalerweise gegen DIY-Frameworks zu Felde geführt werden, greifen heutzutage im Frontend nicht mehr:
</p>

<ul>
<li>Ein minimales Framework hat nur wenige Zeilen Code, enthält nur die wichtigsten Features und ist damit keine signifikante Wartungs-Belastung.</li>
<li><a href="https://de.wikipedia.org/wiki/Truck_Number">Der Bus-Faktor</a> spielt bei ausreichend minimalistischer Konzeption keine Rolle, denn zwei Dependencies mit unter 200 Zeilen Glue Code am Funktionieren zu halten, dürfte für die meisten Teams im Bereich des Machbaren liegen.</li>
<li>Wenn das Framework auf Webstandards setzt (speziell Web Components) ist es kein Problem, Entwickler:innen hierfür anzuheuern, denn HTML kennt jeder und die Kombination aus einer Basisklasse und einer Handvoll Decorators und Hilf-Funktionen lässt sich jedem und jeder schnell erklären.</li>
</ul>

<p>
Diese Einwände lassen sich eher noch besser an die Adresse der etablierten Frontend-Frameworks richten. Ist die Wartungs-Belastung durch ein sich permanent updatendes Framework nebst Ökosystem rechtfertigen? Bedeutet ein großer Bus-Faktor und der Support durch eine große Firma nicht auch, dass es viele Personen (freidrehende CEOs, wegbeförderte Entwickler:innen) gibt, die das Projekt mit individuellen Entscheidungen in Schwierigkeiten bringen könnten? Sollte so etwas Zentrales wie das Framework des Projekts nicht unbedingt der eigenen Kontrolle unterliegen? Kann man es sich leisten, <em>nicht</em> auf die zu jeder anderen Technologie kompatiblen Web Components zu setzen?
</p>

<p>
Der Programmcode hinter der Code.Movie-Webseite hat sich nach dem Abwandern von React definitiv vom Ideal der modernen Frontend-Entwicklung entfernt. Es gibt ein wenig mehr Boilerplate, weniger Typsicherheit und die vielen Klassen und HTML-Strings gewinnen keinen Coolness-Preis. heutzutage für zentral befundene Features wie SSR fehlen komplett. Es handelt sich ohne Zweifel um eine auf die vorliegenden Anforderungen stark zugeschnittene 80/20-Lösung. Aber aus einer Reihe von Gründen – mit dem fehlenden Frontend-FOMO als dem meines Erachtens gewichtigsten Grund – fasse ich den einmal geschriebenen Code nur noch an, wenn <em>ich</em> daran etwas ändern möchte, das sichtbare Features betrifft. Endergebnis: deutlich mehr Produktivität und Entspanntheit. Und alles lädt und kompiliert so viel schneller! Man kann direkt mit den Web-APIs reden! Ich kann diesen Schritt jenen, die ihn sich leisten können, nur empfehlen.<img src="https://ssl-vg03.met.vgwort.de/na/cb8411fb1cb349b889ebc69c2a2e3dcd" alt="">
</p>]]></content:encoded>
</item>
<item>
<title><![CDATA[Code Challenge: Quicksort in 100% TypeScript-Typen]]></title>
<link>https://www.peterkroener.de/code-challenge-quicksort-in-typescript-typen/</link>
<description><![CDATA[<p>
Vor kurzem hat Chris Heilmann auf Mastodon einen Code-Challenge veröffentlicht: <a href="https://mastodon.social/@codepo8@toot.cafe/112768103792692350">Was ist der kürzeste Code, um eine Liste von Strings anhand des vierten Zeichens in den Strings zu sortieren?</a> Und da ich im Moment nichts Besseres zu tun habe (ernsthaft, ich habe gerade wirklich <em>nichts</em> zu tun; <strong><a href="mailto:peter@peterkroner.de">heuert mich an</a></strong>, ich hacke alles rund um Low-Level-Webtech wie JS/TS, mache Code-Reviews, coache Nerds etc.), habe ich mich natürlich sofort der Herausforderung gestellt.&#8230;</p>]]></description>
<pubDate>Tue, 16 Jul 2024 11:06:02 +0200</pubDate>
<guid isPermaLink="false">https://www.peterkroener.de/code-challenge-quicksort-in-typescript-typen/</guid>
<dc:creator>Peter Kröner</dc:creator>
<content:encoded><![CDATA[<p>
Vor kurzem hat Chris Heilmann auf Mastodon einen Code-Challenge veröffentlicht: <a href="https://mastodon.social/@codepo8@toot.cafe/112768103792692350">Was ist der kürzeste Code, um eine Liste von Strings anhand des vierten Zeichens in den Strings zu sortieren?</a> Und da ich im Moment nichts Besseres zu tun habe (ernsthaft, ich habe gerade wirklich <em>nichts</em> zu tun; <strong><a href="mailto:peter@peterkroner.de">heuert mich an</a></strong>, ich hacke alles rund um Low-Level-Webtech wie JS/TS, mache Code-Reviews, coache Nerds etc.), habe ich mich natürlich sofort der Herausforderung gestellt. Und zwar in reinen TypeScript-Typen, damit es bloß nicht zu einfach wird!
</p>

<h3>TypeScript-Typen als eigene Programmiersprache</h3>

<p>
Statt TypeScript-Typen bestimmungsgemäß zur Beschreibung von JavaScript-Programmen einzusetzen, können wir sie auch als ganz eigene Programmiersprache behandeln. Die relevanten Sprachkonstruktionen sind hierfür allesamt vorhanden:
</p>

<pre class="language-typescript prettyprint">// Typ-Alias ≈ Variable
type A = number;

// Generischer Typ ≈ Funktion
type B&lt;T&gt; = [T];

// Tuple ≈ Liste
type C = ["a", "b", "c"];

// Union ≈ Set
type D = "a" | "b" | "c";

// Conditional Type === Conditional
type E&lt;T&gt; = T extends 1 ? true : false;
type R1 = E&lt;1&gt;; // &gt; true
type R2 = E&lt;2&gt;; // &gt; false</pre>

<p>
Der wichtigste Bestandteil am Konzept „Typen als Programmiersprache“ sind die generischen Typen, die wir als Funktionen betrachten können. Ähnlich wie JavaScript-Funktionen sind generische Typen <em>technisch gesehen</em> einfach nur normale Typen, aber praktisch gesehen würde besteht ihr einziger Sinn darin, mit Parmetern gefüttert zu werden um neue Ergebnisse zu erzeugen:
</p>

<pre class="language-typescript prettyprint">// Variablen
let a = 42;
type A = number;

// Funktionen
let b = (x) =&gt; [x];
type B&lt;X&gt; = [X];</pre>

<p>
<code>a</code> und <code>A</code> sind beides normale „Variablen“, die für sich genommen nützlich sind, während <code>b</code> und <code>B</code> wie Funktionen arbeiten und verwendet werden. Kein Web-UI wird je den Inhalt der Variablen <code>b</code> anzeigen und kein konkretes JS-Objekt wird je den Typ <code>B</code> haben. Wenn man aber <code>b</code> oder <code>B</code> mit Inputs füttert, liefern sie nützliche Werte oder Typen – Funktionen eben!
</p>

<p>
Mit Variablen, Funktionen und Conditionals haben wir alle nötigen Programmier-Zutaten zur Hand. Schwierig am Arbeiten in reinen TypeScript-Typen sind (wenn wir die manchmal auch nicht ganz triviale Frage des „warum“ ausklammern) eigentlich nur zwei Dinge:
</p>

<ol>

<li>TS-Typen funktionieren wie eine rein funktionale Programmiersprache. Es gibt weder imperative Sprachkonstrukte (<code>if</code>, <code>for</code> o.&nbsp;Ä.), noch eine wirklich gute Möglichkeit temporäre Variablen anzulegen, noch Features für Debugging. Das ganze „Programm“ ist ein unmittelbar von entweder dem Editor oder dem TypeScript-Compiler ausgewertetes Gleichungssystem, was etwas gewöhnungsbedürftig ist.</li>
<li>Das Typsystem ist eine domänenspezifische Sprache für die Beschreibung von JavaScript-Programmen. Alles außerhalb dieser Domäne (die im Wesentlichen aus der Frage „kann Typ A der Variablen B zugewiesen werden“ besteht) ist nur mit großem Aufwand möglich. Strings manipulieren? Sehr schwierig. Zahlen addieren? Fast unmöglich.</li>

</ol>

<p>
Wie können wir unter diesen Bedingungen zwei Strings anhand ihres vierten Zeichens sortieren? Mit gar nicht mal so viel Aufwand, <em>wenn</em>  wir es schaffen, String-Vergleichbarkeit herzustellen.
</p>

<h3>Das Vergleichbarkeits-Problem</h3>

<p>
Da Chris’ Aufgabenstellung den relevanten Zeichensatz auf ASCII-Kleinbuchstaben zu beschränken scheint, könnte uns als erste Idee eine Lookup-Tabelle in den Sinn kommen, mit der wir einzelne Zeichen über den Umweg ihres ASCII-Codes miteinander vergleichbar machen können:
</p>

<pre class="language-typescript prettyprint">type ASCII = {
  a: 97,
  b: 98,
  c: 99,
  ...
};</pre>

<p>
Einfach das vierte Zeichen der zu vergleichenden Strings mithilfe der Tabelle in Zahlen übersetzen und dann einen Größenvergleich machen. Das Haken daran ist, dass ein Vergleich von zwei Zahlen genau das ist, was TypeScript <em>nicht</em> kann. Die einzige Beziehung zwischen zwei „Werten“ (Typen), die TypeScript kennt, ist die Zuweisungskompatibilität, ein Konzept von „größer als“ gibt es hingegen nicht. Für TypeScript sind die Zahlen <code>1</code> und <code>7</code> wie <code>true</code> und <code>false</code> – sie sind einfach <em>unterschiedliche Werte</em> und haben darüber hinaus keine weiteren Beziehungen wie eben „größer als“ zueinander.
</p>

<p>
Wie können wir dann modellieren, ob „a“ vor oder nach „b“ kommen soll? Es gibt nur einen Ausweg: wenn alles, was uns TypeScript sagen kann, ist, ob ein „Wert“ (Typ) einem anderen „Wert“ (Typ) zugewiesen werden kann, müssen wir einen Weg finden, die Frage der Buchstaben-Sortierung in eine Frage der Typ-Zuweisungskompatibilität zu übersetzen.
</p>

<p>
Mein Plan bestand darin, zunächst einen Tuple-Typ mit dem unterstützten Zeichensatz anzulegen:
</p>

<pre class="language-typescript prettyprint">type Charset = ["a", "b", "c", ...];</pre>

<p>Alle Buchstaben manuell aufzulisten wäre recht mühsam, weshalb ich eine generische String-Typ-Split-Funktion aus meinem Code-Giftschrank gekramt habe:</p>

<pre class="language-typescript prettyprint">type Split&lt;T, R extends string[] = []&gt; = T extends `${infer First}${infer Rest}`
  ? Rest["length"] extends 0
    ? [First, ...R]
    : [First, ...Split&lt;Rest, R&gt;]
  : [];</pre>

<p>
Die Syntax sieht ein wenig wild aus, beschreibt aber eigentlich nur eine rekursive Funktion:
</p>

<ol>

<li>Die „Funktion“ <code>Split&lt;T, R&gt;</code> hat die zwei Parameter <code>T</code> und <code>R</code>, wobei <code>R</code> auf Subtypen von <code>string[]</code> beschränkt ist und mit einer leeren Liste belegt wird, wenn der Parameter nicht explizit angegeben wurde. Die Klausel <code>extends string[]</code> fungiert praktisch als Typ für den Typ (-Parameter) <code>R</code>.</li>

<li>Mit einem Conditional Type prüft die Funktion, ob <code>T</code> dem String Typ <code><code>${infer First}${infer Rest}</code></code> zuweisbar ist. Das ist der Fall, wenn T ein String ist und es der Typinferenz möglich ist, mindestens ein Zeichen am Anfang des Strings zu identifizieren (<code>infer First</code>). Ist das nicht der Fall, liefert die Funktion ein leeres Tuple.</li>

<li>Danach wird geprüft, ob der auf <code>First</code> folgende String-Rest (<code>infer Rest</code>) eine Länge gleich <code>0</code> hat. Man beachte: Das ist <em>kein</em> Größenvergleich, sondern nur die Feststellung, ob <code>Rest["length"]</code> zuweisungskompatibel zu <code>0</code> ist, was nur der Fall ist, wenn der String aus 0 Zeichen besteht. Sollte das der Fall sein, besteht der String offenbar nur aus nur dem Zeichen <code>First</code> und es wird ein Tuple-Typ mit <code>First</code> und dem Inhalt des Tuple-Typs <code>R</code> zurückgegeben.</li>

<li>Wenn der Rest-String nicht leer ist, liefert die Funktion einen Tuple-Typ mit <code>First</code> und dem, was <code>Split&lt;Rest, R&gt;</code> liefert.</li>

</ol>

<p>
Anders gesagt: Die Funktion schneidet von einem String-Typ <code>T</code> das erste Zeichen ab und ruft sich selbst wieder auf, um vom Rest-String das nächste erste Zeichen abzuschneiden – und immer so weiter. <code>R</code> wird nur verwendet, um die Liste der vorher abgeschnittenen Zeichen bei der Rekursion durchzureichen. Und <code>Split&lt;T, R&gt;</code> <em>ist</em> eine ganz klassische rekursive Funktion: einen gibt einen Fall für keinen Input (leerer String = leeres Tuple), einen für <em>einen</em> Input (String mit einem Zeichen = Tuple mit einem Zeichen) und einen Fall für alles mit mehr als einem Input (String mit N Zeichen = Tuple mit dem ersten Zeichen darin plus dem Ergebnis der Rekursion über den Rest).
</p>

<p>
Mit dieser Split-Funktion ist der Zeichensatz einfach und schnell erstellt:
</p>

<pre class="language-typescript prettyprint">type Charset = Split&lt;"abcdefghijklmnopqrstuvwxyz"&gt;;
// &gt; ["a", "b", "c", ..., "y", "z"]</pre>

<p>
Ähnlich wie ein JavaScript-Array ist das Tuple eine Sammlung von Elementen in einer definierten Reihenfolge, in der Einträge doppelt und dreifach vorkommen können. Und ebenfalls wie in JS können wir Elemente anhand ihres Index aus dem Tuple herauskramen; <code>Charset[1]</code> liefert <code>"b"</code>.
</p>

<p>
Aber wie hilft uns das für Vergleiche weiter?
</p>

<h3>Von Tuples zu Unions</h3>

<p>
Die Domäne der TypeScript-Typen ist Zuweisungskompatibilität. Ein Typ <code>A</code> ist einem Typ <code>B</code> zuweisbar, wenn <code>A</code> entweder gleich <code>B</code> oder ein Subtyp von <code>B</code> ist:
</p>

<pre class="language-typescript prettyprint">type R0 = 1 extends number ? true : false;
// true: 1 ist number zuweisbar

type R1 = "a" extends number ? true : false;
// false: "a" ist keine number

type R2 = { x: number, y: any } extends { x: number } ? true : false;
// true: { x: number, y: any } ist Subtyp von { x: number }</pre>

<p>Union Types sind Sets von Typen:</p>

<pre class="language-typescript prettyprint">type A = number;    // Set aller Zahlen
type B = 1 | 2;     // Set der Zahlen 1 und 2
type C = 1 | 2 | 3; // Set der Zahlen 1, 2 und 3</pre>

<p>
Hier gilt, dass Subsets Supertypen von Supersets sind und entsprechend Subsets Supersets zuweisbar sind:
</p>

<pre class="language-typescript prettyprint">type Subset = 1 | 2;
type Superset = 1 | 2 | 3;

type R0 = Subset extends Superset ? true : false;
// true: "1 | 2" ist "1 | 2 | 3" zuweisbar

type R1 = Superset extends Subset ? true : false;
// false: "1 | 2 | 3" ist nicht "1 | 2" zuweisbar</pre>

<p>
An dieser Stelle funktioniert <code>extends</code> wie ein Ist-Subset-von-Operator. Und das ist für unsere Buchstaben-Vergleichbarkeit extrem hilfreich, wenn wir es schaffen, für ein gegebenes Zeichen ein <em>Set der im Alphabet vor diesem Zeichen stehenden Zeichen</em> zu bilden! Für <code>"c"</code> wäre das das Set <code>"a" | "b"</code>, während das Set für das Zeichen <code>"d"</code> aus <code>"a" | "b" | "c"</code> besteht. Da das erste Set ein Subset des zweiten Sets ist, ist ersteres letzterem zuweisbar, was wir als „c kommt vor d“ interpretieren können!
</p>

<pre class="language-typescript prettyprint">type CharsBeforeC = "a" | "b";
type CharsBeforeD = "a" | "b" | "c";

type CBeforeD = CharsBeforeC extends CharsBeforeD ? true : false;
// &gt; true</pre>

<p>
Zu diesen Unions von Zeichen vor einem gegebenen Zeichen kommen wir über eine weiter rekursive Typ-Funktion, die sich so lange durch das Zeichensatz-Tuple arbeitet, bis es ein bestimmtes Zeichen in diesem Tuple erreicht hat:
</p>

<pre class="language-typescript prettyprint">type ValueOf&lt;Needle, List extends string[] = []&gt; = Charset[List["length"]] extends Needle
  ? [...List, Charset[List["length"]]][number]
  : ValueOf&lt;Needle, [...List, Charset[List["length"]]]&gt;;

type CharsBeforeC = ValueOf&lt;"c"&gt;;
type CharsBeforeD = ValueOf&lt;"d"&gt;;

type CBeforeD = CharsBeforeC extends CharsBeforeD ? true : false;
// &gt; true</pre>

<p>
Der Aufbau gleicht der <code>Split</code>-Funktion, mit nur drei Unterschieden:
</p>

<ol>

<li><code>ValueOf</code> arbeitet sich durch einen Tuple-Typ statt durch einen String, verwendet aber die gleiche rekursive Element-vom-Anfang-abschneiden-Technik.</li>

<li>Die Abbruchbedingung ist nicht mehr ein komplettes Durcharbeiten des Inputs, sondern die Rekursion endet, wenn ein Zeichen gleich des Such-Parameters <code>Needle</code> gefunden wird.</li>

<li>Statt eines Tuples liefert die Funktion eine Union der Tuple-Members.</li>

</ol>

<p>
Ein Tuple funktioniert in TypeScript ziemlich genau wie ein JavaScript-Array und wir können entsprechend seine Einzel-Indizes abfragen:
</p>

<pre class="language-typescript prettyprint">type Stuff = ["Apples", "Oranges"];
type First = Stuff[0]; // &gt; "Apples"
type Last = Stuff[1]; // &gt; "Oranges"</pre>

<p>
Weil aber in TypeScript-Typen alle Operationen sowohl mit Einzel-Typen als auch mit Sets von Typen (d.&nbsp;h. Unions) durchgeführt werden können, können wir ein Tuple auch mit dem Typ <code>number</code> (dem Set aller Zahlen) indizieren und bekommen als Ergebnis eine Union aller Inhalte des Tuples:
</p>

<pre class="language-typescript prettyprint">type Stuff = ["Apples", "Oranges"];
type All = Stuff[number]; // &gt; "Apples" | "Oranges"
// Entspricht type All = Stuff[0] | Stuff[1]</pre>

<p>
Wenn wir uns nun noch eine Funktion bauen, die uns für einen gegebenen String das Zeichen an einem gegebenen Index liefern kann&nbsp;…
</p>

<pre class="language-typescript prettyprint">type CharAt&lt;T extends string, I extends number&gt; = Split&lt;T&gt;[I];

type First = CharAt&lt;"hello", 0&gt;; // &gt; "h"
type Second = CharAt&lt;"hello", 1&gt;; // &gt; "e"</pre>

<p>
… ist es fast schon trivial, eine Funktion zu schreiben, die für die von Chris gestellte Anforderung einen Vergleich durchführt:
</p>

<pre class="language-typescript prettyprint">type Comparator&lt;A extends string, B extends string&gt; = ValueOf&lt;CharAt&lt;A, 3&gt;&gt; extends ValueOf&lt;CharAt&lt;B, 3&gt;&gt;
  ? ValueOf&lt;CharAt&lt;B, 3&gt;&gt; extends ValueOf&lt;CharAt&lt;A, 3&gt;&gt;
    ? 0
    : 1
  : -1;

type A = Comparator&lt;"aaaa", "bbbb"&gt;; // &gt; 1
type B = Comparator&lt;"zzzz", "yyyy"&gt;; // &gt; -1
type C = Comparator&lt;"ffff", "ffff"&gt;; // &gt; 0</pre>

<p>
Kommt A vor B, bekommen wir <code>1</code>, für B vor A gibt’s <code>-1</code>, und bei Gleichheit wird <code>0</code> ausgespuckt. Wären wir in normalem JavaScript unterwegs, könnten wir diese Funktion in <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort">Array.prototype.sort</a> verwenden und hätten die Aufgabe bewältigt. Aber natürlich gibt es auf Ebene von TypeScript-Typen keine eingebaute Sortierfunktion, sodass wir uns auch hierum selbst kümmern müssen. Das stellt sich allerdings als das kleinste Problem heraus.
</p>

<h3>Quicksort und Haskell</h3>

<p>
<a href="https://de.m.wikipedia.org/wiki/Quicksort">Quicksort</a> ist ein rekursiver Sortieralgorithmus und damit in TypeScript-Typen wunderbar einfach umzusetzen:
</p>

<pre class="language-typescript prettyprint">type Quicksort&lt;List extends string[]&gt; =
  List extends [infer First extends string, ...infer Rest extends string[]]
    ? [...Quicksort&lt;FilterLte&lt;Rest, First&gt;&gt;, First,...Quicksort&lt;FilterGt&lt;Rest, First&gt;&gt;]
    : [];</pre>

<ol>

<li>Erste Zeile: Funkionssignatur-Äquivalent</li>

<li>Zweite Zeile: Aufteilung von nicht leeren Input-Liste in ein erstes Element <code>First</code> und den Rest <code>Rest</code></li>

<li>Dritte Zeile: Der Rest der Liste <code>Rest</code> wird in eine Liste von Elementen kleiner oder gleich <code>First</code> und eine Liste größer <code>First</code> einsortiert, die Teillisten werden per Rekursion sortiert und via Spread-Operator mit <code>First</code> in der Mitte verkettet</li>

<li>Vierte Zeile: falls die Input-Liste leer ist, eine leere Liste zurückgeben</li>

</ol>

<p>
Da weder Algorithmen noch funktionales Programmieren besondere Stärken von mir sind, habe ich <a href="https://de.m.wikipedia.org/wiki/Quicksort#QuickSort_in_funktionalen_Sprachen">aus dem Wikipedia-Artikel zu Quicksort eine Haskell-Implementierung gemopst</a> und nach TypeScript portiert. Haskell ist auch eine rein funktionale Sprache und meist gut genug lesbar, um als Quell der Inspiration zu dienen. Ich bediene mich für meiner TypeScript-Mentalgymnastik öfter an Algorithmen und Verfahren aus Haskell – man soll schließlich nicht ständig das Rad neu erfinden!
</p>

<p>
Was nun noch fehlt, sind die Filter-Funktionen <code>FilterLte&lt;List, X&gt;</code> (<code>List</code> auf alle Werte kleiner oder gleich <code>X</code> reduzieren) und  <code>FilterGt&lt;List, X&gt;</code> (<code>List</code> auf alle Werte größer <code>X</code> reduzieren), doch die sind, da wir schon eine Vergleichsfunktion haben, schnell gebaut:
</p>

<pre class="language-typescript prettyprint">type FilterGt&lt;List extends string[], Element extends string&gt; =
  List extends [infer First extends string, ...infer Rest extends string[]]
    ? Comparator&lt;First, Element&gt; extends -1
      ? [First, ...FilterGt&lt;Rest, Element&gt;]
      : FilterGt&lt;Rest, Element&gt;
    :[];

type FilterLte&lt;List extends string[], Element extends string&gt; =
  List extends [infer First extends string, ...infer Rest extends string[]]
    ? Comparator&lt;First, Element&gt; extends -1
      ? FilterLte&lt;Rest, Element&gt;
      : [First, ...FilterLte&lt;Rest, Element&gt;]
    : [];</pre>

<p>
Es ist zweimal der fast gleiche Code, nur wird einmal <code>First</code> behalten, wenn die Vergleichsfunktion <code>-1</code> liefert und einmal weggeworfen. Da sich nun herausstellt, dass die Vergleichsfunktion nur den Fall „A kleiner B“ identifizieren können muss, können wir sie noch ein wenig vereinfachen …
</p>

<pre class="language-typescript prettyprint">type Comparator&lt;A extends string, B extends string&gt; =
  ValueOf&lt;CharAt&lt;A, 3&gt;&gt; extends ValueOf&lt;CharAt&lt;B, 3&gt;&gt; ? 1 : -1;
// Kein Anlass, Gleichheit von A und B gesondert zu behandeln</pre>

<p>
<a href="https://www.typescriptlang.org/play/?#code/C4TwDgpgBAymA2BLYAeAKgGigJShAHsBAHYAmAzlOcAE6LEDmA2gLpQC8UrAfB1GnkIkKUAAYASAN70AZhBpQAYohrUAvlNnycEdaIBQUI1AD8O6kwBE8Eg2AALS2wJEylAAyHj3s02WrgLAA6EOwWL28jAC4uf2pgkLgkVGxdQJxucO8Y1gBufX1QSCgAYXsAQ1UIYD4k5BRLcoAjAGNSCBkGe0QAKwBreABbYgB7MABHAIBXADcAd3wQAC9LbnzC8GgANXL4KYgAeRkUADkICFIbLAAZRGpBVxFqOkZWPh4OCNKKquAmW4s1lsDicziEbigZwuNi+Pi4ISCAPSZUq5Gq-zufyBjBBLDxTGIU0GTXkWUi0SgOz2h2OUMuECwTARSKwKN+GMBNhxjjxLDWBSK0DZAEFUAIXMJKM96AwsABJB6SqCE4nyXicOpi7hMOUsdaC0ojQZgSrlYAjGgoYWKiHSxhYABCNqetBl6q+VP2RxQItQwqwAGZuLwJRDPTSfT9RSgHYHg6YoABGKAxAC0if1myUiHgRBoAHFUEjnVLXa8WFgAKI2QYkGqhl0vBju7zFhuUJhaBRxevgxsyhJBLvmXuPUtN1hk8lmEpGk00M0WlA9qs1ushvuUdOw8kJvwqeJQBHKXPyQsoVKH6sQWvEYCZHfkmInvPny-pa+3++PlN5AVZl95GuIgUDbTcqDLZgKygT86xLCCmxbYwwLHLhhx7eC7VlI8QmHd9MMgycfxnOdTXNS0Vxgtc7w3VDt13SIzEAmhgIgC80lXG91x-bJYgPdJjxzPNWPYq9qPvKdIhyPV-2KABFKZEBaPpyAtItMQIic+U+VsNPbNDiDkbt+M0gccKHQztHw-SsKIhjfARBSlJUtTlyEoCQPfLAe2Dbz+IwRzFOU1SaFQZi3w47MAmDSTeL-DZikvKZc2TTgnOC1yrCabKmksLBGnKQq8qgSxSDK0hissFpqpaSrznOJx+QAeiaqBeCsQqivynLcvymravy8qKvy+qICcWToCS3MACY+HSlzQpQKxnnKOYSRoGgQEq+wICQFoxjzSq5l2XRKvKfaaGIRr8hatquEsFa1vkTajpO8htt2pSDvkM6LqulggA">... und schon haben wir Quicksort in TypeScript fertig implementiert und Chris&#39; Code Challenge bewältigt:</a>
</p>

<pre class="language-typescript prettyprint">type Result1 = Quicksort&lt;["bbbb", "aaaa", "dddd", "cccc", "eeee"]&gt;;
// &gt; ["aaaa", "bbbb", "cccc", "dddd", "eeee"]

type Result2 = Quicksort&lt;["strawberry", "helicopter", "wales", "acorn"]&gt;;
// &gt; ["strawberry", "wales", "helicopter", "acorn"]</pre>

<h3>Was lernen wir daraus?</h3>

<p>
Zugegeben: für das Beschreiben der Typen des durchschnittlichen JavaScript-Programms – also das, wofür TypeScript <em>eigentlich</em> gedacht ist – braucht es vermutlich keine Sortieralgorithmen. Ich finde aber, dass die Übung zwei sehr relevante Aspekte von Typ-Level-Programmierung in TS aufzeigt.
</p>

<p>
Vorletzte Woche war ich auf einer etwas enterprisigen Konferenz und im Rahmen eines TypeScript-Talks warf jemand aus dem Publikum ein, dass das gezeigte Typ-Gehacke (das deutlich weniger abgefahren als der Inhalt dieses Artikels war) ja wohl „komplett unlesbar“ und daher „in ernsthaften Projekten“ komplett unbrauchbar sei. Einmal abgesehen davon, dass die einzige Alternative zu Typ-Level-Gebastel <code>any</code>, die <i lang="fr">bête noire</i> der „ernsthaften Entwickler“ ist, würde ich sagen, dass Leserlichkeit nicht das relevante Problem ist. Nicht zuletzt durch die sehr einfache Übernahme des Quicksort-Algorithmus aus Haskell wird deutlich, dass die Struktur eines Typ-Programms gar nicht mal so seltsam ist. Haskell ist freilich nicht ganz so Mainstream wie Java oder JavaScript, aber auch kein totaler Exot – es ist einfach eine funktionale Programmiersprache und damit, sobald man sein Hirn auf Rekursion eingestellt hat, nicht komplett undurchdringbar oder gar „unlesbar“.
</p>

<p>
Zum Anderen sehen wir hier aber auch, wo die Grenzen der Programmiersprache „TypeScript-Typen“ wirklich liegen: nämlich in der Tatsache, dass es sich um eine domänenspezifische Sprache für die Beschreibung von JavaScript-Programmen handelt. Alles, was TS-Typen beschreiben können, ist die Kompatibilität von Typen zu anderen Typen. Nichts jenseits von basaler Set-Logik existiert. Die Verrenkungen, die ich anstellen musste, um zu ermitteln, ob der Buchstabe „a“ im Alphabet vor „b“ steht, fand ich tatsächlich nicht unerheblich. Das Bemerkenswerteste an <a href="https://github.com/codemix/ts-sql">ts-sql</a> (einer zu 100% in TypeScript-Typen implementierten Mini-SQL-Datenbank) ist nicht der Query-Parser, sondern der manuell angelegte Typ <code>Integers</code>:
</p>

<pre class="language-typescript prettyprint">type Integers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ..., 256]</pre>

<p>
TypeScript hat einfach <em>kein Konzept von Zahlen</em>, und sobald man irgendwas mit Zahlen anstellen möchte, wird der Aufwand enorm und absurd.
</p>

<p>
An sich sind die Type-Level-Programmier-Features von TypeScript schon extrem praktisch zu Beschreibung von sehr dynamischen JavaScript-Programmen. Auch wenn TS längst nicht jeden Aspekt von JS modellieren kann, so kann es doch <em>sehr</em> viel. Nutzt man diese Möglichkeiten nicht, schränkt man sich in der Nutzung von dem, was TypeScript eigentlich kann, unnötig stark ein. Ich würde sogar so weit gehen, dass TypeScripts diverse Nachteile bzgl. Kosten (komplexere Toolchain, Breaking Changes, Kompilier-Zeitaufwand) nur dann überkompensiert werden, wenn man sich seiner kompletten Feature-Bandbreite bedient und mithilfe von Typ-Hacking eine Kombination aus Typsicherheit <em>und</em> Flexibilität herstellt.
</p>

<p>
Aber wie immer bei der Programmierung ist irgendwann ein Punkt erreicht, an dem Aufwand und Ertrag in keinem Verhältnis mehr zueinander stehen. Der Zwischenrufer auf der Enterprise-Konferenz sah diesen Punkt offenbar in der originellen Syntax der TypeScript-Typen erreicht – daher der Einwand der „Unlesbarkeit“. Ich würde diesen Punkt aber eher im mit der Komplexität größer werdenden Semantik-Delta zwischen dem, wofür TypeScript gemacht ist (JavaScript beschreiben) und dem, was es <em>so gerade eben kann</em>, verorten. Das Quicksort-Typ-Programm, das wir in diesem Artikel gesehen haben, besteht eigentlich nur ein paar fast identisch aufgebauten rekursiven Funktionen. Sein Kern, die Quicksort-Funktion, sieht noch nicht mal besonders seltsam aus. Die Syntax ist nicht das Problem. Ein Problem entsteht erst, wenn wir gegen unsere Tools ankämpfen müssen, um unsere Ziele zu erreichen – und dieser Punkt ist meiner Meinung nach dann erreicht, wenn wir TypeScript über die Bedeutung von Strings und Zahlen räsonieren lassen wollen. Die Limitierungen von TypeScript machen an dieser Stelle Dinge, die in anderen Sprachen einfach sind, vielleicht nicht unleserlich, aber fast unverständlich und damit auch nicht wirklich empfehlenswert.<img src="https://ssl-vg03.met.vgwort.de/na/81df877346084d25a77719a00a87e591" width="1" height="1" alt="">
</p>]]></content:encoded>
</item>
<item>
<title><![CDATA[Code.Movie]]></title>
<link>https://www.peterkroener.de/code-movie/</link>
<description><![CDATA[<figure class="right"><img src="https://www.peterkroener.de/uploads/2024/code-movie.svg" alt="" title=""></figure>

<p>
Mein <strong>animierter Syntax-Highlighter <a href="https://code.movie/">Code-Movie</a></strong> ist veröffentlicht! Eine einfache JavaScript-API generiert aus Codeschnipseln HTML und CSS, das ohne Laufzeit-JS Schritt-für-Schritt-Animationen darstellt. Einfach durch die Klassen <code>frame0</code> bis <code>frameN</code> durchschalten (oder dazu die <a href="https://github.com/CodeMovie/code-movie-runtime">Wrapper-Komponente</a> verwenden) und den Rest übernehmen standardkonforme&#8230;</p></img>]]></description>
<pubDate>Mon, 01 Jul 2024 13:41:00 +0200</pubDate>
<guid isPermaLink="false">https://www.peterkroener.de/code-movie/</guid>
<dc:creator>Peter Kröner</dc:creator>
<content:encoded><![CDATA[<figure class="right"><img src="https://www.peterkroener.de/uploads/2024/code-movie.svg" alt="" title=""></figure>

<p>
Mein <strong>animierter Syntax-Highlighter <a href="https://code.movie/">Code-Movie</a></strong> ist veröffentlicht! Eine einfache JavaScript-API generiert aus Codeschnipseln HTML und CSS, das ohne Laufzeit-JS Schritt-für-Schritt-Animationen darstellt. Einfach durch die Klassen <code>frame0</code> bis <code>frameN</code> durchschalten (oder dazu die <a href="https://github.com/CodeMovie/code-movie-runtime">Wrapper-Komponente</a> verwenden) und den Rest übernehmen standardkonforme CSS-Transitions:
</p>

<iframe style="box-shadow: none; font-family: monospace; font-size: 14px; height: calc(19 * 2.5ch); width: 55ch" scrolling="no" title="Code.Movie Selbstanimierte Demo" src="https://www.peterkroener.de/uploads/2024/animation-demo.html" frameborder="no" allowtransparency="true" allowfullscreen="true"></iframe>

<p>
Jeder Aspekt des Outputs (Farben, Typografie, Zeilennummern etc.) ist per CSS anpassbar und mit ein bisschen Wrapper-HTML und -CSS bleibt kein Gestaltungswunsch offen. Die meisten Styling-Optionen sind auf der <a href="https://code.movie/docs/styling.html">Dokumentations-Seite</a> aufgelistet. Der Code des aktuell angezeigten Frames kann ganz normal markiert und kopiert werden.
</p>

<p>
Die ersten Iterationen von Code.Movie entstanden ca. 2017, als ich für meine Trainer-Tätigkeit absurd viele Präsentationen rund um Code aus dem Boden stampfen musste. In Powerpoint o.Ä. mit Code-Screenshots zu hantieren, war mir einerseits zu umständlich und andererseits erschienen mir statische Code-Beispiele auch didaktisch eher supoptimal. Statischer Code sieht nicht nur langweilig aus, sondern harte Wechsel zwischen verschiedenen Code-Beispielen sind schlecht nachzuvollziehen. Moderne User Interfaces sind voll mit Animationen, damit wir verstehen, was im UI vor sich geht – und ebenso sollte präsentierter Code animiert sein, damit Refactorings, Tutorials und der Aufbau von Beispielen nachvollziehbar ist. Also schrieb ich eine animierte Syntax-Highlighter-Software, deren dritte Iteration nun erstmals öffentlich zugänglich ist.
</p>

<p>
Code.Movie ist der dritte kompletter Rewrite des Grundkonzepts und sehr unfertig. Es gibt unzählige Features, die bisher nicht reif für die Öffentlichkeit sind (z. B. Code-Dekorationen und Animations-Beeinflussung) und es gibt nur sehr wenige unterstützte Programmiersprachen. Auch die Webseite ist eher rudimentär, aber dokumentiert alles Wesentliche und enthält sogar einen <a href="https://code.movie/play.html">Online-Playground!</a> Sollte sich Interesse am Projekt manifestieren, können die Features und unterstützten Sprachen schnell mehr werden. Lasst mich wissen, welche Features und Sprachen ihr gebrauchen könntet und was eure Use Cases sind!
</p>]]></content:encoded>
</item>
<item>
<title><![CDATA[TIL-Roundup, Februar 2024: Formulare, Compression Streams, Adopted Stylesheets]]></title>
<link>https://www.peterkroener.de/til-roundup-februar-2024-formulare-compression-streams-adopted-stylesheets/</link>
<description><![CDATA[<p>
Auch im letzten Monat haben lautes Nachdenken und lebhafter Austausch auf <a href="https://mastodon.social/@sir_pepe">Mastodon</a> dazu geführt, dass ich einiges über HTML, CSS und JavaScript/DOM erfahren habe, das mir vorher nicht klar war. Und da Mastodon noch immer nicht die intergalaktische Total-Dominanz ausübt, die ihm eigentlich zusteht, kehre ich die gesammelten Erkenntnisse an dieser Stelle nochmals zusammen. Das behalte ich ab nun auch bei, bis ihr <em>alle</em> mir dort folgt.
</p>

<h3>Formulare in Formularen? Jain! (und mit Browserbugs)</h3>

<p>
Ich habe schon vor langer&#8230;</p>]]></description>
<pubDate>Tue, 02 Apr 2024 14:02:00 +0200</pubDate>
<guid isPermaLink="false">https://www.peterkroener.de/til-roundup-februar-2024-formulare-compression-streams-adopted-stylesheets/</guid>
<dc:creator>Peter Kröner</dc:creator>
<content:encoded><![CDATA[<p>
Auch im letzten Monat haben lautes Nachdenken und lebhafter Austausch auf <a href="https://mastodon.social/@sir_pepe">Mastodon</a> dazu geführt, dass ich einiges über HTML, CSS und JavaScript/DOM erfahren habe, das mir vorher nicht klar war. Und da Mastodon noch immer nicht die intergalaktische Total-Dominanz ausübt, die ihm eigentlich zusteht, kehre ich die gesammelten Erkenntnisse an dieser Stelle nochmals zusammen. Das behalte ich ab nun auch bei, bis ihr <em>alle</em> mir dort folgt.
</p>

<h3>Formulare in Formularen? Jain! (und mit Browserbugs)</h3>

<p>
Ich habe schon vor langer Zeit mal <a href="https://mastodon.social/@sir_pepe/111805465678316177">einen Workflow für Form-Value-Handling in Formular-Web-Components ausgebrütet</a>, der darauf basierte, im Shadow DOM der Komponente ein inneres <code>&lt;form&gt;</code>-Element zu haben. Dieses Element lässt sich zu FormData serialisieren, was dann wiederum bequem in Submit-Values, <code>value</code>-Attribute und alle sonstigen für die Komponente relevanten Aspekte transformiert werden kann. Im Firefox funktionierte das auch hervorragend, aber bei einigen Komponenten streikte Chrome. Warum? Weil mein Form-Handling-Workflow ungültiges HTML verwendet (wenn man denn HTML verwendet).
</p>

<p>
Die HTML-Standards <a href="https://html.spec.whatwg.org/#the-form-element">verbieten verschachtelte Form-Elemente</a> und (was ich nicht auf dem Schirm hatte) Formulare in Shadow Roots in Formularen gelten als verschachtelte Form-Elemente! Allerdings besteht diese Regel auch <em>nur für HTML</em>, nicht für das DOM. Der folgende Code resultiert <a href="https://codepen.io/SirPepe/pen/abxNKpE?editors=1010">in nur einem <code>&lt;form&gt;</code>-Element</a>, da der HTML-Parser das innere Element verwirft:
</p>

<pre class="prettyprint language-html">&lt;form&gt;
  &lt;form&gt;&lt;/form&gt;
&lt;/form&gt;

&lt;!-- Ergebnis: ein &lt;form&gt; im DOM --&gt;</pre>

<p>
Wenn wir aber gar keinen HTML-Parser involvieren, sondern per JS direkt das DOM manipulieren, <a href="https://codepen.io/SirPepe/pen/vYMGrgv?editors=0010">erhalten wir verschachtelte Formulare:</a>
</p>

<pre class="prettyprint language-javascript">let outer = document.createElement("form");
let inner = document.createElement("form");
outer.append(inner);
document.body.append(outer);

// Ergebnis: zwei &lt;form&gt; im DOM</pre>

<p>
Das ist auch nicht <em>so</em> besonders bizarr: HTML ist nur eine Serialisierung des DOM und hat daher die Freiheit, sich bestimmter DOM-Konstrukte zu verweigern, genau wie JSON mit zahlreichen Aspekten von JavaScript nichts anfangen kann.
</p>

<p>
Mein Komponenten-Fail in Chrome kam dadurch zustande, dass ich bei den Problem-Komponenten <code>innerHTML</code> für das Shadow-DOM-Setup verwendet habe (und andere DOM-Tools bei den unproblematischen Komponenten). <code>innerHTML</code> verwendet natürlich seinerseits den HTML-Parser, der allerdings offenbar in meinem Haupt-Browser Firefox den Verschachtelungs-Überblick verliert, sobald Shadow DOM involviert ist. Bedeutet: im Firefox funktioniert etwas, das laut HTML-Standard nicht funktionieren dürfte. <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1877971">Endlich kann ich mal einen Bug melden, der nicht einfach nur Gebettel um eine Implementierung von Feature X ist!</a>
</p>

<p>
Meinen Web-Component-Ansatz mit inneren Formularen werde ich beibehalten, obwohl er sich nicht in HTML serialisieren lässt. Solange die inneren Formulare im Shadow DOM bleiben, stören sie nicht, und solange das Shadow DOM ohne einen (bugfreien) HTML-Parser aufgesetzt wird, sollten sie auch funktionieren. Und ich denke nicht, dass deklaratives Shadow DOM ein sinnvolles Einsatzgebiet für Custom Formular-Inputs sein wird, weswegen ich mir erlaube, die Regeln von HTML an dieser Stelle zu ignorieren.
</p>

<h3>Kompatibilitätsprobleme von CompressionStreams (und deren Zubehör)</h3>

<p>
<a href="https://www.peterkroener.de/natives-gzip-in-browsern-node-und-deno/">Nachdem ich im Januar CompressionStreams über den grünen Klee gelobt hatte</a>, fielen mir im Folgemonat einige Kompatibilitätsprobleme auf. Seit dem LTS-Release von Node 20 herrscht in Hinblick auf die Kompressionsalgorithmen durch die Bank die gleiche Unterstützung, aber <a href="https://mastodon.social/@sir_pepe/111879066536855770">Chrome und Chrome-Derivate implementieren nicht <code>@@asyncIterator</code> auf ReadableStream</a>, sodass für diese Browser folgender Polyfill benötigt wird:
</p>

<pre class="prettyprint language-javascript">ReadableStream.prototype[Symbol.asyncIterator] ??= async function* () {
  const reader = this.getReader();
  try {
    while (true) {
      const { done, value } = await reader.read();
      if (done) {
        return;
      }
      yield value;
    }
  } finally {
    reader.releaseLock();
  }
};</pre>

<p>
TypeScript-kompatible Versionen der <code>compress()</code>- und <code>decompress()</code>-Funktionen aus dem Artikel, mit dem o.g. Polyfill <em>und</em> besserem Error Handling <em>und</em> URL-sicherem Base64 <a href="https://github.com/SirPepe/shed/blob/main/src/compress.ts">gibt es in meiner Toolsammlung</a>.
</p>

<h3>Die Reihenfolge von Adopted Stylesheets ist egal</h3>

<p>
Mit <a href="https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/CSSStyleSheet"><code>new CSSStyleSheet()</code></a> erstellte Stylesheet-Objekte können der <a href="https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/adoptedStyleSheets"><code>adoptedStyleSheets</code></a>-Property eines Shadow Root (oder eines Document) zugewiesen werden, um dem entsprechenden Objekt ein bisschen Style überzuhelfen. <code>adoptedStyleSheets</code> kommt als Array daher und trotzdem ist – <a href="https://mastodon.social/@sir_pepe/111883799993719708">für mich überraschend</a> – der Array-Index eines gegebenen Stylesheets für die CSS-Anwendung irrelevant. Es zählt allein die Reihenfolge des Hinzufügens:
</p>

<pre class="prettyprint language-javascript">function createSheet(css) {
  const sheet = new CSSStyleSheet();
  sheet.replaceSync(css);
  return sheet;
}

const host = document.querySelector(".host");
const shadow = host.attachShadow({ mode: "open" });
shadow.innerHTML = "&lt;span&gt;Text&lt;/span&gt;"
shadow.adoptedStyleSheets[1] = createSheet("span { color: red }");
shadow.adoptedStyleSheets[0] = createSheet("span { color: green }");
// Ergebnis: grün</pre>

<p>
Ich hätte mich nicht gewundert, wenn der Text rot geworden wäre, da im Array <code>color: red</code> die letzte Regel ist. Aber da sie zuerst hinzugefügt wurde, <a href="https://codepen.io/SirPepe/pen/jOJKJKz?editors=1010">gewinnt Grün</a>.
</p>

<h3>Safari bleibt der neue IE6: kein <code>d</code> in CSS</h3>

<p>
<a href="https://mastodon.social/@sir_pepe/111929882803678961">Ich musste entsetzt zur Kenntnis nehmen</a>, dass <a href="https://caniuse.com/mdn-svg_elements_path_d_path">Safari <code>d</code> in CSS nicht unterstützt</a> – als einziger relevanter Browser diesseits der Andromeda-Galaxie. Eigentlich ist <code>d</code> <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d">ein Attribut des SVG-Elements &lt;path&gt;</a>, das den zu zeichnenden Pfad beinhaltet. Wie so ziemlich jedes SVG-Attribut (<code>fill</code>, <code>stroke</code> etc.) kann auch <code>d</code> als CSS-Eigenschaft ausgedrückt werden und <code>d</code> wird in dieser Rolle mit einem <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/path"><code>path()</code>-Wert</a> gefüttert, genau wie u. a. <code>clip-path</code>. Einziges Problem: Safari mag <code>d</code> in CSS nicht.
</p>

<p>
Das ist ziemlich verheerend, da damit ein CSS-Manöver verhindert wird, für das ich mich ansonsten ziemlich gefeiert hätte: per Custom Properties konfigurierbare Inline-SVGs! Jetzt muss ich mich damit begnügen, ein endliches Set von vordefinierten <code>d</code>-Werten über Bande per <a href="https://css-tricks.com/the-css-custom-property-toggle-trick/">Custom Property Toggles</a> bereitzustellen. Ein ziemlich enttäuschendes Downgrade.
</p>

<h3>Weitere Erkenntnisse und Fundstücke</h3>

<ul>

<li>Mit SVG/CSS-Filtern <a href="https://mastodon.social/@miunau@meow.social/111911373847444552">lässt sich für einen hellen oder dunklen Hintergrund automatisch die Textfarbe in Hell bzw. Dunkel verwandeln.</a></li>

<li>Kaum <a href="https://mastodon.social/@sir_pepe/111928708978397152">weine ich den guten Aspekten von CoffeeScript hinterher</a>, schon manifestiert sich ein ECMAScript-Proposal für <a href="https://github.com/tc39/proposal-regexp-x-mode">einen x-Mode für RegExp!</a></li>

<li>Noch ein ECMAScript-Proposal: <a href="https://github.com/tc39/proposal-arraybuffer-base64">Uint8Array to/from base64 and hex</a> würde ich gern ASAP umgesetzt sehen, danke!</li>

<li>Eine weniger gute Nachricht aus dem ECMAScript-Feature-Universum: für die USP des Proposals für Records und Tuples <a href="https://github.com/tc39/proposal-record-tuple/issues/387#issuecomment-1881635273">sieht es schlecht aus</a>.</li>

<li>Mit Katzen-Memes kann man <a href="https://mastodon.social/@sir_pepe/111963328536304420">auch auf Mastodon hinreichend viral gehen</a>.</li>

</ul>
<img src="https://ssl-vg03.met.vgwort.de/na/077ba8673ebb409abde43399c23c3b5d" alt="">]]></content:encoded>
</item>
<item>
<title><![CDATA[TIL-Roundup Januar 2024: TextEncoder, @import, type-Attribute]]></title>
<link>https://www.peterkroener.de/til-roundup-januar-2024-textencoder-import-type-attribute/</link>
<description><![CDATA[<p>
Ich nutze meine <a href="https://mastodon.social/@sir_pepe">Mastodon-Präsenz</a> vor allem, um dumme Fragen zu stellen und laut nachzudenken. Fragenstellen und Nachdenken führt zu Erkenntnisgewinn (in meinem Fall meist in Sachen Browserbugs und Webstandards) und dieser Artikel fasst meine Erkenntnis-Highlights aus dem Januar in etwas organsierterer Form zusammen. Top-Fundstück des Monats waren definitiv <a href="https://www.peterkroener.de/natives-gzip-in-browsern-node-und-deno/">die bereits in einem eigenen Artikel verarbeiteten CompressionStreams</a>, aber der Rest kann sich auch sehen lassen!
</p>

<h3>TextEncoder&#8230;</h>]]></description>
<pubDate>Tue, 13 Feb 2024 09:16:00 +0100</pubDate>
<guid isPermaLink="false">https://www.peterkroener.de/til-roundup-januar-2024-textencoder-import-type-attribute/</guid>
<dc:creator>Peter Kröner</dc:creator>
<content:encoded><![CDATA[<p>
Ich nutze meine <a href="https://mastodon.social/@sir_pepe">Mastodon-Präsenz</a> vor allem, um dumme Fragen zu stellen und laut nachzudenken. Fragenstellen und Nachdenken führt zu Erkenntnisgewinn (in meinem Fall meist in Sachen Browserbugs und Webstandards) und dieser Artikel fasst meine Erkenntnis-Highlights aus dem Januar in etwas organsierterer Form zusammen. Top-Fundstück des Monats waren definitiv <a href="https://www.peterkroener.de/natives-gzip-in-browsern-node-und-deno/">die bereits in einem eigenen Artikel verarbeiteten CompressionStreams</a>, aber der Rest kann sich auch sehen lassen!
</p>

<h3>TextEncoder und TextDecoder</h3>

<p>
Mir war neu, dass alle Browser unter der Sonne <a href="https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder">TextEncoder</a> und <a href="https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder">TextDecoder</a> unterstützen. Der Decoder schluckt Bytes und produziert Strings, der Encoder macht das Gegenteil:
</p>

<pre class="prettyprint language-javascript">const utf8bytes = new TextEncoder().encode(&quot;????&quot;);
// &gt; Uint8Array(4) [ 240, 159, 164, 161 ]
const string = new TextDecoder().decode(utf8bytes);  
// &gt; &quot;????&quot;</pre>

<p>
Der Decoder kann natürlich auch mit mehr als UTF-8 umgehen und lief mir <a href="https://www.peterkroener.de/natives-gzip-in-browsern-node-und-deno/">beim Austüfteln von CompressionStreams über den Weg</a>.
</p>

<h3>Kein <code>@import</code> im <code>CSSStyleSheet()</code>-Constructor (und CSS-Modulen)</h3>

<p>
Ein Ansatz für CSS in Shadow DOM besteht darin, mit <code>new CSSStyleSheet()</code> <a href="https://codepen.io/SirPepe/pen/PoLyrZL?editors=1010">Stylesheets aus heißer Luft zu erzeugen und diese einem ShadowRoot (oder Document) zuzuweisen</a>:

<pre class="prettyprint language-javascript">const host = document.querySelector(&quot;div&quot;);
const root = host.attachShadow({ mode: &quot;open&quot; });
root.innerHTML = `Hello`;
const sheet = new CSSStyleSheet();
sheet.replaceSync(&quot;:host { color: red }&quot;); // async-Alternative: replace()
root.adoptedStyleSheets.push(sheet);</pre>

<p>
Die in diesem Stylesheet enthaltenen Regeln gelten dann ausschließlich in den ShadowRoots oder Dokumenten, deren <code>adoptedStyleSheets</code> sie zugewiesen wurden. Zwar handelt es sich hierbei um grundsätzlich zweifelhafte JS-Hexerei auf CSS-Territorium, aber es mangelt nicht an Vorteilen:
</p>

<ul>
<li>Web Components können ihre eigenen kleinen CSS-Dateien haben (anstelle von Strings in JavaScript)</li>
<li>Wer sich etwas Mühe gibt, Memory Leaks zu umgehen, kann ein <code>CSSStyleSheet</code>-Objekt über mehrere Komponenten-Instanzen teilen</li>
<li>Unter Zuhilfenahme von Build-Tools kann CSS zur Compile-Zeit ins JavaScript gebundlet werden, falls das gewünscht ist</li>
</ul>

<p>
Allerdings musste ich feststellen, dass <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@import"><code>@import</code></a> in mit <code>new CSSStyleSheet()</code> und CSS-Modulen genutzten Stylesheets <a href="https://github.com/WICG/webcomponents/issues/870">nicht funktioniert.</a>. Das Problem ist, dass jedes <code>@import</code> traditionell ein Stylesheet von einer URL lädt und für mehrere Requests auf die gleiche Adresse komplett unterschiedliches CSS geliefert bekommen kann. Für ECMAScript-Module hingegen baut der Browser einmal einen Modul-Graph auf und lädt keine URL zweimal – zwei unterschiedliche Antworten innerhalb eines Ladezyklus sind also ausgeschlossen. CSS-Module wollen Syntax wie <code>import css from &#39;./foo.css&#39;</code> ermöglichen, doch hier kollidiert die Funktionsweise von ECMAScript-Modulen mit der von <code>@import</code>. Wir erwarten von <code>import</code>-Statements deterministische Ergebnisse und von <code>@import</code>-Regeln das genaue Gegenteil. Die erwartbare Konsequenz: kein <code>@import</code> in CSS-Modulen und auch kein <code>@import</code> in mit <code>new CSSStyleSheet()</code> erzeugten Stylesheets, in denen sich ein vergleichbarer Widerspruch manifestiert.
</p>

<h3><code>type</code> auf <code>&lt;textarea&gt; und &lt;select&gt;</code> (und mehr)</h3>

<p>
Beim Zusammenstecken einiger Debug-Strings <a href="https://mastodon.social/@sir_pepe/111844086278966282">fiel mir auf</a>, dass auf <code>&lt;textarea&gt;</code> und <code>&lt;select&gt;</code> das IDL-Attribut <code>type</code> existiert:
</p>

<pre class="prettyprint language-javascript">const textarea = document.createElement(&quot;textarea&quot;);
console.log(textarea.type); // &gt; &quot;textarea&quot;

const select = document.createElement(&quot;select&quot;);
console.log(select.type); // &gt; &quot;select-one&quot;

const multiSelect = document.createElement(&quot;select&quot;);
multiSelect.multiple = true;
console.log(multiSelect.type); // &gt; &quot;select-multiple&quot;</pre>

<p>
Anders als bei <code>&lt;input&gt;</code>, wo das Content-Attribut <code>type</code> den Input-Typ bestimmt, ist das IDL-Attribut <code>type</code> bei <code>&lt;textarea&gt;</code> und <code>&lt;select&gt;</code> read-only. Die Idee dahinter scheint zu sein, dass alle Formular-Elemente eine einheitliche API zum Ermitteln ihres Typs haben sollen, denn auch <code>&lt;output&gt;</code> und <code>&lt;fieldset&gt;</code> haben dieses Feature. Unter den verbliebenen <i lang="en">form-associated elements</i> haben <code>&lt;input&gt;</code>, <code>&lt;button&gt;</code> und <code>&lt;object&gt;</code> ohnehin <code>type</code>-Attribute und die einzigen Ausreißer sind <code>&lt;img&gt;</code> (warum ist das überhaupt <a href="https://html.spec.whatwg.org/#form-associated-element">form-associated</a>?) und eventuelle <i lang="en">form-associated custom elements</i>. Was lernen wir daraus?</p>

<ol>
<li>Formular-Elements können wir allein anhand ihres <code>type</code> auseinanderhalten.</li>
<li>Wenn wir <i lang="en">form-assoicated custom elements</i> bauen, sollten sich diese auch die Mühe machen, einen <code>type</code>-Getter zu implementieren, denn sonst funktioniert Punkt 1 nicht mehr.</li>
</ol>

<p>
Punkt 2 ist schon erfüllt, wenn wir einfach nur den folgenden Codeschnipsel in unsere Form-Element-Basisklassen einbauen:
</p>

<pre class="prettyprint language-javascript">export class FormBaseElement extends HTMLElement {
  // Boilerplate...
  
  get type() {
    return this.tagName.toLowerCase();
  }
  
  // ... mehr Boilerplate...
}</pre>

<p>
Damit funktioniert <code>type</code> praktisch wie bei <code>&lt;textarea&gt;</code> und erfüllt damit in 99% aller Fälle schon locker seinen Zweck!
</p>

<h3>Weitere Erkenntnisse und Fundstücke</h3>

<ul>
<li><a href="https://rss-parrot.net/">RSS-Parrot</a> verwandelt Feeds in Mastodon-Accounts</li>
<li>In Chrome <a href="https://mastodon.social/@sir_pepe/111732159212337743">feuern <code>reset</code>-Events nicht auf Formularen in einem neuen Dokument</a>, gleiches <a href="https://hachyderm.io/@Phil_S/111918099072991254">gilt in Safari</a>. Firefox feuert das Event, also hab ich das mal als <a href="https://issues.chromium.org/issues/324827385">Bug gemeldet</a>.</li>
<li>Chrome kann seit über 6 Jahren <a href="https://github.com/whatwg/xhr/issues/55">FormData nicht mit dem Structured Clone Algorithm</a> verdauen, Firefox hingegen schon</li>
<li>In Chrome wird ein Element <i lang="en">draggable</i> im Sinne der HTML D&amp;D-API, wenn es das Content-Attribut <code>draggable</code> hat. Im Firefox wird es aber auch <i lang="en">draggable</i>, wenn es in einen Shadow-Slot projiziert wird, der Kind eines Elements mit <code>draggable</code>-Attribut ist. Ich habe keine Bugs bei den BrowserN gemeldet, da <a href="https://mastodon.social/@sir_pepe/111777225002833600">ich nicht weiß, was der Soll-Zustand ist.</a></li>
<li>Das IDL-Gegenstück zum <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/readonly">Content-Attribut <code>readonly</code></a> heißt... <a href="https://mastodon.social/@sir_pepe/111843851968174066">readOnly</a> (mit großem „O“).</li>
</ul>

<p>
<a href="https://mastodon.social/@sir_pepe">Folgt mir auf Mastodon</a>, wenn ihr dem nächsten Erkenntnis-Paket live beim Entstehen zusehen wollt!<img src="https://ssl-vg03.met.vgwort.de/na/0e9f31b763484b6b85b5f83a48436bb8" width="1" height="1" alt="">
</p>]]></content:encoded>
</item>
<item>
<title><![CDATA[Natives GZIP in Browsern, Node und Deno]]></title>
<link>https://www.peterkroener.de/natives-gzip-in-browsern-node-und-deno/</link>
<description><![CDATA[<p>
Das Komprimieren von mittelgroßen JSON-Payloads ist im Web-Frontend keine <em>so</em> ungewöhnliche Anforderung. Ein ausreichend kleines JavaScript-Objekt kann in eine Base64-Repräsentation eines JSON-Strings verwandelt werden, was es wiederum ermöglicht, Applikationsdaten in URLs vorzuhalten. Auf diese Weise können Webapps Speicher- (via Bookmark) und Sharing-Features (via Copy-and-paste der URL) anbieten, ohne ein komplexes Backend zu benötigen, denn ihre URLs <em>sind</em> die Datenbanken. Beispiele für solche Apps sind <a href="https://babeljs.io/repl">Babel-Sandbox</a> und der <a href="https://www.typescriptlang.org/play">TypeScript-Playground</a>.&#8230;</p>]]></description>
<pubDate>Wed, 24 Jan 2024 13:36:33 +0100</pubDate>
<guid isPermaLink="false">https://www.peterkroener.de/natives-gzip-in-browsern-node-und-deno/</guid>
<dc:creator>Peter Kröner</dc:creator>
<content:encoded><![CDATA[<p>
Das Komprimieren von mittelgroßen JSON-Payloads ist im Web-Frontend keine <em>so</em> ungewöhnliche Anforderung. Ein ausreichend kleines JavaScript-Objekt kann in eine Base64-Repräsentation eines JSON-Strings verwandelt werden, was es wiederum ermöglicht, Applikationsdaten in URLs vorzuhalten. Auf diese Weise können Webapps Speicher- (via Bookmark) und Sharing-Features (via Copy-and-paste der URL) anbieten, ohne ein komplexes Backend zu benötigen, denn ihre URLs <em>sind</em> die Datenbanken. Beispiele für solche Apps sind <a href="https://babeljs.io/repl">Babel-Sandbox</a> und der <a href="https://www.typescriptlang.org/play">TypeScript-Playground</a>. Beide speichern ihre Applikations-States in ihren URLs – den Source Code Base64-codiert, den Rest als ganz normale Query-Parameter. Und da ich im Moment etwas ganz Ähnliches vorhabe, machte ich mich auf den Weg, die Implementierungen von Babel- und TypeScript-Sandbox zu erforschen und den State-in-URL-Ansatz 1:1 zu stibitzen. Jedenfalls war das der Plan.
</p>

<h3>lz-string</h3>

<p>
Dank Open Source war schnell klar, wie die Babel- und TypeScript-Sandboxes funktionieren: Beide verwenden die extrem gut abgehangene JavaScript-Library <a href="https://pieroxy.net/blog/pages/lz-string/index.html">lz-string</a>, die mithilfe des ebenfalls extrem gut abgehangenen <a href="https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Welch">LZW-Algorithmus</a> JavaScript-Strings komprimiert und die Ergebnisse direkt als URL-kompatibles Base64 ausspuckt. Die Kompression reduziert die Länge der jeweiligen Code-Samples und die Base64-Codierung macht das Endergebnis für URLs besser verdaulich. <em>Im Prinzip</em> können URLs auch Unicode enthalten und <a href="https://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers">laut eines Papers der University of Stack Overflow</a> können URLs <em>im Prinzip</em> sehr sehr lang werden, aber im Sinne der besseren Handhabbarkeit sind Kompression und Base64 schon sehr sinnvoll.
</p>

<p>
lz-string ist ziemlich großartig. Die Library ist extrem stabil, einfach zu benutzen, dabei trotzdem vielseitig und sie kommt mit exakt null eigenen Dependencies daher. Leider stellte sie sich aber als trotzdem nicht wirklich brauchbar für meine Zwecke heraus&nbsp;&hellip; denn der Applikations-State, den ich in URLs unterzubringen gedenke, ist <em>viel</em> größer als der State der Babel- und TS-Sandboxes.
</p>

<p>
Bei allen Vorzügen hat die lz-string-Library doch eine Eigenschaft, die nicht <em>nur</em> positiv ist: Der LZW-Algorithmus ist über 40 Jahre alt und nicht ganz so leistungsstark wie modernere Verfahren. Also sah ich mich veranlasst, NPM nach Alternativen zu durchwühlen und fand wenig Brauchbares. Abgesehen vom allgegenwärtigen Qualitätslimbo ist eine Grundregel von Datenkompression, dass Verbesserungen Kosten haben. Moderne Algorithmen sind <em>viel</em> komplexer als LZW und bezahlen für ihre besseren Kompressionsraten mit teilweise signifikant längeren Laufzeiten und/oder erheblich größeren JS-Bundles. Für Web-Frontends stellt lz-string offenbar einen ziemlich optimalen Kompromiss dar – dumm nur, dass dieser Kompromiss für meine Pläne schlichtweg nicht genug Kompressionsleistung mitbrachte. Mir grauste es bereits vor der Auseinandersetzung mit Web Workers oder WASM, bis mir per Zufall auffiel, dass ich für mein Vorhaben nicht eine <em>andere</em> JavaScript-Libraray benötigte, sondern einfach <em>gar keine!</em>
</p>

<h3>Compression Streams API</h3>

<p>
Alle moderneren Browser (sowie Deno und Node) unterstützen offenbar seit Ewigkeiten die <a href="https://developer.mozilla.org/en-US/docs/Web/API/Compression_Streams_API">Compression Streams API</a>, mit der wir JavaScript-Autor:innen ohne irgendwelche Dependencies Zugriff auf <a href="https://en.wikipedia.org/wiki/Deflate">DEFLATE</a> und die DEFLATE-Wrapper <a href="https://en.wikipedia.org/wiki/Gzip">gzip</a> und <a href="https://en.m.wikipedia.org/wiki/Zlib">zlib</a> bekommen! Der Deflate-Algoritmus und seine Wrapper-Formate sind schließlich für HTTP-Kompression ohnehin in jedem Browser vorhanden und eine JavaScript-Durchbindung anzubieten ist nicht die verrückteste Idee. Unbekannt war mir die API trotzdem und leicht zu entdecken war sie in der Flut der mittelmäßigen NPM-Packages und AI-generiertem SEO-Spam auch nicht.</p>
<p>Die API ist <a href="https://wicg.github.io/compression/">noch kein fertiger Webstandard</a> und es gibt <a href="https://github.com/WICG/compression/issues">diverse offene Fragen und Feature Requests</a>, aber die grundsätzliche Funktionalität ist in allen Browsern und JS-Runtimes verfügbar:
</p>

<pre class="language-javascript prettyprint">const deflateStream = someStream.pipeThrough(
  new CompressionStream("deflate-raw")
);
const compressedData = new Blob(
  await Array.fromAsync(deflateStream)
);</pre>

<p>Zur Erklärung:</p>

<ul>

<li><code>someStream</code> im obigen Beispiel ist ein <a href="https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream">ReadableStream</a>, der u. a. aus <a href="https://developer.mozilla.org/en-US/docs/Web/API/Response/body">Responses</a> oder <a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob/stream">Blobs</a> stammen kann</li>

<li>neben <code>deflate-raw</code> (DEFLATE, <a href="https://www.rfc-editor.org/rfc/rfc1951">RFC1951</a>) stehen im Moment nur die DEFLATE-Wrapper-Formate <code>deflate</code> (ZLIB, <a href="https://www.rfc-editor.org/rfc/rfc1950">RFC1950</a>, benannt nach seinem <a href="https://httpwg.org/specs/rfc7230.html#deflate.coding">entsprechenden HTTP Content-Encoding</a>) und <code>gzip</code> (GZIP, <a href="https://www.rfc-editor.org/rfc/rfc1952">RFC1952</a>) zur Verfügung. Weitere Formate wie <a href="https://en.wikipedia.org/wiki/Brotli">Brotli</a> sind in die Diskussion. Die aktuellen APIs erlauben keine Änderung der Standard-Parameter der diversen Formate, insbesondere können keine Dictionaries angegeben werden (<a href="https://github.com/WICG/compression/issues/27">Bug #27</a>)</li>

<li>Der Blob <code>compressedData</code> verwendet <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fromAsync"><code>Array.fromAsync()</code></a>, um den ReadableStream via <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_async_iterator_and_async_iterable_protocols">Async Iteration</a> zu konsumieren und den Inhalt in ein Array zu überführen.</li>

</ul>

<p>
Um mit der API meinen Use Case der App-State-Kompression umzusetzen, brauchte es noch ein paar weitere Zeilen für das Handling von JSON, Base64 und Unicode, aber nicht besonders viele:
</p>

<pre class="language-javascript prettyprint">async function compress(inputData) {
  const json = JSON.stringify(inputData);
  const deflateStream = new Blob([json])
    .stream()
    .pipeThrough(new CompressionStream("deflate-raw"));
  let binString = "";
  for await (const bytes of deflateStream) {
    binString += String.fromCodePoint(...bytes);
  }
  return btoa(binString);
}

async function decompress(inputBase64) {
  const binString = atob(inputBase64);
  const inputBytes = Uint8Array.from(binString, (s) =&gt; s.codePointAt(0));
  const inflateStream = new Blob([inputBytes])
    .stream()
    .pipeThrough(new DecompressionStream("deflate-raw"));
  let json = "";
  for await (const bytes of inflateStream) {
    json += new TextDecoder().decode(bytes);
  }
  return JSON.parse(json);
}

const appState = { user: "foo@bar.de", password: "hunter2" };

const compressedState = await compress(appState);
// Ergebnis: "q1YqLU4tUrJSSsvPd0hKLNJLSVXSUSpILC4uzy9KAYpnlOaVpBYZKdUCAA"

const decompressedState = await decompress(compressedState);
// Ergebnis:  { user: "foo@bar.de", password: "hunter2" }</pre>

<p>
Diese Implementierung verzichtet auf das Stand Anfang 2024 <a href="https://caniuse.com/mdn-javascript_builtins_array_fromasync">noch nicht universell unterstützte</a> <code>Array.fromAsync()</code> sowie auf <a href="https://developer.mozilla.org/en-US/docs/Web/API/FileReader">FileReader</a>. Mit FileReader ist das Lesen von Blobs als Base64 und Text nicht nur asynchron, sondern im Prinzip einfacher, aber mangels Promise-Unterstützung auch nicht <em>gänzlich</em> unumständlich. Außerdem stellt sich schnell heraus, dass wir mit Compression Streams auf den Performance-Bonus des asynchronen FileReader bequem verzichten können.
</p>

<h3>Compression Streams und lz-string im Vergleich</h3>

<p>
Für meine Bedürfnisse gewinnt Compression Streams gegen lz-string schon allein dadurch, dass es (de facto) ein Webstandard ist. Zwar ist lz-string als Library ein Traum – klein, einfach zu benutzen, vielseitig, ohne eigene Dependencies – aber null Dependencies sind immer noch besser als eine Dependency. Einen hemdsärmligen Performance-Vergleich habe ich aber trotzdem mal angestellt und meine <code>compress()</code>-Funktion gegen <code>compressToEncodedURIComponent()</code> aus lz-string antreten lassen. Als Inputs dienten:
</p>

<ul>

<li>Das Winz-Objekt <code>{ user: "foo@bar.de", password: "hunter2" }</code></li>

<li>23k mit <a href="https://json-generator.com">json-generator.com</a> erzeugte Zufallsdaten mit vergleichsweise wenig Redundanzen</li>

<li>Ein 14k großes Objekt, das einen App-State von meinem Projekt enthält … mit vergleichsweise <em>vielen</em> Redundanzen, v.a. in den Objekt-Keys</li>

</ul>

<p>
Die <em>Laufzeit-Performance</em> von Compression Streams ist immer um ein Vielfaches besser als die von lz-string. Das soll nicht bedeuten, dass lz-string langsam ist, sondern nur, dass Compression Streams <em>noch</em> schneller sind. Für meinen Use Case der periodischen App-State-Speicherung wäre beides locker ausreichend.
</p>

<p>
In Hinblick auf die <em>Kompressionsrate</em> schlagen Compression Streams auch immer lz-string, aber der Vorsprung ist nicht immer identisch ausgeprägt. Beim sehr kleinen Objekt ist die Differenz vernachlässigbar, bei den zufälligen Daten sind Compression Streams ca. 30% besser und bei meinem hochredundanten App-State fast 3x besser. Die Struktur des letztgenannten Objekts enthält eine ganze Reihe von Redundanzen und komprimiert daher natürlich besonders gut.</p>
<p>Letztlich sind für mich und meinen Use Case Compression Streams ein klarer Sieger gegen lz-string: Sie sind schneller, besser und ohne Dependencies auf jeder Plattform inkl. der Serverseite verfügbar.
</p>

<h3>Weitere Alternativen zu Compression Streams</h3>

<p>Compression Streams haben in meinen Augen drei nennenswerte Vorteile:</p>

<ol>
<li>Null Dependencies</li>
<li>Cross-Platform-Verfügbarkeit (alle Browser + Node + Deno)</li>
<li>De-Facto-Webstandard (hohe zu erwartende Stabilität)</li>
</ol>

<p>
Fairerweise muss man aber auch sagen, dass es das wirklich alle Vorteilen sind, die Compression Streams zu bieten haben. Wer sich mit der Installation von Dependencies oder der Beschränkung auf einzelne Plattformen anfreunden kann, kann sich eine ganze Reihe anderer Vorteile ins Haus holen:
</p>

<ol>

<li>Außer DEFLATE und seinen Wrappern haben Compression Streams zurzeit keine weiteren Algorithmen anzubieten. Viel moderne Kompressionsverfahren wie etwa Brotli werden von Libraries wie <a href="https://www.npmjs.com/package/brotli-wasm">brotli-wasm</a> unterstützt, aber eben um den Preis von Dependency-Installation und WASM-Gefummel.</li>

<li>Compression Streams bieten z.Z. keine API zum Konfigurieren des DEFLATE-Algorithmus, was abhängig von den zu komprimierenden Daten ein ernsthaftes Hindernis darstellen kann. JS-Implementierungen wie <a href="https://www.npmjs.com/package/deflate-js">deflate-js</a> haben diesbezüglich deutlich mehr zu bieten.</li>

<li>Wer auf Browser-Unterstützung verzichten kann, findet in den Server-JS-Plattformen moderne und konfigurierbare Kompressions-Algorithmen (z.B. <a href="https://nodejs.org/api/zlib.html">Nodes eingebautem Zlib-Modul</a>) vor.</li>

</ol>

<p>
Um es ganz deutlich zu machen: je nach anstehender Aufgabe können Compression Streams mit ihren Einschränkungen bzgl. Algorithmen und Konfigurierbarkeit <em>komplett unbrauchbar</em> sein. Wer für Megabytes an Game-Assets maximale Kompression benötigt, wird mit einer für ein paar Kilobytes JSON ausreichenden Lösung nicht glücklich werden. Wer ohnehin schon über 9000 Dependencies installiert hat und bereits in den Kessel mit WASM-Zaubertrank gefallen ist, hat keine Nachteile durch die zusätzliche Installation von brotli-wasm. Aber wer einfach nur ein bisschen JSON in URLs oder Local Storage quetschen möchte, ist mit Compression Streams durch den Browser <em>out of the box</em> hervorragend versorgt.
</p>

<p>
Compression Streams auf dem Stand von Anfang 2024 ist definitiv nicht unter <em>allen</em> Umständen die erste Wahl, aber wie so viele Webstandards eine 80/20-Lösung, die bei nüchterner Betrachtung für viele Anwendungsfälle ausreichend ist. Speziell für den Use Case der in URLs (oder Local Storage) hinterlegten App-States sind Compression Streams völlig okay und sogar in allen Belangen besser als das sonst hierfür so populäre lz-string.<img src="https://ssl-vg03.met.vgwort.de/na/8d6f3a05d0224f29a203430b407da3f7" width="1" height="1" alt="">
</p>]]></content:encoded>
</item>
<item>
<title><![CDATA[Fragen zu HTML5 und Co beantwortet 28 - getElementsByTagName, neue Elemente, zirkuläre Referenzen, FormData]]></title>
<link>https://www.peterkroener.de/fragen-zu-html5-und-co-beantwortet-28-getelementsbytagname-neue-elemente-zirkulaere-referenzen-formdata/</link>
<description><![CDATA[<div class="artikelserie">
<p>
Dieser Artikel ist Teil einer <b>Serie</b>:
<ol>
<li><a href="fragen-zu-html5-beantwortet/">Fragen zu HTML5 & Co 1</a></li>
<li><a href="noch-mehr-fragen-zu-html5-beantwortet/">Fragen zu HTML5 & Co 2</a></li>
<li><a href="und-noch-mehr-fragen-zu-html5-beantwortet/">Fragen zu HTML5 & Co 3</a></li>
<li><a href="weitere-fragen-zu-html5-und-co-beantwortet/">Fragen zu HTML5 & Co 4</a></li>
<li><a href="fragen-zu-html5-und-co-beantwortet-5/">Fragen zu HTML5 & Co 5</a></li>
<li><a href="fragen-zu-html5-und-co-beantwortet-6/">Fragen zu HTML5 & Co 6</a></li>
<li><a href="fragen-zu-html5-und-co-beantwortet-7-doctypes-css-transformationen-codecs-semantik/">Fragen&#8230;</a></li></ol></p></div>]]></description>
<pubDate>Wed, 03 Jan 2024 08:35:06 +0100</pubDate>
<guid isPermaLink="false">https://www.peterkroener.de/fragen-zu-html5-und-co-beantwortet-28-getelementsbytagname-neue-elemente-zirkulaere-referenzen-formdata/</guid>
<dc:creator>Peter Kröner</dc:creator>
<content:encoded><![CDATA[<div class="artikelserie">
<p>
Dieser Artikel ist Teil einer <b>Serie</b>:
<ol>
<li><a href="fragen-zu-html5-beantwortet/">Fragen zu HTML5 &amp; Co 1</a></li>
<li><a href="noch-mehr-fragen-zu-html5-beantwortet/">Fragen zu HTML5 &amp; Co 2</a></li>
<li><a href="und-noch-mehr-fragen-zu-html5-beantwortet/">Fragen zu HTML5 &amp; Co 3</a></li>
<li><a href="weitere-fragen-zu-html5-und-co-beantwortet/">Fragen zu HTML5 &amp; Co 4</a></li>
<li><a href="fragen-zu-html5-und-co-beantwortet-5/">Fragen zu HTML5 &amp; Co 5</a></li>
<li><a href="fragen-zu-html5-und-co-beantwortet-6/">Fragen zu HTML5 &amp; Co 6</a></li>
<li><a href="fragen-zu-html5-und-co-beantwortet-7-doctypes-css-transformationen-codecs-semantik/">Fragen zu HTML5 &amp; Co 7</a></li>
<li><a href="fragen-zu-html5-und-co-beantwortet-8-cors-z-index-drag-und-drop-default-styles/">Fragen zu HTML5 &amp; Co 8</a></li>
<li><a href="fragen-zu-html5-und-co-beantwortet-9-leerzeichen-canvas-resiszing-slashes-fullscreen-video-ios/">Fragen zu HTML5 &amp; Co 9</a></li>
<li><a href="fragen-zu-html5-und-co-beantwortet-10-outlines-media-metadata-postmessage-parameter-rel-und-rev/">Fragen zu HTML5 &amp; Co 10</a></li>
<li><a href="fragen-zu-html5-und-co-beantwortet-11-main-hgroup-html5-formular-validierung-komplexe-navi/">Fragen zu HTML5 &amp; Co 11</a></li>
<li><a href="fragen-zu-html5-und-co-beantwortet-12-formularvalidierung-media-attribute-h1-headlines-main-element/">Fragen zu HTML5 &amp; Co 12</a></li>
<li><a href="fragen-zu-html5-und-co-beantwortet-13-headlines-main-dom-ueberwachung-parent-selektoren/">Fragen zu HTML5 &amp; Co 13</a></li>
<li><a href="fragen-zu-html5-und-co-beantwortet-14-file-apis-hgroup-element-text-messen-css-shadow-dom/">Fragen zu HTML5 &amp; Co 14</a></li>
<li><a href="fragen-zu-html5-und-co-beantwortet-15-web-components-performance-css-variablen-data-urls-async/">Fragen zu HTML5 &amp; Co 15</a></li>
<li><a href="fragen-zu-html5-und-co-beantwortet-16-flexbox-masonry-block-inline-file-api-es6-klassen/">Fragen zu HTML5 &amp; Co 16</a></li>
<li><a href="fragen-zu-html5-und-co-beantwortet-17-pull-quotes-file-inputs-shared-workers-blobs-flexbox/">Fragen zu HTML5 &amp; Co 17</a></li>
<li><a href="fragen-zu-html5-und-co-beantwortet-18-css-runden-web-components-seo-web-worker-sprungmarken/">Fragen zu HTML5 &amp; Co 18</a></li>
<li><a href="fragen-zu-html5-und-co-beantwortet-19-flexbox-umbrueche-body-background-es6-promises-main-element/">Fragen zu HTML5 &amp; Co 19</a></li>
<li><a href="fragen-zu-html5-und-co-beantwortet-20-graustufen-sql-tail-recursion-arrow-functions-klassen/">Fragen zu HTML5 &amp; Co 20</a></li>
<li><a href="fragen-zu-html5-und-co-beantwortet-21-semantische-elemente-source-maps-a-element-html5-whitespace/">Fragen zu HTML5 &amp; Co 21</a></li>
<li><a href="fragen-zu-html5-und-co-beantwortet-22-addition-polyfills-service-worker-breakpoints/">Fragen zu HTML5 &amp; Co 22</a></li>
<li><a href="fragen-zu-html5-und-co-beantwortet-23-semikolons-flexbox-formulare/">Fragen zu HTML5 &amp; Co 23</a></li>
<li><a href="fragen-zu-html5-und-co-beantwortet-24-html-kommentare-indexed-db-regex-html5-buch/">Fragen zu HTML5 &amp; Co 24</a></li>
<li><a href="fragen-zu-html5-und-co-beantwortet-25-lange-klassen-input-labels-rekursive-typen-html-attribute/">Fragen zu HTML5 &amp; Co 25</a></li>
<li><a href="fragen-zu-html5-und-co-beantwortet-26-globale-ids-offline-modus-fortgeschrittenes-typescript-methoden-syntax/">Fragen zu HTML5 &amp; Co 26</a></li>
<li><a href="fragen-zu-html5-und-co-beantwortet-27-native-tabs-conditional-types-html-imports-top-level-async/">Fragen zu HTML5 &amp; Co 27</a></li>
</ol>
</div>

<p>
Nach drei kurzen Jahren Pause wird es mal wieder Zeit für einen Eintrag im Erfolgsformat „Webentwicklungs-Fragen vom Erklärbär beantwortet“. Es ist nicht so, als hätte es in der Zwischenzeit keine weiteren Fragen von eurer Seite gegeben, aber aus einer Reihe von Gründen konnten diese nicht in Artikel überführt werden. Damit ist jetzt Schluss! Und wenn ihr mein Backlog weiter gefüllt halten möchtet, könnt ihr mir eure Fragen zu Frontend-Themen aller Art wie gewohnt per <a href="kontakt/">E-Mail</a> oder <a href="https://mastodon.social/@sir_pepe">Fediverse</a> übersenden.
</p>

<h3>Was ist so schlimm an <code>getElementsByTagName()</code>?</h3>

<blockquote>
<p>Du hast in einem Podcast mal erwähnt, dass man <code>getElementsByTagName()</code> nicht benutzen sollte. Warum? Ist es die Spezifikation selbst oder geht es um Performance-Probleme?</p>
</blockquote>

<p>
Es gibt im Wesentlichen zwei Argumente, die gegen die Benutzung von <code>getElementsByTagName()</code> sprechen: das Vorhandensein einer mächtigeren Alternative und tatsächlich zumindest <em>mögliche</em> Performance-Probleme.
</p>

<p>
Zum einen gibt nicht wirklich einen Grund, <code>getElementsByTagName()</code> zu verwenden, wenn wir <code>querySelectorAll()</code> als Alternative zur Verfügung haben. Alles, was <code>getElementsByTagName()</code> kann, kann <code>querySelectorAll()</code> auch, plus eine ganze Menge mehr – eben Elemente anhand von mehr als nur ihres HTML-Tags auswählen. Das größere Problem ist aber tatsächlich die Performance und ein etwas unintuitives Verhalten.
</p>

<p>
<code>getElementsByTagName()</code> liefert eine <a href="https://dom.spec.whatwg.org/#interface-htmlcollection">HTMLCollection</a> mit den selektierten Elementen als Inhalt. Eine HTMLCollection sieht zwar aus, als wäre sie einfach nur ein weiteres der zahllosen (und harmlosen) Listen-Objekte im DOM, <a href="https://dom.spec.whatwg.org/#old-style-collections">ist aber in aller Regel <em lang="en">live</em></a>. DOM-Manipulationen, die <em>nach</em> <code>getElementsByTagName()</code> stattfinden, verändern also das zuvor ermittelte Ergebnis und sorgen auf der Performance-Seite für entsprechenden Mehraufwand. <a href="https://codepen.io/SirPepe/pen/gOQzrKO?editors=1010">Und intuitives Programmieren sieht auch anders aus!</a>
</p>

<pre class="prettyprint language-javascript">// Ausgangslage: &lt;div class="a"&gt;&lt;/div&ht;&lt;div class="b"&gt;&lt;/div&gt;
const divs = document.getElementsByTagName("div");
console.log(divs.length, divs[0], divs[1]); // > 2, div.a, div.b
document.body.insertAdjacentHTML("afterbegin", '&lt;div class="x"&gt;&lt;/div&gt;');
console.log(divs.length, divs[0], divs[1]); // > 3, div.x, div.a - WTF!</pre>

<p>
Wenn selbst die DOM-Spezifikationen <code>HTMLCollection</code> als <a href="https://dom.spec.whatwg.org/#old-style-collections">„a historical artifact we cannot rid the web of“</a> bezeichnen, ist es vielleicht wirklich an der Zeit, diesen Objekt-Typ und dazugehörige APIs wie <code>getElementsByTagName()</code> nicht mehr zu verwenden.
</p>

<h3>Neue HTML-Elemente erfinden, ohne sie zu registrieren?</h3>

<blockquote>
<p>
Mir ist aufgefallen, dass ich ein neues HTML-Tag benutzen und auch per CSS gestalten kann, ohne es als Web Component zu registrieren. Alles Wichtige scheint zu funktionieren. Kann es sein, dass so eine spontane Erstellung von Elementen erlaubt ist? Und wenn ja, warum verwenden wir dann überhaupt noch Klassen und IDs? Kann ich nicht einfach statt <code>&lt;div class="mainbox"&gt;</code> gleich <code>&lt;main-box&gt;</code> schreiben?
</p>
</blockquote>

<p>
Dass der Browser angesichts eines unbekannten HTML-Tags nicht mit einer Fehlermeldung abstürzt, sondern versucht, das Beste daraus zu machen, ist ein Fehlerbehandlungsmechanismus. Und das erklärt auch, warum wir unregistrierte HTML-Elemente nicht benutzen sollten: es ist besser, keine Fehler zu machen, als den Browser unsere Fehler ausbügeln zu lassen.
</p>

<p>
Auf technischer Ebene wir das unbekannte Element vom Browser als <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLUnknownElement">HTMLUnknownElement</a> verarbeitet, das für unbekannte Elemente in etwa den Funktionsumfang eines Span-Elements bereitstellt. Klassen, IDs, CSS, Data-Attribute, all das funktioniert in allen gängigen Browsern auf unbekannten Elementen. Sofern der verwendete Tag-Name einen Bindestrich enthält, ist ein solches nicht-angemeldetes unbekanntes Element sogar einigermaßen zukunftssicher, da sich der Tag im geschützten Namensraum für benutzerdefinierte Elemente befindet – es wird also nie ein neues natives Element gleichen Namens eingeführt werden.
</p>

<p>
Trotzdem ist und bleibt das <code>HTMLUnknownElement</code> ein reiner <em>Fehlerbehandlungsmechanismus</em>, den wir nicht absichtlich einsetzen sollten: nicht nur sehen Webentwickler, die ungültige Elemente verwenden, wie schlampige Handwerker aus, es wäre auch extrem unkompliziert, das Element korrekt zu registrieren:
</p>

<pre class="prettyprint language-javascript">window.customElements.define(
  "tipp-box",
  class extends HTMLElement {}
);</pre>

<p>
Das ist so wenig Aufwand, dass nicht wirklich etwas dagegen spricht, es <em>richtig</em> zu machen. Und sollte sich eines Tages herausstellen, dass das Element ein paar Extras wie eingebaute ARIA-Rules benötigt, ist die Klasse, an die diese Extras angebaut werden können, auch bereits vorhanden.
</p>

<p>
Unregistrierte Elemente fallen in die bei HTML <em>sehr</em> umfangreiche Kategorie <i>„ist nicht erlaubt, funktioniert aber“</i>. Dort befinden sich unter anderem auch Framesets, die auch niemand (mehr) verwendet, obwohl es rein <em>technisch</em> möglich wäre und auch immer möglich bleiben wird.
</p>

<h3>Bester Weg, zirkuläre Objektstrukturen zu erkennen?</h3>

<blockquote>
<p>
Wie kann ich am besten herauszufinden, ob eine Property eines Objekts eine Referenz auf das eigene Objekt oder Teile des eigenen Objekts besitzt? Ich verlasse mich bisher darauf, dass <code>JSON.stringify()</code> bei zirkulären Objekten einen Fehler wirft, aber vielleicht gibt es ja einen besseren Weg?
</p>
</blockquote>

<p>
Das Wichtigste vorweg: <code>JSON.stringify()</code> <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#exceptions">wirft zwar tatsächlich Fehler bei zirkulären Objektreferenzen</a>, hat aber auch große blinde Flecken in Form von nicht-JSON-kompatiblen Objekten:
</p>

<pre class="prettyprint language-javascript">const x = {
  a: new Map(),
};

// Böse zirkuläre Referenz
x.a.set("foo", x);

// Aber kein Fehler: JSON.stringify macht Maps zu {}
JSON.stringify(x);</pre>

<p>
JSON ist lange vor der Einführung von Maps und JavaScript definiert worden und kann daher mit Maps nichts Sinnvolles anstellen. Stattdessen verwandelt es Maps unter Missachtung ihres Inhalts einfach in <code>{}</code>, wodurch zirkuläre Referenzen unentdeckt bleiben. Andererseits wirft <code>JSON.stringify()</code> aber auch gerne Fehler bei Objekten ohne zirkuläre Referenzen:
</p>

<pre class="prettyprint language-javascript">// Fehler ohne zirkuläre Referenzen: JSON mag kein BigInt
JSON.stringify({ x: 42n }); // Error!</pre>

<p>
Wir können <code>JSON.stringify()</code> also durchaus nutzen, um <em>einige</em> Arten von zirkulären Referenzen zu erkennen, aber es ist nicht für alle Use Cases geeignet. Dort, wo <code>JSON.stringify()</code> nicht taugt, könnten wir manuell den Objekt-Tree nach zirkulären Referenzen absuchen, aber auch dann gibt es diverse Edge Cases, für die es nicht immer eine eindeutige beste Lösung geben wird:
</p>

<ul>
<li>Sollten Properties, die Symbols oder Non-Enumerable sind, ebenfalls untersucht werden?</li>
<li>Objekte könnten privaten State (via privaten Klassenfeldern oder Closures) haben, die zirkuläre Referenzen halten könnten … was für uns aber nicht feststellbar wäre.</li>
<li>Objekt-Getter könnten nichtdeterministische Ergebnisse liefern uns sich so der Zirkularitätsfrage entziehen.</li>
</ul>

<p>
Was als der wirklich beste Weg ist, hängt letztlich davon ab, ob und wie wir diese Edge Cases berücksichtigen wollen. Möglicherweise ist <code>JSON.stringify()</code> schon der beste und praktikabelste Kompromiss!
</p>

<h3>Wie kann ich ein Formular absenden, ohne ein Formular haben zu müssen?</h3>

<blockquote>
<p>Ich möchte meinem Server vorspielen, jemand hätte ein Formular ausgefüllt und abgeschickt. Wie kann ich das nur mit JavaScript, ohne <code>&lt;form&gt;</code>-Elemente erreichen?</p>
</blockquote>

<p>
<a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData">FormData hilft!</a> Einfach ein FormData-Objekt erstellen, mittels <code>append()</code> Daten (Key-Value-Paare) einfügen und dann via <code>fetch()</code> versenden:
</p>

<pre class="prettyprint language-javascript">const fd = new FormData();
fd.append("foo", "23");
fd.append("bar", "42");
fetch("/", {
  method: "POST",
  body: fd
});</pre>

<p>
Dass diese Möglichkeit besteht, ist auch tatsächlich nicht schlecht. Für normale User Interfaces ist natürlich ein normales, echtes Formular immer das Mittel der Wahl, denn <code>&lt;form&gt;</code>-Elemente liefern Features wie Barrierefreiheit, Formularvalidierung und JS-Ausfall-Sicherheit zum Quasi-Nulltarif mit. Selbst für abgefahrenste JavaScript-Vorhaben ist es immer besser, ein normales Formular anzulegen und sein Verhalten mit eigener Logik zu <em>ergänzen</em> (z. B. per abgefangenem <code>submit</code>-Event), statt das Rad mit JS von 0 neu zu erfinden.
</p>

<p>
Aber es gibt auch einige wenige Umstände, unter denen eine reine JS-Lösung das einzig verfügbare Mittel ist, wie etwa Web Worker. Fetch-API und FormData sind auch in Worker Scopes verfügbar, <code>&lt;form&gt;</code>-Elemente hingegen nicht. In diesem Fall führt also kein Weg an einer reinen JavaScript-Lösung vorbei. Gleiches gilt, wenn wir versuchen, ein Form-Associated Custom Element zu bauen, das mehr als einen Value repräsentieren soll (vergleichbar mit einem <code>&lt;select multiple&gt;</code>).
</p>

<h3>Weitere Fragen?</h3>
<p>
Habt ihr auch dringende Fragen zu Frontend-Technologien? Nur her damit! Alle Fragen in diesem Post wurden mir per <a href="kontakt/">E-Mail</a> oder <a href="https://mastodon.social/@sir_pepe">Fediverse</a> gestellt und ihr könnt das genau so machen! Einfach über einen der genannten Kanäle anschreiben oder gleich <a href="profil/">das komplette Erklärbären-Paket kommen lassen.</a><img src="https://ssl-vg03.met.vgwort.de/na/8dd1f4a715f54a1c8416b039b54d91f6" width="1" height="1" alt="">
</p>]]></content:encoded>
</item>
<item>
<title><![CDATA[Unsortierte Erkenntnisse zu Formular-Elementen mit Custom Elements, Teil 1 von N]]></title>
<link>https://www.peterkroener.de/unsortierte-erkenntnisse-zu-formular-elementen-mit-custom-elements-teil-1/</link>
<description><![CDATA[<p>
In der Theorie können wir Webentwickler:innen mit Custom Elements alles erreichen, was der Browser mit nativen Elementen schafft. Eigene Tags, eigene Attribute, eigene Events, Kapselung mit Shadow DOM – an Werkzeugen herrscht kein Mangel. Selbst eigene Formular-Inputs sind machbar, aber wer <a href="https://mastodon.social/@sir_pepe">auf Mastodon folgt</a>, hat über diverse unzusammenhängende Posts mitbekommen, dass <em>das</em> gar nicht mal so einfach ist. Web-Formulare sind an sich schon kompliziert genug, aber eigene Formular-Elemente zu entwerfen, ist wahrlich noch ein dickes Stück komplexer.&#8230;</p>]]></description>
<pubDate>Tue, 12 Dec 2023 10:27:21 +0100</pubDate>
<guid isPermaLink="false">https://www.peterkroener.de/unsortierte-erkenntnisse-zu-formular-elementen-mit-custom-elements-teil-1/</guid>
<dc:creator>Peter Kröner</dc:creator>
<content:encoded><![CDATA[<p>
In der Theorie können wir Webentwickler:innen mit Custom Elements alles erreichen, was der Browser mit nativen Elementen schafft. Eigene Tags, eigene Attribute, eigene Events, Kapselung mit Shadow DOM – an Werkzeugen herrscht kein Mangel. Selbst eigene Formular-Inputs sind machbar, aber wer <a href="https://mastodon.social/@sir_pepe">auf Mastodon folgt</a>, hat über diverse unzusammenhängende Posts mitbekommen, dass <em>das</em> gar nicht mal so einfach ist. Web-Formulare sind an sich schon kompliziert genug, aber eigene Formular-Elemente zu entwerfen, ist wahrlich noch ein dickes Stück komplexer. In diesem Artikel fasse ich ein paar der Dinge, die ich bis dato zu diesem Thema gelernt habe, etwas weniger unzusammenhängend als auf Mastodon zusammen. Weitere Artikel könnten folgen.
</p>

<h3 id="begrifflichkeiten-und-ziele">Begrifflichkeiten und Ziele</h3>

<p>
Das Ziel meiner Experimente ist, mit Custom Elements neue Formularfelder zu bauen, die im Hinblick auf Features, Verhalten und APIs den nativen Elementen in <em>nichts</em> nachstehen. Abstriche in auch nur einer dieser Kategorien zu machen, ruiniert die Kompatibilität der resultierenden Elemente. Nur Custom Elements, die sich 1:1 wie native Elemente verhalten, sind für sowohl für den Einsatz in handgeschriebenem HTML als auch in fetten Frontend-Frameworks als auch für Vanilla-DOM-Programmierung geeignet&nbsp;… und wenn diese Kompatibilität zu all dem <em>nicht</em> das Ziel ist, sollte man statt einer Web Component einfach eine Fette-Frontend-Framework-Komponente schreiben und sich sehr, <em>sehr</em> viel Arbeit ersparen. Keine Kompromisse!
</p>

<p>
Zuletzt möchte ich noch ein paar nicht offensichtliche Begriffe definieren, die ich im Folgenden zu Felde führen werde:
</p>

<ul>
<li><strong>CFI</strong>: <em>Custom Form Input</em>, ein von mir soeben erfundener Begriff für Custom Elements, die im Allgemeinen <em>form associated</em> sind, im Speziellen den Use Case des Eingabefelds bedienen und vor allem die o.g. Kompatibilitäts-Ziele verfolgen.</li>
<li><strong>Content-Attribut:</strong> via HTML oder <code>setAttribute()</code> gesetzter Attribut-Wert. Immer ein String und tritt praktisch immer zusammen mit einem entsprechenden <em>IDL-Attribut</em> auf.</li>
<li><strong><a href="https://developer.mozilla.org/en-US/docs/Glossary/IDL#content_versus_idl_attributes">IDL-Attribut</a>:</strong> Per JavaScript Getter/Setter-Paar definierte API für ein Attribut. Kann andere Typen als <code>string</code> haben und tritt praktisch immer zusammen mit einem entsprechenden <em>Content Attribut</em> auf.</li>
<li><strong>Constraint Validation:</strong> Auch als „HTML5-Validierungs-API“ bekannte <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation">eingebaute Validierungs-Features</a> für Formulare.</li>
</ul>

<p>
Und nun stürzen wir uns einfach direkt in die nicht mehr ganz so unzusammenhängende Sammlung von Erkenntnissen zu Formular-Elementen, Teil 1 von N!
</p>

<h3 id="api-boilerplate">API-Boilerplate</h3>

<p>
Eine Custom-Element-Klasse kann sich per <code>static formAssociated = true</code> zum <a href="https://html.spec.whatwg.org/#form-associated-element">Form-Associated Element</a> erklären und damit in den Club der Formular-Elemente <code>&lt;input&gt;</code>, <code>&lt;fieldset&gt;</code> und Co eintreten. Per <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals"><code>attachInternals()</code></a> erhalten wir Zugriff auf APIs für den Formular-Element-Zustand (v.a. <code>setFormValue()</code>). Wichtig hierbei ist, dass die <i lang="en">Element Internals</i> neben solchen privaten APIs auch allerlei <strong>öffentliche APIs</strong> enthalten: die <code>checkValidity()</code>-Methode, der <code>form</code>-Getter und alles Sonstige, was man an JS-APIs auf Inputs, Selects und Textareas so erwartet. Diese APIs muss eine CFI-Klasse mauell bereitstellen, in etwa wie folgt:
</p>

<pre class="prettyprint language-javascript">class CFI extends HTMLElement {
  static formAssociated = true;
  #internals = this.attachInternals();

  get labels() {
    return this.#internals.labels;
  }

  get form() {
   return this.#internals.form;
  }

  get willValidate() {
    return this.#internals.willValidate;
  }

  get validity() {
    return this.#internals.validity;
  }
  
  get validationMessage() {
    return this.#internals.validationMessage;
  }
  
  checkValidity() {
    return this.#internals.checkValidity();
  }

  reportValidity() {
    return this.#internals.reportValidity();
  }
}</pre>

<p>
Das implementiert alle JavaScript-Features, die man auf Formular-Inputs standardmäßig erwarten darf – abgesehen von jenen, die Wechselwirkungen mit Content- und IDL-Attributen haben, wie z.&nbsp;B. <code>required</code>, <code>value</code> und <code>name</code>. Und wo wir gerade bei <code>name</code> sind&nbsp;…
</p>

<h3 id="formulare-absenden">Formulare absenden</h3>

<p>
Damit ein CFI von einem Formular mit abgesendet werden kann, braucht es lediglich ein Content-Attribut <code>name</code> und absendbare Daten (<i lang="en">„submission value“</i>). Letzteres kann über die <code>setFormValue()</code>-Methode der <i lang="en">Element Internals</i> definiert werden und ersteres benötigt streng genommen <em>keinen</em> IDL-Gegenpart und daher keine wirkliche Implementierung. Die minimale absendbare CFI-Klasse sieht also wie folgt aus:
</p>

<pre class="prettyprint language-javascript">class CFI extends HTMLElement {
  static formAssociated = true;
  constructor() {
    super();
    this.attachInternals().setFormValue(&quot;foobar&quot;);
  }
}</pre>

<p>
<a href="https://codepen.io/SirPepe/pen/XWOPzBN?editors=1010">Die funktionierende Demo</a> zeigt, dass es nicht nötig ist, <code>name</code> ordentlich (d.&nbsp;h. mit IDL-Attribut) zu definieren, aber im Rahmen der für uns definierten Ziele würde dieser Aufwand natürlich schon anfallen.
</p>

<h3 id="value-state-und-attribute"><code>value</code> State und Attribute</h3>

<p>
Wie gesehen wird der eigentliche Formularfeld-Wert über die <code>setFormValue()</code>-Methode der <i lang="en">Element Internals</i> festgelegt, wobei jedoch die Content- und IDL-Attribute <code>value</code> auch eine Rolle spielen. Der grobe Zusammenhang sieht wie folgt aus:
</p>

<ol>
<li>Der <em>form value</em> initialisiert sich aus dem Content-Attribut <code>value</code> (sofern vorhanden). Standardwert ist der leere String.</li>
<li>Ein <em>dirty flag</em> initialisert sich <code>false</code>.</li>
<li>Der IDL-Getter <code>value</code> reflektiert den aktuellen <em>form value</em>, der IDL-Setter <code>value</code> setzt den aktuellen <em>form value</em>, aber nicht das Content-Attribut <code>value</code>.</li>
<li>Solange das <em>dirty flag</em> <code>false</code> ist, führen Updates des Content-Attributes <code>value</code> zu Updates des <em>form value</em>. Wenn das <em>dirty flag</em> <code>true</code> ist, passiert das nicht mehr, denn getätigte Nutzereingaben sollten selbst bei sich veränderndem DOM erhalten bleiben.</li>
<li>Das <em>dirty flag</em> wird <code>true</code>, sobald der IDL-Setter verwendet wird oder ein Nutzer auf eine Weise mit dem Input interagiert, die den <em>form value</em> verändert.</li>
<li>Form-Resets (siehe unten) setzten das <em>dirty flag</em> zurück auf <code>false</code> und den <em>form value</em> auf den Wert des Content-Attributs <code>value</code> (sofern vorhanden). Standardwert ist auch hier der leere String.</li>
</ol>

<p>
Das <em>dirty flag</em> in der obigen Beschreibung stammt <a href="https://html.spec.whatwg.org/#concept-fe-dirty">direkt aus den Spezifikationen</a> und ist nicht mit dem <i><a href="https://html.spec.whatwg.org/#user-interacted">user interacted flag</a></i> zu verwechseln: letzteres erfährt beim Form-Reset selbst keinen Reset und ist ausschließlich dafür zuständig, dass die Pseudoklassen <code>:user-valid</code> und <code>:user-invalid</code> korrekt greifen.</p>

<h3 id="deaktivierung-und-form-resets">Deaktivierung und Form-Resets</h3>

<p>
Standard-<code>&lt;input&gt;</code> und Co können mithilfe des boolschen <code>disabled</code>-Attribut (Content- und IDL-Attribut) deaktiviert werden, was für unsere Custom Elements genau so gelten sollte. Eine Deaktivierung kann aber auch ausgelöst werden, indem auf dem nächstgelegene Vorfahren-<code>&lt;fieldset&gt;</code> (sofern vorhanden) <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset#disabled">dessen <code>disabled</code>-Attribut</a> (als Content- oder IDL-Attribut) gesetzt wird! Für Custom Elements bedeutet das: Der tatsächliche Aktiviertheits-Zustand eines CFI ergibt sich aus dem eigenen <code>disabled</code>-Attribut <em>und</em> dem <code>disabled</code>-Zustand des nächstgelegenen relevanten Fieldsets. Änderungen am letztgenannten Zustand sind über den Lifecycle-Callback <code>formDisabledCallback()</code> beobachtbar.
</p>

<p>
Der Aktiviertheits-Zustand eines Formularfelds ist nicht mit dem Mutability-Zustand zu verwechseln. Letzterer wird ausschließlich vom <code>readonly</code>-Attribut des betroffenen Formularfelds gesteuert und belässt das Element, anders als der Aktiviertheits-Zustand, validierbar und absendbar.
</p>

<p>
Einen Lifecycle-Callback gibt es auch für Formular-Resets. Dieser Tage sind Formular-Reset-Buttons nicht in Mode, aber sie können vorkommen! CFI sollten daher via <code>formResetCallback()</code> einen eigenen Reset-Algorithmus implementieren, der mindestens den <code>formValue</code> (auf den Wert des Content-Attributes <code>value</code>) und das dazugehörige <em>dirty flag</em> (auf <code>false</code>) zurücksetzt.
</p>

<h3 id="formularvalidierung">Formularvalidierung</h3>

<p>
Mit der <code>setValidity()</code>-Methode von <i lang="en">Element Internals</i> können wir unsere CFI im Rahmen der guten alten Contraint Validation API als ungültig ausgefüllt markieren. Das kann auf verschiedene Weisen nützlich sein, für die die drei möglichen Parameter von <code>setValidity()</code> gezielt jongliert werden wollen.
</p>

<p>
Parameter 1, <code>validity</code>, ist eine Sammlung von mit <a href="https://developer.mozilla.org/en-US/docs/Web/API/ValidityState">ValidityState-Objekten</a> übereinstimmenden Flags, die die typischen Validierungsfehler anzeigen (<code>valueMissing</code>, <code>tooLong</code> etc.). Der Parameter bestimmt, wenig überraschend, den neuen ValidityState des CFI. Nicht angegebene Flags werden per Default auf <code>false</code> gesetzt, d.&nbsp;h. die entsprechenden Fehler liegen dann nicht vor.
</p>

<p>
Parameter 2, <code>message</code>, ist die anzuzeigende Fehlermeldung als String. Er <em>muss</em> (als nicht-leerer String) angegeben werden, wenn Parameter 1 mindestens einen Flag auf <code>true</code> setzt, kann aber weggelassen oder auf einen leeren String gesetzt werden, wenn kein Fehler vorliegt. Zusammen mit Parameter 1 ist es so ein Leichtes, <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setCustomValidity"><code>setCustomValidity()</code></a> für CFI zu implementieren (<a href="https://codepen.io/SirPepe/pen/yLZxdwY?editors=1010">Demo</a>):
</p>

<pre class="prettyprint language-javascript">class CFI extends HTMLElement {
  static formAssociated = true;
  #internals = this.attachInternals();
  setCustomValidity(msg) {
    this.#internals.setValidity({ customError: true }, msg);
  }
  get validationMessage() {
    return this.#internals.validationMessage;
  }
}</pre>

<p>
Parameter 3, <code>anchor</code>, hat einen extrem spezifischen, aber auch extrem nützlichen Use Case. Wenn unser CFI in seinem Shadow DOM andere Form-Associated Elements enthält und primär als Wrapper um diese Elemente fungiert (z.&nbsp;B. im Rahmen einer Pattern Library), muss der Validierungs-State des CFI den Validierungs-State der gewrappten Elemente widerspiegeln. Das ist nötig, da die Elemente im Shadow DOM vom Formular des CFI isoliert sind und somit selbst gar nicht der Constraint Validation unterliegen. Die Elemente können aber schon dafür genutzt werden, Validierungsfehler anzuzeigen, bei Auftreten eines Fehlers Fokus zu erhalten und ganz allgemein ist es im genannten Szenario korrekt, wenn die <em>Verursacher-Elemente</em> ihr Standard-Fehlerverhalten zeigen und das <em>Wrapper-Element</em> nur das Bindeglied zwischen Formular und Shadow DOM spielt.
</p>

<p>
In Konsequenz bedeutet das: wenn ein CFI ein Wrapper um andere Inputs ist und Validierungsfehler aus den gewrappen Inputs stammen, können wir mit dem <code>anchor</code>-Parameter den Validierungs-State des CFI als Fehler auf dem gewrappen Input anzeigen. Das ist z.&nbsp;B. im Pattern-Library-Szenario auch genau, was wir wollen. Umgekehrt folgt daraus, dass wenn das CFI kein Wrapper um andere Inputs ist, es keinen <code>anchor</code> und damit auch keine Standard-UX für Validierungsfehler-Anzeige gibt. Solche Non-Wrapper-CFI müssten hierfür eine ganz eigene UX erfinden.
</p>

<h3 id="zwischenstand-und-ausblick">Zwischenstand und Ausblick</h3>

<p>
Die in diesem Post gesammelten Erkenntnisse <a href="https://gist.github.com/SirPepe/8b1a2a79c82a61fc178d46fa393248a5">konnte ich in einem <code>@formElement</code>-Decorator</a> so zusammenfassen, dass es damit (und mit <a href="https://www.npmjs.com/package/@sirpepe/ornament">Ornament</a>) einigermaßen einfach möglich wird, simple Wrapper-CFI zu definieren. Die folgenden Zeilen implementieren eine Abstraktion über ein <code>&lt;input type=&quot;number&quot;&gt;</code>, die nur ganze Zahlen zulässt, aber u.&nbsp;a. <code>required</code> und <code>disabled</code> weiter als Attribute unterstützt:
</p>

<pre class="prettyprint language-javascript">import { render, html } from &quot;uhtml&quot;;
import { define, attr, int, bool, reactive, debounce } from "@sirpepe/ornament";

@define(&quot;int-input&quot;)
@formElement()
export class IntInput extends HTMLElement {
  #root = this.attachShadow({
    mode: &quot;closed&quot;,
    delegatesFocus: true
  });

  @attr(bool()) accessor required = false;
  @attr(int({ nullable: true })) accessor min = null;
  @attr(int({ nullable: true })) accessor max = null;

  @reactive()
  @debounce({ fn: debounce.raf() })
  render() {
    render(
      this.#root,
      html&#x60;
        &lt;input
          value=&quot;${this.defaultValue}&quot;
          min=${this.max ?? &quot;&quot;}
          min=${this.max ?? &quot;&quot;}
          step=&quot;1&quot;
          type=&quot;number&quot;
          ?disabled=${this.disabledState}
          ?required=${this.required} /&gt;&#x60;
    );
  }
}</pre>

<p>
Für das, was es tut, ist das fast eine akzeptable Code-Menge! Immerhin leisten diese paar Zeilen:
</p>

<ol>
<li>Definition eines neuen Form-Associated Custom Element</li>
<li>Implementierung der öffentlichen Standard-Formular-APIs (<code>setCustomValidity()</code> etc.)</li>
<li>Implementierung der öffentlichen Standard-Formular-Attribute <code>name</code>, <code>disabled</code> und <code>value</code> (mit Content- und IDL-Attributen) mit dazugehörigem Verhalten bei Resets und deaktivierten Fieldset-Vorfahren</li>
<li>Implementierung der zusätzlichen Attribute <code>required</code>, <code>min</code>, und <code>max</code> (mit Content- und IDL-Attributen, wobei <code>min</code> und <code>max</code> sich sogar die Mühe machen, ihre States als <code>BigInt</code> zu repräsentieren)</li>
<li>Durchschleifen von Value- und Validity-Updates des gewrappten Inputs an den Wrapper, Meldung der Validity-Errors mit dem gewrappten Input als <code>anchor</code></li>
</ol>

<p>
Die Dependency-Kosten belaufen sich allein auf die &lt;4k von <strong>Ornament</strong> (theoretisch optional, würde aber die Komplexität des Rests explodieren lassen), die Rendering-Engine <a href="https://github.com/WebReflection/uhtml"><strong>uhtml</strong></a> (theoretisch könnte auch jeder andere Render-Mechanismus verwendet werden) und die 250 Zeilen Form-Decorator.
</p>

<p>
Das ist ein brauchbarer Zwischenstand, aber gibt es noch viel mehr zu erforschen: CFI könnten mehr als nur ein Form-Element wrappen, was ein komplexeres Value-Composing notwendig machen würde. Um diverse Lifecycle-Events wie <code>formStateRestoreCallback</code> habe ich mich noch gar nicht gekümmert und das leidige Thema der TypeScript-Kompatibilität steht auch noch im Raum. Dazu (und zu bestimmt noch vielen anderen Features und Randaspekten) dann mehr in folgenden Artikeln.<img src="https://ssl-vg03.met.vgwort.de/na/7c88f180c9ab44edac77227ef7083cfc" alt="">
</p>]]></content:encoded>
</item>
</channel>
</rss>