JavaScript-Startoptimierung

Wenn wir Websites erstellen, die stark auf JavaScript angewiesen sind, zahlen wir manchmal für das, was wir senden, auf eine Weise, die wir nicht immer leicht erkennen können. In diesem Artikel erfährst du, warum ein wenig Disziplin hilfreich sein kann, wenn du möchtest, dass deine Website auf Mobilgeräten schnell geladen und interaktiv wird. Weniger JavaScript bedeutet weniger Zeit für die Netzwerkübertragung, weniger Zeit für die Dekomprimierung von Code und weniger Zeit für das Parsen und Kompilieren dieses JavaScripts.

Netzwerk

Wenn die meisten Entwickler über die Kosten von JavaScript nachdenken, denken sie an die Download- und Ausführungskosten. Je mehr Byte JavaScript über die Leitung gesendet werden, desto länger dauert es, je langsamer die Verbindung eines Nutzers ist.

Wenn ein Browser eine Ressource anfordert, muss diese Ressource abgerufen und dann dekomprimiert werden. Ressourcen wie JavaScript müssen vor der Ausführung geparst und kompiliert werden.

Das kann ein Problem sein, da der effektive Netzwerkverbindungstyp eines Nutzers möglicherweise nicht 3G, 4G oder WLAN ist. Sie können sich im WLAN eines Cafés befinden, aber mit einem Mobilfunk-Hotspot mit 2G-Geschwindigkeit verbunden sein.

So können Sie die Kosten für die Netzwerkübertragung von JavaScript reduzieren:

  • Nur den Code senden, den ein Nutzer benötigt.
    • Verwenden Sie die Codeaufteilung, um JavaScript in kritische und nicht kritische Elemente aufzuteilen. Modul-Bundler wie webpack unterstützen Code-Splitting.
    • Nicht kritischen Code verzögert laden.
  • Reduzierung
  • Komprimierung
    • Verwenden Sie mindestens gzip, um textbasierte Ressourcen zu komprimieren.
    • Verwenden Sie stattdessen Brotli ~q11. Brotli übertrifft gzip beim Komprimierungsverhältnis. CertSimple konnte damit die Größe der komprimierten JS-Bytes um 17 % reduzieren und LinkedIn die Ladezeiten um 4 % verkürzen.
  • Nicht verwendeten Code entfernen
  • Code im Cache speichern, um Netzwerkaufrufe zu minimieren.
    • Verwenden Sie HTTP-Caching, damit Browser Antworten effektiv im Cache speichern. Bestimme die optimale Lebensdauer für Skripts (max-age) und stelle Validierungstokens (ETag) bereit, um die Übertragung unveränderter Bytes zu vermeiden.
    • Das Caching von Service Workern kann Ihre App netzwerkresistent machen und Ihnen schnellen Zugriff auf Funktionen wie den Code-Cache von V8 ermöglichen.
    • Verwenden Sie langfristiges Caching, um zu vermeiden, dass Ressourcen, die sich nicht geändert haben, noch einmal abgerufen werden müssen. Wenn Sie Webpack verwenden, finden Sie weitere Informationen unter Dateinamens-Hashing.

Parsen/Kompilieren

Nach dem Herunterladen ist einer der größten Kostenfaktoren von JavaScript die Zeit, die eine JS-Engine benötigt, um diesen Code zu parsen/kompilieren. In den Chrome-Entwicklertools sind Parsen und Kompilieren Teil der gelben „Scripting“-Zeit im Bereich „Leistung“.

ALT_TEXT_HERE

Auf den Tabs „Bottom-up“ und „Aufrufbaum“ werden die genauen Parse-/Kompilierungszeiten angezeigt:

ALT_TEXT_HERE
Chrome-Entwicklertools > Bereich „Leistung“ > „Bottom-up“. Wenn die Runtime Call Stats von V8 aktiviert sind, können wir die Zeit sehen, die in Phasen wie „Parse“ und „Compile“ verbracht wird.

Aber warum ist das wichtig?

ALT_TEXT_HERE

Wenn das Parsen/Kompilieren von Code lange dauert, kann sich die Interaktion eines Nutzers mit Ihrer Website erheblich verzögern. Je mehr JavaScript Sie senden, desto länger dauert es, bis es geparst und kompiliert wird, bevor Ihre Website interaktiv ist.

Byte für Byte ist JavaScript für den Browser teurer zu verarbeiten als ein Bild oder eine Webschriftart gleicher Größe – Tom Dale

Im Vergleich zu JavaScript ist die Verarbeitung von Bildern gleicher Größe mit zahlreichen Kosten verbunden (sie müssen immer noch decodiert werden!). Auf durchschnittlicher mobiler Hardware wirkt sich JS jedoch eher negativ auf die Interaktivität einer Seite aus.

