Auch in Zeiten der Seuche herrscht an Webentwicklungs-Fragen kein Mangel und aufgrund von fortgesetzter coronabedingter Arbeitslosigkeit habe ich beschlossen, mein beachtliches Backlog in Angriff zu nehmen! Wenn auch ihr Fragen zu Frontend-Themen aller Art habt, stellt sie mir per E-Mail oder Twitter und ich verspreche zeitnahe Antworten!

Warum gibt es kein HTML-Element für Tab-Widgets?

Warum gibt es eigene HTML-Elemente für Fortschrittsbalken und das Details-Element für Klapp-Dialoge, aber kein eingebautes Tab-Widget-Element?

Es gibt einen fundamentalen Unterschied zwischen einem Tab-Widget und dem <details>-Element und dieser Unterschied ist die vermutlich beste Erklärung dafür, warum ersteres nicht im HTML-Standard ist (und meiner Prognose nach dort auch nie landen wird): ein Tab-Widget ist ein sehr konkretes und komplexes UI-Konzept, während <details> extrem abstrakt bzw. allgemein spezifiziert ist. Um die Spezifikationen zu zitieren:

The details element represents a disclosure widget from which the user can obtain additional information or controls.

Das <details>-Element ist mitnichten ein „Element für Klapp-Dialoge“, sondern definiert einen Container für ein- und ausblendbare Informationen und damit ein extrem allgemeines Konzept. Eine konkrete Umsetzung der damit verbundenen User Experience ist nicht vorgeschrieben und es ist nur mehr oder minder zufällig so, dass alle bekannten Browser das Element als Klapp-Dialog umsetzen. Die Definition von <details> lässt aber auch viele andere Umsetzungen zu, was für Screenreader, Terminal-Anwendungen und vor allem für zukünftige UI-Konzepte, die es 2020 noch gar nicht gibt, sehr wichtig ist. Die Webplattform konnte nur über 30 Jahre relevant bleiben, indem von der <h1> bis hin zu <details> alles so flexibel und zukunftssicher (man könnte auch sagen: unkonkret) gehalten wurde, dass alle neuen Entwicklungen wie z.B. Smartphones mitgegangen werden konnten. Ein weiteres Beispiel für solch allgemeine Spezifizierungen sind die HTML5-Formularelemente. So ist z.B. für das <input type="date"> nicht vorgeschrieben, wie ein dazugehöriger Datumspicker aussehen soll. Die Browser können selbst entscheiden und daher für Desktop wie Mobiltelefon jeweils passende Interfaces auswählen.

Ein hypothetisches Tab-HTML-Element ist im Prinzip auch nur ein weiteres „disclosure widget“ wie <details>-Element, aber konzentriert sich mit dem Fokus auf ein Tab-Interface schon sehr auf eine konkrete User Experience. Diese so zu spezifizieren, dass sie zum einen so allgemein und anpassungsfähig bleibt, wie es sich bei HTML gehört und dabei trotzdem (auf den heutigen Geräten) so etwas wie ein Tab-Widget festzuschreiben, dürfte der Quadratur des Kreises gleichkommen. Entweder es ist ein flexibles „disclosure widget“ oder es ist ein konkretes Tab-Element – beides auf einmal ist, wenn überhaupt, nur sehr schwer unter einen Hut zu bekommen.

Ein etwas zynischeres Argument gegen das HTML-Tab-Widget habe ich aber auch noch: egal wie es am Ende spezifiziert wird, es wird für 95% der Use Cases nicht reichen. Das <input type="date"> zeigt sehr schön, was passiert, wenn HTML versucht, komplexe UI-Elemente zu definieren: aus den genannten Gründen muss die Spezifikation sehr offen bleiben, was nicht nur in verschiedene Browsern zu unterschiedlichen UIs führt, sondern auch diese UIs dazu verdammt, je nach Projekt entweder zu komplex oder zu simpel zu sein. Eine Reisebuchungs-Webseite kann Datepicker gebrauchen, die zwei Monate auf einmal anzeigen können, eine Behörden-Webapp, der man sein Geburtsdatum mitteilen möchte, braucht das nicht. Beide benötigen aber sehr wohl sofortigen Cross-Browser-Support, weswegen der Griff zu einem JS-Datepicker nahe liegt. Ähnlich wird es sich beim Tab-Widget verhalten: die fette Enterprise-App braucht eine scrollbare Tab-Leiste, die zu 99% aus Whitespace bestehende Startup-Landingpage sicher nicht. All diese Details zu spezifizieren wäre zum einen eine nie dagewesene (und kaum zu bewältigende) Mammutaufgabe und würde zum anderen den Zwang zur Flexibilität unterlaufen.

Tab-Widgets sind allgegenwärtig, aber das bedeutet nicht, dass sie einfach unter einer vereinheitlichten Definition zu fassen sind. Ich glaube, dass es nicht möglich ist, ein allgemeines natives HTML-Tab-Widget vernünftig zu spezifizieren. Und sollte das möglich sein, ist immer noch fraglich, ob dieses native Widget am Ende auch genutzt wird, wenn als Alternative die in das JS-Framework der Wahl integrierte, konfigurierbare, in allen Browsern funktionierende NPM-Modul winkt.

Reinhard fragt: Conditional TypeScript-Types für Methoden-Signaturen verwenden?

Ich habe eine Frage zu Conditional Types in TypeScript. Folgendes Szenario:

