Warum wir Webcomponents bei der Entwicklung in Django nutzen

Webcomponents und Django

In der jüngeren Vergangenheit haben wir größere Webapplikationen meist mit einem getrennten Back- und Frontend Entwickelt. Hierzu haben wir meistens Django im Backend und React in Frontend eingesetzt. Die Kommunikation zwischen beiden erfolgte dann entweder per REST-API (Django Restframework) oder per Graphql (graphene-django).

Dieser Ansatz hat viele Vorteile: Die Frontendentwicklung profitiert in der Regel sehr von einem komponentenbasierten Ansatz. Websiten bestehen nun mal aus einzelnen Komponenten. Und diese bestehen beinhalten in der Regel HTML, CSS und Javascript, was man auch gerne gemeinsam organisieren möchte, beispielsweise um es auch an andere Stelle wieder zu nutzen.

Natürlich gibt es eine ganze Menge weiterer Gründe warum für ein Projekt ein separates Frontend mit client side rendering sinnvoll ist (z.B. offline Verfügbarkeit, Geschwindigkeit, mobile Apps). Und dazu kommen neue Herausforderungen (z.B. bei der SEO). Leider erzeugt dieser Ansatz oft einen nicht unerheblichen Overhead.

Der Einsatz von Graphql erleichtert die Koordinierung der Schnittstelle zwischen Back- und Frontend. Aber trotzdem müssen immer wieder Informationen über Schnittstellen zur Verfügung gestellt werden nur damit das Frontend genug Informationen hat um anschließend die eigentlichen Nutzdaten vom Backend anfordern zu können.
Wenn man in der Vergangenheit viel mit Django Templates und Forms gearbeitet hat, vermisst man schnell die Leichtigkeit, die man vom Arbeiten mit, von Server gerenderten Templates, z.b. mit Django Forms gewöhnt war. Hat man einmal seine „Grundgerüst“ fertig können neue Funktionalitäten oder ganze Unterseiten oft ohne eine einzige Zeile Frontendcode erstellt werden.

Webcomponents bieten einem eine Möglichkeit Komponenten zu erstellen die man sowohl im server- als als auch im clientseitigen Rendering einsetzen kann. Sind sie einmal gebaut kann man sie immer wieder verwenden und ist dabei völlig frei bei der Wahl der Systemarchitektur.
Klingt gut? Dann schauen wir uns das doch mal im Detail an. Fangen wir ganz am Anfang an.

Native HTML Elemente

Wenn du eine Webseite erstellst, dann setzt du diese aus einzelnen Komponenten zusammen. Dazu nutzt du auf – die ein oder andere weise – die im HTML Standard festgelegten Elemente wie z.B. input, select, textarea, h, p, nav oder table.

Diese werden dann mithilfe von CSS (selbstgeschrieben oder von einem Framework wie z.B. Bootstrap oder Antdesign), und ggf. Bildern bzw. Icons in Form gebracht, so dass einem nicht mehr die Augen bluten – blankes HTML ohne jedes Styling ist in keinen Browser ein schöner Anblick.
Wenn man eine sehr einfache Website erstellen möchte dann braucht man auch nicht mehr. Die nativen HTML Elemente sind im Laufe der Jahre immer besser geworden. Mittlerweile kann man einfach eine Farbauswahl über ein <input type=“color“> oder Video über das <video> Tag einbinden. Sogar ein <dialog> Element für Popup Dialoge ist in der mache (https://developer.mozilla.org/de/docs/Web/HTML/Element/dialog).
Wenn man aber eine kompliziertere Webapplikation entwickelt, stellt man schnell fest, dass wesentliche Komponenten einfach nicht existieren. Ein relative übliches Beispiel wäre der eben erwähnte Popup Dialog. Man kann ihn zwar bei aktuellen Browsern schon per Polyfill „nachrüsten“ (https://github.com/GoogleChrome/dialog-polyfill), aber man hat auch schon vor 15 Jahren Popup Dialoge in Websiten genutzt. Ein anderes prominentes Beispiel wäre eine Bewertungskomponente mit 0-5 Sternen, wie man sie beispielsweise von Online Stores kennt.

Was hat man also vor 15 Jahren gemacht wenn man einen UI-Dialog brauchte den man auch stylen kann? Naja, es gab noch kein React oder Vue.js – also hat man sich halt was geschnitzt.

The Old School way – jQuery-UI, Boostrap und Konsorten

Natürlich kann man eine Dialog Komponente einfach selbst schreiben: ein divs, ein paar Buttons, ein wenig css (z-index, display:none/block),ein wenig Javascript fürs Event Handling und fertig ist die Laube.

Das ganze lässt sich dann auch Wiederverwenden und funktioniert auch überall. Typischerweise vergibt man dann beim Einsatz ein paar Klassen und bekommt nutzt das ganze dann in etwas so:

<div class=“dialog dialog--container“>
    <div class=“dialog dialog—title“>Mein Title</div>
    <div class=“dialog dialog—content>Super Content</div>
     <div class=“dialog dialog—buttons“>
         <button class=“button button-primary>Ok</button>
         <button class=“button button-secondary>Nee Abbrechen</button>
    </div>
</div>
 
<script>
function loadDialogs() {
    let dialogs = document.querySelectorAll(„.dialog—container“);
    for (dialog of dialogs) {
        my_js_lib_dialog_func(element)
    }
}
loadDialogs()
</script>

Natürlich war auch Javascript vor 15 Jahren noch echt nicht gut entwickelt und an sowas wie documen.querySelectorAll war nicht zu denken. Dankenswerter Weise gab es vor 15 Jahren von jQuery was wir dafür definitiv genutzt hätten. Und weil viele Popup Dialoge und auch alles mögliche andere (Slider, Color- und Datepicker, Drag and Drop usw.) brauchten gabs dann auch schnell fertige Komponentensammlungen in einheitlichem Design wie beispielsweise jQuery-UI.

Vermutlich wirst auch du auf Komponenten dieser Art, auf die eine oder andere Weise, genutzt haben. jQuery ist ziemlich aus der Mode gekommen – aber kombiniert mit einem GridSystem und mit Augenmerk auf Responsive Design, findet man den gleichen Ansatz auch in moderneren Frontendkomponenten Frameworks, wie beispielsweise Bootstrap, AntDesign, Material usw. Allerdings muss man zum nutzen ziemlich viel Boilerplate Code schreiben, die HTML Struktur unseres Dialogs muss korrekt eingehalten werden und das ganze ist alles andere als intuitiv.

Den Boilerplate kann man mit einem Template System bekämpfen. Im Falle von djangomittemplates mit {% include %} und der Nutzung von{% with title=“Mein Title“ content=content_context_var %}. Aber intiutiv ist definitiv anders. Insbesondere wenn man mehrere in einander verschachtelte Komponenten nutzt wird es sehr schnell schwierig nachzuvollziehen was eigentlich vorgeht. Was wir gerne hätten wäre sowas wie:

<Dialog title=“Mein Title“ content=“{{ content }}“>
    <DialogButtonscallbacks={{ button_data }} />
</Dialog>

Diese Idee wird von komponentenbasierten JS Frameworks wie beispielsweis eAngular,React oder Vue.js verfolgt.

„Moderne“ komponentenbasierte Frontendentwicklungsframeworks

In React, Angular oder Vue.js definiert man eigene Komponenten, die dann auch ihren eigenes HTML Element bekommen. Sind sie einmal geschrieben können sie in etwa wie oben skizziert genutzt werden. Dabei nutzt jedes Framework sein eigenes Templatesystem und verschiedene Arten der Stateverwaltung und des Datenaustausches zwischen einzelnen Komponenten.

Die Systeme funktionieren gut und schnell und solange man das jeweilige Framework nicht verlässt und alle benötigten Daten vom Backend geliefert bekommt (wahrscheinlich am besten über eine Schnittstelle die am Ende JSON ausliefert, das man leicht in Javascript parsen und dann weiterverarbeiten kann) ist die Welt in Ordnung.
Wie zuvor beschrieben opfert man dafür ein wenig Komfort im Backend und erzeugt etwas Overhead, jedoch gerade bei größeren Projekten überwiegen schnell die Vorteile eines sauber geordneten und in Komponenten gegliederten Frontends.

Jedoch sind die Komponenten aber auch nicht unter den einzelnen Frameworks kompatibel. Für weitverbreitete UI-Frameworks wie die oben erwähnten (Bootstrap, AntDesign, Material) existieren Portierungen, aber das ist natürlich nicht für alle Komponenten der Fall. Und wenn man selbst Komponenten schreibt kann man diese leider auch nur in anderen Projekten wiederverwenden die den gleichen Techstack nutzen. Für Projekte in denen aus anderen Gründen serverseitiges Rendering gewünscht wird, ist eine Integration nicht unmöglich aber ziemlich umständlich.

Desweiteren handelt es sich hier immer um ein eigenes Ökosystem und vor 10 Jahren hätten wahrscheinlich auch die wenigsten vermutet das jQuery heute nur noch so wenig verwendet wird. Über viele Jahre gewachsene und gepflegte Projekt wie beispielsweise jquery DataTables (https://www.datatables.net/) veralten so und können in „modernen“ Frontendstacks nicht mehr eingesetzt werden.

Wäre es nicht schön wenn Komponenten nur mit nativen Webtechnologien bauen könnte, so dass sichergestellt ist das diese genau wie ein input Element auch nach 25 Jahren noch genauso überall einsetzbar sind?
Dieses Versprechen wollen Webcomponents erfüllen.

Was sind Webcomponents und wofür sind sie da?

Webcomponents werden entstehen im Grunde aus der Kombination von drei nativen Webtechnologien die mittlerweile von jedem modernen Browser (für die alten eckligen gibt es Polyfills) unterstützt werden. Diese werden über eine Javascript API gesteuert.

Die drei Technologien sind:

  • Custom Elements
    Hierüber lässt sich ein HTML Element mit ,in relativ großem Rahmen, frei definierbarenNamen erstellen. Beispielsweise <modal-dialog></modal-dialog>.
  • Shadow Dom
    Hiermit kann können alle children eines HTML Elements quasi nach außen abgekapselt werden. Schließlich sollte es nach außen egal sein aus welchen Einzelteilen sich meine 5 Sterne Bewertungskomponente zusammen setzt.
    Diese Technologie wird so oder so ähnlich auch beispielsweise von React oder Vue.js massiv eingesetzt um die Geschwindigkeit des Renderings massiv zu erhöhen.
  • HTML Templates
    <template> und <slot> sind native HTML Element die genau das tuen was der Name suggeriert. Hiermit kann man in nativen HTML mit ein wenig JS ein Templatesystem aufbauen.

Eine genau Beschreibung, wie man mithilfe dieser drei Technologien Webcomponents erstellt, würde wohl den Rahmen dieses Beitrags sprengen deswegen verweise ich hier auf die exzellente Dokumentation von Mozzilla. Auf Deutsch ist diese ein wenig seltsam übersetzt: (https://developer.mozilla.org/de/docs/Web/Web_Components). Wahrscheinlich ist das ganze auf Englisch: (https://developer.mozilla.org/en-US/docs/Web/Web_Components) flüssiger zu lesen.
Wenn du dich einmal eingelesen hast wirst du aber schnell feststellen, dass hier wieder eine menge Boilerplate Code anfällt und der Einstieg gar nicht mal so einfach ist. Deswegen gibt es Webcomponent Frameworks wie z.B. stencil.js (https://stenciljs.com/).
Damit lassen sich Komponenten in typescript erstellen. Das ganze fühlt sich sehr ähnlich an wie die Entwicklung von einzelnen React Komponenten.

Aber Moment – da haben wir ja wieder ein Framework das veralten kann. Was haben wir also gewonnen?

Nun ja, wenn man eine einzelne Komponente fertig entwickelt hat ist das Ergebnis genau wie bei einer Vue.js, React oder Angular Anwendung, nativer statischer Code, den man in einem beliebigen Webprojekt einfach durch einen JS import einbinden kann.
Aber im Gegensatz zu Vue.js, React oder Angular kann man einzelne Komponenten exportieren, die sich dann auch im nativen HTML genauso nutzen lassen. Wir sind also bei unserem herbeigeträumten:

<Dialog title=“Mein Title“ content=“{{ content }}“>
    <DialogButtonscallbacks={{ button_data }} />
</Dialog>

angekommen.

Alles was wir dazu tuen müssen ist die gewünschten Komponente(n) vorher zu exportieren und dann beispielsweise in Django so in unserer vermutlich vorhandenen base.html zu importieren:

<script type="module" src="{% static &apos;webcomponents/webcomponents.esm.js&apos; %}"></script>

TL;DR

Webkomponents können ohne besondere Anforderungen oder Abhängigkeiten zu bestimmten Frontend Bibliotheken entwickelt werden und basieren auf standardisierten Technologien die auch in vielen Jahren noch von allen Browsern unterstützt werden sollten.
Daher eignen sie sich hervorragend zum Entwickeln von Komponenten mit hohem Wiederverwendungswert.
Auch bei der Geschwindigkeit, der Kapselung und in der Einfachheit des Gebrauchs sind sie den moderen Frontend Frameworks, wie React oder Vue.js, mindestends ebenbürtig. Deswegennutzen wir diese auch bei Webrunners – nicht nur in Kombination mit Django.