Digitale Barrierefreiheit

Die Barrierefreiheit von Webseiten ist tief im HTML-Standard verankert. Dennoch sind viele Webseiten für Menschen nicht nutzbar, wenn sie keine Standardhardware bedienen, nicht gut sehen, hören, komplexe Sachverhalte verstehen oder sich nicht gut konzentrieren können. Das sind viel mehr Nutzer:innen als landläufig angenommen. Bisher wurden Innovationszyklen von Webseiten vor allem visuell von Brandagenturen und Marketing oder durch technische Lösungen vorangetrieben, bei denen nur auf die „normalen“ Interaktionsmöglichkeiten Rücksicht genommen wurde. Da der Markt die Barrierefreiheit in 30 Jahren nicht vorangetrieben, sondern eher verhindert hat, sind in den USA und der EU die Gesetzgeber eingesprungen.

Der Stichtag ist der 28. Juni 2025 – das Datum liegt bei Erscheinen dieses Artikels noch knapp zwei Jahre in der Zukunft [1]. Ab diesem Tag müssen alle neu erstellten Webseiten barrierefrei sein, es sei denn, es handelt sich um ein Dienstleistungsangebot einer KMU mit weniger als 10 Mitarbeitenden und weniger als 2 Millionen Euro Jahresumsatz bzw. Jahresbilanzsumme. Für alle anderen Webseiten gilt eine Frist von 5 Jahren. Wenn in der Zwischenzeit das Angebot auf diesen Seiten verändert wird, gilt die Pflicht ab der Veröffentlichung dieser Veränderung. Alle anderen Seiten müssen ebenfalls bis zum 27. Juni 2030 barrierefrei überarbeitet werden [2]. Das will die EU mit der Richtlinie (EU) 2019/882 [3], auch EAA (European Accessibility Act) genannt, erreichen.

Warum wird digitale Barrierefreiheit zur Pflicht?

„Der Bedarf an barrierefreien Produkten und Dienstleistungen ist groß, und die Zahl der Menschen mit Behinderungen wird voraussichtlich noch deutlich steigen. Ein Umfeld mit besser zugänglichen Produkten und Dienstleistungen ermöglicht eine inklusivere Gesellschaft und erleichtert Menschen mit Behinderungen ein unabhängiges Leben. Dabei sollte berücksichtigt werden, dass in der Union mehr Frauen als Männer eine Behinderung haben.“ So heißt es im zweiten Absatz der aufgeführten Gründe, die der Richtlinie vorangestellt wurden.

Weiterhin geht es darum, dass der Flickenteppich an Richtlinien, die derzeit in den Mitgliedsstaaten gelten, vereinheitlicht werden soll. Dadurch soll es einfacher werden, Produkte innerhalb der EU grenzüberschreitend zu vermarkten. Es geht auf der einen Seite also darum, Menschen mit Behinderung bewusst an der digitalen Welt teilhaben zu lassen. Das ist gut, wichtig und mehr als überfällig. Begründet wird dies aber auch mit dem wirtschaftlichen Aspekt von größeren Zielgruppen und Märkten. Dabei gibt es drei Richtlinien:

  1. WCAG des W3C
  2. EN 301 549 der EU enthält viele der WCAG-Kriterien und ergänzt sie
  3. BITV enthält die Übertragung der EN 301 549 in deutsches Recht

WCAG

WCAG steht für „Web Content Accessibility Guidelines“. Version 2.1 [4] gilt seit dem 05.06.2018. Version 2.2 hat den Status „Candidate Recommendation Draft“ [5]. Die finale Version wird noch 2023 erwartet.

Die WCAG 2.1 [6] enthält 78 Erfolgskriterien, die vier Prinzipien zugeordnet und in drei Konformitätsstufen unterteilt sind. Level A ist das absolute Minimum, das erfüllt werden muss, Level AA ist der Standard und Level AAA betrifft nur bestimmte Inhalte, die nicht auf allen Seiten Anwendung finden.

In Tabelle 1 sehen Sie die Matrix, wie die Prinzipien und die Konformitätsstufen sich anhand der Quick-Referenz der WCAG 2.2 überschneiden [7].

4 Prinzipien 14 Unterkapitel 78 Erfolgskriterien 3 Konformitätsstufen
A AA AAA
1) perceivable/ wahrnehmbar Textalternativen 1 1
zeitbasierte Medien 9 4 1 4
anpassbar 6 3 2 1
unterscheidbar 13 2 7 4
wahrnehmbar gesamt 29 10 10 9
2) operable/ bedienbar mit der Tastatur zugänglich 4 3 1
genug Zeit 6 2 4
Krämpfe und physische Reaktionen 3 1 2
navigierbar 10 4 3 3
Eingabemodalitäten 6 4 2
bedienbar gesamt 29 14 3 12
3) understandable/ verständlich lesbar 6 1 1 4
vorhersagbar 5 2 2 1
Eingabehilfen 6 2 2 2
verständlich gesamt 17 5 5 7
4) robust* kompatibel 3 2 1
robust gesamt 3 2 1 0
Gesamt 78 31 19 28
* robust: alle Inhalte müssen mit einer Vielzahl an Geräten und Browsern auch dann wahrnehmbar und bedienbar sein, wenn assistive Technologien, z. B. Screenreader, Braillezeilen, Bildschirmlupen, unterschiedliche Eingabemethoden (Maus/Touch/Fokus) genutzt werden. Außerdem gibt es unzählige adaptive Technologien/Einstellungsmöglichkeiten in Standardhardware, auf die dieser Grundsatz zutrifft.

Tabelle 1: Die WCAG-Erfolgskriterien mengenmäßig den Prinzipien und Konformitätsstufen zugeordnet

EN 301 549

EN steht für „Europäische Norm“. Die Nummer 301 549 enthält die „Accessibility requirements for ICT products and services“, die „Barrierefreiheitsanforderungen für IKT-Produkte und -Dienste“. IKT steht für Information- und Kommunikationstechnik. Version 3.2.1 gilt seit dem 21.05.2021.

BITV 2.0

BITV steht für „Barrierefreie-Informationstechnik-Verordnung“. Version 2.0 gilt seit dem 21.05.2019.

Die Prüfschritte der BITV 2.0 [8] orientieren sich an der EN 301 540 Version 3.1.1. und 3.2.1. Die Nummern von BITV-Prüfschritten, die auf der EN 301 549 basieren, sind weitgehend mit der Nummerierung dort identisch. Manche der EN-301-549-Prüfschritte werden in der BITV jedoch in mehrere Einzelschritte unterteilt.

Auf der Seite barrierefreiheit-dienstekonsolidierung.bund.de wird klargestellt: Die WCAG-Erfolgskriterien der Konformitätsstufen A und AA sind damit mit dem Standard EN 301 549 verbindlich einzuhalten. Die WCAG-Erfolgskriterien der Konformitätsstufe AAA werden im Standard EN 301 549 informatorisch beziehungsweise als erweiterte Kriterien aufgelistet, die nicht für alle Inhalte einer Webseite relevant sind (Tabelle 2) [9].

Kapitel Kriterien EN 3.1.1 EN 3.21 WCAG 2.1 Level
A AA AAA
1. allgemeine Anforderungen 3 3
2. Zwei-Wege-Sprachkommunikation 16 14 2
3. Videofähigkeiten 9 7 2
4. Textalternativen 4 (1 nur BITV) = = 3
5. zeitbasierte Medien 5 (1 nur BITV) = = 2 2
6. anpassbar 12 = = 10 2
7. unterscheidbar 9 (1 A + AA) = = 2 8
8. per Tastatur zugänglich 3 = = 3
9. ausreichend Zeit 2 = = 2
10. Anfälle 1 (2*AAA extra) = = 1 (2)
11. navigierbar 7 = = 5 2
12. Eingabemodalitäten 4 = = 4
13. lesbar 2 = = 1 1
14. vorhersehbar 4 = = 2 2
15. Hilfestellung bei der Eingabe 4 = = 2 2
16. kompatibel 3 = = 2 1
17. benutzerdefinierte Einstellungen 1 1 =
18. Autorenwerkzeuge 4 4 =
19. Dokumentation und Support 5 5 =
Gesamt 98 34 4 37 20 (2)

Tabelle 2: Die BITV-Prüfkriterien mengenmäßig den Kapiteln und den EN-301-549- und WCAG-Konformitätsstufen zugeordnet

Die EN 301 549 und die BITV enthalten einige Punkte, die in der WCAG nicht enthalten sind und umgekehrt. Zum Beispiel fehlen in der BITV einige Punkte der WCAG zur Verständlichkeit. Diese wiederum sind in Leichter Sprache sehr wichtig (Kasten: „Beispiel Leichte Sprache“).

Beispiel Leichte Sprache

In Bezug auf die Leichte Sprache und wie diese generell gut für die Zielgruppe, aber auch grundsätzlich barrierefrei eingebunden werden kann, sind die Regeln nicht ausgereift. Die gesetzlichen Vorgaben dazu sind derart detailliert festgeschrieben, dass sie die allgemeine Barrierefreiheit im Web komplett torpedieren: „Texte werden linksbündig ausgerichtet. Jeder Satz beginnt mit einer neuen Zeile. Der Hintergrund ist hell und einfarbig.“

Das berücksichtigt weder den Bedarf nach Dark Mode, noch die Tatsache, dass von der Zielgruppe eher Smartphones als Computer für den Webzugang genutzt werden, auf denen die oft genutzten PDF-Dateien zu klein dargestellt und visuelle Einstellungen der Browser ignoriert werden.

Leider sind die BITV- und EN-301-549-Kriterien, die nicht in der WCAG auftauchen, keinen Konformitätsstufen zugeordnet. Dennoch gilt es, sie ebenfalls zu erfüllen, um den BITV-Test zu bestehen, sofern sie auf die jeweils geprüfte Seite anwendbar sind.

Sind Angebote, die nach der Prüfung barrierefrei sind, auch garantiert inklusiv?

Nein.

Zum einen entwickelt sich die Technik schneller weiter als es in Regulierungen fixiert werden kann. So sind z. B. automatische Anpassungen an Einstellungen der Nutzenden möglich, die von der WCAG auch in Version 2.2 noch nicht erfasst wurden, jedoch in der BITV als Prüfschritt 11.7 „Benutzerdefinierte Einstellungen“ [10] enthalten sind, leider ohne Konformitätsstufe. Zum anderen kommen auch immer wieder neue Erkenntnisse hinzu.

Ein paar Dinge sind aktuell weder gesetzlich gefordert noch automatisch anpassbar: Es gibt keine Vorgabe, dass alle aktiven Elemente mit nur einer ggf. in der Beweglichkeit stark eingeschränkten Hand vom Bildschirmrand aus gut erreichbar sein müssen.

Die Prüfschritte basieren oft auf einzelnen Einschränkungen. Die Anforderungen, die Mehrfachbehinderungen mit sich bringen, werden nicht durchgängig abgedeckt. So wird zwar berücksichtigt, dass Menschen, die weder sehen noch hören können, mit der Braillezeile auf Inhalte zugreifen können müssen. Aber dass das auch für Texte in Leichter Sprache gelten sollte, wird bisher nicht beachtet. Menschen, die gebärden und auf eine weniger komplexe oder stärker kontrastierte Gebärdensprache angewiesen sind, fallen gänzlich durchs Raster.

Auch was die Verständlichkeit von interaktiven Systemen allgemein betrifft, muss die Lücke zwischen Usability und Barrierefreiheit noch stärker erforscht werden.

Wie groß ist die Zielgruppe?

Die Schätzungen, wie viele Menschen mit einer permanenten Behinderung leben, gehen weit auseinander. Die meisten Zahlen liegen zwischen 10 und 15 Prozent der Weltbevölkerung, in Industrieländern mehr als in Entwicklungsländern. In den USA ging das CDC, das Centers for Disease Control and Prevention, kürzlich gar von 27 Prozent aus [11].

Nur 3-4 Prozent der Behinderungen sind angeboren. Alle anderen Behinderungen werden erst im Laufe des Lebens erworben bzw. treten erst später in Erscheinung. Dabei kann es sich um Unfälle, Krankheitsfolgen, Umwelteinflüsse und nicht zuletzt zunehmendes Alter handeln.

Das Statistische Bundesamt hat im Juni 2022 eine Quote von 9,4 Prozent veröffentlicht – als schwerbehindert gelten dabei „Personen, denen die Versorgungsämter einen Behinderungsgrad von mindestens 50 zuerkannt sowie einen gültigen Ausweis ausgehändigt haben.“ [12]. Genaue Zahlen sind das nicht. Das Beispiel Blindheit zeigt das Problem ganz gut: „Blinde und sehbehinderte Menschen werden in Deutschland nicht gezählt.“ [13].

Manchmal sind es auch nicht die primär anerkannten Behinderungen, die zu einer Einschränkung führen, die für die digitale Barrierefreiheit relevant ist. Auch Menschen mit kurzzeitigen Einschränkungen, zum Beispiel Verletzungen, die wieder komplett ausheilen, und Menschen die sich situativ in ihren Bewegungen, in ihrer Sicht, ihrer Hörfähigkeit oder anderweitig eingeschränkt sind, profitieren von Maßnahmen, die im Rahmen der digitalen Barrierefreiheit umgesetzt wurden.

Von einer (einheitlichen) Zielgruppe kann in diesem Zusammenhang also nicht gesprochen werden. Aber das ist sekundär. Die Vergangenheit zeigt immer wieder Beispiele, dass Erfindungen, die ursprünglich für Menschen mit bestimmten Behinderungen erfunden wurden, allen Menschen nutzen bzw. von vielen Menschen angenommen werden. Dieses Phänomen wird „Curb Cut Effect“ [14] genannt, weil abgesenkte Bordsteinkanten und großzügigere Bewegungsflächen im öffentlichen Raum allen Menschen zugutekommen, die sich dort bewegen. Barrierefreiheit, wenn sie gut umgesetzt wurde, ist für uns alle gut.

Was sind die häufigsten Barrieren im Web?

WebAIM („Web Accessibility In Mind“) ermittelt seit 2019 in der Studie „The WebAIM Million“ [15] jährlich, wie viele der Top-1 000 000-Webseiten Barrieren aufweisen und welche am häufigsten sind. 96,3 Prozent der untersuchten Seiten haben 2023 Probleme in der Barrierefreiheit aufgewiesen. Seit 2019 ist der Wert nur um 1,5 Prozent (von 97,8 Prozent) gesunken.

Hier die Top-6-Fehlerquellen, die auf den Seiten zu finden waren:

  1. 83,6 Prozent der Seiten hatten Schrift mit zu geringem Farbkontrast zum Hintergrund.
  2. 58,2 Prozent der Seiten enthielten Bilder ohne Alternativtexte, die den Inhalt beschreiben.
  3. 50,1 Prozent der Seiten enthielten „leere“ Links, die Icons darstellen, aber keinen Text.
  4. 45,9 Prozent der Seiten enthielten Eingabefelder, die nicht korrekt beschriftet waren.
  5. 27,5 Prozent der Seiten enthielten Buttons, die leer bzw. nicht korrekt beschriftet waren.
  6. 18,6 Prozent der Seiten haben keine korrekte Sprachauszeichnung enthalten.

Die genannten Fehlerquellen beziehen sich in erster Linie auf Fehler, die sich vor allem für Menschen negativ auswirken, die nicht über 100 Prozent Sehkraft verfügen. Barrieren für Nutzer:innen mit Behinderungen, die das Hören, kognitive oder psychische Fähigkeiten oder die Feinmotorik (diese Liste ließe sich fortsetzen) betreffen, tauchen in dieser Liste noch nicht mal auf.

Der Curb Cut Effect im Internet – angenehmere Nutzung für alle!

Die wirklich gute Nachricht ist, dass digitale Barrierefreiheit auch auf andere Aspekte des Internets einen guten Effekt hat. Da wäre zum Beispiel: Stress. Die Internetagentur Cyber Duck hat 2020 eine Studie mit 1 100 „gesunden“ User:innen durchgeführt, die alle nichts mit dem Erstellen von Webseiten zu tun und sich als souveräne Anwender:innen beschrieben haben [16]. Im Test wurde der Anstieg des systolischen Blutdrucks als Stressindikator gemessen, wenn die Testwebseiten bestimmte Probleme aufgewiesen haben. Diese lassen sich fast alle auch Barrierefreiheitskriterien zuordnen (Tabelle 3).

Ungewollte Wechselwirkungen mit anderen Themenbereichen

Generell ist bei der Barrierefreiheit oft des einen Freud des anderen Leid, wie Sie an den folgenden drei Beispielen sehen:

  • Beispiel Schriftgröße: Es ist nicht einfach damit getan, die Schrift auf einer Webseite doppelt so groß wie üblich zu machen. Das würde vor allem für Menschen, die nur mühsam scrollen können, das Nutzungserlebnis verschlechtern, wenn sie gut sehen können oder gar kleine Schrift bevorzugen. Hier soll es aber auch vor allem um Wechselwirkungen mit Themenbereichen aus Sicht der Betreibenden gehen, denn manchmal geraten die einzelnen Themen miteinander in Konflikt.
  • Beispiel SEO: Wenn man SEO-Alternativtexte von Bildern für das Ranking nutzen möchte, der SEO-Text aber für blinde Nutzer:innen keinen Informationswert [17] enthält, da sie eine objektive Beschreibung bevorzugen, gilt es abzuwägen, was wichtiger ist. Hier kann ggf. das HTML-Element <figure> weiterhelfen und die <figcaption> hinzugezogen werden, um Informationen für SEO einem Bild hinzuzufügen.
  • Beispiel DSGVO/Cookie-Banner-Pflicht: Cookie-Banner nerven uns alle. Vor allem, wenn sie als Pop-up daherkommen, führen sie zu vermehrtem Stress. Für viele sind sie einfach lästig. Wer sich um die eigenen Daten sorgt, möchte oft nicht zustimmen und hat so manches verwirrende Interaktionsmuster gesehen, mit dem Nutzer:innen doch noch ein Einverständnis abgerungen werden soll. Abgesehen davon, dass hier oft schon sogenannte Dark Patterns, speziell das „Privacy Zuckering“ [18] zum Einsatz kommen, gibt es noch ganz konkrete Probleme mit der Barrierefreiheit, speziell mit der Tastaturnavigation. Da der Code für das Overlay oft am Ende der Seite eingebunden wird, müssen erst alle Links, die visuell hinter dem Overlay liegen, übersprungen werden, bis das Overlay erreicht wurde und durch Auswahl einer Option entfernt werden kann. Blinde Nutzer:innen können die Inhalte dabei normal vom Screenreader vorlesen lassen. Sehende Menschen, die nur die Tastaturnavigation nutzen können, können hingegen die Inhalte derweil nicht sehen. Egal wie sorgfältig vorher auf die Barrierefreiheit geachtet wurde: Einmal ein falsches Plug-in gewählt und schon ist die Seite wieder eine einzige Barriere.

Kann eine Webseite nachträglich barrierefrei gemacht werden?

Bedingt.

Es hängt vor allem davon ab, mit welcher Technik die Seite erstellt wurde. Am einfachsten ist es bei handgeschriebenen Seiten, die nicht von der Barrierefreiheit der verwendeten Frameworks abhängig sind. Webseiten, die auf Content-Management-Systemen wie WordPress aufgesetzt sind, werden immer nur so barrierefrei sein wie die verwendeten Themes und Plug-ins. Hier sind die Anbieter gefragt. Leider ist es möglich, ein gutes Level an Barrierefreiheit mit der Wahl eines einzigen falsch gewählten Plug-ins zurück auf komplett nicht barrierefrei zu setzen. Das gilt auch für nicht geprüfte Updates und insbesondere auch für Overlay-Tools.

Zum Beispiel mit einem Overlay-Tool?

Nein.

Overlay-Tools sind kein Garant für Barrierefreiheit! Oftmals können Overlay-Tools Seiten, die bereits recht barrierearm waren, sogar komplett unzugänglich machen, wie die Seite Overlay Factsheet zusammengestellt hat [19].

Außerdem kann es zusätzlich zu Verstößen gegen die DSGVO kommen, wenn das eingesetzte Tool Userdaten zum Beispiel außerhalb der EU sammelt.

Beispiel für einen misslungenen Einsatz eines Overlay-Tools

Eine Seite mit hektischen Videos erfüllt die Level-A-Kriterien BITV 9.2.2.2 „Bewegte Inhalte abschaltbar“ [20] und ggf. auch 9.2.3.1 „Verzicht auf Flackern“ [21] nicht, wenn es nicht pausierbar ist und darin Elemente häufiger als dreimal die Sekunde aufblitzen. Das kann für Menschen unangenehm werden, die durch Flackern ein Anfallrisiko haben. Aber auch Menschen mit Seheinschränkungen, Neurodivergenz und andere, die von Bewegungen stark abgelenkt werden, können damit Probleme haben. Es fällt ihnen schwer, den Link zum Öffnen des Overlays überhaupt zu identifizieren, um dort die Einstellung zu finden, die das Video pausiert.

Was genau ist ein Overlay-Tool und wie funktioniert es?

Ein Overlay-Tool verspricht, Webseiten dadurch barrierefrei zu machen, dass sie um Funktionen wie Schrift- und Farbeinstellungen ergänzt werden. Diese Einstellungen können teilweise in Profilen gespeichert werden. Problematisch ist vor allem, dass

  • es sich um einen proprietären Ansatz handelt – welche Lösung eingebunden wird, ist vom Anbietenden abhängig, nicht von der Wahl der Nutzenden.
  • weiterer Code übertragen und ausgeführt werden muss.
  • ein zusätzlicher Log-in nötig ist.
  • bestehende adaptive Einstellungen der Nutzer:innen ignoriert werden.
  • die Overlays die Barrierefreiheit letztlich nicht garantieren können, sondern den Zugang eher erschweren.

Die Message hinter Overlay-Tools ist daher oft: „Wir wissen, dass unsere Website barrierefrei sein sollte, darum haben wir das Overlay eingebunden – aber eigentlich ist uns egal, ob sie wirklich für alle nutzbar ist.“ Besser:

  • auf das Video verzichten
  • das Video nicht automatisch starten lassen, vor allem nicht, wenn im Code nicht abgefragt wird, ob „Bewegungen reduzieren“ im Betriebssystem aktiviert wurde.
  • ein Overlay-Tool nur dann ergänzend einsetzen, wenn alle automatischen Adaptionsmöglichkeiten ausgeschöpft wurden

Warum wurde meine Webseite nicht längst barrierefrei umgesetzt?

Im Idealfall werden Webseiten zum Zeitpunkt ihrer Erstellung zum dann gültigen Stand der Technik umgesetzt. Leider sehe ich auch noch über 13 Jahre nach der Veröffentlichung des Artikels „Responsive Web Design [22]“, dass neue Webseiten nicht responsiv umgesetzt werden und damit auf verschiedenen Geräten unbrauchbar sind.

Laut BITV verstoßen sie damit nicht nur gegen gängige Marktstandards, sondern auch gegen 9.1.4.10 „Inhalte brechen um“ [23] (AA) und 9.1.3.4 „Keine Beschränkung der Bildschirmausrichtung“ [24] (AA).

Dass das responsive Internet letztlich seinen Durchbruch hatte, lag nicht zuletzt daran, dass Google mobile Webseiten ab dem 21. April 2015 bei der mobilen Suche bevorzugt hat [15] und ab März 2021 auf Mobile-First-Index [26] umgestellt hat.

Aber das ist nur die Spitze des Eisbergs: Auf der einen Seite wurde das Studium von Medieninformatiker:innen durch die Einführung des Bachelors um ein Jahr verkürzt. Auf der anderen Seite kamen zusätzlich zu den Veränderungen der Standards von HTML und CSS auch jährlich neue Frameworks für CSS (z. B. Bootstrap, Tailwind) und JavaScript (z. B. jQuery) auf den Markt. Teilweise wurden sie sofort an den Hochschulen gelehrt, die Standards darüber vernachlässigt. Kompatibilität zwischen verschiedenen Browsern (Stichwort Vendor-Prefix, Modernizr) und immer neue gestalterische Ideen zu ermöglichen waren lange Zeit einfach schicker als die Berücksichtigung von Barrierefreiheitsprinzipien.

Was genau muss geändert werden?

Das kommt ganz auf Ihre Seite an. Manchmal sind es nur Kleinigkeiten, manchmal Kleinigkeiten, die sich läppern, teilweise lautet die Antwort „am besten neu kodieren“, manchmal aber auch „es muss alles ab dem Konzept neu“. Wie Sie das anfangen und auf welche Details geachtet werden muss, wird an dieser Stelle nach und nach Thema sein.

Wer soll das alles umsetzen?

Wir sind alle gefragt! Wir müssen die Menschen, mit denen wir zusammenarbeiten bzw. die wir beauftragen, zunächst für das Thema sensibilisieren. Dann müssen sich alle passend zu ihrer Rolle weiterbilden:

  • Alle Rollen müssen sich mit den Prüfkriterien und den realen Anforderungen der digitalen Barrierefreiheit vertraut machen, um entsprechend darauf reagieren zu können. Es ist wichtig, dass dieses Thema nicht nur an einer Person im Team liegt, die dann immer nur sagen kann, was falsch ist.
  • Product-Owner:innen müssen Barrierefreiheit mit in die Akzeptanzkriterien der User Stories aufnehmen, damit alle Teammitglieder das Thema immer berücksichtigen lernen.
  • UX-Designer:innen müssen sich bewusst machen, welche Interaktionspatterns barrierefrei sind.
  • UI-Designer:innen müssen nicht nur für unterschiedliche Bildschirmgrößen gestalten, sondern auch für unterschiedliche Größen der Schriften und Klickflächen, sowie für unterschiedliche Farbvarianten etc.
  • Frontend-Entwickler:innen müssen sich präziser mit UX/UI-Designer:innen abstimmen, um sicherzustellen, ob die Übergabewerte korrekt sind und was bereits berücksichtigt wurde. Außerdem muss das bestehende Wissen um HTML-Elemente, Attribute, CSS-Properties und JavaScript-Methoden mit den Anforderungen der Barrierefreiheit abgeglichen werden. Nicht alle Methoden, die zwischendurch (inoffizieller) Industriestandard waren, können für barrierefreie Webseiten eingesetzt werden.
  • Backend-Entwickler:innen müssen wissen, wie sie bestimmte Elemente verfügbar machen können. Zum Beispiel, damit Redakteur:innen nur eine H1 anlegen können oder Text, der fett dargestellt wird, nicht als <b>, sondern als <strong> ausgezeichnet wird. Schließlich heißt es in den BITV-Prüfkriterien: „Wenn es sich bei der zu testenden Webanwendung um ein Autorenwerkzeug handelt, soll die Anwendung die Erstellung von barrierefreien Dokumenten erlauben und den Nutzer dabei unterstützen.“ [27]
  • SEO-Expert:innen müssen den Spagat zwischen barrierefreien und SEO-optimierten Titeln und Bildbeschreibungen im Alt-Text hinbekommen. An vielen Stellen können sie nun aber auch darauf verweisen, dass ihre Arbeit für SEO und Barrierefreiheit gut und wichtig ist.
  • Redakteur:innen müssen lernen, Dokumente in Word und anderen Programmen so anzulegen, dass daraus barrierefreie PDFs erzeugt werden können. Sie müssen wissen, welche semantischen Auszeichnungsmöglichkeiten es gibt und sie konsequent anwenden. Außerdem benötigen Sie Kontakte zu Übersetzer:innen für Leichte Sprache und/oder einen Zugang zu entsprechender KI [28]. Gleiches gilt für Videos, die den Text in Deutsche Gebärdensprache (DGS) übersetzen, bzw. in die Gebärdensprachen der Zielländer. Auch hier sind KI-gestützte Avatare in der Vorbereitung, es gilt jedoch neben dem wirtschaftlichen Aspekt auch die Akzeptanz der Gebärdenden zu berücksichtigen, die dieser Technik 2023 noch ablehnend gegenüberstehen [29].
  • Bildredakteur:innen müssen nicht nur lernen, Bilder nach deren inhaltlicher Verständlichkeit zu bewerten, sondern auch, wie Personen deutlicher hervorgehoben werden können und wie optimale Alt-Texte geschrieben werden. Diese konsequent auch für Social-Media-Bilder einzusetzen, muss zur Gewohnheit werden.
  • Qualitätstester:innen müssen wissen, wie sie die unterschiedlichen Barrieren ausfindig machen können, um sie melden und die Korrektur prüfen zu können.

Nicht zu vernachlässigen ist eine Dokumentation der Maßnahmen, die getroffen wurden. Zum einen wird es ab 2025 im Rahmen der EAA für manche Seitentypen Pflicht sein, sie zu führen, zum anderen ermöglicht es Ihnen auch eine einfachere Übergabe von Projekten an andere oder neue Teammitglieder.

Shopify ist dies bereits einmal misslungen. Die Plattform wird in einem Artikel über aria-current als gutes Beispiel genannt, weil dort die aktuelle Seite in der Navigation korrekt als aria-current=“page“ [30] ausgezeichnet wurde. Dem ist inzwischen nicht mehr so. Nur noch die aktuell ausgewählte Sprache und Region wird im Footer mit aria-current=“true“ ausgezeichnet. Welche Seite im Menü die derzeit angezeigte ist, geht für Screenreader-Nutzer:innen nur aus dem <title> hervor.

Fazit

Sie werden nicht um das Thema herumkommen. Sie können jetzt langsam mit digitaler Barrierefreiheit anfangen, oder es in zwei Jahren unter Zeitdruck tun, wenn die Konkurrenz an Ihnen vorbeizieht. Dabei ist es egal, ob Ihr Produkt eine Komponente, ein Tool oder eine Plattform ist. Je eher Sie anfangen, umso größer wird Ihr Vorsprung sein. Nicht zu vernachlässigen ist auch der soziale Aspekt: Im Idealfall hilft es uns als Gesellschaft, mehr Verständnis füreinander und unsere unterschiedlichen Voraussetzungen zu erhalten, die bisher einfach als Unzulänglichkeit Einzelner abgetan wurden.

Problem % Widerspricht BITV … (Level nach WCAG)
langsam ladende Seiten 21
mehrere Pop-ups 9.1.4.13 Eingeblendete Inhalte bedienbar (AA)
automatisch abspielende Musik 20 9.1.4.2 Ton abschaltbar (A)
kaputte Seiten (404 Error) 17 WCAG-Prinzip Robust (A und AA)
automatisch abspielende Videos mArKeD mArKeD mArKeD 1 mArKeD6 9.2.2.2 Bewegte Inhalte abschaltbar (A) 9.2.3.1 Verzicht auf Flackern (A/AA)
mArKeD mArKeD mArKeD nicht klickbare Button mArKeDs 14 9.1.1.1a Alternativtexte für Bedienelemente (A) 9.4.1.1 Korrekte Syntax (A)
schwer lesbare Schrift 13 9.1.4.3 Kontraste von Texten ausreichend (AA)
Bilder, die nicht laden (und keine Alt-Tags haben) 12 WCAG-Prinzip Robust (A und AA) 9.1.1.1b Alternativtexte für Grafiken und Objekte (A)
Bilderkarussells 10 9.1.1.1a Alternativtexte für Bedienelemente (A) 9.4.1.2 Name, Rolle, Wert verfügbar (A)
ablenkende Animationen 5 9.2.3.1 Verzicht auf Flackern (A) 9.2.2.2 Bewegte Inhalte abschaltbar (A)

Tabelle 3: Die Stressfaktoren im Internet den Anforderungen der Barrierefreiheit zugeordne

brinkmann_annika_sw.tif_fmt1.jpgAnnika Brinkmann, Web-Designerin seit 2003, sensibilisiert und schult Teams, die an Konzeption, Gestaltung, Programmierung und Redaktion barrierefreier Webseiten beteiligt sind. Auf Barrieren-fasten.de bietet sie Entwickler:innen einen niedrigschwelligen Einstieg.

Links & Literatur

[1] https://online-accessibility-countdown.eu/

[2] https://gehirngerecht.digital/digitale-barrierefreiheit-pflicht-wissen/

[3] https://eur-lex.europa.eu/legal-content/DE/TXT/HTML/?uri=CELEX:32019L0882

[4] https://www.w3.org/TR/WCAG21/

[5] https://www.w3.org/TR/WCAG22/

[6] https://www.w3.org/WAI/WCAG21/quickref/ wurde bereits 2018 finalisiert

[7] https://www.w3.org/WAI/WCAG22/quickref/ wird noch um neue Anforderungen ergänzt

[8] https://ergebnis.bitvtest.de/pruefverfahren/bitv-20-web

[9] https://www.barrierefreiheit-dienstekonsolidierung.bund.de/Webs/PB/DE/gesetze-und-richtlinien/en301549/en301549-node.html

[10] https://ergebnis.bitvtest.de/pruefschritt/bitv-20-web/11-7-benutzerdefinierte-einstellungen

[11] https://www.cdc.gov/ncbddd/disabilityandhealth/infographic-disability-impacts-all.html

[12] https://www.destatis.de/DE/Themen/Gesellschaft-Umwelt/Gesundheit/Behinderte-Menschen/_inhalt.html

[13] https://www.dbsv.org/zahlen-fakten.html

[14] https://accessibleweb.com/civil-rights/the-curb-cut-effect-7-ways-the-ada-is-for-everyone/

[15] https://webaim.org/projects/million/

[16] https://www.netimperative.com/2020/12/09/blood-pressure-study-which-website-issue-cause-users-the-most-stress/ leider ist nur noch Sekundärliteratur online, bei Cyber-duck selbst taucht die Studie nicht mehr auf: https://www.cyber-duck.co.uk/

[17] https://www.dbsv.org/bildbeschreibung-4-regeln.html

[18] https://de.wikipedia.org/wiki/Dark_Pattern#Beispiele_f%C3%BCr_Dark_Patterns

[19] https://overlayfactsheet.com/

[20] https://ergebnis.bitvtest.de/pruefschritt/bitv-20-web/9-2-2-2-bewegte-inhalte-abschaltbar

[21] https://ergebnis.bitvtest.de/pruefschritt/bitv-20-web/9-2-3-1-verzicht-auf-flackern

[22] https://alistapart.com/article/responsive-web-design/ von Ethan Marcotte erschien am 25. Mai 2010

[23] https://ergebnis.bitvtest.de/pruefschritt/bitv-20-web/9-1-4-10-inhalte-brechen-um

[24] https://ergebnis.bitvtest.de/pruefschritt/bitv-20-web/9-1-3-4-keine-beschraenkung-der-bildschirmausrichtung

[25] https://www.googlewatchblog.de/2015/02/mobile-websuche-apps-ranking/

[26] https://ebakery.de/google-mobile-first-index/

[27] https://ergebnis.bitvtest.de/pruefschritt/bitv-20-web/11-8-2-barrierefreie-erstellung-von-inhalten

[28] https://summ-ai.com/

[29] https://www.br.de/nachrichten/deutschland-welt/gehoerlose-aeussern-kritik-an-gebaerdensprach-avataren,TfMrXDf?mc_cid=f07dedcb3d&mc_eid=712cfaf1f0

[30] https://www.aditus.io/aria/aria-current/




Von wegen flache Hierarchieebenen

Wenn man visuelles Design (oder eben in unserem Fall UI-Design) in einem Satz zusammenfassen möchte, so würde der vielleicht in etwa so lauten: „Dinge so gestalten, dass sie gut aussehen“. Schon hat man als Frontend-Code-Jockey keinen Bock mehr drauf. Da sind sie wieder, das angeborene künstlerische Talent und die immerwährende, jederzeit abrufbare Kreativität. Allerdings ist es erstaunlich, dass einer der wichtigsten Faktoren für ein „gutes Aussehen“ überhaupt nichts mit künstlerischen Fähigkeiten und kreativer Gestaltung zu tun hat – nämlich die visuelle Hierarchie.

Die visuelle Hierarchie bezieht sich darauf, wie wichtig die Elemente einer Benutzeroberfläche im Verhältnis zueinander erscheinen, und sie ist das effektivste Werkzeug, um etwas „gestaltet“ wirken zu lassen. Wenn alles auf einer Benutzeroberfläche um Aufmerksamkeit konkurriert, wirkt sie laut und chaotisch, in etwa wie ein Platz voller digitaler Werbebotschaften und Marktschreier. Viel Lärm und wenig Signal. Bei all dem (visuellen) Chaos ist es uns nicht klar, was wirklich von Bedeutung ist. Zu dieser Thematik gibt es viele lesenswerte Publikationen, u. a. [1].

Achtung, hier wieder aufmerksam lesen, denn ich verrate euch ein Geheimnis, und zwar das Geheimnis des Designs: Trennt das Primäre vom Sekundären und vom Tertiären oder in anderen Worten, fragt euch, was ist das Wichtigste, was ist das Zweitwichtigste und was das Drittwichtigste. Wenn wir bewusst sekundäre und tertiäre Informationen zurückstellen und uns darum bemühen, die wichtigsten Elemente hervorzuheben, wirkt das Ergebnis sofort ansprechender, auch wenn sich das Farbschema, die Schriftart und das Layout nicht geändert haben.

Ein super Hilfsmittel dafür sind sogenannte Priority Guides. Priority Guides werden als die Content-First-Alternative zu Wireframes gehandelt. Da man bei Wireframes irgendwie auch schon malen können muss, bei Priority Guides aber nicht, macht das einem die Dinger schon gleich sympathisch (übrigens auch dem Projektleiter, da sie in ihrer Erstellung weniger aufwendig als Wireframes sind). Einfach ausgedrückt enthält ein Priority Guide Inhalte und Elemente für einen mobilen Bildschirm (also auch Mobile First), sortiert nach Wichtigkeit von oben nach unten und ohne jegliche Layoutvorgaben. Die Hierarchie basiert auf der Relevanz für die Nutzer, wobei die Inhalte, die für die Befriedigung der Nutzerbedürfnisse und die Unterstützung der Ziele der Nutzer (und des Unternehmens) am wichtigsten sind, weiter oben stehen.

Das Format eines Priority Guides ist nicht festgelegt: Er kann digital sein (Text-, Office-, Figma-Dateien) oder er kann physisch sein, mit Papier und Post-its. Am wichtigsten ist, dass ein Priority Guide von Anfang an automatisch Content First ist und den Nutzer:innen den besten Nutzen bietet. Ein Beispiel sehen Sie in Abbildung 1 mit einem Priority Guide für die Flugbuchungsseite einer Fluggesellschaft aus „Priority Guides: A Content-First Alternative to Wireframes“ [2].

Abb. 1: Ein detaillierter digitaler Priority Guide für eine FlugbuchungsseiteAbb. 1: Ein detaillierter digitaler Priority Guide für eine Flugbuchungsseite

Anmerkungen sind ein wichtiger Bestandteil von Priority Guides, da sie die Funktionen und das Verhalten der Seite erklären, die Komponententypen benennen und die Priority Guides einer Seite mit den Priority Guides anderer Seiten verknüpfen. In diesem Beispiel wird beschrieben, was passiert, wenn ein Benutzer auf eine Schaltfläche oder einen Link klickt.

Bevor wir noch irgendetwas gemalt haben, können wir mit diesem Tool beginnen, das Wichtige vom Unwichtigen zu trennen und uns dem Attribut „gestaltet“ weiter annähern. Bei der Erstellung von Priority Guides konzentriert man sich automatisch darauf, die Probleme von Menschen zu lösen, ihre Bedürfnisse zu befriedigen und ihnen zu helfen, ihre Ziele zu erreichen. Die Benutzeroberfläche ist immer mit Inhalten gefüllt, die eine Botschaft vermitteln oder dem Benutzer weiterhelfen. Indem wir uns auf den Inhalt konzentrieren, entscheiden wir im Sinne unserer Benutzer. Genau dieser Fokus auf den Content bringt uns zum Design Algorithm [3], der uns als Frontend-Schaffende näher an die uns vertraute Denkweise bringt (Abb. 2). Der einfachste Weg zu einem „gestalteten“ Design und einem guten Ergebnis führt über vier Schritte:

  1. Der Inhalt: In diesem Schritt müssen wir erstmal verstehen, mit welcher Art von Inhalten wir arbeiten, welche Texte, Bilder und andere Komponenten es gibt – kurz gesagt: Inventur machen.
  2. Es folgt die Struktur: das Primäre vom Sekundären zu trennen, zu verstehen, in welche Blöcke der Inhalt aufgeteilt werden kann und welche Beziehungen zwischen den Blöcken bestehen.
  3. Jetzt geht es ans Layout: Wir erstellen ein Layout, das die Idee widerspiegelt und den Inhalt und seine Struktur am besten darstellt; die Art und Weise, wie das Design funktionieren soll.
  4. Zuletzt folgt der Stil: Design, Stil, Farbe und andere visuelle Details, die Art und Weise, wie das Design aussehen soll. Die Farbe kommt erst zum Schluss. Das Großartige an diesem Algorithmus ist, dass das „gestaltete“ Design bereits nach dem dritten Schritt wirklich funktional und zu testen ist. Wie cool ist das denn?

Abb. 2: Der Designalgorithmus eignet sich für jede Art von ProjektAbb. 2: Der Designalgorithmus eignet sich für jede Art von Projekt

Schauen wir jetzt mal auf das „echte“ UI und versuchen einmal, was man mit Hierarchie so alles retten kann. Beim Separieren von Wichtigem und Unwichtigem kommt einem sofort die Größe in den Sinn, aber Größe ist nicht alles. Es ist ein Fehler, sich nur auf die Schriftgröße zu verlassen, um die Hierarchie zu kontrollieren, da das oft dazu führt, dass der primäre Inhalt zu groß und der sekundäre Inhalt zu klein ist. Effektivere Stellschrauben sind der Schriftschnitt und die Farbe.

600 ist das neue 24

Ein primäres Element fetter zu machen, erlaubt es weiterhin, eine vernünftige und verhältnismäßige Textgröße zu verwenden. Ebenso macht die Verwendung einer sanfteren Farbe für den sekundären Begleittext anstelle einer kleinen Schriftgröße deutlich, dass der Text zweitrangig ist, während die Lesbarkeit weniger darunter leidet. Farbe und Schriftschnitt statt purer Größe. Man könnte hier auch von einem Algorithmus der Farbe sprechen (Abb. 3). Wir beschränken uns auf zwei oder drei Farben:

  • eine dunkle Farbe für primäre Inhalte wie z. B. die Überschrift eines Artikels
  • ein Grau für sekundäre Inhalte wie das Veröffentlichungsdatum des Artikels
  • ein abgestuftes helleres Grau für tertiäre Inhalte, z. B. den Copyrighthinweis in der Fußzeile

In ähnlicher Weise sind zwei Schriftstärken für eine Benutzeroberfläche vollkommen ausreichend:

  • ein normaler Schriftschnitt (400 oder 500, je nach Schriftart) für den generellen Text
  • ein fetterer Schriftschnitt (600 oder 700) für Text, der in den Vordergrund gerückt werden soll

Abb. 3: Gestaltung mit Hilfe der Schriftgröße oder der FarbeAbb. 3: Gestaltung mit Hilfe der Schriftgröße oder der Farbe

Pro-Tipp: Wir sollten Schriftschnitte unter 400 für UI-Arbeiten tunlichst vermeiden. Sie eignen sich zwar für große Überschriften, sind aber in kleineren Größen zu schwer zu lesen.

Stärkung durch Abschwächung

Manchmal befinden wir uns in einer Situation, in der ein Element einer Benutzeroberfläche gegenüber den umgebenden Elementen nicht ausreichend hervorgehoben ist. Allerdings gibt es auch auf den ersten Blick nichts, was wir noch hinzufügen oder womit wir es „vercoolern“ (Danke, Emilia, für die Leihgabe) könnten, um es stärker hervorzuheben. Nachfolgendes Beispiel zeigt ein Menu, dessen aktives Element bereits durch eine andere Farbe betont ist, aber dennoch kann es sich nicht visuell gegen die inaktiven Elemente durchsetzen.

In solchen Situationen sollten wir nicht versuchen, dieses eine Element, auf das wir die Aufmerksamkeit lenken wollen, noch mehr hervorzuheben, sondern wir sollten einen Weg finden, die Elemente, die damit in Konkurrenz stehen, weniger auffällig zu machen.

Im folgenden Beispiel wird das dadurch erreicht, dass die inaktiven Elemente eine weichere Farbe als das aktive Element bekommen und damit visuell den Platz für das aktive Element frei machen (Abb. 4).

Abb. 4: Wir geben den inaktiven Elementen eine weichere Farbe und senken den KontrastAbb. 4: Wir geben den inaktiven Elementen eine weichere Farbe und senken den Kontrast

Diese Denkweise können wir auch auf größere Teile eines UIs anwenden. Wenn zum Beispiel eine Seitenleiste visuell mit dem Hauptinhalt konkurriert, sollten wir ihr keine Hintergrundfarbe geben, sondern den Inhalt der Seitenleiste direkt auf dem Hintergrund der Webseite platzieren (Abb. 5).

Abb. 5: Das Prinzip der Abschwächung lässt sich auch bei größeren UI-Organismen einsetzenAbb. 5: Das Prinzip der Abschwächung lässt sich auch bei größeren UI-Organismen einsetzen

Label mich nicht voll

Ich weiß, Labels sind heilige Kühe und da hört bei vielen der Spaß auf. Bei Formularen ist das auch absolut okay, nur in Card Controls, Produktbeschreibungen o. Ä. dürfen wir uns gerne mal Gedanken darüber machen, warum eigentlich meistens der Key einen fetteren Schriftschnitt hat als der Value, der uns eigentlich interessiert.

