Kolumne

Enterprise Tales: Single-Page Applications – Was tun, wenn es langsam wird?

Sven Kölpin

© SuS_Media

Mit Single-Page Applications lassen sich performante Webanwendungen entwickeln. Bei wachsender Projektgröße kann es aber zu Geschwindigkeitseinbußen kommen. Was kann man tun, wenn eine SPA langsam wird?

Das war doch mal alles viel schneller?

Egal ob Angular, React oder selbst entwickelt: Ab einer bestimmten Projektgröße kann es passieren, dass eine Single-Page Application (SPA) nicht mehr flüssig läuft und sich Ladezeiten vervielfachen. Der Grund hierfür ist häufig trivial: Die Anwendung ist schlichtweg zu groß. Durch die Verlagerung von Status und Logik in den Browser führt jedes neue Feature implizit zum Anstieg der Menge an JavaScript-Code. Zusätzlich wächst auch die Größe und Anzahl anderer Ressourcen, wie zum Beispiel von Stylesheets oder Bildern.

Eine große Datenmenge führt nicht nur zu einer höheren Netzwerklatenz und damit zu längeren Ladezeiten, sie hat vor allem einen negativen Einfluss auf die Ausführungszeit des JavaScript-Codes. Dieser muss nämlich zunächst analysiert („Parsing“) und kompiliert werden, bevor er überhaupt ausführbar ist. Vor allem bei älteren oder leistungsschwächeren Endgeräten (etwa Netbooks oder Smartphones) macht sich das durchaus bemerkbar. Aber auch normale Desktoprechner können, je nach Browser, bei großen Mengen an JavaScript-Code an ihre Grenzen kommen. Beispielsweise kann eine SPA im Chrome-Browser schnell laden und flüssig laufen, während die gleiche Anwendung im Internet Explorer 11 um ein Vielfaches langsamer ist. Abbildung 1 zeigt die Zeit, die verschiedene Endgeräte zum Parsen einer 1 MB großen JavaScript-Datei benötigen. Die Zahlen sind durchaus alarmierend, vor allem vor dem Hintergrund, dass viele SPAs weitaus mehr als nur 1 MB groß sind.

Abb. 1: Vergleich der benötigten Zeit für das Parsen von 1 MB JavaScript-Code

Schnelle Analyse

Die meisten der aktuellen Browser liefern umfangreiche Tools zur Untersuchung der Performanz von Webanwendungen. Beispielsweise findet man in den Developertools des Chrome-Browsers Werkzeuge zur Visualisierung von Ausführungszeiten sowie ein Tool zur Durchführung von Qualitätsaudits. Für diesen Beitrag wurden Teile der mobilen Twitter-Seite Twitter Lite mithilfe der Chrome-Tools analysiert. Diese SPA ist mit React entwickelt und benötigt insgesamt ungefähr 2 MB JavaScript-Code für die Ausführung.

Abbildung 2 zeigt die Verwendung von Chromes Visualisierungs- und Simulationstools. Hier lässt sich zum Beispiel erkennen, dass sich die benötigte Ausführungszeit des Codes von nur einer auf über vier Sekunden erhöht, wenn die Anwendung von einem Endgerät mit einer schwachen Leistung verwendet wird. Die Gerätesimulation ist also sehr hilfreich, um etwaige Flaschenhälse einer Webanwendung aufzudecken, obgleich natürlich nicht die vollkommene Realität widergespiegelt wird. Die Erfahrung hat aber gezeigt, dass es sich durchaus lohnen kann, ein Feature vor der Veröffentlichung auf langsameren Geräten zu simulieren.

Abb. 2: Twitter Lite mit und ohne „Throttling“

Abbildung 3 zeigt das Ergebnis des Qualitätsaudits. Chrome verwendet zur Analyse das Open-Source-Tool Lighthouse. Dieses ermöglicht sehr umfangreiche Audits von Webanwendungen nach verschiedenen Kriterien. Twitter Lite schneidet bei der Performanceanalyse mit 35 von 100 möglichen Punkten eher mittelmäßig ab. Vor allem das initiale Rendern („First meaningful paint“) schlägt hier mit über vier Sekunden Wartezeit negativ ins Gewicht – ein typisches Problem bei rein clientseitig rendernden Webapplikationen. Lighthouse kann aber noch viel mehr, als nur die JavaScript-Ausführungszeit zu messen. Es bewertet zum Beispiel, ob Ressourcen effizient ausgeliefert werden (Komprimierung, ungenutzte CSS-Regeln …), oder ob Best Practices der modernen Webentwicklung zum Einsatz kommen. Außerdem lässt sich die Umsetzung von Barrierefreiheit überprüfen und analysieren, ob die Webanwendung dem Progressive-Web-App-Standard entspricht. Eine komplette Beschreibung von Lighthouse findet sich hier.