ALT_TEXT_HERE
JavaScript- und Bild-Bytes haben sehr unterschiedliche Kosten. Bilder blockieren in der Regel nicht den Hauptthread und verhindern nicht, dass Oberflächen während der Decodierung und Rasterung interaktiv werden. JS kann jedoch die Interaktivität aufgrund von Parsing-, Kompilierungs- und Ausführungskosten verzögern.

Wenn wir davon sprechen, dass das Parsen und Kompilieren langsam ist, ist der Kontext wichtig. Wir sprechen hier von durchschnittlichen Smartphones. Durchschnittliche Nutzer haben möglicherweise Smartphones mit langsamen CPUs und GPUs, ohne L2-/L3-Cache und mit begrenztem Arbeitsspeicher.

Netzwerk- und Gerätefunktionen stimmen nicht immer überein. Ein Nutzer mit einer hervorragenden Glasfaserverbindung hat nicht unbedingt die beste CPU, um JavaScript zu parsen und auszuwerten, das an sein Gerät gesendet wird. Das gilt auch umgekehrt: Eine schlechte Netzwerkverbindung, aber eine extrem schnelle CPU. – Kristofer Baxter, LinkedIn

Unten sehen Sie die Kosten für das Parsen von ca. 1 MB dekomprimiertem (einfachem) JavaScript auf Low-End- und High-End-Hardware. Die Zeit, die zum Parsen/Kompilieren von Code benötigt wird, unterscheidet sich bei den schnellsten Smartphones auf dem Markt und durchschnittlichen Smartphones um das Zwei- bis Fünffache.

ALT_TEXT_HERE
Diese Grafik zeigt die Parse-Zeiten für ein 1 MB großes JavaScript-Bundle (~250 KB gzipped) auf Computern und Mobilgeräten verschiedener Klassen. Bei den Kosten für das Parsen sind die dekomprimierten Zahlen zu berücksichtigen, z. B. werden ca. 250 KB mit gzip komprimiertes JS zu ca. 1 MB Code dekomprimiert.

Wie sieht es mit einer echten Website wie CNN.com aus?

Auf dem High-End-iPhone 8 dauert das Parsen/Kompilieren des JS von CNN nur etwa 4 Sekunden, im Vergleich zu etwa 13 Sekunden auf einem durchschnittlichen Smartphone (Moto G4). Dies kann sich erheblich darauf auswirken, wie schnell ein Nutzer vollständig mit dieser Website interagieren kann.

ALT_TEXT_HERE
Oben sehen wir die Parse-Zeiten, die die Leistung des A11 Bionic-Chips von Apple mit dem Snapdragon 617 in durchschnittlicher Android-Hardware vergleichen.

Das zeigt, wie wichtig es ist, auf durchschnittlicher Hardware (wie dem Moto G4) zu testen und nicht nur auf dem Smartphone, das du vielleicht in der Tasche hast. Der Kontext ist jedoch wichtig: Optimieren Sie für die Geräte und Netzwerkbedingungen Ihrer Nutzer.

ALT_TEXT_HERE
Google Analytics kann Aufschluss darüber geben, mit welchen Mobilgeräteklassen die tatsächlichen Nutzer auf eure Website zugreifen. So können Sie die tatsächlichen CPU-/GPU-Einschränkungen besser nachvollziehen.

Senden wir wirklich zu viel JavaScript? Äh, möglicherweise :)

Wenn wir das HTTP-Archiv (die ca. 500.000 besten Websites) verwenden, um den Status von JavaScript auf Mobilgeräten zu analysieren, sehen wir, dass 50 % der Websites mehr als 14 Sekunden brauchen, um interaktiv zu werden. Diese Websites benötigen bis zu 4 Sekunden, um JS zu parsen und zu kompilieren.

ALT_TEXT_HERE

Wenn man die Zeit berücksichtigt, die zum Abrufen und Verarbeiten von JS und anderen Ressourcen benötigt wird, ist es vielleicht nicht überraschend, dass Nutzer eine Weile warten müssen, bis sie das Gefühl haben, dass Seiten einsatzbereit sind. Das können wir auf jeden Fall besser machen.

Wenn Sie nicht kritisches JavaScript von Ihren Seiten entfernen, können Sie die Übertragungszeiten, das CPU-intensive Parsen und Kompilieren sowie den potenziellen Arbeitsspeicher-Overhead reduzieren. Außerdem werden Ihre Seiten so schneller interaktiv.

Ausführungszeit

Nicht nur das Parsen und Kompilieren kann Kosten verursachen. Die JavaScript-Ausführung (Ausführen von Code nach dem Parsen/Kompilieren) ist einer der Vorgänge, die im Hauptthread ausgeführt werden müssen. Lange Ausführungszeiten können auch dazu führen, dass Nutzer erst später mit Ihrer Website interagieren können.

ALT_TEXT_HERE

Wenn die Ausführung des Scripts länger als 50 ms dauert, wird die Time to Interactive um die gesamte Zeit verzögert, die zum Herunterladen, Kompilieren und Ausführen des JS erforderlich ist. – Alex Russell

