Zurück

Frontend ohne Framework – nativ, nicht naiv!

von Jan Müller

„Ein modernes Web-Frontend kann man nur mit Angular, React o. ä. bauen, weil die HTML- und JavaScript-Bordmittel bei Weitem nicht ausreichen. Da helfen auch ein paar kleine Libraries nicht.“ Das hört man häufiger, aber ich bin nicht überzeugt. Stattdessen möchte ich dem reflexartigen Griff zu Angular & Co. eine Alternative gegenüberstellen: WebComponents, also doch die nativen Bordmittel.

Mit WebComponents lässt sich eine Web-Applikation aus unabhängigen, wiederverwendbaren Komponenten entwickeln, ohne sich an ein schwergewichtiges Framework zu binden. Dabei werden die üblicherweise von einem Framework bereitgestellten Features und Tools durch Libraries ersetzt. Diese lassen sich unabhängig voneinander aktualisieren oder austauschen.

Vorab – ich will und kann nicht behaupten, dass Angular & Co. generell schlecht sind und ein reiner WebComponents-Ansatz besser. Es geht mir nur darum, diese Option ins Spiel zu bringen, da sie häufig erst gar nicht ernsthaft in Erwägung gezogen wird.

So auch in meinem aktuellen Projekt, bei dem eine Migration weg vom veralteten Framework AngularJS zu bewältigen war. Wir konnten uns erst im zweiten Anlauf für eine WebComponents-Lösung erwärmen, blicken aber heute auf eine erfolgreiche Migration zurück und sind mit dem Ergebnis glücklich: Ein gelungenes Frontend mit der Library Lit. Entscheidend war unter anderem, dass wir die Migration Schritt für Schritt durchführen und damit die ganze Zeit über lieferfähig bleiben konnten.

Frameworks

Was verstehe ich hier eigentlich unter einem (Frontend-)Framework? Ich meine damit ein Softwaresystem, das mir das Entwickeln eines Frontends an vielen Stellen erleichtert und vieles bereits mitbringt, z. B.: Bootstrapping, State Management, Application Lifecycle, Templating, Data Binding, Dependency Injection, Utilities, Testumgebung, Build-Unterstützung, Orchestrierung von Code und Komponenten, ...
Besonders letzteres ist typisch für ein Framework: Das Framework ruft meinen Code auf und baut ihn zur großen Anwendung zusammen. Anders eine Library: Sie wird im Wesentlichen von meinem Code aufgerufen. Nach dieser Einteilung zählt beispielsweise Angular klar als Framework. React hingegen wird meistens als Library eingestuft, einige sprechen von einer framework-artigen Library. Die Spitzfindigkeiten, wie React jetzt genau einzuordnen ist, sind hier auch gar nicht so wichtig.

Bisher klingt es, als seien Frameworks außerordentlich hilfreich. Was spricht dann dagegen, sie einfach immer zu verwenden?

  • Die Entscheidung für ein Framework ist weitreichend und kann schwer rückgängig gemacht werden. Was passiert, wenn es das falsche ist? Gibt es überhaupt ein Framework, das dauerhaft das richtige ist? In meinem Projekt z. B. war AngularJS seinerzeit sogar eine gute Entscheidung gewesen. Leider aber war es irgendwann schlicht veraltet. Davon wegzukommen bedeutete dann sehr viel Aufwand.
  • Man kauft das Komplettpaket mit allen Vor-, aber auch allen Nachteilen und Beschränkungen. Das ist wie bei einer Pauschalreise, bei der zwar alles vorbereitet, aber dafür schwerer individuell anpassbar ist. Wenn einzelne Teile nicht zu den Projektanforderungen passen, kann man sie nicht einfach austauschen. Stattdessen behilft man sich mit Workarounds und fängt an zu hacken, um das Framework so zu bedienen, wie es bedient werden will.
  • Frameworks sind schwergewichtig.

WebComponents

Dieser HTML-Standard bringt bereits vieles mit, was man für ein komponentenbasiertes Frontend braucht. Beispielsweise Custom Elements: Das sind Komponenten, die man selbst entwickelt, mit JavaScript-Logik und einem eigenen HTML-Tag versieht, z. B.