Abb. 3: Twitter-Lite-Performanceaudit mit Lighthouse

JavaScript-Code verringern

Für den Browser ist die Verarbeitung von JavaScript im Vergleich zu anderen Ressourcen sehr aufwendig. Eine Verringerung des auszuliefernden JavaScript-Codes sollte deshalb stets oberstes Ziel für die Implementierung einer performanten SPA sein. Das heißt natürlich nicht, dass Entwickler dazu aufgefordert sind, ihren Code nur noch in kurzen und kryptischen Ausdrücken zu schreiben, oder gar ganze Features zu entfernen. Die Optimierung des JavaScript-Codes muss während des Build-Prozesses mithilfe von Tools umgesetzt werden.

Optimierung durch Code Splitting

Häufig wird bei SPAs der gesamte Anwendungscode auf einmal an den Browser versendet. Dieses Vorgehen ist durchaus sinnvoll, weil so Netzwerkanfragen eingespart werden. Durch die größere Codemenge erhöhen sich aber die initialen Parse- und Ausführungszeiten. In den seltensten Fällen wird der gesamte JavaScript-Code einer SPA zur gleichen Zeit benötigt. Beispielsweise würde es bei Twitter nur wenig Sinn ergeben, den Code für die Profilverwaltung mitzusenden, wenn ein Benutzer ausschließlich seine Timeline besucht. Deshalb ist es Best Practice, große SPAs in Module aufzuteilen und Code dynamisch zur Laufzeit nachzuladen. Dieses Vorgehen wird Code Splitting genannt. Alle aktuellen SPA-Frameworks und Module Bundler wie webpack oder Parcel unterstützten dieses Feature. Code Splitting kann auf Routenbasis (etwa jede Seite oder jeder Menüpunkt bekommt ein eigenes Bundle) oder auf Komponentenbasis (Komponenten wie Dialoge werden bei Bedarf dynamisch nachgeladen) vorgenommen werden.

Das dynamische Nachladen von JavaScript-Code kann die Performanz einer SPA erheblich verbessern. Dieses Vorgehen führt jedoch auch zu mehr Netzwerkabfragen und zu Verzögerungen beim Navigieren durch die Anwendung – und das sollte bei SPAs ja eigentlich der Vergangenheit angehören. Aggressive Caching-Strategien können hier Abhilfe schaffen. Wenn mehrere Module die gleichen Abhängigkeiten benötigen, kann es zudem passieren, dass beim Erstellen der JavaScript-Bundles Duplikate auftreten. Um dieses Problem zu lösen, bieten Module Bundler wie webpack Mechanismen, um Doppelungen zu erkennen (webpack Bundle Analyzer) und zu beheben (SplitChunksPlugin).

Machine Learning und Code-Splitting

Machine Learning lässt sich dazu verwenden, datengetriebenes und „intelligentes“ Code Splitting zu betreiben. Ein Tool für die Umsetzung hierfür ist Guess.js. Die Bibliothek ermöglicht es, das Verhalten von Benutzern vorauszusagen und so bestimmte Module zu bereits zu laden, bevor sie tatsächlich benötigt werden. Ob und wie viele Module vor der eigentlichen Navigation heruntergeladen werden, wird abhängig von der verfügbaren Bandbreite zur Laufzeit entschieden. Guess.js nutzt Reports von Google Analytics, um Verhaltensmodelle für Benutzer zu berechnen. Das Tool kann in Webpack integriert werden und funktioniert sowohl mit React als auch mit Angular. Zum Zeitpunkt des Verfassens dieses Beitrags ist Guess.js aber noch in der Alphaversion.

Unsere Interview-Reihe: Jakarta EE im Klartext

In unserer Interview-Reihe sprechen Experten der Enterprise-Java-Szene Klartext darüber, was sich im Laufe der letzten Wochen und Monate verändert hat und in welche Richtung sich Jakarta EE entwickelt. Zentrales Thema ist dabei unter anderem, wie Jakarta EE zum neuen Zuhause für Cloud-native Java werden soll.