Es gibt tatsächlich nur eine Situation, die rechtfertigt, dass das Label eine deutlichere Hervorhebung als der eigentliche Wert hat, und zwar wenn wir wissen, dass unsere Betrachter nach der Beschriftung suchen werden. Nur dann ist es sinnvoll, die Beschriftung anstelle der Daten zu betonen. Das ist z. B. bei den technischen Daten eines Produkts der Fall. Wenn ein Mensch die Abmessungen eines Regals sucht, wird meistens nach Begrifflichkeit wie „Länge“, „Breite“ oder „Tiefe“ gesucht, nicht aber nach „41,5 cm“. Auch in solchen Fällen ist der Wert immer noch die primäre Information. Daher ist es ausreichend, eine dunklere Farbe für die Beschriftung und eine etwas hellere Farbe für den Wert zu verwenden.

Daneben halten wir uns bei der Verwendung von Labels fast dogmatisch an das Formatschema Beschriftung, Doppelpunkt, Wert oder für Informatiker:innen Key, Colon, Value. In vielen Situationen können wir jedoch allein anhand des Formats erkennen, worum es sich bei einem Eintrag handelt. Nochmal zur Deutlichkeit: Wir reden nicht von Formularen, das ist eine andere Liga.

Aber wieder zurück zu unseren Datensätzen. Zum Beispiel ist m.muster@beispielwebsite.de eine E-Mail-Adresse, +49 (0)69 630 089 0 eine Telefonnummer und 1,99 € eine Preisangabe. Wenn das Format nicht ausreicht, hilft uns eben der Kontext und der ist bekanntlich König. Ein Eintrag wie „Frontend-Entwickler“ unter dem Namen eines Mitarbeiters auf der Teamseite ist ein sicheres Indiz dafür, dass es sich um einen Mitarbeitenden handelt, der Frontends für Webapplikationen entwickelt. Ein Label würde hier ziemlich übers Ziel hinausschießen. Deutlich sehen Sie das in den zwei Beispielen aus Abbildung 6.

Abb. 6: Labels können oft die schnelle Erfassung von Inhalten beeinträchtigenAbb. 6: Labels können oft die schnelle Erfassung von Inhalten beeinträchtigen

Wenn Datensätze ohne Beschriftungen dargestellt werden können, ist es viel einfacher, wichtige oder identifizierende Informationen hervorzuheben, wodurch das UI benutzerfreundlicher und gleichzeitig intuitiver wird. Das zwanghafte Festhalten an der LDW-Struktur (Label Doppelpunkt Wert) führt oftmals auch zu hakeligen Formulierungen auf unserer Oberflächen – „Auf Lager: 15“. Ihr merkt an dieser Stelle selbst, dass es sich komisch liest. Welche Formulierung würden wir erwarten? Ja eben: „Es sind noch 15 auf Lager“. Die Taktik, einen Wert in einen erläuternden Text einzubetten ist durchaus legitim und liest sich flüssiger. In Kombination mit einem hervorhebenden Schriftschnitt für den eigentlichen Wert können zwei Fliegen mit einer Klappe geschlagen werden – also gerne mal das LDW-Dogma mit einem (kurzen) lesbaren Satz aufbrechen (Abb. 7).

Abb. 7: Besser, so liest es sich nicht wie KaugummiAbb. 7: Besser, so liest es sich nicht wie Kaugummi

Um noch einmal den Bogen zum Beginn unseres Labelexkurses zu schlagen: Labels sind sekundärer Content! Es gibt Situationen oder Darstellungen, in denen wir wirklich eine Beschriftung benötigen, etwa wenn wir mehrere vom Typ ähnliche Daten anzeigen und diese auch auf den ersten Blick leicht ablesbar sein müssen, wie z. B. in einem Dashboard. Dann sollten wir aber das Label auf jeden Fall als sekundären und unterstützenden Content betrachten. Die Daten sind die primäre, wichtige Information, die Beschriftung dient nur der Übersichtlichkeit. In einem solchen Fall verkleinern wir das Label, verringern den Kontrast, oder verwenden einen leichteren Schriftschnitt oder eine Kombination aus allen drei Möglichkeiten (Abb. 8).

Abb. 8: Labels sind sekundär, gerade in DashboardsAbb. 8: Labels sind sekundär, gerade in Dashboards

Visuelle Hierarchie ist nicht gleich Dokumentenhierarchie

Bei der Erstellung von Webinhalten sollten wir immer auf semantisches Markup zurückgreifen. Das bedeutet, dass wir unsere Inhalte strukturieren und z. B. Überschriften mit den geläufigen HTML-Tags wie h1, h2 oder h3 kennzeichnen. Out of the Box versehen die Webbrowser die Überschriftentags mit entsprechenden visuellen Eigenschaften. So werden mit aufsteigender Zahl hinter dem h den Überschriften immer kleinere Schriftgrößen zugewiesen – h1 groß und h6 klein. Grundsätzlich ist das schon mal super, aber in einem „gestalteten“ UI kann dieses Verhalten der Browser auch schon mal zu Fehlinterpretationen führen. Ein h1-Tag für eine Überschrift wie „Neues Benutzerprofil“ ist semantisch absolut sinnvoll. Weil wir allerdings „evolutionär“ bzw. „erlernt“ den Drang verspüren, h1-Überschriften immer groß zu gestalten, kann es leicht dazu kommen, dass wir der Überschrift mehr Prominenz verschaffen als sie visuell eigentlich brauchen würde (Abb. 9).

Abb. 9: Abschnittstitel sollten nicht die ganze Aufmerksamkeit auf sich ziehenAbb. 9: Abschnittstitel sollten nicht die ganze Aufmerksamkeit auf sich ziehen

Ähnlich wie Labels unterstützen Überschriften den Inhalt und sollten daher nicht die ganze Aufmerksamkeit auf sich ziehen. Normalerweise sollte der Inhalt des Abschnitts im Mittelpunkt stehen, nicht der Titel, also sollten Überschriften in den meisten Fällen verhältnismäßig klein sein. Denn auch dann erfüllen sie noch gut ihre eigentliche gliedernde Funktion. Lassen wir uns beim Designen nicht von den „geerbten“ visuellen Eigenschaften der HTML-Elementen beeinflussen. Wir sollten die Elemente aus semantischen Gründen heraus wählen und sie dann für eine optimale visuelle Hierarchie gestalten.

Dunkel is the new fett

Kontrast eignet sich hervorragend zur Kompensation des visuellen „Gewichts“ eines Elementes. Das Verständnis dieser Beziehung wird unter anderem bei der Arbeit mit Icons wichtig. Genau wie fett gedruckter Text sind auch Symbole (insbesondere Volltonicons) in der Regel ziemlich „schwer“ und nehmen viel Pixelfläche ein. Wenn wir also ein Icon neben einem Text platzieren, wird das Symbol mehr hervorgehoben als der Text. Und jetzt kommt der Haken: Im Gegensatz zum Text kann das „Gewicht“ eines Symbols nicht so einfach verändert werden. Um ein Gleichgewicht herzustellen, muss das Symbol auf andere Weise an Prominenz einbüßen.

Am einfachsten und auch wirksamsten ist es, den Kontrast des Symbols zu reduzieren hin zu einer weicheren Farbausprägung. Diese Technik funktioniert überall dort, wo wir Elemente mit unterschiedlicher visueller Gewichtung an- bzw. ausgleichen müssen. Die Kontrastverringerung lässt schwerere Elemente optisch leichter erscheinen, obwohl sich ihr Gewicht (die eingenommene Pixelfläche) nicht geändert hat (Abb. 10).

Abb. 10: Verringerung des Kontrasts durch eine weichere FarbeAbb. 10: Verringerung des Kontrasts durch eine weichere Farbe

Genauso wie die Reduzierung des Kontrasts eine Möglichkeit zur Abschwächung visuell gewichtiger Elemente ist, ist die Kontrasterhöhung bzw. die Erhöhung des visuellen Gewichts eine gute Möglichkeit, kontrastärmere Elemente ein wenig zu boosten. Nehmen wir eine ein Pixel dünne Linie mit zu hellem, weichem Grauton. In der einen Version ist die Linie schlecht sichtbar und geht im Design unter, auf der anderen Seite lässt ein dunkler Grauton das Design zu hart erscheinen. Die Lösung ist hier, die Linie ein wenig aufzublähen, indem man ihre Breite vergrößert. Dann können wir unser subtiles Grau behalten und die Linie trotzdem hervorheben, ohne das Design zu sprengen (Abb. 11).

Abb. 11: Etwas dickere Linien helfen, sie zu betonen, ohne dass der subtile Eindruck verloren gehtAbb. 11: Etwas dickere Linien helfen, sie zu betonen, ohne dass der subtile Eindruck verloren geht

Semantik spielt die zweite Geige

Semantik kann, wenn es um Benutzerschnittstellen geht, gerne mal ein wenig in die Irre führen, gerade wenn Menschen die Auswahl zwischen mehreren Aktionen (Schaltflächen) haben, die sie in unserem UI ausführen können (und das ist bei UIs bekanntlich meistens der Fall). Hierarchy to the rescue – denken wir an die Priority Guides und stellen uns die Frage, was ist das Wichtigste, das Zweitwichtigste und so weiter. Meistens gibt es nur eine primäre Aktion, mehrere sekundäre und selten ein paar tertiäre Aktionen. Genau an dieser Stelle muss das Design die Wichtigkeit der Aktion widerspiegeln (Abb. 12):

  • Primäre Aktionen müssen offensichtlich sein, hier auch gerne mal mit Farbe.
  • Sekundäre Aktionen sollten deutlich, aber nicht zu auffällig sein. Konturlinien oder Hintergrundfarben mit geringerem Kontrast sind hier eine gute Option.
  • Tertiäre Aktionen sollten auffindbar, aber visuell zurückhaltend sein. Die Gestaltung dieser Aktionen als Links ist in der Regel der beste Ansatz.

Abb. 12: Wir geben den Benutzenden eine klare Richtung vorAbb. 12: Wir geben den Benutzenden eine klare Richtung vor

Eine hierarchische Anordnung der Aktionen auf der Seite führt zu einer weniger vollgestopften Benutzeroberfläche, die ihre Aktionen klarer kommuniziert und auch das Papageienoutfit mancher UIs wird vermieden. Eben „don’t make me think“ (Abb. 13).

Abb. 13: Aufteilung von Interaktionsmöglichkeiten mit abnehmender visueller ProminenzAbb. 13: Aufteilung von Interaktionsmöglichkeiten mit abnehmender visueller Prominenz

Wenn eine Aktion destruktiv ist, z. B. etwas zu löschen, bedeutet das noch nicht automatisch, dass die Schaltfläche gleich groß, rot und fett sein muss. Wenn die destruktive Aktion nicht die primäre Aktion in unserem UI ist, kann es besser sein, ihr eine sekundäre oder tertiäre Ausprägung zuzuweisen. In Kombination mit einer nachfolgenden Bestätigung, bei der die destruktive Aktion die primäre Aktion einnimmt, lässt sich ein stimmiges Design destruktiver Aktionen erzielen und in der Confirm-Message dürfen wir es auch mit „groß“, „rot“ und „fett“ richtig krachen lassen (Abb. 14).

Abb. 14: Bei der Bestätigung darf’s auch ordentlich rot seinAbb. 14: Bei der Bestätigung darf’s auch ordentlich rot sein

Fazit

Seht ihr, (fast) keine Farben, Bilder, Frameworks, nix – nur Gestaltprinzipien und die visuelle Hierarchie. Dennoch sind diese beiden Methoden entscheidend für die Gestaltung einer erfolgreichen und leicht verständlichen Benutzeroberfläche. Wir Webschaffenden im Frontend sollten uns auf jeden Fall mit diesen Konzepten vertraut machen und sie bei der Gestaltung unserer Anwendungen berücksichtigen und gezielt einsetzen, und dann kommt die Farbe …

Weiterführende Informationen

Es gibt viele Onlineressourcen, die uns dabei helfen können, unsere Skills über Gestaltungsprinzipien und Gestaltungshierarchien zu vertiefen. Nachfolgend einige nützliche Websites und Ressourcen:

  • Smashing Magazine [4]: Eine Website, die sich auf Webdesign und -entwicklung spezialisiert hat. Hier finden Sie viele Artikel und Tutorials zu Gestaltungsprinzipien und UI-Hierarchien.
  • UX Planet [5]: Eine Website, die sich auf UX-Design spezialisiert hat. Hier finden Sie viele Artikel und Tutorials zu UX-Design-Prinzipien und -Techniken.
  • Nielsen Norman Group [6]: Eine Forschungsgruppe, die sich auf UX-Design spezialisiert hat. Hier finden Sie viele Artikel und Forschungsberichte zu UX-Design-Prinzipien und -Techniken.
  • UX Design [7]: UX Design ist eine Onlinepublikation, die sich auf UX-Design, UI-Design und Produktstrategie konzentriert. Die Website bietet Artikel, Fallstudien und Ressourcen von Experten aus der Branche. Ein Special: „The Ultimate Guide With All The Secrets You Will Need To Know To Become A Fabulous Design Unicorn“ [8].
  • Awwwards [9]: Awwwards ist eine Plattform, die das Beste in Webdesign und -entwicklung anerkennt und fördert. Die Website bietet eine Galerie von preisgekrönten Projekten und Fallstudien, die als Inspiration für UI- und UX-Designer dienen können.
  • Dribbble [10]: Dribbble ist eine Selbstdarstellungsplattform und ein soziales Netzwerk für Designer und digitale Kreative. Es dient als Design-Portfolio-Plattform, Job- und Recruiting-Website und ist eine der größten Plattformen für Designer, um ihre Arbeit online zu teilen. Eine sehr gute Quelle der Inspiration für UI- und UX-Designer.

fries_henning_sw.tif_fmt1.jpgHenning Fries ist Senior User Interface Architect und beschäftigt sich mit Themen wie Ideation, Accessibility, UX und UI. Mit mehr als fünfzehn Jahren Berufserfahrung arbeitet er als Berater, Trainer, Entwickler, Project Manager und (Web-)Designer in Deutschland und Luxemburg.

Links & Literatur

[1] https://www.refactoringui.com

[2] https://alistapart.com/article/priority-guides-a-content-first-alternative-to-wireframes/

[3] https://imperavi.com/books/ui-typography/principles/design-algorithm/

[4] https://www.smashingmagazine.com

[5] https://uxplanet.org

[6] https://www.nngroup.com

[7] https://uxdesign.cc

[8] https://start.uxdesign.cc

[9] https://www.awwwards.com

[10] https://dribbble.com




End-to-End-Tests (E2E) überprüfen, ob ein Programm aus Sicht des Nutzers wie vorgesehen funktioniert.

Die Automatisierung ermöglicht es, die Tests für jeden neuen Stand zu wiederholen. Smart geschriebener Testcode findet Fehler, bevor ein Anwender beim Zugriff auf das Echtsystem darauf stößt.

In diesem Beitrag grenzen wir E2E-Tests theoretisch von anderen Testtypen ab und überlegen, weshalb sie sinnvoll sind. Die praktischen Beispiele bauen auf Joomla! [1] auf. Das CMS ermöglicht es, komplexere Problemstellungen aufzuzeigen. Die Verwendung einer fertigen Anwendung hat Vor- und Nachteile. Ein Minus ist, dass Joomla! installiert werden muss, um dem Beispielcode [2] praktisch zu folgen. Wer den Aufwand scheut, findet in den Abbildungen Orientierungshilfen zum Mitdenken.

Ich bin überzeugt, dass Tests, die

  • möglichst zeitnah zur Programmierung,
  • automatisch und
  • häufig (idealerweise nach jeder Programmänderung)

durchgeführt werden, mehr bringen, als sie kosten. In diesem Beitrag möchte ich diejenigen Entwickler motivieren, die schon immer Tests für ihre Software schreiben wollten – es aber aus verschiedenen Gründen nie getan haben. Unter Umständen räumt Cypress Hindernisse aus dem Weg. Beginnen wir mit etwas Theorie.

Das magische Dreieck

Lohnt es sich, Zeit und Geld in das Programmieren von Tests zu investieren? Das magische Dreieck [3] beschreibt den Zusammenhang zwischen Kosten, Zeit und Qualität. Das Spannungsverhältnis zwischen diesen Faktoren wurde ursprünglich im Projektmanagement beschrieben. Dort stellte man fest, dass ein höherer Kostenaufwand positive Auswirkungen auf die Qualität und/oder den Fertigstellungstermin – die Zeit – hat (Abb. 1). Umgekehrt wird eine Kosteneinsparung die Qualität mindern und/oder die Fertigstellung verzögern (Abb. 2).

Abb.1: Das magische Dreieck: Mehr Geld wirkt sich positiv auf Qualität/Zeit ausAbb.1: Das magische Dreieck: Mehr Geld wirkt sich positiv auf Qualität/Zeit aus

Abb. 2: Das magische Dreieck: Geringere Investitionen wirken sich negativ auf Qualität/Zeit ausAbb. 2: Das magische Dreieck: Geringere Investitionen wirken sich negativ auf Qualität/Zeit aus

Jetzt kommt die Magie ins Spiel: Auf lange Sicht wird dieses Spannungsverhältnis überwunden. Unter Umständen haben Sie selbst einmal erlebt, dass das Sparen an der Qualität langfristig keine Kosten mindert. Die technische Schuld führt oft zu Mehraufwand (Abb. 3).

Abb. 3: Auf lange Sicht kann der Zusammenhang zwischen Kosten, Zeit und Qualität überwunden werdenAbb. 3: Auf lange Sicht kann der Zusammenhang zwischen Kosten, Zeit und Qualität überwunden werden

Unter technischer Schuld versteht man den Aufwand, der bei der Änderung oder Erweiterung von minderwertig programmierter Software entsteht. Martin Fowler [4] unterscheidet vier Arten von technischen Schulden: solche, die man bewusst und solche, die man ungewollt eingegangen ist. Außerdem differenziert er zwischen vorsichtigen und risikofreudigen technischen Schulden (Abb. 4).

https://software-architecture-summit.de/session-qualification/sas-ber23-infoblock/?layout=contentareafeed&widgetversion=0&seriesId=aXkrJMRqaY6TETENr

Abb. 4: Technische SchuldAbb. 4: Technische Schuld

In der Literatur findet man immer wieder niederschmetternde Statistiken über die Erfolgsaussichten von Softwareprojekten. In den 1990er Jahren zeigte eine Studie von A. W. Feyhl [5], dass 70 Prozent der Projekte eine Kostenabweichung von mindestens 50 Prozent aufweisen. Sollte man also auf Kostenschätzungen verzichten und der Argumentation der #NoEstimates-Bewegung [6] folgen? Je mehr Erfahrungen ich sammle, desto mehr komme ich zu dem Schluss, dass extreme Ansichten nicht weiterhelfen. Die Lösung liegt meistens in der Mitte.

Wann ist nun der beste Zeitpunkt, Tests in ein Projekt zu integrieren? Werfen wir einen Blick auf die Kosten für die Behebung eines Fehlers in den verschiedenen Projektphasen (Abb. 5). Je früher Sie einen Fehler finden, desto geringer sind die Kosten für dessen Behebung. Zu Ende gedacht bedeutet das, dass es sinnvoll ist, Tests so früh wie möglich zu integrieren.

  • Tests finden unbekannte Fehler während der Entwicklung. Das Auffinden der Fehlfunktion ist teuer. Lokalisierung und Korrektur sind billig.
  • Debugger klären Fehler, die nach der Fertigstellung auftauchen. Das Auffinden der Fehlfunktion ist kostenlos. Lokalisierung und Behebung sind in der Regel teuer.

Abb. 5: Relative Kosten für die Fehlerbehebung in den unterschiedlichen ProjektphasenAbb. 5: Relative Kosten für die Fehlerbehebung in den unterschiedlichen Projektphasen

Kontinuierliche Integration (Continuous Integration, CI)

Es ist wichtig, jederzeit zu wissen, in welchem Zustand ein Softwareprojekt sich gerade befindet. Neuentwicklungen, die nicht in die bestehende Software passen, sollten erst dann integriert werden, wenn sichergestellt ist, dass sie keine negativen Auswirkungen auf das Gesamtsystem haben. In Zeiten, in denen immer häufiger Sicherheitsprobleme gefunden werden, sollte in einem Projekt jederzeit eine neue Version erstellt werden können. Und hier kommt die kontinuierliche Integration ins Spiel. CI integriert den neuen Code permanent. Die Software wird in kleinen Zyklen erstellt und getestet. Auf diese Weise stößt man frühzeitig auf mögliche Probleme und die Fehlersuche ist müheloser, da Fehler zeitnah zur Programmierung entdeckt werden. Cypress bietet eine ausführliche Dokumentation zur Verwendung von CI mit verschiedenen CI-Anbietern [7].

Um sicherzustellen, dass man jederzeit Tests für alle Programmteile zur Verfügung hat, bietet sich die testgetriebene Entwicklung (Test-driven Development, TDD) an. Dabei handelt es sich um eine Programmiertechnik, bei der in kleinen Schritten entwickelt wird. Zuerst schreibt man den Testcode. Erst dann erstellt man den zu testenden Programmcode. Neue Tests schlagen zunächst fehl, weil die gewünschte Funktion nicht im Programm implementiert ist. Erst im zweiten Schritt erstellt man den Code, der den Test erfüllt. Der Merksatz dazu lautet: TDD-Tests helfen, das Programm richtig zu schreiben.

Wenn Sie zum ersten Mal von dieser Technik hören, werden Sie sich mit dem Konzept vielleicht nicht wohlfühlen. Viel lieber möchte man mit dem produktiven Teil beginnen. Probieren Sie es aus. Manchmal freundet man sich mit einer neuen Technik an, nachdem man sie kennengelernt hat. In Projekten mit hoher Testabdeckung fühle ich mich deutlich wohler.

Idealerweise wird zusammen mit TDD die verhaltensgesteuerte Entwicklung (Behaviour-driven Development, BDD) eingesetzt. Das ist eine Art Best Practice, bei der nicht die Implementierung des Programmcodes im Vordergrund steht, sondern das Verhalten des Programms. Ein Test prüft, ob die Anforderung des Anwenders erfüllt wird. Der Merksatz: BDD-Tests helfen, das richtige Programm zu schreiben.

Was meine ich damit? Es kommt vor, dass Anwender Dinge anders sehen als Entwickler. Der Arbeitsablauf beim Löschen eines Artikels im CMS Joomla! ist so ein Beispiel. Immer wieder treffe ich Anwender, die im Papierkorb auf das Statusicon klicken und sich wundern, dass der Beitrag danach wieder aktiv ist. Der Anwender geht intuitiv davon aus, dass das Element dauerhaft gelöscht wird. Für den Entwickler ist ein Klick auf das Statussymbol ein Toggeln des Status. Aus Entwicklersicht ist die Funktion fehlerfrei implementiert. Stelle ich mich auf die Seite eines Benutzers, merke ich, dass das an dieser Stelle nicht die richtige Funktion ist.

