Hinweis:
Dieser Blogartikel ist älter als 5 Jahre – die genannten Inhalte sind eventuell überholt.
Für größere Projekte, in denen mehrere Teams parallel an Web-Applikationen entwickeln, können wir bedenkenlos die Entwicklung und den Einsatz einer Komponentenbibliothek empfehlen. Im Folgenden möchten wir diesen Begriff erläutern und aus unseren Erfahrungen berichten.
Moderne JavaScript Frontend-Bibliotheken wie z.B. React, Angular oder Vue.js setzen allesamt auf einen komponentenbasierten Aufbau. In diesem Ansatz wird versucht, jedes beliebig große Teilstück einer Web-Applikation abstrakt zu fassen und isoliert zu betrachten. Eine Komponente kapselt dabei die Darstellung und Anwendungslogik eines HTML-Fragments ab.
Dies führt zu einer Reihe von Vorteilen:
- Jede Komponente erfüllt einen speziellen Zweck.
- Komponenten können ineinander geschachtelt und über ihre API verknüpft werden. Auf diese Art lassen sich komplexere Anwendungsfälle abbilden.
- Das Isolieren von Komponenten ermöglicht eine hohe Testbarkeit mit minimalem Aufwand bei gleichzeitig hoher Wiederverwendbarkeit.
Eine Komponentenbibliothek, manchmal auch Pattern Library oder Styleguide genannt, ist eine zentralisierte Sammlung wiederverwendbarer Komponenten und kann in größeren Systemlandschaften teamübergreifend eingesetzt werden. In der Konsequenz kann die Bereitstellung einer Komponentenbibliothek eine Time to value erheblich reduzieren. Die Entwicklung an einer zentralen Stelle minimiert zudem die Wartungskosten.
Der Mehrwert einer Komponentenbibliothek ist insbesondere auch die enge Verzahnung der Entwicklung mit dem Design. Sie schafft zum einen eine gemeinsame Sprache, ein gemeinsames Verständnis beim Schnitt der Anwendungen und setzt zum anderen die UI/UX-Vorgaben direkt um.
Je nach Implementierung kann die Geräte- und Browser-übergreifende Kompatibilität der Komponenten durch sinnvolle E2E-Tests sichergestellt werden. Die Erstellung von barrierefreiem Inhalt kann vereinfacht werden, wenn dies innerhalb der Komponenten bereits berücksichtigt und durch entsprechende Tests, z.B. mithilfe von aXe, abgesichert wird.
Es sollte jedoch unbedingt bedacht werden, ob Komponenten in realen Projekten wiederverwendet werden können, oder ob diese nur eine Idealvorstellung darstellen. Möglicherweise sind Applikationen und/oder organisatorische Strukturen derart heterogen, dass sich der zusätzliche Aufwand, den eine Implementierung erfordert, nicht rechnet. Dies gilt insbesondere für (kleine) Teams mit sehr unterschiedlichen Applikationsanforderungen.
Technische Entscheidungen
Beim Aufbau einer Komponentenbibliothek gilt es, eine Reihe von technischen Entscheidungen zu treffen, die je nach Use-Case Vor- und Nachteile mit sich bringen. Wir wollen auf die wichtigsten Punkte eingehen und einen High-Level-Überblick über mögliche Lösungen bieten. Hierbei erheben wir keinen Anspruch auf Vollständigkeit, sondern legen vor allem die in unseren Augen wesentlichen Punkte dar, über die es nachzudenken gilt.
Auswahl des Frameworks
Eine der komplexesten und weitreichendsten Entscheidungen ist die Auswahl des Frameworks, da hiervon die gesamte Anwendung betroffen ist, nicht nur die UI-Komponenten. Hinzu kommt, dass sich dieser Bereich der Web-Entwicklung aktuell sehr schnell bewegt und in kurzen Abständen zahlreiche neue Frameworks veröffentlicht werden. Dieser Artikel soll nicht dazu dienen Frameworks zu vergleichen, sondern sich der Arbeit mit Komponenten widmen, daher werden wir nicht näher auf einzelne Frameworks eingehen – mit einer Ausnahme, den Web Components. Einen guten Überblick über den aktuellen Stand der Frameworks und deren Produktiveinsatz gibt auch die JavaScript Survey 2017.
Web Components
Eine Besonderheit stellen Web Components dar. Hierbei handelt es sich um den Versuch der WHATWG, eine standardisierte Lösung für Custom Elements zu etablieren. Web Components erlauben es, Komponenten von einem Framework loszulösen und direkt in einer Abstraktionsschicht zu definieren, die moderne Browser mitbringen. Das hat den Vorteil, dass die daraus entstehenden Komponenten, analog zu nativen HTML-Elementen, in jedem Kontext einsetzbar sind und nicht nur im Rahmen eines Frameworks, wie dies zum Beispiel bei React- oder Angular-Komponenten der Fall ist. Damit eignen sie sich prinzipiell gut für eine Komponentenbibliothek in einem Umfeld heterogener Web-Technologien.
Nachteilig ist jedoch, dass sich Browser-Hersteller bei wichtigen Details der Web-Components-Spezifikation noch nicht einig sind, was dazu führt, dass nicht alle Browser eine einheitliche API unterstützen. Stellvertretend hierfür sei die Diskussion um das is
-Attribut genannt. Wir sind der Meinung, dass man momentan je nach Einzelfall prüfen und entscheiden sollte, ob es Sinn macht, seine Komponentenbibliothek mit Web Components aufzubauen. Vor allem der Browsersupport spielt hier eine zentrale Rolle.
Nach aktuellem Stand der Technik eigenen sich zudem einige Frameworks mehr als andere, um mit Web Components nahtlos zusammen zu spielen. Besonders hervorzugehen sind hier Angular und Vue.js, wohingegen React und AngularJS einige Probleme haben. Mehr technische Details hierzu sind auf der Seite Custom Components Everywhere gelistet.
Hinzu kommt, dass derzeit nach unserer Einschätzung im Bereich Web Components noch sehr viel experimentiert wird und wenig Zuverlässiges existiert. So hat Google im Oktober 2018 bekannt gegeben, ab Polymer 3 auf eine ihrer Kernideen, den HTML-Import, zu verzichten und nutzt statt dessen ES2015 Imports. Für die Anwender:innen und Early Adopter von Web Components via Polymer bedeutet dies einige Arbeit, denn bereits existierende Komponenten müssen hierfür angepasst werden.
Auch andere Frameworks wie Stencil.js oder Skate.js werden bisher nur vereinzelt produktiv eingesetzt und können, Stand heute, nur unter Vorbehalt empfohlen werden, da deren Weiterentwicklung häufig nur an einzelnen Entwickler:innen hängt.
Zudem stellen sich auch bei Web Components Fragen nach SEO, Performance und Accessibility, die es zu beantworten gilt. Außerdem stehen Web Components immer in einem globalen Namespace zur Verfügung, der eine Versionierung schwierig macht. All diese Fragestellungen sprengen jedoch den Umfang dieses Beitrags und wären einen eigenen Blogpost wert.
Bereitstellung der Komponentenbibliothek
Die entwickelten Komponenten sollten den Applikationsentwickler:innen zentral bereit gestellt werden. Im JavaScript-Kontext gibt es hierfür unterschiedliche Möglichkeiten:
- npm-Package Die Komponentenbibliothek kann über die zentrale Paketverwaltung npm bereitgestellt werden. Soll die Bibliothek nicht öffentlich verfügbar sein, muss hierzu entweder ein privates Repository oder eine eigene Registry wie beispielsweise Nexus vorgeschalten sein.
- Content-Delivery-Network (CDN) Die Komponentenbibliothek kann auch mittels eines CDNs bereitgestellt werden. Die Komponenten müssen dann als UMD-Module bereitgestellt werden.
Einige der Vor- und Nachteile der beiden Möglichkeiten sind:
Methode | npm | CDN |
---|---|---|
Keine globale Variablen notwendig | ✓ | ✗ |
Tree-Shaking im Build möglich | ✓ | ✗ |
Verwenden von unterschiedlichen Versionen
der Komponentenbibliothek möglich |
✓ | ✗ |
Anwendungsübergreifendes Caching möglich | ✗ | ✓ |
Strukturierung
Häufig verwendet man zur Strukturierung von Komponenten die Methodik des Atomic Design. Beim Design und Schnitt der Komponenten wird hierbei zunächst nach nicht weiter zerlegbaren Elementen, den Atomen, gesucht. Diese können beispielsweise ein Button oder ein Input-Feld sein – selbstverständlich im entsprechenden Corporate Design. Ausgehend von den Atomen können komplexere Elemente wie eine UI Card entstehen, welche als Moleküle bezeichnet werden. Eine Verschachtelung von Molekülen führt dann zu den sogenannten Habitaten.
Atome sind die Elemente mit den wenigsten Annahmen über ihre Umgebung. Sie müssen eine Vielzahl von Anwendungsfällen unterstützen und sollten sehr generisch gehalten werden. Zum Beispiel sollte ein Eingabefeld sowohl für Suchbegriffe als auch für die Eingabe eines Passwort verwendet werden können.
Moleküle hingegen setzen häufig schon einen gewissen Kontext voraus. Eine UI Card zum Beispiel ist selbst-konsistent, kann und soll aber nicht für jeden Use-Case eingesetzt werden. Es kann hingegen unterschiedliche Ausprägungen von Karten geben, die einen unterschiedlichen Use-Case implementieren, wie z.B. eine UserCard
zur Anzeige von Benutzer:inneninformationen und ProductCard
, die Produktdaten bereitstellt.
Generell ist beim Definieren der Komponenten darauf zu achten, dass diese so generisch wie möglich sind – allerdings immer nur unter den gegebenen Rahmenbedingungen.
Für das Komponenten-Design gilt daher:
So generisch wie möglich, so voreingenommen wie nötig.
Definieren einer API
Jede Komponente stellt ihre eigene Funktionalität zur Verfügung. Hierzu muss sie mit ihrer Umgebung kommunizieren und stellt zu diesem Zweck eine eigene API (Application Programming Interface) bereit.
Das Interface einer Komponente ist das, womit jede:r Anwender:in arbeiten muss. Es ist die einzige Schnittstelle zur Außenwelt, die Komponente stellt ansonsten eine Blackbox dar. Das ist sinnvoll, denn ein Anwender soll sich keine Gedanken machen müssen, wie die Komponente funktioniert, sondern nur darüber, wie man sie anspricht.
An die Spezifikation von Komponenten sind hohe Anforderungen gestellt. Die API sollte den folgenden Rahmenbedinungen genügen:
- Stabilität Eine API sollte sich über die Zeit so wenig wie möglich ändern (v.a. keine nicht-rückwärtskompatiblen Changes, sogenannte Breaking Changes), da jede Änderung Anpassungen beim Anwender notwendig macht. Im schlechtesten Fall lösen solche Änderungen in den Anwendungen kaskadierende Effekte aus, die hohe Kosten- und Zeitaufwände nötig machen.
- Konsistenz Die API sollte sich konsistent verhalten – auch über mehrere Komponenten hinweg. Kann man z.B. mehreren Elementen einen Tooltip mitgeben, sollte das entsprechende Attribut immer gleich genannt werden und sich immer gleich verhalten. Der Vorteil liegt auf der Hand: Die Komponentenbibliothek verhält sich wie aus einem Guss und dies erleichtert den täglichen Umgang mit ihr.
Ausliefern von CSS
Die Komponenten werden, wie in der Web-Entwicklung üblich, mit CSS (Cascading StyleSheets) gestyled. In der klassischen Web-Entwicklung werden oftmals einige wenige CSS-Dateien angelegt, die dann Regeln für alle Teile der Website beinhalten (vgl. Bootstrap, Foundation). Das Isolationsprinzip bei der komponentenbasierten Entwicklung hingegen sieht vor, dass jede Komponente unabhängig von anderen existieren kann und ein in sich geschlossenes System darstellt. Dazu gehören auch die Styles, so dass in diesem Ansatz jede Komponente ihr CSS definiert und selbst mitbringt.
Jede Komponente sollte isoliert verwendbar sein, Redundanzen sind also häufig unabdingbar und können nicht vollständig vermieden werden. Im Source-Code hingegen können Redundanzen, also Schnittmengen zwischen Komponenten, über Mixins abgebildet werden. Diese können mit einem CSS-Präprozessor wie z.B. LESS oder SASS abgebildet werden, um den Code möglichst DRY zu halten.
Eine Nomenklatur wie BEM hilft bei der spezifischen Benennung der Klassen und vermeidet somit Kollisionen im Namensraum. Außerdem sollte, falls erforderlich, eine Versionierung der StyleSheets über den Namensraum berücksichtigt werden.
Es existieren unterschiedliche Methoden, um das CSS an den User auszuliefern.
- Dynamisch Hierbei ist jede Komponente selbst verantwortlich, ihr CSS in den Dokumentenbaum (DOM) einzubetten. Typischerweise stellen Module-Bundler wie beispielsweise webpack hierfür die notwendigen Code-Snippets bereit (z.B. style-loader).
- Statisch Bei diesem Ansatz wird aus dem modularisierten CSS beim Build-Schritt eine globale CSS-Datei erstellt, welche das CSS von allen Komponenten beinhaltet. Dieses wird nun einmalig in die Seite eingebettet. Dieser Ansatz widerspricht dabei nicht der Isolation, da die Definition der CSS-Regeln wie oben beschrieben innerhalb der Komponente passiert und erst beim Build extrahiert und global abgelegt wird (z.B. webpack-extract-text-plugin).
Organisation
Eine Komponentenbibliothek zu erstellen und zu verwalten stellt auch organisatorisch eine Herausforderung dar. Es müssen viele Fragen bedacht und beantwortet werden, darunter die folgenden:
- Wer plant und schneidet Komponenten und definiert deren API?
- Wer ist zuständig für die Entwicklung/Weiterentwicklung von Komponenten (Owner)?
- Wer fixt Bugs in der Komponentenbibliothek?
- Wer dokumentiert die Komponenten?
- Wer ist zuständig für das Testen von Komponenten auf allen unterstützten Browsern und Betriebsystemen?
- Wer kümmert sich um die allgemeine Pipeline, die notwendig ist, um ein Release zu erzeugen?
In unserer Erfahrung mit Komponentenbibliotheken hat sich gezeigt, dass es oft sinnvoll ist, ein dediziertes Komponenten-Team bereitzustellen, das sich um die Planung, Entwicklung, Dokumentation, das Testen und Weiterentwickeln der Komponenten sowie die dafür notwendige Infrastruktur kümmert. Aufgabe dieses Teams ist es dabei auch, eng mit Designer:innen und Fachkräften der User-Experience-Abteilung (UX) zusammen zu arbeiten. In die Zuständigkeit des Teams fällt auch die Pflege der Komponenten (Maintenance).
Es ist aber auch vorstellbar, viele Teams an der Komponentenbibliothek arbeiten zu lassen und diese je nach Bedarf um die Komponenten anzureichern, die im aktuellen Entwicklungsschritt in diversen Projekten gebraucht werden.
Ein eigenes Komponenten-Team hilft aus unserer Erfahrung, einige der im folgenden gelisteten Ziele einfach zu erreichen.
Einheitliche API
Jede Komponente definiert ihre eigene API. Jedoch sind die Komponenten aus Sicht von Entwickler:innen nicht gänzlich unabhängig voneinander, sondern bilden in ihrer Gesamtheit eine Einheit, die widerspruchsfrei definiert sein sollte. Dies spiegelt sich vor allem in der API wider. Beim Designen neuer Komponenten muss daher bekannt sein, wie bisherige Komponenten geschrieben sind, so dass sich ihre API wenn möglich analog zu der anderer Komponenten verhält.
Beschäftigt sich ein dediziertes Team mit der Aufgabe einer Komponentenbibliothek, so hat dieses Team den Überblick über alle existierenden Komponenten, deren API und Use Cases. Neue Komponenten können so einfach kritisch hinterfragt und so ausdefiniert werden, dass sie sich widerspruchsfrei in die schon existierenden Komponenten einbetten lassen. Der große Vorteil ist dann eine konsistente Ausführung der Komponenten, was es anderen Teams erlaubt, eine gewisse Routine im Umgang und Geschwindigkeit in der Anwendung der Komponenten zu entwickeln.
Kontextfrei
Oft fällt es Teams schwer Komponenten, die im Zuge einer Projektarbeit entstehen sollen, vollständig vom Projektkontext zu lösen. Es ist schwierig, die notwendige Abstraktion zu erreichen, die es anderen Teams erlaubt, die Komponente ohne Probleme in einem unterschiedlichen Kontext zu verwenden. Dies ist nicht weiter verwunderlich, da jedes Team seinen eigenen Projektkontext sehr genau kennt und Design-Entscheidungen häufig in Bezug auf diesen Kontext fällt. Eine so entstandene Komponente ist dann nur allzu oft in anderen Kontexten nicht zu gebrauchen und muss signifikant (breaking changes) geändert werden, wenn ein anderes Team diese nutzen möchte. Dies birgt die Gefahr von aufwändigen Refactorings aufgrund fehlender Abstraktion.
Auf der anderen Seite hilft es jedoch, sich immer wieder mit der Frage zu beschäftigen, in welcher Form die Komponenten eingesetzt werden. Unter Umständen lassen sich immer wiederkehrende Muster in der Verwendung bereits in einer Komponentenbibliothek integrieren, um somit eine Zeitersparnis bei allen Konsumenten zu schaffen.
Semantic Versioning/Changelog
Bei einer Komponentenbibliothek sollte unbedingt auf semantische Versionierung geachtet werden. Semantisches Versionieren erlaubt es in nur drei Zahlen auszudrücken, ob Änderungen an der Bibliothek weitreichende Code-Anpassungen beim Konsumenten erforderlich machen.
Aus unserer Sicht ist es zudem sehr sinnvoll ein Changelog zu führen, in dem alle Veränderungen zwischen Versionen notiert werden, sodass Anwender der Bibliothek einfach nachvollziehen können, ob und welche Codestellen im eigenen Code angepasst werden müssen.
Idealerweise sind bei Breaking Changes, also Änderungen, die eine Anpassung erfordern, auch die entsprechenden Migrationswege dokumentiert.
Composability
Komponenten sollten so geschrieben sein, dass sie schachtelbar sind. Im Geiste von HTML, das über das Document Object Model (DOM) beschrieben wird, beinhalten Komponenten oft andere Komponenten und sollten im Allgemeinen unvoreingenommen sein gegenüber den Komponenten, die ihnen als Kind-Elemente hereingereicht werden.
Eine Ausnahme bilden sogenannte Leaf-Nodes, z.B. Input-Felder, die keine weiteren Kind-Elemente beinhalten können, oder solche, die gezielt nur dafür gebaut werden, mit ganz speziellen Kind-Elementen zurecht zu kommen (vgl. das HTML Select Element).
Dokumentation
Ein essenzieller Teil einer Komponentenbibliothek ist deren Dokumentation. Die Anwender:innen einer solchen Bibliothek sind andere Entwickler:innen, sodass der Fokus der Dokumentation darauf gerichtet sein sollte, dass sich Entwickler:innen schnell darin zurechtfinden. Die Dokumentation muss neben der vollständigen API einer Komponente auch deren gewünschtes Verhalten beschreiben, sowie ggfs. den Rahmen definieren, in dem die Komponente verwendet werden soll.
Im Idealfall bieten Dokumentationen den Anwendern:innen auch die Möglichkeit, Live-Beispiele auszuprobieren und den Code zu verändern, um so spielerisch die Komponente zu erfahren und kennenzulernen. Dies erlaubt es Anwender:innen relativ einfach, ohne komplizierte Beispielprojekte aufsetzten zu müssen, abzuschätzen, ob eine Komponente das erfüllt, was sie im Rahmen einer technischen Anforderung leisten muss.
Zudem bietet die Dokumentation den perfekten Ort, um Entscheidungen im Hinblick auf die User Experience zu dokumentieren. Zum Beispiel den Kontext, in dem die Komponente eingesetzt werden darf. In klassischen Projekten existiert dieses Wissen oft entweder nur in den Köpfen oder in parallel geführten Dokumenten wie Word-Dokumenten oder ähnlichem. Dies führt häufig dazu, dass UX-Richtlinien nur mangelhaft umgesetzt werden. Die Dokumentation hingegen bietet die richtige Stelle, zusätzliche Regeln, die nicht sowieso schon innerhalb der Komponente gekapselt sind, niederzuschreiben und sie den Entwicklern zu verdeutlichen.
Case Study: ERGO Elements
Im Rahmen des Projekts “Ergo Elements“ für unseren Kunden ERGO Direkt implementierten wir React-Komponenten und stellten diese weiteren Teams als npm-Bibliothek zur Verfügung. Diese Bibliothek wurde nach den oben genannten Maßstäben und Empfehlungen angefertigt. Die Komponenten wurden daraufhin in Online-Tarifstrecken eingesetzt.
Die Kernentwicklung von etwa 40 Komponenten (mehrheitlich Atome und Moleküle) und die Anfertigung eines Styleguides benötigte in Summe etwa 250–300 PPT (Projektpersonentage). Zu den Komponenten gehörten sowohl einfache Elemente wie ein Button, der sich im Wesentlichen nur durch ein angepasstes Styling auszeichnet, als auch komplexere Komponenten wie ein Datepicker, eine Vergleichstabelle und diverse Komponenten für Fragebögen.
Das Erstellen einer Komponentenbibliothek resultierte in gestiegener Qualität und Zeitgewinn: Darstellungs- und browserspezifische Probleme konnten in der Bibliothek zentral behoben werden. Die Anzahl der Probleme in den Projekten wurde dadurch deutlich reduziert, sodass sie die Teams nicht weiter blockierten. Eine individuelle Fehlererfassung z.B. von Styling-Problemen war nicht weiter erforderlich.
Bei einem tiefgreifendem Redesign ein oder mehrerer Web-Applikationen bietet es sich also an, mit ausreichend Vorlauf mit der Ausarbeitung der Komponenten zu beginnen. Diese können anschließend von allen Teams eingesetzt werden, um deutlich Zeit einzusparen.
Für die Darstellung unserer Komponenten in einem Living Styleguide setzen wir die Open Source Software React Styleguidist1 ein. Diese bietet eine Reihe wertvoller Features wie
- Dokumentation in Markdown2,
- Verwendung von JSDoc, um die Komponenten-APIs darzustellen3 und
- Interaktiv bearbeitbare Code-Beispiele.
Der Styleguide beinhaltet sowohl die technische Dokumentation als auch die Dokumentation für Anwender:innen. Zu allen Komponenten werden mehrere Einsatzbeispiele gelistet, welche die unterschiedlichen Zustände der Komponenten dokumentieren. Insbesondere wurde darauf geachtet, realitätsbezogene Anwendungsbeispiele zu dokumentieren.4
Des weiteren enthält der Styleguide alle wichtigen Informationen zur Einrichtung und Verwendung der Komponentenbibliothek und Zusatzinformationen zu den häufigsten Use-Cases, wie z.B. der Verwendung der Komponentenbibliothek im Zusammenspiel mit Validierungs-Frameworks.
Der entstandene Styleguide ist nicht öffentlich zugänglich, sodass wir uns im folgenden auf eine Reihe von Screenshots beschränken, die den Styleguide zeigen. In Absprache mit unserem Kunden ERGO Direkt dürfen wir den Styleguide an dieser Stelle zeigen, wofür wir uns recht herzlich bedanken wollen. Generell kann die Funktionsweise des React Styleguidist in deren Demo-Anwendung nachvollzogen werden.
Referenzen
- Styleguide in React, der die Google Material Design Richtlinien aufgreift: Material UI
- Taking The Pattern Library To The Next Level
- On Building Component Libraries
- Die Software steht unter MIT-Lizenz.
- Code-Beispiele können in Markdown Code-Fences definiert werden. Diese werden im Styleguide in Beispiele übersetzt, die live im Browser verändert werden können.
- Die Properties der Komponenten können wahlweise mittels React Prop-Types (JavaScript) oder Interfaces (TypeScript) definiert werden.
- Wir haben uns bewusst dagegen entschieden jede mögliche Konfiguration zu dokumentieren. Häufig gibt es schlicht zu viele Konfigurationen, die zwar technisch möglich, aber oft nicht sinnvoll sind. Wir versuchen im Rahmen des Styleguides, die häufigsten Anwendungsfälle abzudecken. Im Rahmen von Tests werden jedoch auch die Anwendungsfälle abgedeckt, die technisch spezifiziert sind, jedoch nicht zu den primären Use Cases gehören.
ERGO Elements! Tolle Komponentenbibliothek! 🙂