Ab geht die Reise ins Jakarta-EE-Universum mit unseren Experten!

Optimierung beim Transpilieren

Zum typischen Build-Prozess einer SPA gehört das Transpilieren von JavaScript-Code. Dieser Build-Schritt ermöglicht es, während der Entwicklung neue JavaScript-Sprachfeatures oder alternative, typsichere Sprachen wie TypeScript verwenden zu können, ohne dass zur Laufzeit eine umfassende Browserunterstützung verloren geht. Dazu wird die jeweilige Codebasis in eine Version von JavaScript zurückübersetzt, die von einer breiten Masse an Browsern verstanden wird.

Beim Transpilieren wird im Vergleich zur ursprünglichen Version des Codes im Mittel mehr Code erzeugt, weil etwaige fehlende Sprachfeatures vom Transpiler nachgebildet werden müssen (Tabelle 1). Zwar sind viele der aktuellen Browser heute bereits in der Lage, modernes JavaScript (die aktuelle Version ist ECMA 2018) zu verstehen, trotzdem wird während des Build-Prozesses häufig nur eine nach ECMA 5 transpilierte Version der SPA erzeugt. ECMA 5 ist die JavaScript-Version, die beispielsweise auch der Internet Explorer 11 versteht. Diese ist aber aus den genannten Gründen zumeist größer als die moderne Alternative, was negative Auswirkungen auf die Netzwerklast und Parsezeiten hat und so die Ausführung der SPA verlangsamt.

Es bietet sich an, während des Build-Prozesses unterschiedlich transpilierte JavaScript-Bundles von einer SPA für die verschiedenen Browserversionen zu erstellen. So kann beispielsweise dem Internet Explorer 11 eine nach ECMA 5 transpilierte Version der Anwendung übermittelt werden, während aktuelle Browser wie Edge, Chrome oder Firefox die moderne Alternative bekommen. Für das browserabhängige Transpilieren gibt es eine breite Toolunterstützung, als Beispiel sei hier das Babel-preset-env-Plug-in für den Babel-Transpiler genannt. Ein Nachteil dieses Vorgehens liegt vor allem darin, dass sich die benötigte Build-Zeit der SPA deutlich verlängert – schließlich muss der gleiche Build-Prozess mehrfach für die verschiedenen Sprachversionen ausgeführt werden. Zudem muss ein Server zur Laufzeit ermitteln, welcher Browser die Webanwendung aufruft, um die entsprechende Version des JavaScript-Codes zu versenden. Dazu muss bei einem Request zunächst der User Agent String analysiert werden. Dieser wird vom Browser als HTTP-Header mitgesendet und ermöglicht dessen grundsätzliche Identifizierung. Leider ist die Prüfung des User Agent Strings eine vergleichsweise aufwändige Operation – das Format ist alles andere als trivial und schwer zu parsen. Zudem ist eine hundertprozentig zuverlässige Ermittlung des Browsers leider nicht möglich, weil der Headerparameter leicht manipuliert oder überschrieben werden kann. Das Ausliefern browserabhängig transpilierter JavaScript-Dateien kann trotzdem zu einer Verbesserung der SPA-Performanz beitragen.

ECMA Script 2018 Transpiled to ECMAScript 5
01 class TwitterApp {

02 }

01 function _classCallCheck(i,c) {…}

02

03 var TwitterApp = function TwitterApp() {

04    _classCallCheck(this, TwitterApp);

05 };

Verstehen moderne Browser (z. B. Chrome) Verstehen auch ältere Browser (z. B. IE 11)

Tabelle 1: Codeunterschiede bei der Transpilation mit Babel

Doch serverseitig rendern?

Rein clientseitige Webanwendungen brauchen zur Darstellung des initialen Inhalts naturgemäß länger als serverseitig erstellte Applikationen. Der benötigte JavaScript-Code muss schließlich erst geladen und geparsed werden, bevor das eigentliche Rendern möglich ist. Deshalb sieht ein Benutzer bei SPAs häufig nur eine leere Seite oder einen Ladebildschirm bis relevante Informationen angezeigt werden können (Abb. 3: First meaningful paint).

Sofern die initiale Seitenladezeit einer SPA Probleme bereitet, lohnt es sich über Server Side Rendering (SSR) nachzudenken. Die Idee dahinter ist, die erste Seite einer SPA statisch auf dem Server zu rendern und so fertiges HTML an den Browser zu senden. Das vorgerenderte HTML wird dann auf dem Client mit dem Quellcode der SPA angereichert. So wird der relevante Inhalt einer Seite bereits vor der JavaScript-Ausführung sichtbar.