Bei der verhaltensgesteuerten Entwicklung werden die Anforderungen an die Software durch Beispiele beschrieben, die als Szenarien oder User Stories bezeichnet werden. Merkmale der verhaltensgesteuerten Entwicklung sind

  • die Einbindung des Anwenders in den Entwicklungsprozess,
  • die Dokumentation mit Fallbeispielen in Textform,
  • das automatische Testen dieser Beispiele und
  • die sukzessive Implementierung.

Das Joomla!-Projekt hat BDD in einem Google-Summer-of-Code-Projekt [8] eingeführt. Man hoffte, dass Nutzer ohne Programmierkenntnisse mit Gherkin [9] leichter in die Entwicklung einbezogen werden könnten. Der Ansatz wurde nicht weiterverfolgt. Zu dieser Zeit verwendete Joomla! Codeception als Testwerkzeug. Mit Cypress ist auch die BDD-Entwicklung [10] möglich.

Planung

Es gibt verschiedene Arten von Tests:

  • Ein Unit-Test prüft kleine Programmeinheiten unabhängig voneinander.
  • Ein Integrationstest testet das Zusammenspiel der einzelnen Einheiten.
  • E2E-Tests stellen sicher, dass ein Programm die definierte Aufgabe erfüllt.

Ebenso gibt es unterschiedliche Strategien: Top-down und Bottom-up sind zwei verschiedene Ansätze zur Darstellung komplexer Sachverhalte (Abb. 6). Top-down geht Schritt für Schritt vom Abstrakten/Allgemeinen zum Konkreten/Speziellen. Beispiel: Ein CMS wie Joomla! stellt Websites im Allgemeinen in einem Browser dar. Konkret gibt es in diesem Prozess Teilaufgaben, beispielsweise das Löschen eines Elements aufgrund eines Klicks auf eine Schaltfläche. Bottom-up beschreibt die umgekehrte Richtung.

Nun kommt BDD ins Spiel. Es beinhaltet die Beschreibung des Verhaltens der Software in Form von Szenarien. Diese helfen bei der Erstellung von E2E-Tests.

Der übliche Ansatz für die Erstellung von Tests erfolgt von unten nach oben. Wer die verhaltensgesteuerte Softwareentwicklung bevorzugt, verwendet idealerweise die Top-down-Strategie und erkennt so Missverständnisse zwischen Endanwendern und Entwicklern frühzeitig.

Abb. 6: Teststrategien – Top-down und Bottom-upAbb. 6: Teststrategien – Top-down und Bottom-up

  • Bei der Anwendung der Top-down-Strategie beginnt man mit den E2E-Tests. Der Schwerpunkt liegt darauf, zu testen, wie ein Benutzer mit dem System interagiert.
  • Bei der Bottom-up-Strategie beginnt man mit Unit-Tests. Das Problem des Bottom-up-Ansatzes ist, dass es schwierig ist, zu testen, wie eine Komponente später in realen Umgebungen eingesetzt wird.

Aber wie viele Tests von welchem Typ wendet man nun sinnvollerweise an? Mike Cohns Testpyramide empfiehlt viele Unit-Tests bei der Entwicklung von Softwareanwendungen. Integrations- und E2E-Tests sollten einen geringeren Anteil ausmachen. Auf diese Weise werden Fehler innerhalb einer Unit schnell aufgedeckt. Auf der mittleren Ebene befinden sich Integrationstests, die gezielt kritische Schnittstellen prüfen. An der Spitze der Pyramide stehen die langsamen E2E-Tests, die das Verhalten der Anwendung als Ganzes testen. Nach dieser theoretischen Einführung legen wir jetzt mit der Praxis los.

Cypress und Joomla! einrichten

Unser Beispiel baut bewusst auf dem CMS Joomla! auf. Die Entwicklerversion des CMS ist für die Arbeit mit Cypress konfiguriert. Außerdem gibt es implementierte Tests, an denen man sich orientieren kann.

Wir gehen folgende Schritte zur Einrichtung der lokalen Umgebung:

  1. Klonen Sie das Repository in das Stammverzeichnis Ihres lokalen Webservers:
$ git clone https://github.com/joomla/joomla-cms.git
$ cd joomla-cms

  1. Laut der Joomla!-Roadmap [11] wird die Major-Version 5.0 [12] im Oktober 2023 erscheinen. Ich baue auf dieser Entwicklungsversion auf. Wechseln Sie zum Branch 5.0-dev:
$ git checkout 5.0-dev

  1. Installieren Sie alle nötigen composer-Pakete:
$ composer install

  1. Installieren Sie alle nötigen npm-Pakete:
$ npm install

Wenn Sie diese Schritte gegangen sind, ist alles fertig konfiguriert. Nur die individuellen Daten sind in der Datei joomla-cms/cypress.config.js anzupassen. Orientierung bietet die Vorlage joomla-cms/cypress.config.dist.js. Weitere Informationen stellen Joomla! [13] und Cypress [14] auf ihren Websites bereit.

Cypress verwenden

Rufen Sie npm run cypress:open via CLI im Joomla!-Stammverzeichnis auf. Kurze Zeit später öffnet sich die Cypress-App (Abb. 7). Dass Cypress die zuvor erstellte Datei joomla-cms/cypress.config.dist.js lädt, erkennt man daran, dass E2E Testing als konfiguriert markiert ist (Abb. 7).

Abb. 7: Cypress-App öffnet sich nach dem Aufruf von npm run cypress:openAbb. 7: Cypress-App öffnet sich nach dem Aufruf von npm run cypress:open

Nach dem Klick auf E2E Testing selektiert man den bevorzugten Browser. Für dieses Beispiel habe ich die Option Start Testing in Firefox gewählt (Abb. 8).

Abb. 8: E2E-Tests in der Cypress-App: Wähle den zu verwendenden BrowserAbb. 8: E2E-Tests in der Cypress-App: Wähle den zu verwendenden Browser

Die nächste Ansicht listet alle implementierten Tests auf (Abb. 9). Wenn man eine Datei per Mausklick auswählt, werden die darin enthaltenen Tests aufgerufen. Der Ablauf ist im Browser live sichtbar.

Abb. 9: Joomla!-Testreihe in Firefox über Cypress-AppAbb. 9: Joomla!-Testreihe in Firefox über Cypress-App

Während die Tests ablaufen, sieht man links den Testcode und rechts das visuelle Ergebnis (Abb. 10). Klickt man später in der Historie zurück, kann man den HTML-Code zu diesem Zeitpunkt analysieren.

Abb. 10: Der Test, der die korrekte Installation von Joomla! sicherstellt, während der AusführungAbb. 10: Der Test, der die korrekte Installation von Joomla! sicherstellt, während der Ausführung

Probieren Sie es aus. Starten Sie den Test, der die korrekte Installation sicherstellt. Als Ergebnis verfügen Sie über ein System, auf dem wir aufbauen können.

Falls Sie als db_host eine externe Ressource verwenden, funktioniert der Test nicht. Installieren Sie in diesem Fall Joomla! manuell mit den Angaben, die in der Datei joomla-cms/cypress.config.js eingetragen sind.

Standardmäßig ruft cypress run alle Tests headless [15] auf. Der Befehl $ npm run cypress:runin führt die implementierten Tests aus und speichert im Fehlerfall Screenshots im Verzeichnis /joomla-cms/tests/cypress/output/screenshots.

Weitere CLI-Befehle

Es gibt weitere hilfreiche Befehle, die nicht in der package.json des Joomla!-Projektes als Skript implementiert sind. Ich führe diese via npx [16] aus.

Der Befehl cypress verify überprüft, ob Cypress korrekt installiert ist:

$ npx cypress verify
 
✔  Verified Cypress! /.../Cypress/12.8.1/Cypress

Der Befehl cypress info gibt Informationen über Cypress und die aktuelle Umgebung aus (Listing 1).

Listing 1

$ npx cypress info
Displaying Cypress info...
Detected 2 browsers installed:
1. Chromium
 - Name: chromium
 - Channel: stable
 - Version: 113.0.5672.126
...

Die Dokumentation [17] bietet zu jedem Befehl ausführlichere Informationen.

Der erste Test

In der Entwicklungsversion des Joomla!-CMS gibt es fertige Cypress-Tests im Verzeichnis /tests/System/integration. Diejenigen, die gern am Beispiel lernen, finden hier einen Einstieg.

Das Joomla!-Projekt stellt mit joomla-cypress [18] auch Code für gängige Testfälle bereit. Diese werden bei der Installation der Entwicklungsversion des CMS via npm install per package.json (Listing 2) und während der Entwicklung der Tests per Support-Datei /tests/System/support/index.js importiert. Der Speicherort der Support-Datei ist in der Datei cypress.config.js festgelegt.

Listing 2

// package.json
 
{
  "name": "joomla",
  "version": "5.0.0",
  ...
  "devDependencies": {
    ...
    "joomla-cypress": "^0.0.16",
    ...
  }
}

Ein Beispiel für eine Funktion, die via joomla-cypress bereitgestellt wird, ist der Klick auf eine Schaltfläche in der Toolbar. Der Aufruf Cypress.Commands.add(‚clickToolbarButton‘, clickToolbarButton) bewirkt, dass in den eigenen Tests der Befehl clickToolbarButton() zur Verfügung steht. cy.clickToolbarButton(’new‘) simuliert einen Mausklick auf den Button New. Der hierfür erforderliche Code ist in Listing 3 auszugsweise dargestellt. Listing 4 zeigt ein weiteres Beispiel, die Anmeldung im Administrationsbereich.

Listing 3

// /node_modules/joomla-cypress/src/common.js
...
const clickToolbarButton = (button, subselector = null) => {
  switch (button.toLowerCase())
  {
    case "new":
      cy.get("#toolbar-new").click()
      break
      ...
  }
}
Cypress.Commands.add('clickToolbarButton', clickToolbarButton)
...

Listing 4

// /node_modules/joomla-cypress/src/user.js
...
const doAdministratorLogin = (user, password, useSnapshot = true) => {
  cy.visit('administrator/index.php')
  cy.get('#mod-login-username').type(user)
  cy.get('#mod-login-password').type(password)
  cy.get('#btn-login-submit').click()
  cy.get('h1.page-title').should('contain', 'Home Dashboard')
}
 
Cypress.Commands.add('doAdministratorLogin', doAdministratorLogin)
...

Aufgaben, die in der individuellen Umgebung häufig vorkommen, befinden sich im Verzeichnis /tests/System/support. Sie werden über die Support-Datei /tests/System/support/index.js importiert. Ein Beispiel ist die Anmeldung im Administrationsbereich mit den konkreten Log-in-Daten. Die zuvor behandelte Funktion doAdministratorLogin im Paket joomla-cypress erfordert die Angabe der Log-in-Daten beim Aufrufen. Die Datei /tests/System/support/commands.js bietet mit der Funktion doAdministratorLogin die Möglichkeit, auf die Angabe der Daten zu verzichten. Listing 5 zeigt, wie die Log-in-Daten aus der Konfiguration cypress.config.js genutzt werden. Cypress.env(‚username‘) wird mit dem Wert der Eigenschaft username in der Gruppe env belegt. Außerdem sehen wir, wie man Befehle überschreibt. Cypress.Commands.overwrite(‚doAdministratorLogin‘ …) tritt an Stelle des Codes im Paket joomla-cypress.

Listing 5

// /tests/System/support/commands.js
...
Cypress.Commands.overwrite('doAdministratorLogin', (originalFn, username, password, useSnapshot = true) => {
  const user = username ?? Cypress.env('username');
  const pw = password ?? Cypress.env('password');
 
  if (!useSnapshot) {
    Cypress.session.clearAllSavedSessions();
    return originalFn(user, pw);
  }
  return cy.session([user, pw, 'back'], () => originalFn(user, pw), { cacheAcrossSpecs: true });
});
...

Damit wir über eigenen Code zum Testen verfügen, installieren wir eine simple Beispielkomponente [19] über das Joomla!-Backend (Abb. 11).

Abb. 11: Installation einer eigenen Joomla!-ErweiterungAbb. 11: Installation einer eigenen Joomla!-Erweiterung

Nach der Installation ist die Ansicht zum Verwalten der Beispielkomponente in der linken Seitenleiste des Joomla!-Backends (Abb. 12) integriert.

Abb 12: Ansicht der Beispielkomponente im Joomla!-BackendAbb 12: Ansicht der Beispielkomponente im Joomla!-Backend

Der erste eigene Test

Beim Testen des Joomla!-Backends beginnt jeder Test mit einem Log-in. Redundanten Code verhindern wir, indem wir die Funktion beforeEach() verwenden. Der Hook führt Code vor jedem Test aus. Daher der Name beforeEach().

Cypress bietet verschiedene Arten von Hooks [20], darunter before und after Hooks, die vor oder nach dem Test in einer Testgruppe ausgeführt werden, sowie beforeEach und afterEach Hooks, die vor oder nach jedem einzelnen Test in der Gruppe ablaufen. Hooks können global oder innerhalb eines bestimmten described-Blocks definiert werden. Der Code aus Listing 6 bewirkt, dass eine Anmeldung im Backend vor jedem Test innerhalb des described-Blocks Test com_foos features durchgeführt wird.

Listing 6

// tests/System/integration/administrator/components/com_foos/FoosList.cy.js
 
describe('Test com_foos features', () => {
  beforeEach(() => {
    cy.doAdministratorLogin()
  }) 
  ...
})

In der Datei /tests/System/support/index.js sind Hooks implementiert, die global für jede Testdatei und jeden Test gelten. Die Komponente, die wir zum Testen installierten, beinhaltet drei Elemente. Zuerst testen wir, ob diese Elemente erfolgreich angelegt wurden (Listing 7).

Listing 7

// tests/System/integration/administrator/components/com_foos/FoosList.cy.js
 
describe('Test com_foos features', () => {
  beforeEach(() => {
    cy.doAdministratorLogin()
  })
 
  it('list view shows items', function () {
    cy.visit('administrator/index.php?...')
 
    cy.get('main').should('contain.text', 'Astrid')
    cy.get('main').should('contain.text', 'Nina')
    cy.get('main').should('contain.text', 'Elmar')
 
    cy.checkForPhpNoticesOrWarnings()
  })
})

Um sicherzustellen, dass ein Element angelegt wurde, suchen wir das DOM-Element main mit dem Cypress-Befehl get [21] und stellen via should(‚contain.text‘, ‚…‘) sicher, dass das Element sich in der Übersichtsliste befindet.

Neu ist auch die Funktion checkForPhpNoticesOrWarnings(). Sie stellt sicher, dass keine Warnungen ausgegeben werden. Die Implementierung der Funktion befindet sich in der Datei /node_modules/joomla-cypress/src/support.js.

Die gerade erstellte Testdatei FooList.cy.js sollte nach dem Speichern und Neuladen in der Liste der verfügbaren Tests in Cypress Browseroberfläche angeboten werden (Abb. 13). Klicken Sie auf den Namen FooList, um den Test auszuführen (Abb. 14).

Abb. 13: Joomla!-Test für die eigene Erweiterung ausführenAbb. 13: Joomla!-Test für die eigene Erweiterung ausführen

Abb. 14: Ansicht, nachdem der Test erfolgreich durchgelaufen istAbb. 14: Ansicht, nachdem der Test erfolgreich durchgelaufen ist

Fügen Sie jetzt die Zeile cy.get(‚main‘).should(‚contain.text‘, ‚Sami‘) in den Testcode ein, sodass der Lauf fehlschlägt. Nach dem Speichern der Testdatei erkennt Cypress die Änderung und führt alle Tests in der Testdatei erneut aus. Wie erwartet schlägt der Test fehl (Abb. 15). Sie können jeden Testschritt in der linken Seitenleiste sehen. Für jeden Schritt gibt es einen Snapshot, sodass man das Markup jederzeit überprüfen kann.

Abb. 15: Ansicht, nachdem der Test fehlgeschlagen istAbb. 15: Ansicht, nachdem der Test fehlgeschlagen ist

Testausführung organisieren

Im nächsten Schritt fügen wir einen Test zum Überprüfen des Empty-State-Layouts des Beispielcodes hinzu (Listing 8). Da wir nun zwei Tests in dieser Datei haben, wird Cypress bei jedem Speichern immer beide Tests ausführen. Via .only() ist es möglich, sich während der Entwicklung auf einen Test zu konzentrieren.

Listing 8

// tests/System/integration/administrator/components/com_foos/FoosList.cy.js
 
describe('Test com_foos features', () => {
  beforeEach(() => {
    cy.doAdministratorLogin()
  })
 
  it('list view shows items', function () {
    ...
  })
 
  it.only('emptystate layout', function () {
    ...
  })
})

Das Frontend der Beispielerweiterung testen wir mit der Datei /tests/System/integration/site/components/com_foos/FooItem.cy.js. Bisher verwendeten wir CSS-Klassen, um ein HTML-Element zu finden. Das funktioniert, wird aber nicht empfohlen. Warum nicht? Wenn man CSS-Klassen verwendet, bindet man die Tests an etwas, das sich in der produktiven Umgebung ändern kann. Um die Tests stabiler zu implementieren, empfiehlt Cypress die Verwendung spezieller Testattribute. Ich werde das Attribut data-test für die Elemente verwenden. Zuerst füge ich das Attribut data-test=“foo-main“ zum Produktionscode hinzu (Listing 9). Danach verwende ich das Attribut [data-test=“foo-main“], um Elemente im Test zu finden (Listing 10).

Listing 9

// /components/com_foos/tmpl/foo/default.php
 
<?php
  \defined('_JEXEC') or die;
?>
<div data-test="foo-main">
  Hello Foos
</div>

Listing 10

// tests/System/integration/site/components/com_foos/FooItem.cy.js
describe('Test com_foo frontend', () => {
  it('Show frondend via query in url', function () {
    cy.visit('index.php?...')
    cy.get('[data-test="foo-main"]').should('contain.text', 'Hello Foos')
    cy.checkForPhpNoticesOrWarnings()
  })
})

Jetzt testen wir die Erstellung eines Menüpunkts innerhalb von Joomla! für die Testkomponente. Das tun wir in der Datei /tests/System/integration/administrator/components/com_foos/MenuItem.cy.js. Der Code ist komplex und weist eine Reihe von Besonderheiten auf.

Zunächst habe ich eine Konstante definiert, in der ich alle wichtigen Eigenschaften des Menüpunkts einstelle. Das hat den Vorteil, dass ich bei Änderungen der Menüpunktdaten nur an einer Stelle etwas anpassen muss:

const testMenuItem = {
  'title': 'Test MenuItem',
  'menuitemtype_title': 'COM_FOOS',
  'menuitemtype_entry': 'COM_FOOS_FOO_VIEW_DEFAULT_TITLE'
}

Listing 11 zeigt den relevanten Code der Datei MenuItem.cy.js.

Listing 11

// tests/System/integration/administrator/components/com_foos/MenuItem.cy.js
 
describe('Test menu item', () => {
  it('creates a new menu item', function () {
    cy.intercept('index.php?...').as('item_list')
    cy.clickToolbarButton('Save & Close')
    cy.wait('@item_list')
    cy.get('#system-message').contains('...saved').should('exist')
    ...
    cy.visit('administrator/index.php?...')
    cy.setFilter('published', 'Trashed')
    cy.searchForItem(testMenuItem.title)
    ...
    cy.on("window:confirm", (s) => {
      return true;
    });
    ...
    cy.get('#system-message').contains('...deleted').should('exist')
  })
})

Zum einen zeigt dieses Beispiel, wie man einen Test so aufbaut, dass am Ende wieder der Ausgangszustand hergestellt ist. Auf diese Art und Weise können die Tests unbegrenzt oft wiederholt werden. Ohne das Wiederherstellen des Ausgangszustands würde der zweite Testlauf fehlschlagen, weil Joomla! das Speichern von zwei Elementen mit dem gleichen Namen ablehnt. Jeder Test sollte

  • wiederholbar sein;
  • einfach gehalten sein. Konkret bedeutet das, dass man eine begrenzte Problemstellung testen sollte und der Code hierzu sollte nicht zu umfangreich sein;
  • unabhängig von anderen Tests sein.

Der Test zeigt ebenfalls, wie man eine mit cy.intercept() [22] definierte Route als Alias verwendet und dann via cy.wait() [23] auf diese wartet. Beim Schreiben von Tests ist man versucht, im Befehl cy.wait zufällige Zeitwerte wie cy.wait(2000); zu verwenden. Das Problem bei diesem Ansatz ist, dass ein reibungsloser Testdurchlauf nicht garantiert ist. Warum? Weil das zugrunde liegende System von Umständen abhängt, die sich kaum vorhersagen lassen. Daher ist es immer besser, genau zu definieren, worauf man wartet.

Der Code zeigt weiterhin, wie man auf einen Alert wartet und diesen via window:confirm bestätigt. Nicht zuletzt enthält der Testcode viele Funktionen, die von Entwicklern wiederverwendet werden können. Beispielsweise cy.setFilter(‚published‘, ‚Trashed‘) oder cy.clickToolbarButton(‚Save & Close‘).

Asynchroner und synchroner Code

Cypress-Befehle sind asynchron, das heißt, sie geben keinen Wert zurück, sondern „erzeugen“ ihn. Cypress ordnet die Befehle seriell in einer Warteschlange. Wenn man in Tests asynchronen und synchronen Code mischt, erhält man unter Umständen unerwartete Ergebnisse.

Wenn Sie den Code aus Listing 12 aufrufen, wird wider Erwarten ein Fehler auftreten, weil mainText === ‚Initial‘ am Ende noch gültig ist. Warum ist das so? Cypress führt erst den synchronen Code aus, der am Anfang und am Ende steht. Danach ruft es den asynchronen Teil innerhalb von then() auf. Das heißt, die Variable mainText wird initialisiert und gleich danach geprüft, ob sie sich geändert hat – was natürlich nicht so ist.

Listing 12

let mainText = 'Initial';
cy.visit('administrator/index.php?...')
cy.get("main").then(
  ($main) => (mainText = $main.text())
);
 
if (mainText === 'Initial') {
  throw new Error('Der Text hat sich nicht geändert. Er lautet: ${mainText}');
}

Anschaulich wird das Abarbeiten der Warteschlange, wenn man den folgenden Code in der Konsole des Browsers beobachtet. Der Text Cypress Test. erscheint lange bevor der Inhalt des Elements main ausgegeben wird, obwohl die Codezeilen in einer anderen Reihenfolge stehen.

cy.get('main').then(function(e){
  console.log(e.text())
})
console.log('Cypress Test.')

Spy und Stub

Ein Stub bietet die Möglichkeit, das Verhalten einer Funktion zu simulieren. Anstatt die eigentliche Funktion aufzurufen, ersetzt der Stub diese und gibt einen vordefinierten Wert zurück. Ein Spy ändert das Verhalten einer Funktion nicht. Vielmehr erfasst es einige Informationen darüber, wie die Funktion aufgerufen wird. Es prüft beispielsweise, wie oft oder mit welchen Parametern ein Aufruf erfolgte.