<hello-button name="Jan"></hello-button>

Der native TypeScript-Code zu dieser Komponente könnte wie folgt aussehen:

class HelloButton extends HTMLElement {
  private name: string;
  private button: HTMLElement;

  connectedCallback(): void {
    this.name = this.getAttribute("name") ?? "";
    this.button = document.createElement("button");
    this.button.innerHTML = "Click me!";
    this.button.addEventListener("click", () => this.updateText());
    this.appendChild(this.button);
  }

  updateText(): void {
    this.button.innerHTML = `Hello ${this.name}!`;
  }
}

customElements.define("hello-button", HelloButton);

Man erkennt sicherlich, dass dieser Ansatz in seiner Rohform nicht skalieren wird. Ein großes Frontend mit interaktiven Elementen und komplexeren Abhängigkeiten ist damit nicht realisierbar. Aber dafür gibt es ja Libraries, z. B. Lit.
Lit ist eine Library zum Erstellen von WebComponents und ermöglicht komfortabel Data Binding, Templating und Lifecycle-Management der Komponenten. Im Gegensatz zu einem Framework wirkt es sich nur auf die gerade entwickelte Komponente aus, dass ich Lit benutze. Es hat keine Nebenwirkungen auf oder Abhängigkeiten zum Rest meines Frontends. Andere Aspekte, wie Dependency Injection, sind Lit fremd. Dafür kann man eine andere Library verwenden (in meinem Projekt z. B. haben wir tsyringe im Einsatz). Für HTTP-Anfragen hingegen braucht man ggf. nicht einmal eine Library, da die native fetch-API bereits ausreicht.

Obiges Beispiel könnte mit Lit beispielsweise so aussehen:

@customElement("hello-button")
class HelloButton extends LitElement {
  @property({ attribute: "name" })
  name: string;
  @state() 
  private text: string = "Click me!";
  
  render(): TemplateResult {
    return html`<button @click=${this.updateText}>${this.text}</button>`;
  }
  
  updateText(): void {
    this.text = `Hello ${this.name}!`;
  }
}

Randnotiz: dieses Beispiel ist nicht genau äquivalent zum obigen nativen Beispiel. Lit erzeugt hier eine Komponente mit Shadow-DOM, eine weitere Säule des WebComponent-Standards, ganz in Sinne abgeschlossener, wiederverwendbarer Komponenten.

Man könnte sagen: Mit Angular entwickelt man ein „Angular-Frontend“. Mit dem hier vorgestellten Ansatz erhält man kein „Lit-Frontend“, sondern ein „Frontend mit Lit“. Natürlich ist Lit nur eine von mehreren Libraries für diesen Zweck. Je nachdem, wie schlank man React benutzt, gilt das eben Gesagte genauso für React.

Was sind die Vorteile dieses Ansatzes?

  • Er ist leichtgewichtig.
  • Man ist nah am Browser-Standard. In gewisser Weise ist der Browser selbst das native „Framework“, das meinen Code aufruft.
  • Er ist langlebig, eben weil Browser-Standards länger leben als Frameworks.
  • Er lässt viel Freiheit für individuelle Gestaltung.

Was sind die Nachteile?

  • Es gibt besonders am Anfang mehr, um das man sich kümmern muss.
  • Man muss umsichtiger sein und wissen was man tut, da man nicht die „Rückendeckung“ eines Frameworks hat.

Letztlich ist es wie bei einer individuell zusammengestellten Reise gegenüber einem All-Inclusive-Urlaub: Man muss sich um vieles kümmern und trägt mehr Verantwortung, dafür bekommt man aber mehr Freiheit. Womit man besser fährt, ist eine individuelle Entscheidung. Es ist gut zu wissen, dass man auch die Variante ohne Framework wählen kann. Das gilt sowohl auf der grünen Wiese, als auch in einer bestehenden Code-Basis.

Mehr zum Thema Webentwicklung:

Desktop, Mobile oder App - professionelle Entwicklung im Web Zurück