class Node {…}

class Factory {
  sendMessage(type: "create" | "remove") {}
}

Die Factory soll bei sendMessage() mit "create" eine Node zurückliefern und bei "remove" eine number. D.h. ich will folgendes schreiben können:

const node = factory.sendMessage("create");
// hier soll node jetzt direkt vom typ "Node" sein

Geht das überhaupt? Ich vermute, hier wären beim Rückgabetyp von sendMessage() Conditional Types hilfreich …

Dein Ziel kannst du mit Overloads besser als mit Conditional Types erreichen. Immer wenn in der Signatur einer Funktion (oder Methode) unterschiedliche Parameter-Typen unterschiedliche Rückgabetypen produzieren sollen, ist ein Overload das Mittel der Wahl. Anders formuliert: wenn die Beziehung zwischen Input- und Output-Typen in eine Tabelle passt, sind Overloads optimal. In diesem Fall ist die Tabelle:

Parameter-Typ Return-Typ
"create" Node
"remove" number
"create" | "remove" Node | number

Der letzte Fall in der Tabelle umschreibt die eigentliche Implementierung der Funktion, während die beiden ersten Fälle die jeweiligen Spezialisierungen festlegen. In Code formuliert sieht die Tabelle wie folgt aus:

class Factory {
  sendMessage(type: "create"): Node;
  sendMessage(type: "remove"): number;
  sendMessage(type: "create" | "remove"): Node | number {
    let x: any;
    return x;
  }
}

Gegenüber Conditional Types hat Überladen einen großen Vorteil: Es ist sowohl für die TypeScript-Typinferenz als auch für Menschen leichter verständlich. Für TS ist die 1:1-Beziehung zwischen Input- und Output-Typen von Vorteil und die meisten TS-Autoren dürften mit Overloads eher vertraut sein als mit den vergleichsweise esoterischen Coditional Types.

Robert fragt: Was ist aus HTML-Imports geworden?

Was ist denn aus HTML-Imports geworden? Damit könnte man ziemlich viele Probleme erschlagen, für die man sonst zu JavaScript und Bundlern greifen muss.

Von Anfang an hat Mozilla HTML-Imports eine Absage erteilt und seither sind HTML-Imports weitgehend in der Versenkung verschwunden. ECMAScript-Module können (u.U. über Umwege) auch HTML importieren und da ES-Module definitiv existieren und HTML-Imports eigentlich nur ein anderes UI für die gleiche Funktionalität (Datei-Request durchführen und anschließend verarbeiten) darstellen, ist es durchaus vertretbar, ES-Module den Vorzug zu geben.

Außerdem unterstelle ich, dass HTML-Imports und vergleichbare Tools viel weniger nützlich sind, als manche annehmen. Vor langer Zeit habe ich zum clientseitigen Zusammenstückeln von Präsentationen eine eigene Variante von HTML-Imports gebaut, die ich zu diesem Zweck bis heute verwende. Aber auch nur zu diesem Zweck. Kein einziges anderes Projekt verlangte je nach HTML-Imports oder ähnlichem, und wenn doch, war ich stets innerhalb von JavaScript unterwegs und konnte mir mit einem Modul-Import behelfen.

Markus fragt: Top-Level-Await außerhalb von Async Functions?

Hattest du auf den JavaScript-Days nicht gesagt, dass fetch() async ist und mit await verwendet werden kann? Bei mir schlägt await fetch("/playlists") fehl und die Fehlermeldung ist: await is only valid in async functions. Heißt das, ich muss fetch() selber asynchron wrappen?

Genau richtig: await funktioniert heutzutage eigentlich nur innerhalb von asynchronen Funktionen. Und der Grund dafür ist eigentlich recht interessant. Async/Await ist eigentlich nur syntaktischer Zucker für Generator Functions bzw. das yield-Statement, das seinerseits prinzipbedingt nur in Generator Functions vorkommen kann. Dieses Video erklärt, wie mit Generators und einer kleinen Runtime die gleiche Funktionalität wie Async/Await umgesetzt werden kann und das reale Async/Await funktioniert ziemlich genau so.

Du hast also zwei Möglichkeiten das Problem anzugehen:

  1. das gute alte .then() statt await benutzen, zumindest im Top-Level außerhalb von anderen asynchronen Funktionen
  2. den ganzen Code in einen Async-Wrapper verpacken, z.B. (async () => { /* dein Code */ })()

Top-Level Await ist als offizielles JS-Feature in Arbeit, aber funktioniert weder im Browser (außerhalb der Chrome-Devtools-Konsole) noch via Babel. Es gibt experimentellen Support in Webpack und Rollup, aber von flächendeckender Einsatzbereitschaft sind wir noch etwas entfernt. Das macht aber nichts: Das Top-Level-Async-Problem besteht fast ausschließlich in Index-Modulen und CLIs-Scripts und deren Menge (und daher die Anzahl der einzubauenden Workarounds) ist in jedem Projekt endlich.

Weitere Fragen?

Habt ihr auch dringende Fragen zu Frontend-Technologien? Nur her damit! Alle Fragen in diesem Post wurden mir per E-Mail oder Twitter gestellt und ihr könnt das genau so machen! Einfach über einen der genannten Kanäle anschreiben oder für die Zeit nach Corona schon mal das komplette Erklärbären-Paket reservieren.