Listing 13 zeigt Spy und Stub in Aktion. Via const stub = cy.stub() erstellen wir das stub-Element und bestimmen im nächsten Schritt, dass beim ersten Aufruf false und beim zweiten true als Antwort zurückgegeben wird. Mittels cy.on(‚window:confirm‘, stub) erreichen wir, dass das Stub anstelle von ‚window:confirm‘ eingesetzt wird. Anschließend erstellen wir via cy.spy(win, ‚confirm‘).as(‚winConfirmSpy‘) das Spy-Element, das den Aufruf von ‚window:confirm‘ beobachtet. Nun simulieren wir im Testcode eine Situation, in der beim ersten Aufruf das Löschen einer Kategorie abgelehnt und beim zweiten Aufruf bestätigt wird. Dabei sorgt das Stub dafür, dass beim ersten Aufruf false und danach true per ‚window:confirm‘ zurückgegeben wird. cy.get(‚@winConfirmSpy‘).should(‚be.called…‘) hilft sicherzustellen, dass die Funktion tatsächlich in der erwarteten Häufigkeit aufgerufen wurde.

Listing 13

// tests/System/integration/administrator/components/com_foos/FoosList.cy.js
...
const stub = cy.stub()
 
stub.onFirstCall().returns(false)
stub.onSecondCall().returns(true)
 
cy.on('window:confirm', stub)
 
cy.window().then(win => {
  cy.spy(win, 'confirm').as('winConfirmSpy')
})
 
cy.intercept('index.php?...').as('cat_delete')
cy.clickToolbarButton('empty trash');
 
cy.get('@winConfirmSpy').should('be.calledOnce')
cy.get('main').should('contain.text', testFoo.category)
 
cy.clickToolbarButton('empty trash');
cy.wait('@cat_delete')
 
cy.get('@winConfirmSpy').should('be.calledTwice')
 
cy.get('#system-message-container').contains('Category deleted.').should('exist')
...

Kurzgefasst

In diesem Beitrag haben Sie theoretische Grundlagen und praktische Besonderheiten von E2E-Tests kennengelernt. Das geschah anhand von eingängigen Beispielen. Ich hoffe, dass Sie den Rundgang durch Cypress und Joomla! genossen und Wissen und Anregungen für sich mitgenommen haben.

guenther_astrid_sw.tif_fmt1.jpgSeit 2017 programmiert Astrid Günther individuelle Websites und schreibt Bücher für Menschen, denen ihr Auftritt im Web wichtig ist. Am liebsten mit Joomla! und sehr gerne in Kombination mit geografischen Daten. Dabei schaut sie immer gerne über den Tellerrand hin zu anderen Open-Source-Projekten.

Links & Literatur

[1] https://www.joomla.de

[2] https://www.codeberg.org/astrid/entwickler_magazin_beispielcode_cypress_joomla (bit.ly/42qy2Aw)

[3] https://www.wikipedia.org/wiki/Projektmanagement#Stakeholdererwartungen

[4] https://www.martinfowler.com/bliki/TechnicalDebtQuadrant.html

[5] https://www.link.springer.com/chapter/10.1007/978-3-322-92018-8_1

[6] https://www.oikosofyseries.com/no-estimates-book-order

[7] https://www.learn.cypress.io/advanced-cypress-concepts/running-cypress-in-ci

[8] https://www.magazine.joomla.org/all-issues/june-2016/how-to-make-joomla-cms-tests-better-with-gherkin-and-codeception (bit.ly/3IYrouO)

[9] https://www.wikipedia.org/wiki/Cucumber_(Software)

[10] https://www.github.com/badeball/cypress-cucumber-preprocessor

[11] https://www.developer.joomla.org/roadmap#5x

[12] https://www.github.com/joomla/joomla-cms/tree/5.0-dev

[13] https://www.docs.joomla.org/Setting_up_your_workstation_for_Joomla_development/de (bit.ly/3oMWBdg)

[14] https://www.docs.cypress.io/guides/getting-started/installing-cypress (bit.ly/45Qt4js)

[15] https://www.wikipedia.org/wiki/Headless_(Informatik) (bit.ly/43mP8kr)

[16] https://www.docs.npmjs.com/cli/v9/commands/npx

[17] https://www.docs.cypress.io/guides/guides/command-line

[18] https://www.github.com/joomla-projects/joomla-cypress/tree/develop (bit.ly/3Cf8Hz6)

[19] https://www.codeberg.org/astrid/entwickler_magazin_beispielcode_cypress_joomla/releases (bit.ly/3WReofY)

[20] https://www.docs.cypress.io/guides/core-concepts/writing-and-organizing-tests#Hooks (bit.ly/43HJetD)

[21] https://www.docs.cypress.io/api/commands/get

[22] https://www.docs.cypress.io/api/commands/intercept

[23] https://www.docs.cypress.io/api/commands/wait




Daten aus Social Media automatisiert herunterladen

Das Automatisieren von Downloads spielt bei der Auswertung von Daten eine große Rolle. Oftmals liegen riesige Datenmengen vor, die allerdings auf mehrere Dateien aufgeteilt wurden. Das betrifft vor allem Daten, die von diversen Diensten wie einem Fahrradverleih zur Verfügung gestellt werden. Andererseits sind Daten von sozialen Medien gefragt, anhand derer sich beispielsweise erkennen lässt, wie beliebt ein Post, Tweet oder Video ist.

Für etliche Anbieter von sozialen Medien existieren bereits Bibliotheken, die Zugriff auf das API eines sozialen Netzwerks ermöglichen [1]. Bei diversen Downloadproblemen ist allerdings die Verwendung der allgemeineren Requests-Bibliothek erforderlich [2]. Requests beschäftigt sich nämlich mit den POST– und GET-Anfragen, die an HTTP-Server übermittelt werden. Anschließend kann die gewünschte Seite oder Datei mittels geeigneter Funktion heruntergeladen werden (Abb. 1).

Abb. 1: Requests-BibliothekAbb. 1: Requests-Bibliothek

Alles in allem belegen diverse Statistiken, dass das Datenvolumen exponentiell wächst, womit es für Anwender unausweichlich wird, das Herunterladen von Daten beziehungsweise den Zugriff auf Daten zu automatisieren [3].

Soziale Medien

Üblicherweise platzieren Unternehmen beziehungsweise Organisationen Links zu den sozialen Netzwerken auf der eigenen Webseite, was vor allem auf die Kontaktseite zutrifft. So befinden sich die Icons der bekannten Anbieter von sozialen Medien eingebettet auf der Kontaktseite. Andererseits ist es üblich, die Icons im Footer einer x-beliebigen Seite der Organisation zu platzieren. Der dafür erforderliche HTML-Code, um das Icon eines Anbieters für soziale Medien auszugeben, könnte wie folgt aussehen:

<a target="_blank" href="https://www.facebook.com/GradeSaverLLC">
  <img class="socialMedia__image" src="/assets/footer/facebook-beb8875d903a5a5d32d7d55667361d763ad2f0fb1c533b86afa19056eb4cbbf8.png">
</a>

Sobald ein Anwender auf eines dieser Icons klickt, leitet der Browser den Anwender auf die Social-Media-Seite der Organisation weiter. Die Reichweite des Links umfasst dabei das komplette Social-Media-Icon.

Um Links aus Webseiten extrahieren zu können, eignet sich das Paket „Extract Social Media“ in der Version 0.4.0. Zusätzlich ist der Einsatz des Requests-Pakets erforderlich. Mit pip lassen sich die Pakete wie folgt installieren [4]:

pip install extract-social-media
pip install requests

Wird nun ein URL der selbstdefinierten Methode get_links übergeben, parst sie die Webseite nach möglichen Links und gibt diese aus (Listing 1).

Listing 1

import requests
from extract_social_media import find_links_tree
from html_to_etree import parse_html_bytes
 
def get_links(url):
  res = requests.get(url)
  tree = parse_html_bytes(res.content, res.headers.get('content-type'))
  link_set = set(find_links_tree(tree))
  for s in link_set:
    print(s)
 
get_links('https://www.gradesaver.com/contact')

In der Methode get_links wird zunächst eine GET-Anfrage requests.get an die Webseite gesendet. Der daraufhin übermittelte Inhalt der Webseite wird in der Variable res gespeichert. Anschließend fischt die Methode find_links_tree die Links heraus und speichert sie in einem Set ab (Abb. 2).

Abb. 2: Extrahierte Links einer WebseiteAbb. 2: Extrahierte Links einer Webseite

Dateidownload

Das Herunterladen von Dateien lässt sich unter Python in mehreren Schritten bewerkstelligen, wobei das Python-Skript davon ausgeht, dass die Links zu den Dateien bereits vorhanden sind. Vor allem die Auswertung von Dateien wird durch den automatisierten Download erleichtert, da sich heruntergeladene Text- oder CSV-Dateien im Anschluss in einen Dataframe importieren lassen. Für den Download mit Python werden die Importe aus Listing 2 gebraucht.

Listing 2

import numpy as np
import pandas as pd
import requests
import zipfile
import os
import glob

Das Python-Skript legt zunächst einen Ordner an, um die Dateien dort abzuspeichern. Der Ordner wird lediglich dann erstellt, wenn er noch nicht existiert:

def make_dir(folder):
  if not os.path.exists(folder):
    os.makedirs(folder)

Beim anschließenden Herunterladen der Dateien wird durch eine Liste mit Links links iteriert, wobei erneut von der Requests-Bibliothek in Version 2.28.2 Gebrauch gemacht wird (Listing 3). Um den Fortschritt des Downloads anzuzeigen, wird auf Python-Bordmittel zurückgegriffen. So wird der Index des aktuellen Links ermittelt, wobei die Anzeige zusätzlich die Gesamtzahl der Links zusammen mit der Antwort des Servers beinhaltet. In der Produktion sollte die GET-Anfrage requests.get unter Berücksichtigung eines Timeout ausgegeben werden. Dadurch wartet das Python-Skript lediglich für eine bestimmte Zeit in Sekunden auf eine Antwort des Servers, um anschließend weiterzumachen. Ansonsten besteht die Gefahr, dass sich das Programm aufhängt [5]. Mit den letzten zwei Zeilen werden die Dateien im Ordner abgespeichert (Abb. 3).

Listing 3

# downloads all zip files that are mentioned in the list:
def download(links, folder):
  size = len(links)
  for l in links:
    i = links.index(l)
    msg = '( '+str(i+1)+'/'+str(size)+' ) '+'downloading file from: '+l
    response = requests.get(l,timeout=30)
    print(msg)
    print(response)
    print(response.elapsed)
 
    with open(os.path.join(folder, l.split('/')[-1]), mode='wb') as file:
      file.write(response.content)

Abb. 3: Fortschrittsanzeige der DownloadsAbb. 3: Fortschrittsanzeige der Downloads

Das Programm könnte mit Listing 3 vorbei sein. Allerdings lässt sich das Programm noch weiter ausbauen, indem beispielsweise heruntergeladene ZIP-Archive automatisch entpackt werden (Listing 4). Die Methode extract erledigt genau das, indem sie zusätzlich die entpackten Dateien an einem beliebigen Ort auf der Festplatte ablegt.

Listing 4

# Extracts all contents from zip file
def extract(folder):
  all_files = glob.glob(folder + "/*.zip")
  archive = folder + '/' + working_path
  for f in all_files:
    with zipfile.ZipFile(f, 'r') as myzip:
      myzip.extractall(path=folder)

Handelt es sich bei den heruntergeladenen Dateien um Datenreihen, die beispielsweise als Textdatei oder im CSV-Format vorliegen, dann bietet es sich an, diese Daten in einem Dataframe zu importieren (Listing 5). So iteriert die Methode merge durch alle Dateien vom Typ CSV. Danach werden die Datenreihen schrittweise in einen einzigen Dataframe geladen.

Listing 5

# merges all csv files into one dataframe
def merge(folder):
  all_files = glob.glob(folder + "/*.csv")
  li = []
  for filename in all_files:
    df = pd.read_csv(filename, index_col=None, header=0)
    li.append(df)
  fordgobike = pd.concat(li, axis=0, ignore_index=True)
  # displays the columns and their datatypes
  fordgobike.info()

Listing 6 beherbergt die Testdaten. So können Sie dort entnehmen, wie die Liste mit den Links definiert worden ist und wie sich die einzelnen Methoden aufrufen lassen (vgl. test_files_download).

Listing 6

folder_name = 'fordgobike'
working_path = 'archive'
# urls of zip files
urls = ['https://s3.amazonaws.com/fordgobike-data/201801-fordgobike-tripdata.csv.zip',
        'https://s3.amazonaws.com/fordgobike-data/201802-fordgobike-tripdata.csv.zip',
        'https://s3.amazonaws.com/fordgobike-data/201803-fordgobike-tripdata.csv.zip',
        'https://s3.amazonaws.com/fordgobike-data/201804-fordgobike-tripdata.csv.zip',
        'https://s3.amazonaws.com/fordgobike-data/201805-fordgobike-tripdata.csv.zip',
        'https://s3.amazonaws.com/fordgobike-data/201806-fordgobike-tripdata.csv.zip',
        'https://s3.amazonaws.com/fordgobike-data/201807-fordgobike-tripdata.csv.zip',
        'https://s3.amazonaws.com/fordgobike-data/201808-fordgobike-tripdata.csv.zip',
        'https://s3.amazonaws.com/fordgobike-data/201809-fordgobike-tripdata.csv.zip',
        'https://s3.amazonaws.com/fordgobike-data/201810-fordgobike-tripdata.csv.zip',
        'https://s3.amazonaws.com/fordgobike-data/201811-fordgobike-tripdata.csv.zip',
        'https://s3.amazonaws.com/fordgobike-data/201812-fordgobike-tripdata.csv.zip',
        'https://s3.amazonaws.com/fordgobike-data/201901-fordgobike-tripdata.csv.zip',
        'https://s3.amazonaws.com/fordgobike-data/201902-fordgobike-tripdata.csv.zip',
        'https://s3.amazonaws.com/fordgobike-data/201903-fordgobike-tripdata.csv.zip',
        'https://s3.amazonaws.com/fordgobike-data/201904-fordgobike-tripdata.csv.zip']
        
def test_files_download():
  print('making dir')
  make_dir(folder_name)
  print('downloading files')
  download(urls, folder_name)
  print('extracting files')
  extract(folder_name)
  print('merging files into to dataframe')
  merge(folder_name)

Vimeo-Bot

Vimeo ist ein beliebter Videodienst. Die gute Nachricht ist, dass es bereits eine Bibliothek gibt, die das Vimeo-API implementiert [6]. In Version 0.4.1 lässt sich die Bibliothek Vimeo Downloader wie folgt installieren:

pip install vimeo-downloader

Zusätzlich braucht Ihr Programm den folgenden Import, um auf die Methoden und Attribute des Vimeo-API zuzugreifen:

from vimeo_downloader import Vimeo

Um mehr über ein Video zu erfahren, können Sie zunächst die Metadaten eines Videos abrufen (Listing 7). Sobald ein Objekt vom Typ Vimeo durch Übergabe eines Links initialisiert wird, lassen sich der Titel sowie die Zahl der Likes und Views abrufen. Zusätzlich können Sie alle Kategorien des Metaobjekts anzeigen lassen (Abb. 4).

Listing 7

def print_info(url):
  v = Vimeo(url)
  meta = v.metadata
  print('Title:', meta.title)
  print('Number of likes: ', meta.likes)
  print('Number of views: ', meta.views)
  print(meta._fields)
  return v

Abb. 4: Metadaten eines Vimeo-VideosAbb. 4: Metadaten eines Vimeo-Videos

Damit Sie nun einen Vimeo-Bot generieren können, ist es lohnenswert, den Bot auf eine Liste von Links anzuwenden. So initialisiert die Schleife jedes Mal das Vimeo-Objekt bei Übergabe des aktuellen Links. Dabei können Sie gegebenenfalls den Titel anpassen. So lassen sich beispielsweise alle Sonderzeichen sowie Leerzeichen mit geeigneten Methoden entfernen. Den Titel für einen Link rufen Sie zunächst über die Metadaten ab, indem Sie der Methode get_title das zuvor initialisierte Vimeo-Objekt übergeben:

def get_title(v):
  meta = v.metadata
  return meta.title

Danach können Sie den Titel eines Videos mittels der Methode set_filename anpassen (Listing 8). Bei dieser Methode werden alle Sonderzeichen einschließlich der Leerzeichen entfernt. Anschließend prüft die Methode die Länge des Strings und kürzt ihn gegebenenfalls [7].

Listing 8

def set_filename(s):
  max_filename = 255
  normal_string = "".join(ch for ch in s if ch.isalnum())
  stripped_s = normal_string.strip()
  split_string = stripped_s[:max_filename]
  return split_string

Das eigentliche Herunterladen des Videos findet in der Methode download unter Übermittlung des Vimeo-Objekts, des Download-Ordners sowie eines neuen Dateinamens statt. Normalerweise beherbergt der Aufruf von vimeo.streams eine Liste voller Streams, die für den jeweiligen Titel verfügbar sind. Allerdings handelt es sich beim letzten Listenelement um den Stream mit der höchsten Auflösung:

def download(vim,path,filename):
  stream = vim.streams
  best_stream = stream[-1]
  best_stream.download(download_directory=path, filename=filename)

Alternativ lässt sich über die Liste verfügbarer Streams iterieren, um den gewünschten Stream herauszufiltern. Sofern Sie beispielsweise einen Stream mit einer Auflösung von 720 herunterladen wollen, können Sie wie in Listing 9 vorgehen [8].

Listing 9

for s in stream:
  if s.quality == '720p':
    s.download(download_directory='video', filename=v.metadata.title)
    break
  else:
    print('quality not found')

Die Methode download_all lädt schließlich alle Videos herunter, indem auf die zuvor erwähnten Hilfsmethoden zurückgegriffen wird (Listing 10). Abgesehen davon, beherbergt diese Methode eine Fortschrittsanzeige, die den aktuellen Download samt Downloadraten ausgibt (Abb. 5).

Listing 10

vimeo_path = '/home/Videos/vimeo'
 
v_1 = 'https://vimeo.com/136491689'
v_2 = 'https://vimeo.com/509738789'
v_3 = 'https://vimeo.com/546042556'
v_4 = 'https://vimeo.com/58326464'
v_5 = 'https://vimeo.com/396053468'
v_6 = 'https://vimeo.com/560619626'
v_7 = 'https://vimeo.com/4510860'
 
videos = [v_1, v_2, v_3, v_4, v_5, v_6]
 
def download_all(path,urls):
  size = len(urls)
  for url in urls:
    v = Vimeo(url)
    title = get_title(v)
    new_title = set_filename(title)
    i = urls.index(url)
    print('Downloading video ('+str(i+1)+'/'+str(size)+')')
    download(v,path,new_title)

Abb. 5: Download von Vimeo-VideosAbb. 5: Download von Vimeo-Videos

YouTube-Bot

Genauso wie für Vimeo existiert eine Bibliothek, die Zugriff auf das YouTube-API ermöglicht. Aktuell liegt pytube in der Version 12.1.2 vor und lässt sich wie folgt installieren [9]:

pip install pytube

Bei Bots können Sie abgesehen von Listen Dateien einlesen und durch diese zeilenweise iterieren, um aus dem aktuellen Link ein youtube-Objekt zu generieren. In der Regel verfügt ein YouTube-Link über etliche Streams, sodass es empfehlenswert ist, die Auswahl weiter einzuschränken. So lässt sich angeben, dass beispielsweise lediglich Streams mit der Dateiendung .mp4 heruntergeladen werden. Die Einstellung progressive=true sorgt dafür, dass lediglich Streams ausgewählt werden, die sowohl eine Audio- als auch Videospur beinhalten. Die Anweisung streams.order_by(‚resolution‘) wählt schließlich unter allen noch verfügbaren Streams das Video mit der höchsten Auflösung aus. Abgesehen davon beinhaltet die filter-Methode weitere Parameter, die in Listing 11 jedoch nicht erscheinen [10].

Mit der Downloadmethode stream.download() aus dem YouTube-API wird der ausgewählte Stream heruntergeladen [11]. Dabei lässt sich der Download weiter einstellen, indem auf geeignete Parameter zurückgegriffen wird. So kann zusätzlich der Pfad angegeben werden. Der Timeout-Parameter hingegen gibt an, wie lange auf eine Antwort vom Server gewartet wird. Daneben lässt sich die Zahl der Downloadversuche weiter eingrenzen (Abb. 6).

Listing 11

def download_yt():
  link = open('/home/Videos/chemistry/links_file.txt', mode='r')
  SAVE_PATH = '/home/Videos/chemistry'
  for i in link:
    try:
      youtubeObject = YouTube(i)
      d_video = youtubeObject.streams.filter(progressive=True, file_extension='mp4').order_by('resolution').desc().first()
      print ('Stream: '+d_video)
      print('Downloading video: ' + d_video.title)
      d_video.download(SAVE_PATH, timeout=30, max_retries=3)
    except:
      print("An error has occurred")
  print("Download is completed successfully")

Abb. 6: Download von YouTube-Videos ohne FortschrittsanzeigeAbb. 6: Download von YouTube-Videos ohne Fortschrittsanzeige

Twitter-Bot

Twitter-Seiten erhalten eine Vielzahl an Informationen, angefangen von Tweets, Retweets, Followern, bis hin zu eingebetteten Videos etc. Dabei ermöglicht die Tweepy-Bibliothek 4.13.0 Zugriff auf das Twitter-API, sodass sich damit Etliches an Daten herunterladen lässt [12]. Um Zugriff auf das Twitter-API zu erhalten, brauchen Sie allerdings einen Entwickleraccount von Twitter. Anhand der folgenden Schritte können Sie einen Entwickleraccount bei Twitter beantragen:

  • normales Benutzerkonto auf der Twitter-Seite erstellen [13]
  • Benutzerkonto für Entwickler einrichten und sich für den erweiterten Zugang bewerben [14]
  • Tokens, Secrets sowie Schlüssel generieren

Als Nächstes installieren Sie die Tweepy-Bibliothek:

pip install tweepy

Wenn Twitter Ihren Antrag genehmigt hat, sollten Sie über diverse Tokens, Schlüssel sowie Secrets verfügen. Sie können dafür sorgen, dass die Accountdaten griffbereit sind, indem Sie Ihre Zugangsdaten in einer Konfigurationsdatei speichern. Diese Datei enthält Schlüssel-Wert-Paare und ist unter Python wie in Abbildung 7 aufgebaut. Anschließend speichern Sie die Konfigurationsdatei mit der Dateiendung .cfg ab. Angenommen der Dateiname lautet config.cfg, dann lässt sich die Konfigurationsdatei wie in Listing 12 einlesen. Die Schlüssel-Wert-Paare eines Abschnitts (section) werden dabei in einem Dictionary abgelegt [15].

Listing 12

import configparser
 
def get_config_dict(section):
  config = configparser.RawConfigParser()
  config.read(r'config.cfg')
  if not hasattr(get_config_dict, 'config_dict'):
    get_config_dict.config_dict = dict(config.items(section))
  return get_config_dict.config_dict

Abb. 7: Konfigurationsdatei unter PythonAbb. 7: Konfigurationsdatei unter Python