Alle großen SPA-Frameworks (Angular, React) unterstützen SSR, benötigen aber häufig eine JavaScript Engine für die Erzeugung des HTMLs auf dem Server. Deshalb wird beim SSR oftmals Node.js als serverseitige Runtime verwendet. Es darf aber an dieser Stelle nicht verschwiegen werden, dass der Einsatz von SSR bei komplexen Anwendungen nicht trivial ist und eine zusätzliche Komplexitätsstufe in die Architektur eines Systems bringt. Zudem muss nun neben dem JavaScript-Code noch komplexes HTML übertragen werden, die Netzwerklast steigt also an.

Netzwerklast verringern

Browserspezifisch transpilierte JavaScript-Bundles und die Verwendung von Code Splitting helfen dabei, die auf einmal zu übertragene Datenmenge zu verringern. Es ist aber für die Beschleunigung einer Webanwendung zusätzlich unbedingt notwendig, auch die Gesamtdatenmenge aller Ressourcen zu reduzieren. Deshalb sollten neben den JavaScript-Dateien auch jegliche Stylesheets oder textuelle Bildformate wie SVG minifiziert werden, sodass diese beispielsweise keine Whitespaces oder langen Variablennamen mehr enthalten. Das Minifizieren von Ressourcen gehört mittlerweile zum Standardworkflow aller relevanten Module Bundler und kann in der Regel ohne großen Aufwand aktiviert werden.

Webserver, die eine SPA ausliefern, sollten Datenkompression (etwa gzip) aktiviert haben, um die zu übertragene Datenmenge zu verringern. Bei den meisten Servern ist das ohne großen Aufwand konfigurierbar und das Ergebnis beachtlich. Beispielsweise ist das Hauptmodul der eingangs erwähnten Twitter-Lite-Applikation ungefähr 400 KB groß, durch die Verwendung von gzip müssen aber nur rund 120 KB über die Leitung geschickt werden.

Nicht unerwähnt darf außerdem bleiben, dass vor allem Caching-Strategien dabei helfen können, Requests zu vermeiden und so die Applikation zu beschleunigen. Mit aktuellen Browsern können beispielsweise mithilfe von Service Workern Progressive Web Apps (PWA) implementiert werden, die zu einer Reduzierung von Netzwerklasten und damit zu einer deutlichen Geschwindigkeitszunahme von SPAs beitragen. Twitter Lite ist im Übrigen als PWA umgesetzt. Es gibt eine lesenswerte Fallstudie über die Implementierung dieser Single-Page Application als PWA.

Fazit

Single-page Applications benötigen in der Regel mehr Rechenleistung auf dem Client als klassische, serverseitig gerenderte Webanwendungen. Eine effiziente Umsetzung und Auslieferung spielt hier deshalb eine nicht zu vernachlässigende Rolle. Vor allem End-Consumer-Anwendungen, die heutzutage in der Regel mit leistungsschwächeren mobilen Endgeräten bedient werden, können von performanten SPAs profitieren. Die gute Nachricht ist, dass sich in den letzten Jahren bereits eine Vielzahl an Tools, Frameworks und Best Practices entwickelt haben, die eine performante Gestaltung von Webanwendungen ermöglichen. So lassen sich bereits mit überschaubarem Aufwand beachtliche Optimierungen erzielen.

Selbstverständlich gibt es neben den erwähnten Tools und Patterns noch zahlreiche weitere Strategien, um die Ladezeiten von Webanwendungen zu verringern. Die Analyse und Optimierung des ausgelieferten JavaScript-Codes mit den hier gezeigten Mitteln ist ein guter Anfang für die Beschleunigung einer Single-Page Application.

In diesem Sinne: Stay tuned.

Geschrieben von
Sven Kölpin
Sven Kölpin
Sven Kölpin ist Enterprise Developer bei der open knowledge GmbH in Oldenburg. Sein Schwerpunkt liegt auf der Entwicklung webbasierter Enterprise-Lösungen mittels Java EE.
Kommentare

Hinterlasse einen Kommentar

1 Kommentar auf "Enterprise Tales: Single-Page Applications – Was tun, wenn es langsam wird?"

avatar
400
  Subscribe  
Benachrichtige mich zu: