JavaScript-Strukturierung für HTML: Kopplung minimieren

Posted by in clean code, JavaScript

In der letzten Woche kam eine interessante Frage zum Aufbau einer modularen JavaScript-Codebase auf. Es ging vor allem darum, wie eine bestimmte Funktionalität auf unterschiedlichen HTML-Seiten aktiviert wird. Da es mehrere Module mit unterschiedlichen Features gibt, standen mehrere Ansätze zur Diskussion.

Das Problem mit webpack

In unserem Projekt werden alle Module mit webpack zu einer bundle.js zusammengefasst, die dann auf jeder Seite eingebunden wird. Das JavaScript ist modularisiert in ES2015 Modulen. Die Aktivierung der Funktionalität läuft über die Instanziierung von Klassen, deren Konstruktor über jQuery DOM-Elemente mit EventListenern versorgt.

Soweit so gut! Oder doch nicht? Eher doch nicht, da das von webpack erzeugte bundle.js jedes Modul instanziiert. Nachfolgende Abbildung zeigt das Problem grafisch.

js_html_entkopplung_ein_bundle
image-732

Die HTML-Seite H1 braucht nur Modul JS1, während H2 sowohl JS1 als auch JS2 benötigt. H3 braucht nur das Modul JS3. Aber alle Seiten binden alle JavaScript-Module über die bundle.js ein.

Dabei gibt es mehrere Probleme, die auftreten können:

  • Eine Instanziierung geht schief, weil benötigte DOM-Elemente nicht vorhanden sind auf einer HTML-Seite
  • Alle Module werden auf jeder Seite instanziiert, was bei steigender Anzahl einen Overhead erzeugt, der sich negativ auf die Usability der Seite auswirkt

Lösung 1: Module separat bundeln

Die einfachste Möglichkeit, um Kopplung zu minimieren, ist, die JavaScript-Module in separate Bundles auszulagern. So kann jede Seite selbst entscheiden, was gebraucht wird und was nicht. In unserem Beispiel mit den drei Modulen JS1, JS2 und JS3 würde H1 das Bundle für JS1 laden und H2 jeweils das Bundle für JS1 und JS2. H3 nur das Bundle für JS3.

js_html_entkopplung_separate_bundles
image-733

Der Entwickler kann sich sozusagen die benötigte Funktionalität selbst über die Einbindung zusammenstellen. Es besteht vonseiten des JavaScript nur die Anforderung an das Vorhandensein bestimmter DOM-Elemente, was zu minimaler Kopplung führt, da das JavaScript nichts von der Implementierung der HTML-Seite wissen muss.

Als Nachteil sehe ich die eventuell höhere Netzwerklast aufgrund von mehreren JavaScript-Bundles, die geladen werden müssen. Das dürfte sich aber spätesten mit HTTP2 erledigen.

Lösung 2: HTML steuert Instanziierung über Variable

Wer sich nicht mit mehreren Bundles auseinandersetzen will, da das eventuell zu viel Verwaltungsaufwand bedeutet, der kann diese Lösung probieren: Bei der Instanziierung aller Module wird pro Modul auf das Vorhandensein einer Variable getestet. Ist sie vorhanden, dann wird das Modul instanziiert, ansonsten nicht. So könnte JS1 über die Variable js1, JS2 über js2 und JS3 über js3 aktiviert werden. H1, H2 und H3 steuern über einen Script-Tag mit diesen Variablen die Instanziierung der Module gezielt.

js_html_entkopplung_variablen_aktivierung
image-734

Dieser Ansatz hat die gleichen Vorteile wie der erste. Leider ist die Kopplung etwas größer, da sich das JavaScript jetzt nicht mehr ohne Weiteres ändern kann. Der Variablenname ist nämlich festgelegt. Ändert er sich, dann funktioniert das Modul auf den Seiten, die die alte Schreibweise benutzen nicht mehr!

Für diese Lösung spricht die leichte Handhabbarkeit der JavaScript-Dateien – Es gibt ja nur eine 😉 .

Lösung 3: Module robust implementieren

Für eine einzelne bundle.js gibt es noch eine weitere Möglichkeit: Die Instanziierung der Module robust gestalten. Das würde heißen, wenn ein Modul auf bestimmte DOM-Elemente zugreift und diese sind nicht vorhanden, dann fangen sie das Problem für den Benutzer im Hintergrund ab. Soll eine Seite ein bestimmtes Modul verwenden, muss nur auf das richtige HTML geachtet werden.

Auch hier ist die Kopplung wieder sehr gering, was positiv ist. Wie bei Lösung 2 ist die Handhabbarkeit der JavaScript-Dateien sehr leicht.

Einen Nachteil gibt es jedoch: Gezielt Module ausschalten ist nicht möglich, was bei steigender Anzahl von Modulen eventuell Probleme geben könnte. Wenn sich nämlich zwei Module aufgrund der benötigten HTML-Struktur in die Quere kommen.

Fazit

Egal für welche von den drei Lösungen man sich entscheidet. Die höchste Priorität sollte haben, dass die einzelnen Module keine direkte Kopplung zum HTML besitzen. Sie sollten außerdem bei falscher Benutzung nicht zu einem Konsolenfehler führen und damit die Seite lahmlegen.

Die Steuerung über eine Variable in einem Script-Tag des HTML zur Aktivierung von Funktionalität finde ich einen sehr guten Kompromiss zwischen leichter Handhabbarkeit des Buildprozesses und minimaler Kopplung.

Sie wird nur noch von separaten JavaScript-Dateien übertroffen, die zielgerichtet auf jeder HTML-Seite eingebunden werden. Das hat jedoch den Nachteil, dass es schwerer zu durchblicken ist und mehr Anfragen an den Server gestellt werden.