Um etwas bei Twitter automatisieren zu können, ist es erforderlich, sich erst einmal zu authentifizieren. So liest die Methode create_api zunächst die zuvor generierten Tokens, Schlüssel und Secrets unter Angabe des Abschnitts ein (Listing 13). Die eigentliche Authentifizierung beim Twitter-API erfolgt mittels der Methode api.verify_credentials(), wobei die zurückgegebene API-Variable für weitere Aktionen gebraucht wird [16].

Listing 13

import tweepy as tw
from tweepy import OAuthHandler
 
def create_api():
  config_details = get_config_dict('TWITTER')
  c_key = config_details['c_key']
  c_secret = config_details['c_secret']
  a_token = config_details['a_token']
  a_secret = config_details['a_secret']
  auth = OAuthHandler(c_key, c_secret)
  auth.set_access_token(a_token, a_secret)
  api = tw.API(auth, wait_on_rate_limit=True)
  try:
    api.verify_credentials()
  except Exception as e:
    print("Error creating API")
    raise e
  print("API created")
  return api

Bei Datenanalysen ist es relevant, Tweets zu einem bestimmten Hashtag zu analysieren. Die Suchergebnisse lassen sich dabei in einem Dataframe ablegen, um hinterher in der Lage zu sein, große Datenmengen zu analysieren. Bei dieser Suche erfolgt die Authentifizierung mit dem Bearer-Token (Inhabertoken), das ebenfalls über die Konfigurationsdatei abrufbar ist. So nimmt die Methode get_tweets zunächst die Authentifizierung vor (Listing 14). Danach sucht die Methode client.search_recent_tweets nach Tweets der letzten sieben Tage, indem zuvor die Suchanfrage query definiert wird. Die Suchanfrage beinhaltet neben dem Hashtag auch die Sprache der in Frage kommenden Tweets, wobei die Suche lediglich aus #<Suchwort> bestehen kann. Dabei wird die maximale Zahl an Suchergebnissen auf 100 begrenzt. Außerdem lässt sich in der Suchmethode client.search_recent_tweets definieren, welche Felder eines Tweets gespeichert werden (Abb. 8). Zusätzlich lassen sich die Tweets in einem Dataframe ablegen [17].

Listing 14

def get_tweets(search):
  config_details = get_config_dict('TWITTER')
  token = config_details['b_token']
  client = tw.Client(bearer_token=token)
  # What to search for
  query = '#'+search+' -is:retweet lang:de'
  max_results = 100
  # We grab tweets + context annotations + create date + user name of tweeter
  tweets = client.search_recent_tweets(query=query, tweet_fields=['context_annotations', 'created_at'], user_fields=['name'], expansions='author_id', max_results=max_results)
  for tweet in tweets.data:
    print(tweet.text)
    if len(tweet.context_annotations) > 0:
      print(tweet.context_annotations)
  # Return the tweet data as a dataframe
  df2 = pd.DataFrame(tweets.data).astype(str)
  # Collect the user IDs
  df_users = pd.DataFrame(tweets.includes['users'])

Abb. 8: Tweets zum Hashtag #Mavropanos zusammen mit AnnotationenAbb. 8: Tweets zum Hashtag #Mavropanos zusammen mit Annotationen

Abgesehen von Tweets basierend auf Hashtags ist es möglich, alle möglichen Tweets eines Twitter-Accounts herunterzuladen. Hierfür wird angenommen, dass die Tweet-ID eines Tweets zusammen mit weiteren Kategorien wie Timestamp, Text etc. in einer CSV-Datei gespeichert ist (Abb. 9). Dieser Twitter-Bot ist dann in der Lage, die Tweets von einem Account anhand der ID zu identifizieren und herunterzuladen.

Abb. 9: CSV-Datei zusammen mit den Tweet-IDs eines Twitter-AccountsAbb. 9: CSV-Datei zusammen mit den Tweet-IDs eines Twitter-Accounts

Die selbstdefinierte Methode save_data_to_file ermöglicht unter Angabe des API, dem Pfad zum Twitter-Archiv sowie dem Zielpfad für die Tweets das Herunterladen von Tweets zu automatisieren (Listing 15). Nach der Authentifizierung wird zunächst ein Twitter-Archiv namens twitter-archive-enhanced.csv eingelesen. Anschließend werden vom zuvor erzeugten Dataframe die Tweet-IDs extrahiert und in einer Liste abgelegt. Durch diese Liste wird anschließend iteriert, wobei pro Tweet eine JSON-Datei heruntergeladen wird. Die heruntergeladenen JSON-Dateien landen später alle in der Textdatei tweet_json.txt. Anhand einer selbstdefinierten Fortschrittsanzeige weiß der Benutzer sofort Bescheid, welche Tweets sich herunterladen lassen und bei welchen Tweets der Download scheitert (Abb. 10). Besteht der Download hingegen aus mehreren Tausend Tweets, kommt das Rate Limit von Twitter zum Tragen und der Download wird in bestimmten Zeitintervallen für einige Minuten unterbrochen. Dadurch wird der Download einer großen Menge Tweets in die Länge gezogen.

Listing 15

import tweepy as tw
from tweepy import OAuthHandler
import json
from timeit import default_timer as timer
import pandas as pd
 
def save_data_to_file(api, sFrom, sTo):
  df = pd.read_csv(sFrom)
  tweet_ids = df.tweet_id.values
  len(tweet_ids)
  count = 0
  fails_dict = {}
  start = timer()
  # Save each tweet's returned JSON as a new line in a .txt file
  with open(sTo, 'w') as outfile:
    # This loop will likely take 20-30 minutes to run because of Twitter's rate limit
    for tweet_id in tweet_ids:
      count += 1
      print(str(count) + ": " + str(tweet_id))
      try:
        tweet = api.get_status(tweet_id, tweet_mode='extended')
        print("Success")
        json.dump(tweet._json, outfile)
        outfile.write('\n')
      except tw.errors.TweepyException as e:
        print("Fail")
        fails_dict[tweet_id] = e
        pass
  end = timer()
  print(end - start)
  print(fails_dict)

Abb. 10: Download von TweetsAbb. 10: Download von Tweets

minosi_anzela_sw.tif_fmt1.jpgAbgesehen vom Schreiben bietet Anzela Minosi Dienstleistungen auf Legiit.com an. Dort erstellt Anzela Datenanalysen, Datenbanksoftware, Python-Skripte sowie Kommandozeilentools für den Raspberry Pi. Bevor Anzela sich selbständig gemacht hat, war sie zehn Jahre lang in den Automobil-, Bildungs- und Telekommunikationsbranchen tätig, wo sie diverse IT-Tätigkeiten ausübte: Support, Softwaretests sowie Webentwicklung. Anzela verbringt ihre Freizeit gerne an der ligurischen Küste, fährt Fahrrad und spielt Retrospiele auf dem Raspberry Pi. Sie steht für Redaktionsprojekte sowie für persönliche Beratungsgespräche zur Verfügung.

Links & Literatur

[1] PyPi: https://pypi.org/

[2] Requests: https://www.geeksforgeeks.org/get-post-requests-using-python/

[3] Datenvolumen: https://firstsiteguide.com/big-data-stats/

[4] Extract Social Media: https://pypi.org/project/extract-social-media/

[5] Requests: https://requests.readthedocs.io/en/latest/user/quickstart/

[6] Vimeo: https://pypi.org/project/vimeo-downloader/

[7] Sonderzeichen in Strings: https://www.scaler.com/topics/remove-special-characters-from-string-python/

[8] Vimeo-Download: https://jakeroid.com/blog/how-to-download-vimeo-video-using-python/

[9] pytube: https://pytube.io/en/latest/

[10] Streams: https://pytube.io/en/latest/user/streams.html

[11] YouTube-Download: https://www.geeksforgeeks.org/pytube-python-library-download-youtube-videos/

[12] Tweepy: https://pypi.org/project/tweepy/

[13] Twitter: https://twitter.com

[14] Entwicklerportal: https://developer.twitter.com/en/support/twitter-api/developer-account

[15] Konfigurationsdatei: https://stackoverflow.com/questions/19379120/how-to-read-a-config-file-using-python

[16] Twitter-Bot: https://realpython.com/twitter-bot-python-tweepy

[17] Tweet-Suche: https://towardsdatascience.com/how-to-access-data-from-the-twitter-api-using-tweepy-python-e2d9e4d54978




Mobile App Entwicklung im Überblick

Es scheint fast für jeden etwas dabei zu sein. So oder ähnlich kann man die Situation beschreiben, wenn es um Technologien, Frameworks und Vorgehensweisen zur Entwicklung von Mobile-Apps geht. Wir haben versucht, den Überblick zu behalten.

Redet man über die Entwicklung von Apps für mobile Systeme, dann meint man damit die Entwicklung für Android und iOS. Doch wie kommt man zu einer solchen App? Hier können wir gleich am Anfang des Artikels festhalten, dass es für dieses Ziel eine sehr große Anzahl sehr unterschiedlicher Technologien und Vorgehensweisen gibt. Wir möchten in diesem Artikel einen Überblick über die verschiedenen Möglichkeiten geben und dabei versuchen, diese unterschiedlichen Ansätze nach bestimmten Kriterien zu systematisieren. Starten wir zunächst mit den Kriterien.

Systematik der Entwicklungsansätze

Bei der Entwicklung von Mobile-Apps kann man unterschiedliche Kriterien für die Systematisierung heranziehen. Nach den folgenden Kriterien ist eine Einteilung möglich

  • nach der Art der entstehenden App, d. h. nativ, webbasiert oder hybrid
  • nach der verwendeten Programmiersprache und den zum Einsatz kommenden Framework
  • nach dem Umfang der Toolunterstützung, d. h. einer codebasierten Erstellung oder dem Einsatz von RAD- oder Low-Code-Tools
  • nach dem Zielsystem oder ob mit Cross-Platform- bzw. Cross-Device-Programmierung mehrere Systeme aus einer gemeinsamen Quellcodebasis angesprochen werden

Weitere Kategorien sind sicherlich denkbar, die meisten relevanten Ansätze dürften sich jedoch hierunter subsumieren lassen. Die folgenden Textabschnitte stellen einige interessante Ansätze vor und versuchen sie entsprechend einzuordnen. Das wird mit Blick auf die Kriterien nicht immer überschneidungsfrei gelingen, dennoch dürfte auf diese Weise ein umfassender Überblick über die aktuellen Technologien zur Entwicklung von mobilen Apps entstehen. Starten wir mit dem naheliegendsten Kriterium, der Art der entstehenden App.

Native Apps

„Entscheidend ist, was hinten rauskommt“, meinte der ehemalige Bundeskanzler Helmut Kohl – zwar in einem vollständig anderen Zusammenhang, wir können diesen Ausspruch jedoch auch auf den Bereich der Entwicklung von Mobile-Apps übertragen. Man kann technologisch grundsätzlich drei Arten von Apps unterscheiden: native Apps, Web-Apps und hybride Apps.

Native Apps sind die Applikationen, deren Programmcode unmittelbar durch das Betriebssystem auf dem mobilen Gerät ausgeführt wird. Sie laufen in keinem Browser, sondern greifen direkt auf die Programmierschnittstellen von Android und iOS zurück. Mit dem direkten Zugriff auf die APIs der Systeme ist eine vollumfängliche Nutzung aller Geräte- und Hardwarefunktionen möglich. Beispielsweise kann man alle Sensoren ohne Einschränkungen verwenden. Auch die Ausführungsgeschwindigkeit der App ist die bestmögliche. Native Apps können offline betrieben werden und man kann sie über die App-Stores vertreiben. Wie gelangt man zu diesen nativen Apps? Als Erstes sind hier die von den Herstellern der Betriebssysteme vorgesehenen Vorgehensweisen und Tools zu nennen. Für Android ist es die Entwicklungsumgebung Android Studio [1] mit der Programmiersprache Kotlin bzw. Java (Abb. 1).

Abb. 1: Projektassistent von Android Studio für die Entwicklung von nativen Android-AppsAbb. 1: Projektassistent von Android Studio für die Entwicklung von nativen Android-Apps

Die Auswahl der Programmiersprache können Sie beim Anlegen eines Projekts treffen. Beide Sprachen können jedoch auch gemischt genutzt werden. In der Entwicklungsumgebung steht u. a. ein Designer zur Verfügung, um die Oberfläche zu gestalten. Mit diesem Entwicklungsansatz können Sie auch für alle anderen Formfaktoren von Android-Devices entwickeln, beispielsweise für Android TV, Android for Car und verwandte Betriebssysteme wie Wear OS (Smartwatch) und Chrome OS. Um direkt gegen das Android SDK zu programmieren, könnte man auch eine andere Entwicklungsumgebung verwenden, die meisten Entwickler:innen werden jedoch Android Studio einsetzen.

Kommen wir zu iOS. Hier wird Xcode [2] als integrierte Entwicklungsumgebung benutzt. Diese läuft ausschließlich auf macOS und man benötigt daher zwingend einen Mac. Apple erlaubt keine andere Vorgehensweise, um das App-Package final für das Zielsystem zu erstellen. Während der Entwicklung kann man sich ggf. mit Hilfe eines in die Cloud ausgelagerten Remote-Apple behelfen. Schauen Sie sich dabei beispielsweise die Optionen unter [3] an. Hier können Sie einen Mac mieten und mit Xcode arbeiten. Auch das Ausführen eines Simulators ist möglich, der Bildschirm wird dazu über das Netzwerk auf den eigenen Entwicklungsrechner projiziert. Dieses Vorgehen dürfte interessant sein, wenn man mit Hilfe eines anderen Ansatzes (Cross-Platform, hybrid) eine App für iOS erstellen möchte und sich dabei die Anschaffung von mehreren Macs (bei Teamarbeit) wirtschaftlich nicht lohnt. Zurück zur iOS-Entwicklung: Als Programmiersprache wird Swift verwendet. Das User Interface wird in SwiftUI deklarativ im Quellcode erstellt. Swift und SwiftUI haben Objective-C und die Nutzung von Storyboards für das Erstellen der Benutzeroberfläche weitgehend abgelöst. Die Live-Preview in Xcode zeigt bereits während des Entwurfs und der Entwicklung eine Vorschau des User Interface an und beschleunigt damit die Entwicklung maßgeblich (Abb. 2).

Abb. 2: Live-Preview von Xcode zur Entwicklung von nativen iOS-AppsAbb. 2: Live-Preview von Xcode zur Entwicklung von nativen iOS-Apps

Es besteht kein Zweifel: Wenn Sie alle Features von Android und iOS nutzen bzw. die neuste Hardware adressieren und keinerlei Kompromisse bei der Gestaltung des User Interface machen wollen, dann bleibt Ihnen nur die Entwicklung mit diesen beiden von den Herstellern Google und Apple vorgeschlagenen Ansätzen. Will man beide Plattformen bedienen, dann muss die App zweifach programmiert werden. Es ist ein nahezu doppelter Aufwand, denn der Quellcode lässt sich zwischen den Systemen kaum teilen, wenn man von einigen übergreifenden Konzepten absieht. Hinzu kommt der Umstand, dass Android-Entwickler:innen meistens nicht über ausreichende Kennnisse in der nativen iOS-Entwicklung verfügen und umgekehrt. Man braucht daher auch zwei Teams.

Auch wenn man sich für die Cross-Platform- oder hybride Entwicklung von Apps entscheidet, ist es hilfreich, mit den nativen Ansätzen zu experimentieren. Gelegentlich hackelt es bei der Cross-Platform-Programmierung, man muss einige plattformspezifische Einstellungen vornehmen oder man muss Plattformcode schreiben, um beispielsweise einen Sensor zu erreichen. Das geht dann nur mit Hilfe von Kenntnissen der APIs der Plattformen. Es ist auf jeden Fall nützlich, sich Wissen über iOS und Android anzueignen.

Cross-Platform- und Cross-Device-Programmierung

Es ist ein ewiger Entwicklerwunsch: „Write once, run anywhere (WORA)“. Cross-Platform-Programmierung verfolgt das Ziel, eine App zu erstellen, die auf unterschiedlichen Betriebssystemen, zum Beispiel Android und iOS, ausgeführt werden kann. Cross-Device-Programmierung geht dabei noch einen Schritt weiter. Wir adressieren hier nicht nur unterschiedliche Betriebssysteme, sondern auch verschiedene Formfaktoren der Geräte, beispielsweise Smartphone, Tablet, PC, Notebook, TV usw. Nach dieser Lesart ist die Cross-Platform-Programmierung also ein Teil der Cross-Device-Entwicklung.

Unter Cross-Platform-Programmierung verstehen wir Ansätze mit dem Ziel, Apps für Android und iOS aus einer gemeinsamen Quellcodebasis zu generieren. Die entstehenden Apps sollten dabei im Ergebnis einer nativen App möglichst nahekommen. Web-Apps bzw. hybride Apps, die wir bei einer weitergehenden Betrachtung auch als Cross-Platform bezeichnen könnten, meinen wir damit explizit nicht. Sehen wir uns einige Möglichkeiten an.

Ein neuerer Ansatz ist .NET MAUI von Microsoft [4]. Man erreicht damit die Systeme iOS, Android, Windows und macOS und über den Herstellersupport von Samsung auch Tizen. Entwickelt wird in der Programmiersprache C# und das User Interface wird mittels XAML deklarativ erstellt. Das funktioniert für alle Zielsysteme, wobei plattformspezifische Anpassungen möglich sind. Zur Gestaltung der Oberfläche stehen visuelle Controls zur Verfügung, die während des Kompilierens auf die User-Interface-Elemente der Zielsysteme gemappt werden. Statt eines grafischen Designers wird mit dem Hot-Reload-Feature gearbeitet, d. h., Änderungen am Quellcode werden während der Entwicklung direkt in die laufende App übernommen (Abb. 3). .NET MAUI ist der Nachfolger des Frameworks Xamarin [5] und eine Migration für bestehende und weiterzuführende Projekte sollte geprüft werden.

Abb. 3: Hot Reload beschleunigt die Entwicklung von MAUI-Apps (Visual Studio for macOS)Abb. 3: Hot Reload beschleunigt die Entwicklung von MAUI-Apps (Visual Studio for macOS)

Aus dem Hause Google stammt der Ansatz Flutter [6]. Mit Flutter kann man Apps für nahezu alle relevanten Clientsysteme erstellen, d. h. für Desktop, Mobile und Web und damit auch Apps für Android und iOS. Als Programmiersprache wird Dart verwendet. Diese Sprache wurde ebenfalls von Google entwickelt. Es entstehen native Apps. Das Rendering des User Interface erfolgt über die C++-2D-Rendering-Engine Skia, die u. a. auch in Google Chrome, Mozilla Firefox und anderen Programmen verwendet wird. Flutter zeichnet die User-Interface-Controls eigenständig und stellt dazu vielfältige Widgets zur Implementierung der Benutzeroberfläche zur Verfügung. Die Entwicklung mit dem Flutter SDK kann auf unterschiedlichen Systemen durchgeführt werden (Windows, macOS, Linux, Chrome OS). Auch bei der Wahl des Editors bzw. der Entwicklungsumgebung ist man flexibel, beispielsweise Android Studio (bevorzugt) oder Visual Studio Code.

Mit RAD Studio [7] gibt es einen weiteren Ansatz, um eine App für mehrere Plattformen zu erstellen. Programmiert wird in der Sprache Delphi (Object Pascal) und man kann Anwendungen für die Systeme Windows, macOS, Linux, iOS und Android erstellen. Die Entwicklungsumgebung bietet einen grafischen Designer, in der die Benutzeroberfläche der App vollständig aus visuellen Controls zusammengesetzt wird (Abb. 4).

Abb. 4: Grafischer Designer in RAD Studio zur UI-Erstellung mit FireMonkeyAbb. 4: Grafischer Designer in RAD Studio zur UI-Erstellung mit FireMonkey

Nichtvisuelle Controls stehen beispielsweise für den Zugriff auf Gerätefunktionen (z. B. Dateisystem) oder Sensoren (z. B. Bluetooth) zur Verfügung. Es handelt sich um native Apps, die direkt auf den Zielsystemen ausgeführt und in die App Stores eingestellt werden können. RAD Studio nutzt für das Rendering des UI das geräteübergreifende Grafikframework Fire Monkey, das die systemeigenen Grafikbibliotheken der Zielsysteme verwendet. Die besprochenen und weitere Ansätze zur geräte- und plattformübergreifenden Entwicklung haben wir für Sie in Tabelle 1 zusammengefasst.