Um dieses Problem zu beheben, ist es sinnvoll, JavaScript in kleine Blöcke aufzuteilen, damit der Hauptthread nicht blockiert wird. Prüfen Sie, ob Sie den Arbeitsaufwand während der Ausführung reduzieren können.

Sonstige Kosten

JavaScript kann die Seitenleistung auch auf andere Weise beeinträchtigen:

  • Memory. Seiten können aufgrund von GC (Garbage Collection) häufig ruckeln oder pausieren. Wenn ein Browser Speicher freigibt, wird die JS-Ausführung pausiert. Ein Browser, der häufig Speicher freigibt, kann die Ausführung also häufiger pausieren, als uns lieb ist. Vermeiden Sie Speicherlecks und häufige GC-Pausen, um Seiten ohne Ruckeln zu halten.
  • Während der Laufzeit kann lang laufendes JavaScript den Hauptthread blockieren, was dazu führt, dass Seiten nicht mehr reagieren. Wenn Sie die Arbeit in kleinere Teile aufteilen (mit requestAnimationFrame() oder requestIdleCallback() für die Planung), können Sie Probleme mit der Reaktionsfähigkeit minimieren und so den Messwert Interaction to Next Paint (INP) verbessern.

Muster zur Reduzierung der JavaScript-Bereitstellungskosten

Wenn Sie die Parsing-/Kompilierungs- und Netzwerkübertragungszeiten für JavaScript gering halten möchten, können Sie Muster wie routenbasiertes Chunking oder PRPL verwenden.

PRPL

PRPL (Push, Render, Pre-cache, Lazy-load) ist ein Muster, das die Interaktivität durch aggressives Code-Splitting und Caching optimiert:

ALT_TEXT_HERE

Sehen wir uns die Auswirkungen an.

Wir analysieren die Ladezeit beliebter mobiler Websites und progressiver Web-Apps mithilfe der Laufzeitaufrufstatistiken von V8. Wie wir sehen, nimmt die Parse-Zeit (orange dargestellt) einen erheblichen Teil der Zeit ein, die viele dieser Websites benötigen:

ALT_TEXT_HERE

Wego, eine Website, die PRPL verwendet, schafft es, eine niedrige Parse-Zeit für ihre Routen aufrechtzuerhalten und sehr schnell interaktiv zu werden. Viele der anderen oben genannten Websites haben Codeaufteilung und Leistungsbudgets eingeführt, um ihre JS-Kosten zu senken.

Progressives Bootstrapping

Viele Websites optimieren die Sichtbarkeit von Inhalten auf Kosten der Interaktivität. Um bei großen JavaScript-Bundles ein schnelles First Paint zu erreichen, verwenden Entwickler manchmal serverseitiges Rendering und „aktualisieren“ es dann, um Event-Handler anzuhängen, wenn das JavaScript schließlich abgerufen wird.

Seien Sie vorsichtig – dies ist mit eigenen Kosten verbunden. 1) Ihr sendet im Allgemeinen eine größere HTML-Antwort, was die Interaktivität beeinträchtigen kann. 2) Der Nutzer kann sich in einem „Uncanny Valley“ befinden, in dem die Hälfte der Inhalte erst dann interaktiv ist, wenn die JavaScript-Verarbeitung abgeschlossen ist.

Progressives Bootstrapping ist möglicherweise ein besserer Ansatz. Senden Sie eine Seite mit minimaler Funktionalität, die nur aus dem HTML/JS/CSS besteht, das für die aktuelle Route erforderlich ist. Wenn mehr Ressourcen verfügbar sind, kann die App mehr Funktionen per Lazy Loading laden und freischalten.

ALT_TEXT_HERE
Progressive Bootstrapping von Paul Lewis

Der heilige Gral ist, Code proportional zu dem zu laden, was im sichtbaren Bereich ist. PRPL und progressives Bootstrapping sind Muster, die dabei helfen können.

Zusammenfassung

Die Übertragungsgröße ist für Netzwerke mit geringer Bandbreite entscheidend. Die Parse-Zeit ist für CPU-gebundene Geräte wichtig. Es ist wichtig, diese Werte niedrig zu halten.

Teams haben gute Erfahrungen mit strengen Leistungsbudgets gemacht, um die Übertragungs- und Analyse-/Kompilierungszeiten für JavaScript niedrig zu halten. Weitere Informationen finden Sie im Artikel „Can You Afford It?: Real-world Web Performance Budgets" for guidance on budgets for mobile.

ALT_TEXT_HERE
Es ist sinnvoll, sich zu überlegen, wie viel JS-„Spielraum“ die von uns getroffenen Architekturentscheidungen für die App-Logik lassen können.

Wenn ihr eine Website für Mobilgeräte erstellt, solltet ihr versuchen, sie auf repräsentativer Hardware zu entwickeln, die JavaScript-Analyse-/Kompilierungszeiten niedrig zu halten und ein Leistungsbudget festzulegen, damit euer Team die JavaScript-Kosten im Auge behalten kann.

Weitere Informationen