Ansatz/IDE Zielsystem Programmier-sprache/Framework Betriebssystem Hersteller Hinweise Link
Android Studio Android Kotlin Java Windows Linux macOS Google der direkte und offizielle Weg zu einer nativen App für Android Zugriff auf alle APIs der Plattform keine Einschränkungen bei den Features und den Kompatibilitäten https://developer.android.com/studio
Xcode iOS Swift SwiftUI macOS Apple der direkte und offizielle Weg zu einer nativen App für iOS Zugriff auf alle APIs der Plattform keine Einschränkungen bei den Features und den Kompatibilitäten https://developer.apple.com/xcode/
.NET MAUI/ Visual Studio iOS Android Windows (WinUI 3) macOS (Tizen) C# XAML .NET-Plattform Windows macOS Microsoft native Apps für die genannten Zielsysteme aus einer gemeinsamen Quellcodebasis UI wird deklarativ mittels XAML erstellt Plattformanpassungen und plattformspezifischer Code sind möglich Einsatz von Controls für das UI Nachfolger von Xamarin. https://dotnet.microsoft.com/en-us/apps/maui
Xamarin iOS Android Windows (UWP) (Tizen macOS 10.13 GTK# WPF) C# XAML .NET-Plattform Windows macOS Microsoft gemeint ist hier die Entwicklung mit Xamarin.Forms technologischer Vorgänger von .NET MAUI, wird aktuell noch gepflegt eine Migration nach .NET MAUI ist zu prüfen https://dotnet.microsoft.com/en-us/apps/xamarin
RAD Studio iOS Android Windows macOS Linux Delphi Windows Embarcadero RAD-Tool zur Entwicklung von App für unterschiedliche Zielsysteme, u. a. Mobile-Apps für iOS und Android grafischer Designer, in dem mittels Definition von Eigenschaften die Benutzeroberfläche erstellt wird https://www.embarcadero.com/products/rad-studio
Flutter iOS Android Windows macOS Linux Web Dart Windows macOS Linux Chrome OS Google geräteübergreifende Programmierung von Apps für diverse Clientbetriebssysteme und das Web programmiert wird in der Sprache Dart User Interface wird mittels Widgets erstellt und eigenständig gezeichnet https://flutter.dev/
Native-Script iOS Android JavaScript TypeScript Windows macOS Linux ursprünglich Telerik; aktuell Community Erstellung nativer Apps für Android und iOS es werden die APIs der Zielsysteme verwendet Programmierung in JavaScript (TypeScript) User Interface kann mit Hilfe von Komponenten erstellt werden (XML) https://nativescript.org
React-Native iOS Android JavaScript TypeScript React Windows macOS Linux Metaplattform Ermöglicht das Erstellen von mobilen Apps für Android und iOS mit Hilfe des React-Frameworks https://reactnative.dev

Tabelle 1: Übersicht über Ansätze zur geräte- bzw. plattformübergreifenden Entwicklung

Web-Apps

Nicht immer ist es notwendig oder sinnvoll, eine native App zu erstellen. Der Anteil der Nutzer:innen mit mobilen Endgeräten nimmt stetig zu. Gerade in der jüngeren Generation wird heute bereits ein Großteil der Aufgaben im Internet über das Smartphone erledigt. Wird eine Webseite bzw. -applikation für die Nutzung auf einem Smartphone optimiert (Mobile First), dann handelt es sich um eine Web-App. Eine solche Web-App läuft direkt im Browser und wird nicht installiert. Nach der Eingabe der Internetadresse ist sie sofort nutzbar. Da die Applikation auf dem Server läuft, ist eine ständige Datenverbindung notwendig. Dadurch, dass keine Installation auf dem Zielgerät notwendig ist, können diese Apps auch nicht in den Store eingestellt werden. Auch wollen Nutzer:innen diese oftmals ad hoc verwenden, zum Beispiel, um einmalig ein Ticket zu buchen, und eine Installation einer App aus dem Store ist daher nicht zumutbar.

Technisch betrachtet sind Web-Apps meist clientseitige Single-Page Applications. Für eine gute Reaktionsfähigkeit und eine bestmögliche User Experience werden diese typischerweise mit den bekannten Webframeworks Angular, Vue oder React entwickelt. Im Kern basieren sie technisch auf HTML (Struktur), CSS (Layout, Design) und JavaScript (Interaktion). Durch die Verwendung von auf die mobile Nutzung ausgerichteten UI-Bibliotheken kann man das Design der Web-Apps weitgehend identisch zu einer nativen App gestalten. Ein Beispiel für eine solche UI-Bibliothek ist OnsenUI [8]. OnsenUI bietet einen umfassenden Satz von grafisch reichhaltigen UI-Komponenten, die speziell für Mobile-Apps entwickelt wurden. Diese orientieren sich an den Design-Guidelines von Android und iOS. Das Framework steht unter einer Open-Source-Lizenz (Apache v2) zur Verfügung und kann mit Vue, Angular und React eingesetzt werden.

Beispiele zeigen, dass man mit Web-Apps heute das Feeling (Design, Handling) von nativen Apps nahezu erreicht. Abbildung 5 zeigt die Ansicht einer Wetter-App, die mit Onsen UI gestaltet wurde [9].

Abb. 5: Wetter-App mit Onsen UIAbb. 5: Wetter-App mit Onsen UI

Da es sich um eine Webapplikation handelt, kann man diese direkt im Browser eines jeden beliebigen Systems aufrufen und starten. Interessant ist dabei auch, dass man das Design an iOS bzw. Android anpassen kann. Wie man sieht, kommt man dem nativen App-Erlebnis hier sehr nahe. Eine Alternative für die Optimierung des UI für die mobile Nutzung bietet auch das Framework Ionic [10], das mit React, Vue und Angular kombiniert werden kann. Ein Beispiel für eine mobile App mit einem Kalender-Control, implementiert mit Ionic und Angular, zeigt Abbildung 6.

Abb. 6: App mit Kalender-Control mit dem Ionic-FrameworkAbb. 6: App mit Kalender-Control mit dem Ionic-Framework

Diese UI-Frameworks bieten Lösungen für die Gestaltung der mobilen App. Da diese direkt im Browser des Systems läuft, sind Geräte- und Systemzugriffe zunächst auf das beschränkt, was über den Browser unmittelbar möglich ist. Das JavaScript API zur Ortung kann beispielsweise ohne Einschränkungen genutzt werden. Dagegen ist ein Zugriff auf die Sensoren eines Smartphones eingeschränkt bzw. nahezu ausgeschlossen.

Hier kommen wir jedoch zu zwei weiteren wichtigen Erweiterungsmöglichkeiten. Eine Web-App, die auf einen mobilen Device ausgeführt wird, kann mit Hilfe einer Progressive Web App (PWA) und mittels eines nativen App-Containers (hybride App) erweitert werden. Mit einer PWA ist es möglich, eine Web-App auch mit Offlinefunktionalität auszustatten. Ebenso kann man für eine solche PWA auch ein Icon auf den Home Screen des mobilen Geräts ablegen. Bei einer hybriden App wird die Web-App im internen Browser einer nativen App verpackt und erhält dann über diesen Zugriff auf die Geräte- und Hardwarefeatures des Smartphones oder Tablets. Wir werden im kommenden Abschnitt noch genauer auf diese Technologie eingehen.

Üblicherweise entwickelt man Web-Apps mit Webtechnologien und -frameworks. Es gibt jedoch auch zu dieser Vorgehensweise Alternativen. An Entwickler:innen, die in der Java-Welt zu Hause sind, richtet sich Vaadin [11]. Mit Hilfe dieses Frameworks kann man eine Web-App mit Java-Quellcode generieren, ohne direkt HTML, CSS und JavaScript schreiben zu müssen. Es steht auch hier eine Reihe von UI-Komponenten zur Verfügung, um die Benutzeroberfläche zu gestalten.

Vaadin kann man auch innerhalb der integrierten Entwicklungsumgebung RapidClipse [12] nutzen. Rapid-Clipse ist eine IDE, die auf Eclipse aufsetzt und u. a. einen grafischen Designer für Vaadin bietet, mit dem man die Benutzeroberflächen intuitiv gestalten kann. Für die Entwicklung von Mobile-Apps (Mobile First) gibt es ein nützliches Feature: Durch einen Button im GUI Builder wird die erstellte Seite lokal gehostet. Für diese Seite wird dann ein QR-Code generiert, den man mit dem Smartphone einscannen kann (Abb. 7). Die Seite wird anschließend direkt auf dem mobilen Gerät angezeigt. Voraussetzung ist, dass sich der Entwicklungsrechner mit dem lokalen Server und das Smartphone im gleichen lokalen Netzwerk befinden.

Abb. 7: Web-App mit RapidClipse und Vaadin erstellen und für mobile Geräte testenAbb. 7: Web-App mit RapidClipse und Vaadin erstellen und für mobile Geräte testen

Hybride Apps

Das Prinzip ist einfach: Wir haben einen nativen App-Container, der auf das jeweilige Zielsystem angepasst ist. Innerhalb dieses Containers läuft ein Browser (ohne Möglichkeit der Änderung des URL), in dem die Web-App ausgeführt wird. Für das Erstellen der App steht das gesamte Spektrum der bisher behandelten Technologien zur Verfügung. Der Container fungiert als Schnittstelle zwischen den APIs des betreffenden Betriebssystems und der App und ermöglicht dieser einen Zugriff auf die Gerätefunktionen und Sensoren des mobilen Geräts. Das bekannteste Framework ist Cordova [13]. Die Architektur ist in Abbildung 8 zu sehen.

Abb. 8: Die Architektur von Cordova für hybride AppsAbb. 8: Die Architektur von Cordova für hybride Apps

Über Plug-ins wird der Zugriff auf die Systemfunktionen sichergestellt. Hybride Apps erscheinen für das System wie eine native Anwendung – sie können offline genutzt werden und man kann sie in den App Stores einstellen. Das Prinzip der hybriden App ist also eine interne Web-View, die eine Webapplikation anzeigt.

Auch hier gibt es weitere Technologien zur Umsetzung. Beispielsweise geht das Framework Wisej.NET einen anderen Weg: Hier wird die Webapplikation auf Basis von .NET erstellt und auf einem Webserver gehostet. Als Entwicklungsumgebung kommt Visual Studio zum Einsatz und man arbeitet mit visuellen Komponenten für die Gestaltung der Oberfläche. Das Framework übersetzt die .NET-Applikation, die auf dem Server läuft, in eine Single-Page App. Diese kann wiederum in einer Web-View auf den mobilen Geräten angezeigt werden. Dazu bietet der Hersteller vorbereitete native Apps, die man individuell an die Web-App anpassen kann. Gleichwohl sorgt diese Web-View mittels Wisej Mobile dafür, dass man aus der Web-App Zugriff auf das API des mobilen Geräts hat. Die Besonderheit dieses Ansatzes liegt in der Erstellung von Web-Apps. Dazu kann, vergleichbar mit Windows Forms, in Visual Studio per Drag and Drop die Oberfläche mit einem grafischen Designer konfiguriert werden. Die Businesslogik wird in C# programmiert, wodurch es nicht notwendig ist, HTML-, CSS- und Java-Script-Code zu schreiben. Informationen zum Erstellen von Mobile-Apps mit Wisej.NET finden Sie unter [14].

Weitere Ansätze

Haben wir jetzt alle Vorgehensweisen zum Erstellen einer Mobile-App beisammen? Zumindest die grundsätzlichen Ansätze zur Entwicklung von nativen, webbasierten und hybriden Apps, einschließlich der plattformübergreifenden Entwicklung haben wir aufgeführt. Dennoch gibt es weitere Technologien. Daher folgen in diesem Abschnitt einige weitere Tools, mit deren Hilfe man ebenfalls zu einer Mobile-App kommt:

  • Qt: Das plattformübergreifende GUI-Toolkit kann auch dazu genutzt werden, Mobile-Apps für Android und iOS zu erstellen. Die Programmlogik wird in C++ erstellt und das User Interface mittels QML deklariert. Ein grafischer Designer (Qt-Creator) erleichtert die Gestaltung. Informationen für den Einstieg findet man in der Dokumentation [15].
  • Xojo: Hierbei handelt es sich um ein RAD-Tool zur Anwendungsentwicklung für sehr unterschiedliche Zielsysteme. Der Schwerpunkt liegt auf einer beschleunigten Entwicklung mit Hilfe eines grafischen Designers und vorgefertigten Komponenten. Die Businesslogik wird mit Hilfe eines Basic-Dialektes erstellt. Gemäß der Dokumentation kann man mit Hilfe von Xojo auch Apps für iOS erstellen. Android-Apps stehen auf der Roadmap für künftige Releases der Entwicklungsumgebung. Informationen zur App-Entwicklung für iOS findet man in der Onlinedokumentation unter [16].
  • NativeScript [17]: Ein Open-Source-Framework zum Entwickeln von Apps für iOS und Android. Als Programmiersprachen werden JavaScript und TypeScript eingesetzt. Der Einsatz von Frameworks wie Vue oder Angular wird unterstützt. Es entstehen native Apps, die die APIs von Android und iOS verwenden. Zur Gestaltung des User Interface gibt es entsprechende Komponenten. Die Oberfläche kann in XML oder in JavaScript bzw. TypeScript kodiert werden.
  • React Native [18]: React Native setzt auf React auf und erlaubt, native Apps mit JavaScript für Android und iOS zu realisieren.
  • Kotlin Multiplatform [19]: Dieses Framework geht einen anderen Weg. Im Fokus der Cross-Platform-Entwicklung steht die Entwicklung der Geschäftslogik mit Hilfe einer gemeinsamen Codebasis. Und das UI? Das wird weiterhin nativ mit den Technologien von Android und iOS erstellt. Als Programmiersprache kommt Kotlin zum Einsatz, als Entwicklungsumgebung Android Studio.

Wenn Sie im Netz suchen, werden Sie noch weitere, durchaus interessante Ansätze finden, um eine mobile App für Android oder iOS zu erstellen. Nicht erwähnt haben wir hier Low-Code-Tools. Auch mit diesen Entwicklungstools kommt man bei speziellen Anforderungen ans Ziel. Für alle webbasierten Technologien gilt: Packt man die Web-App in einen nativen Container, kann man sie über die hybride Technologie dem nativen App-Feeling deutlich näherbringen.

Fazit

Lassen wir diesen Artikel mit einem allgemein bekannten Spruch ausklingen: „Wer die Wahl hat, hat die Qual“. Das gilt in der Tat für die Auswahl der Entwicklungsansätze für das Mobile-App-Development. Doch diese Qual hat einen großen Vorteil: Entwickler:innen können sich den Technologiestack heraussuchen, der den eigenen Kenntnissen in Sachen Programmiersprache, Framework und bisherigen Erfahrungen am nächsten kommt. An erster Stelle der Auswahl wird i. d. R. der geforderte Funktionsumfang der App stehen. Ebenso ist es wichtig, wie oft man eine App für die Mobile-Systeme erstellen möchte. Ist es nur ein Nebenprodukt, dann tut es vielleicht eine webbasierte App. Regelmäßige App-Entwickler werden dagegen meist zu den Cross-Platform-Ansätzen greifen, um keine oder nur wenige Einschränkungen auf den Zielsystemen in Kauf nehmen zu müssen.

Muss die bestmögliche Plattformanpassung erreicht werden, dann bleibt nur die Entwicklung mit den Werkzeugen der Hersteller, in diesem Fall separat für iOS und Android. Für die meisten Zwecke dürfte man jedoch mit der Cross-Platform- bzw. hybriden Entwicklung gut bedient sein.

krypczyk_veikko_dr_sw.tif_fmt1.jpgDr. Veikko Krypczyk ist Softwareentwickler, Trainer und Fachautor und u.a. auf die Themen WinUI 3 und .NET MAUI spezialisiert. Sein Wissen gibt er über Fachartikel, Seminare und Workshops gern an Interessierte weiter und steht mit seiner Expertise auch für eine individuelle Unterstützung in Projekten zur Verfügung.

bochkor_elena_sw.tif_fmt1.jpgElena Bochkor arbeitet am Entwurf und Design mobiler Anwendungen und Webseiten. Beginnend bei einer systematischen Nutzerforschung, über visuelle Prototypen bis hin zu einem barrierefreien Design gestaltet sie die User Experience moderner digitaler Produkte.

Trainings und Workshops zu diesen Themen können Sie unter https://wp.larinet.com anfragen und die Agenda vorab einsehen.

Links & Literatur

[1] https://developer.android.com/studio

[2] https://developer.apple.com/xcode/

[3] https://www.macincloud.com

[4] https://dotnet.microsoft.com/en-us/apps/maui

[5] https://dotnet.microsoft.com/en-us/apps/xamarin

[6] https://flutter.dev

[7] https://www.embarcadero.com/products/rad-studio

[8] https://onsen.io

[9] http://argelius.github.io/react-onsenui-redux-weather/demo.html

[10] https://ionicframework.com

[11] https://vaadin.com

[12] https://rapidclipse.com

[13] https://cordova.apache.org

[14] https://docs.wisej.com/mobile

[15] https://www.qt.io/product/mobile-app-development/

[16] https://documentation.xojo.com/getting_started/quickstarts/ios_quickstart.html

[17] https://nativescript.org

[18] https://reactnative.dev

[19] https://kotlinlang.org/lp/mobile/




Ein breiteres Spektrum – komplexe Rendering Patterns

Nachdem im ersten Teil die Grundlagen für ein besseres Verständnis davon gelegt wurden, wie Inhalte im Web geladen und gerendert werden, wenden wir uns nun komplexeren Ansätzen zu.

Im ersten Teil dieser Artikelserie haben wir die Geschichte des Webs und die daraus resultierende Entstehung verschiedener Rendering Patterns betrachtet. Dabei wurde auf die grundlegenden Patterns wie SSG (Static Site Generation), SSR (Serverseitiges Rendering) und CSR (Clientseitiges Rendering) eingegangen. Ihre Vor- und Nachteile wurden unter anderem mit Hilfe der drei Web-Vitals-Metriken TTFB (Time to First Byte), FCP (First Contentful Paint) und TTI (Time to Interactive) unter sechs Schwerpunkten bewertet.

In diesem Teil werden darauf aufbauende Patterns behandelt, die die bisherigen Ansätze verbessern, neue Ideen hinzufügen oder Patterns kombinieren. Zuerst betrachten wir ein Pattern, das das statische Rendering verbessert. Anschließend werden wir uns mit Möglichkeiten beschäftigen, die Nachteile von clientseitig gerenderten Single-Page Applications (SPAs) zu reduzieren.

Incremental Static Regeneration

Incremental Static Regeneration (ISR), oder auch unter der Abkürzung iSSG (Incremental Static Site Generation) bekannt, ist eine erweiterte Variante des statischen Renderings (SSG), das wir bereits aus dem ersten Teil der Serie kennen. Es stellt eine Art Hybrid zwischen statischem und serverseitigem Rendering dar. ISR eliminiert einen der größten Nachteile von SSG: die lineare Skalierung der Build-Zeit mit der Anzahl der Seiten, wodurch in großen Projekten selbst kleinste Anpassungen einer Seite einen langwierigen Build auslösen.

Stattdessen werden die einzelnen Seiten nun inkrementell erzeugt. ISR ermöglicht es, neue Seiten nach dem Build einzubinden oder bestehende Seiten zu aktualisieren, indem die statische Generierung pro Seite zur Laufzeit angestoßen wird. Wird eine Seite angefordert, die zum Zeitpunkt des Build noch nicht generiert wurde, wird bei der klassischen statischen Variante in der Regel eine Fehlerseite mit HTTP404 (Not Found) zurückgegeben. Bei ISR hingegen wird die Generierung der Webseite bei der ersten Anfrage an den Server angestoßen (Abb. 1). Während des Generierungsprozesses sieht der Nutzer eine Ladeanimation oder einen Platzhalter. Da die Inhalte statisch sind, ist der Generierungsprozess sehr schnell. Sobald die Generierung abgeschlossen ist, wird die Seite ausgeliefert (geringes TTFB) und gecacht. Da alle Inhalte des HTML-Dokuments bereits gerendert sind, ergibt sich ein schneller FCP und eine direkt interaktive Seite (TTI).

https://phpconference.com/session-qualification/ipc-webentwicklung/?layout=contentareafeed&widgetversion=0&utmtrackerversion=1&seriesId=XWrH7HpMZMrnZNHid

Bei der Generierung wird ein Zeitstempel hinterlegt, der angibt, wie lange die Seite aktuell ist. Jede weitere Anfrage an diese zuvor unbekannte Seite wird nun aus dem serverseitigen Cache mit der soeben generierten Seite beantwortet. Um die Aktualität der Inhalte zu gewährleisten, wird nach einer definierten Zeit (Zeitstempel plus konfigurierbarer Zeitraum) ab der nächsten Anfrage die Aktualität der Seite validiert. Dabei sollte der Zeitraum sinnvoll gewählt werden. Als Faustregel gilt: Je öfter sich der Inhalt der Seite ändert, desto kürzer sollte das Intervall sein. Ist die Seite im Cache nicht mehr aktuell ist, weil sich der Inhalt geändert hat, wird im Hintergrund eine Generierung der Seite angestoßen. In der Zwischenzeit werden Anfragen weiterhin aus dem Cache mit den veralteten Daten bedient. Dieser Ansatz wird als Stale-while-revalidate [1] bezeichnet. War die Generierung erfolgreich, wird der Cacheeintrag für diese Seite invalidiert und durch die neue Seite ersetzt. Falls ein Fehler aufgetreten ist, können Anfragen immerhin noch mit dem alten Eintrag beantwortet werden.

Abb. 1: Client-Server-Kommunikation von ISR mit Stale-while-revalidate-MechanismusAbb. 1: Client-Server-Kommunikation von ISR mit Stale-while-revalidate-Mechanismus

Neben dieser passiven Art der Revalidierung existiert auch ein aktiver Ansatz. Dieser wird als On-Demand Revalidation bezeichnet. Damit wird es ermöglicht, den Cache manuell zu revalidieren. Dazu kann z. B. nach der Aktualisierung des Inhalts einer Webseite in einem CMS ein serverseitiges API aufgerufen werden, das die Revalidierung anstößt. Diese Variante sollte verwendet werden, wenn sich der Inhalt nur von Zeit zu Zeit ändert. Selbstverständlich können beide Varianten auch kombiniert eingesetzt werden.

Das inkrementelle Vorgehen hat den Vorteil, dass zur Build-Zeit nicht alle Seiten auf einmal gebaut werden müssen und Anpassungen schneller produktiv sind. Das soll am Beispiel einer E-Commerce-Webseite mit 100 000 Produkten demonstriert werden. Wir nehmen an, dass die Generierung einer Produktseite im optimistischen Mittel lediglich zehn Millisekunden dauert. Ein kompletter Build würde also knapp 17 Minuten in Anspruch nehmen. Für eine Webseite, die schnell auf Preisanpassungen reagieren muss, könnte das bereits zu lang sein. Eine Lösung wäre, nur die 1 000 populärsten Produkte zum Zeitpunkt des Build mit ISR zu generieren. Die restlichen 99 000 Produkte können just in time erzeugt werden, sobald sie angefordert werden. Das würde die Build-Zeit auf wenige Sekunden reduzieren.

Ein weiterer Vorteil von ISR ist die Persistierung der Seiten zwischen verschiedenen Deployments. Das bedeutet, dass es möglich ist, sofort ein Rollback durchzuführen, ohne die generierten Seiten auszutauschen. Beispielsweise könnte nach dem Deployment mit der ID A auf der Webseite in Version 1 ein Tippfehler festgestellt werden. Dieser wird im CMS korrigiert. Durch die automatische Revalidierung ist kein erneutes Deployment notwendig. Es wird automatisch die aktualisierte Webseite in Version 2 erzeugt und im Cache abgelegt. Anschließend wird ein Feature entwickelt und in einem weiteren Deployment mit ID B bereitgestellt. Dieses Feature ist jedoch fehlerhaft. Daher geschieht ein Rollback zum Deployment A. Obwohl der Schreibfehler zum Zeitpunkt des Deployments A bestand, ist er nach dem Rollback nicht mehr vorhanden, da die Seite mit Version 2 unabhängig vom Deployment persistiert wurde. Dieser Mechanismus kann jedoch auch als Nachteil ausgelegt werden, da damit atomare, unveränderliche (immutable) Deployments nicht mehr garantiert werden können [2].

Ein Problem bei diesem Pattern ist, dass die ausgelieferten Seiten nicht für jeden Nutzer dieselben sind. Aufgrund des Stale-while-revalidate-Mechanismus kann es vorkommen, dass zwischen der Änderung der Webseite, der erneuten Validierung und der Generierung der neuen Version veraltete Inhalte ausgeliefert werden. In Abbildung 1 liefert die zweite Antwort des Servers eine veraltete Version der Seite aus. Erst mit der nächsten Anfrage wird der aktuelle Inhalt ausgeliefert. Das ist nicht optimal für den SEO-Score, tritt jedoch nur bei einem Bruchteil der Anfragen auf. Es hat aber größere negative Auswirkungen auf die User Experience und die Developer Experience, da Nutzer verschiedene Inhalte zu sehen bekommen und somit auch das Debugging erschwert wird. Schließlich erhöht dieses Pattern im Gegensatz zum klassischen statischen Rendering die Komplexität sowohl auf der Seite der Infrastruktur als auch auf der Seite der Developer Experience.

Aus diesen Gründen eignet sich Incremental Static Regeneration nicht für kleine Projekte. Es kann sogar völlig unnötig sein, wenn das Intervall für die Revalidierung größer als die gesamte Build-Zeit ist. Seine Stärken spielt es bei einer großen Anzahl statischer Seiten aus, z. B. bei E-Commerce-Webseiten. Es ist auch denkbar, dass nur stark frequentierte Seiten, wie z. B. Landing Pages, vorgerendert werden und die restlichen Seiten zur Laufzeit generiert werden. Ursprünglich stammt dieses Pattern vom auf React aufsetzenden Metaframework Next.js ab, findet jedoch auch Einzug in weitere Frameworks, wie beispielsweise dem Pendant Nuxt im Vue-Umfeld. Zusammengefasst sehen Sie die Vor- und Nachteile von ISR in Tabelle 1 und Abbildung 2.

Vorteile Nachteile
performant wenig Dynamik
niedrige TTFB keine native User Experience
TTI = FCP erhöhte Komplexität
SEO-freundlich potenziell veraltete Seiten
CDN-fähig (Skalierbarkeit, Cache) keine atomaren Deployments
mit deaktiviertem JS nutzbar
wenige Angriffsvektoren
kürzerer Build-Prozess als SSG
geringerer Ressourcenverbrauch als SSG
Fallback durch Cache
Persistierung der Seiten unabhängig vom Deployment

Tabelle 1: Vor- und Nachteile von ISR

Abb. 2: Bewertung ISR: Developer und User Experience steigen im Vergleich zu SSGAbb. 2: Bewertung ISR: Developer und User Experience steigen im Vergleich zu SSG

Clientseitiges Rendering mit Prerendering

Einer der größten Nachteile von clientseitig gerenderten SPAs ist ihre schlechte SEO-Unterstützung. Das initiale HTML-Dokument enthält nur eine leere Hülle mit einem Einstiegspunkt für die JavaScript-Applikation. Crawler können daher keinen auswertbaren Inhalt finden. Um den SEO-Score zu verbessern, müsste bei der initialen Antwort des Servers mehr interpretierbarer Inhalt im Dokument geliefert werden.

Daher gibt es sogenannte Prerender Services oder -Tools, die als Middleware oder Plug-in in die bestehende Anwendungsinfrastruktur integriert werden können. Sie nutzen den Code der SPA, um daraus eine statische Version zu rendern. Dieses statische HTML wird dann initial ausgeliefert (Abb. 3). Anschließend wird der Java-Script-Code geladen, der die Kontrolle übernimmt.

Abb. 3: Zuerst wird das statische HTML ausgeliefert, anschließend die SPA geladenAbb. 3: Zuerst wird das statische HTML ausgeliefert, anschließend die SPA geladen

Das Prerendering kann zu unterschiedlichen Zeitpunkten erfolgen. Entweder vorab zur Build-Zeit oder automatisiert in einem definierten Intervall. Bei der Variante zur Build-Zeit wird die SPA einmalig vorgerendert. Dadurch wird der Build-Prozess aufwendiger und die Inhalte können nur aktualisiert werden, wenn die Anwendung neu gebaut wird. Dieser Nachteil wird abgemildert, wenn die Anwendung stattdessen in einem festgelegten Intervall neu gerendert wird.

Da es sich beim Prerendering-Ansatz um eine Kombination aus dem CSR- und dem SSG-Pattern handelt, gelten größtenteils deren Vor- und Nachteile. So ist es beispielsweise nicht möglich, dynamische Inhalte vorab zur Build-Zeit zu rendern.

Prerendering steigert den SEO-Score. Darüber hinaus verbessert es die Zeit zum FCP, da die ersten Inhalte direkt nach der Interpretation des HTML-Dokuments gerendert werden, anstatt eine leere Seite anzuzeigen. Außerdem wird die Seite dadurch resistenter, da auch ohne JavaScript Inhalte gerendert werden und Inhalte nicht pro Anfrage gerendert werden müssen.

Durch die Verwendung eines externen Rendering-Diensts entsteht jedoch eine weitere Abhängigkeit, die außerhalb der eigenen Kontrolle liegt. Zusätzlich kann die Generierung zur Laufzeit zu einer längeren Antwortzeit (TTFB) führen, abhängig davon, wie schnell der Prerender Service ist.

Dieses Pattern kann zusätzlich um eine Laufzeitkomponente erweitert werden. Abhängig vom anfragenden User Agent wird unterschieden, ob die clientseitig gerenderte oder die statische Webseite ausgeliefert werden soll (Abb. 4). So erhält ein Crawler, für den JavaScript unerheblich ist, die statische Variante und ein normaler Nutzer die statische Seite mit dem SPA-Code. Das wird auch Dynamic Rendering [3] genannt.

Abb. 4: Dynamic Rendering mit Prerender ServiceAbb. 4: Dynamic Rendering mit Prerender Service

Prerendering eignet sich, wenn SEO-Verbesserungen an einer bestehenden SPA vorgenommen werden sollen und eine Migration zu einer serverseitigen Lösung nicht im Verhältnis zum Aufwand steht. Vor allem dann, wenn die Seiten statische Daten beinhalten und sich deren Inhalte nur wenig ändern. Ein Dienst, der dieses Pattern anbietet, ist prerender.io [4]. In Tabelle 2 und Abbildung 5 sehen Sie die Vor- und Nachteile von Prerendering auf einen Blick.

Vorteile Nachteile
gute User Experience TTI > FCP (verbesserte Zeit zum FCP gegenüber CSR)
Trennung zwischen Client- und Servercode verteiltes mentales Modell
CDN-fähig potenziell längere TTFB als bei CSR
SEO-freundlich viele Angriffsvektoren
nachrüstbar Abhängigkeit von externen Services
mit deaktiviertem JS eingeschränkt nutzbar keine Dynamik

Tabelle 2: Vor- und Nachteile von Prerendering

Abb. 5: Prerendering verbessert den SEO-Score erheblichAbb. 5: Prerendering verbessert den SEO-Score erheblich

Serverseitiges Rendering mit Hydration

Neben dem Prerendering-Ansatz zur Build-Zeit gibt es noch eine weitere Möglichkeit, die Probleme von clientseitig gerenderten SPAs zu lösen: serverseitiges Rendering mit einem Hydrationsschritt. Hier findet das Rendering zur Laufzeit statt. Oft wird dieser Prozess auch als Hydration oder isomorphes bzw. universales Rendering bezeichnet. Das Pattern ist eine Kombination aus SSR und CSR.

Die Bezeichnung isomorph ist deshalb zutreffend, weil sowohl auf dem Server als auch auf dem Client der SPA-Code gerendert wird. Begonnen wird dabei auf der Serverseite (Abb. 6). Dazu werden serverseitig die notwendigen Daten geladen und das DOM bzw. der Komponentenbaum der Anwendung damit angereichert. Anschließend wird daraus das HTML-Dokument gerendert und an den Client ausgeliefert. So erhält der Client schnell eine Seite mit interpretierbarem Inhalt, anstatt eine leere Seite anzuzeigen.

Abb. 6: Client-Server-Kommunikation – neu ist der Hydration-Schritt am EndeAbb. 6: Client-Server-Kommunikation – neu ist der Hydration-Schritt am Ende

Im zweiten Schritt wird clientseitig der JavaScript-Code für die SPA geladen und der Komponentenbaum erzeugt. Im Anschluss startet die Hydration, die die servergerenderte Anwendung auf dem Client wiederherstellt. Dazu wird die Initialisierung der gesamten Anwendung erneut abgespielt. Dabei wird das DOM mit Event Handlern versehen und Zustände einzelner Komponenten wiederhergestellt. Es ist, als würde das zuvor „trockene“ HTML mit dem „Wasser“ der Interaktivität und des Event Handling hydriert werden [5]. Sobald dieser Prozess für alle Komponenten abgeschlossen ist, verhält sich die Anwendung wie eine klassische SPA, da ab diesem Zeitpunkt der JavaScript-Code die Kontrolle übernimmt.

Die Vorteile dieses Ansatzes liegen auf der Hand. Statt wie bei einer rein clientseitig gerenderten SPA können dynamische Inhalte schneller angezeigt werden und sind auch für Suchmaschinen indizierbar. Die Größe des Java-Script Bundle hat kaum Einfluss auf die FCP-Metrik, da bereits vor dem clientseitigen Laden des Bundles serverseitig generierte Inhalte gerendert werden. Nach dem Laden bestehen weiterhin die Vorteile des clientseitigen Rendering, wie z. B. eine native User Experience. Somit haben wir alle unsere Probleme gelöst. Oder? Der Schein trügt. Hydration ist nämlich ein ineffizienter und fehleranfälliger Prozess. Große Teile der Webseite sind doppelt vorhanden. Einmal im servergerenderten HTML-Dokument und einmal im JavaScript Bundle für den Client. Diese Doppelung erhöht den Datenverbrauch und die Auslastung des Clients, da sowohl das HTML als auch das JavaScript geladen, interpretiert und ausgeführt werden müssen. Je nach Endgerät kann dieser Vorgang längere Zeit in Anspruch nehmen.

Da die clientseitige Anwendung den vom Server generierten Code übernimmt, muss der Komponentenbaum des Servers genau mit dem des Clients übereinstimmen. Um das zu gewährleisten, muss vor der Hydration abgewartet werden, bis das JavaScript vollständig geladen wurde. Ansonsten kann es im besten Fall zu einer Verlangsamung der Applikation führen, im schlimmsten Fall könnten beispielsweise Event Handler für das falsche Element initialisiert werden [6]. Auch können Situationen auftreten, bei denen das initial vom Server erzeugte DOM zerstört wird und komplett neu gerendert werden muss.

Das größte Problem stellt das sogenannte Uncanny Valley dar. Solange die SPA clientseitig nicht initialisiert ist (d. h., der Hydration-Schritt für alle Komponenten abgeschlossen ist), kann der Nutzer nicht mit der statischen Seite interagieren, obwohl diese bereits durch das serverseitig generierte HTML interaktiv erscheint.

Aus diesen Gründen ist Hydration ein komplexes Thema [7] und es werden inkrementelle Verbesserungen vorgenommen. Beispielsweise arbeitet das React-Team seit einigen Jahren an der Suspense-Architektur, die es erlaubt, nur Teile des Komponentenbaums zu rendern und zu hydrieren. Dieser Ansatz ist unter [5] sehr gut beschrieben. In den folgenden Abschnitten werden daher zwei weitere Varianten des Hydration-Patterns betrachtet, wodurch die Probleme reduziert werden sollen: progressive und partielle Hydration.

Hydration ist heutzutage bei vielen Frontend-Frameworks der Standard für die Entwicklung von Webapplikationen. Durch die dynamische Berechnung zur Laufzeit ist es weitläufig einsetzbar. Trotzdem ist es keine One-size-fits-all-Lösung. Für überwiegend statische Seiten könnte beispielsweise ISR genutzt werden. Alle großen JavaScript-Frameworks wie React oder Vue unterstützen Hydration. In der Regel wird jedoch auf die darauf aufbauenden Metaframeworks wie Next.js oder Nuxt zurückgegriffen, da diese Frameworks den gesamten Prozess rund um Hydration automatisieren. Zusammengefasst sehen Sie die Vor- und Nachteile von Hydration in Tabelle 3 und Abbildung 7.

Vorteile Nachteile
performant potenziell hohe TTFB
SEO-freundlich TTI > FCP (Uncanny Valley)
gute User Experience initiales Rendering abhängig von Konnektivität
dynamische, personalisierte Inhalte Performance abhängig von Nutzerzahl und Serverstandort
mit deaktiviertem JS eingeschränkt nutzbar ineffizient und fehleranfällig
mangelnde CDN-Fähigkeit
viele Angriffsvektoren

Tabelle 3: Vor- und Nachteile von Hydration

Abb. 7: Hydration ist ein guter Allrounder, wäre da nicht die verschwenderische RessourcennutzungAbb. 7: Hydration ist ein guter Allrounder, wäre da nicht die verschwenderische Ressourcennutzung

Progressive Hydration

Wie wir gesehen haben, stellt die initiale Hydration aller Komponenten ein großes Problem dar. Es ist daher naheliegend zu versuchen, diesen Aufwand zu reduzieren. Ein Ansatz dazu kann Lazy Loading sein. Diese Idee wurde bereits 2016 in einem Blogbeitrag [8] unter dem Namen „Progressive Booting“ beschrieben.

Anstatt den gesamten Komponentenbaum auf einmal zu hydrieren, werden zunächst nur die nötigsten Komponenten initialisiert. Die Hydration von weniger relevanten Zweigen im Komponentenbaum kann anfangs ausgesetzt und zu einem späteren Zeitpunkt angestoßen werden. Als Auslöser können hierfür der Viewport, die Interaktionswahrscheinlichkeit, oder die aktuelle Auslastung des Clients dienen. Beispielsweise kann eine Footer-Komponente, die sich außerhalb des Viewports befindet, erst zu dem Zeitpunkt hydriert werden, wenn sie sichtbar wird (Abb. 8). Hierfür wird der JavaScript Chunk für den Footer erst zu diesem Zeitpunkt angefordert.

Abb. 8: Die Hydration für den Footer findet erst zu einem späteren Zeitpunkt stattAbb. 8: Die Hydration für den Footer findet erst zu einem späteren Zeitpunkt statt

Für die Aufteilung der Applikation in einzelne Chunks müssen geeignete Grenzen und Prioritäten definiert werden. Diese können unter anderem vom Entwickler vorgegeben werden. Ein Beispiel hierfür sind Astros Clientdirektiven [9], die pro Komponente definiert werden können. Durch diesen progressiven Ansatz verringert sich die initiale Größe des JavaScript-Bundles, da bei der ersten Anfrage nur noch das Notwendigste versendet wird. Somit reduziert sich auch das Uncanny Valley, da es die TTI für die Hauptkomponenten verbessert.

Wahre progressive Hydration ist aufgrund der vielen Fallstricke und Komplexität schwer zu erreichen. Ein Großteil der heutigen Frameworks basiert immer noch auf einem Top-down-Ansatz bei der Hydration, da diese Frameworks ursprünglich dazu gedacht waren, eine SPA zu bauen. Dadurch ist nur ein einzelner Einstiegspunkt für die Hydration gegeben. Somit muss ein großer Teil des Codes geladen und ausgeführt werden, selbst wenn lediglich eine Komponente am Ende des Komponentenbaumes hydriert werden muss. Für den progressiven Ansatz wären jedoch mehrere unabhängige Einstiegspunkte besser. Optimalerweise genau an der Stelle im Komponentenbaum, die hydriert werden soll.

Außerdem gestaltet es sich in der Praxis als schwierig, die richtige Grenze für die Chunks zu ziehen. Aufgrund der Eltern-Kind-Beziehungen kann es sein, dass die Grenze an einer Stelle gezogen wird, die von einem anderen Teil des Baumes abhängig ist. Daher ist es notwendig, auch diesen Teil zu hydrieren. Neben diesen Punkten gibt es noch weitere, die eine gute Umsetzung von progressiver Hydration erschweren. Für interessierte Leser empfehle ich die Quellen [7] und [10].

Progressive Hydration kann überall dort eingesetzt werden, wo auch Hydration in Frage kommt und die verbesserte Performance und Ressourcennutzung notwendig sind. Ein Framework, das progressive Hydration implementiert, ist das bereits erwähnte Astro. Aber auch andere Frameworks aus dem JavaScript-Ökosystem bieten diesen Ansatz an. Die Vor- und Nachteile sehen Sie zusammengefasst in Tabelle 4 und Abbildung 9.

Vorteile Nachteile
siehe Tabelle 3 siehe Tabelle 3
effizienter durch kleineres initiales JavaScript Bundle erhöhte Komplexität
geringere TTI, Reduktion des Uncanny Valley

Tabelle 4: Vor- und Nachteile von progressiver Hydration

Abb. 9: Progressive Hydration reduziert den initialen RessourcenaufwandAbb. 9: Progressive Hydration reduziert den initialen Ressourcenaufwand

Partielle Hydration und Island Architecture

Partielle Hydration und Island Architecture werden häufig miteinander verwechselt oder als Synonyme verwendet. Die Abgrenzung dieser beiden Begriffe ist nicht klar definiert. Das Ergebnis ist jedoch in beiden Fällen das gleiche. Im Gegensatz zum progressiven Ansatz, der sich auf den Zeitpunkt der Hydration auswirkt, ist es mit Hilfe der partiellen Hydration möglich, nur ausgewählte Teile des Komponentenbaums zu hydrieren.

Stellen Sie sich eine Anwendung vor, bei der nur zwei unabhängige kleine Teile interaktiv sind. Der Rest ist statisch. Wie zuvor bereits erläutert wurde, muss für diesen kleinen interaktiven Teil der gesamte Baum hydriert werden. Das ist ineffizient. Eine Lösung wäre, nur genau diese dynamischen Teile zu hydrieren (Abb. 10).

Abb. 10: Die Inseln werden unabhängig angefragt und verarbeitetAbb. 10: Die Inseln werden unabhängig angefragt und verarbeitet

Dazu müssen geeignete Grenzen definiert werden. Das Ergebnis ist einerseits ein statischer Teil aus HTML und andererseits dynamische Bereiche bestehend aus HTML und JavaScript. Letztere werden als Inseln bezeichnet. Damit verschwindet der zuvor bestehende Top-down-Ansatz des Komponentenbaumes, da nur noch unabhängige Teile mit verschiedenen Einstiegspunkten existieren. In unserem Beispiel hätten wir jetzt zwei unabhängige Inseln.

Für die Definition der Grenzen gibt es verschiedene Ansätze. Einige sind manueller Natur, andere arbeiten automatisch. Das bereits erwähnte Astro-Framework überlässt diese Aufgabe dem Entwickler. Dieser muss anhand der Clientdirektiven definieren, welche Teile nicht statisch sind. Andere Frameworks wie Fresh lösen das Problem, indem der Code für die Inseln in einem speziellen Ordner abgelegt werden muss. Wieder andere (z. B. Marko.js) erkennen die Inseln automatisch über einen Compiler.

Ähnlich wie beim progressiven Ansatz sinkt auch hier die Größe des clientseitigen Codes. Dadurch, dass sehr viel weniger teuer auszuführendes JavaScript ausgeliefert wird, ist die Anwendung schnell interaktiv. Das vermindert den Uncanny-Valley-Effekt. In Kombination mit progressiver Hydration kann dieser sogar ganz entfallen, da initial kein JavaScript mehr ausgeliefert werden muss. Die Seite ist somit wie beim statischen oder klassischen serverseitigen Rendering sofort interaktiv.

Da es sich um isolierte Inseln handelt, können sie auch unabhängig vom Rest der Seite geladen werden. Im Gegensatz zum Top-down-Ansatz verzögert keine Elternkomponente die Initialisierung einer Insel. Diese Vorteile werden jedoch durch eine erhöhte Komplexität erkauft. Das manuelle Setzen von Grenzen kann fehleranfällig und kompliziert sein [11]. Einen Compiler zu schreiben, der dieses Problem löst, ist eine große Herausforderung, da die Architektur des Frameworks dies berücksichtigen muss.

Ein bereits erwähnter Vorteil der Inseln ist ihre Unabhängigkeit vom Rest der Seite, erschwert ist jedoch die Kommunikation zwischen den Inseln, z. B. um den globalen Anwendungszustand zu teilen. Im klassischen Ansatz können Daten über Props ausgetauscht werden. Da die Komponenten jedoch isoliert voneinander sind, ist dieser Mechanismus nicht mehr möglich. Daher wird eine zusätzliche Vermittlungsschicht benötigt, z. B. ein globaler Speicher für den Anwendungszustand, der von allen Inseln angesprochen werden kann.

Die partielle Hydration kann in ähnlicher Weise wie die progressive Hydration verwendet werden. Es ist jedoch zu bedenken, dass dieses Pattern die Developer Experience verringern kann, da es mehr Komplexität mit sich bringt. Außerdem ist dieses Pattern nicht für interaktionslastige Seiten geeignet, da solche sehr viele Inseln erfordern würden. Genutzt wird dieses Pattern bei den bereits erwähnten Frameworks Astro und Fresh. Die Vor- und Nachteile finden Sie in Tabelle 5 und Abbildung 11 zusammengefasst.

Vorteile Nachteile
siehe Tabelle 3 siehe Tabelle 3
effizienter durch kleineres JavaScript Bundle erhöhte Komplexität
geringere TTI, Reduktion des Uncanny Valley erschwerter Austausch zwischen Inseln
isolierte Komponenten

Tabelle 5: Vor- und Nachteile partieller Hydration

Abb. 11: Partielle Hydration steigert die Performance und reduziert unnötige DatenAbb. 11: Partielle Hydration steigert die Performance und reduziert unnötige Daten

Fazit

Wie wir gesehen haben, versuchen alle diese Patterns, die Schwächen der drei grundlegenden Patterns zu mildern, indem sie verschiedene Ansätze daraus kombinieren und neue Ideen einbringen. Anhand der Bewertungen der jeweiligen Patterns ist zu erkennen, dass diese sich immer weiter verbessern. Besonders hervorzuheben sind die Sprünge in den Bereichen Developer Experience und User Experience. Das deckt sich auch mit der Entwicklung des Webs, die wir bereits im ersten Teil der Serie betrachtet haben.

Leider nimmt auch die Komplexität der Lösungen stetig zu. Das ist vor allem bei den Hydrations-Ansätzen zu beobachten, die heutzutage häufig den Standard in der Entwicklung mit JavaScript-Frameworks bilden. In letzter Zeit tauchen aber auch vermehrt neuartige Ansätze auf, die teilweise gänzlich ohne Hydration auskommen, wie z. B. Resumability. Unter anderem auf dieses Pattern werden wir im kommenden und letzten Teil der Serie eingehen.

schaefer_julian_sw.tif_fmt1.jpgJulian Schäfer arbeitet als Full-Stack-Softwareentwickler bei der synyx GmbH & Co. KG in Karlsruhe. Daneben beschäftigt er sich mit Webtechnologien und versucht, dieses Wissen auch während seines Projektalltags weiterzugeben.

Twitter

Links & Literatur

[1] https://web.dev/stale-while-revalidate/

[2] https://www.netlify.com/blog/2021/03/08/incremental-static-regeneration-its-benefits-and-its-flaws/

[3] https://developers.google.com/search/docs/crawling-indexing/javascript/dynamic-rendering

[4] https://prerender.io

[5] https://github.com/reactwg/react-18/discussions/37

[6] https://www.joshwcomeau.com/react/the-perils-of-rehydration/

[7] https://dev.to/this-is-learning/why-efficient-hydration-in-javascript-frameworks-is-so-challenging-1ca3

[8] https://aerotwist.com/blog/when-everything-is-important-nothing-is/

[9] https://docs.astro.build/en/reference/directives-reference/#client-directives

[10] https://www.builder.io/blog/why-progressive-hydration-is-harder-than-you-think

[11] https://www.theguardian.com/info/2022/mar/25/react-islands-on-theguardiancom