JavaScript-Start-up-Optimierung

Addy Osmani
Addy Osmani

Da wir Websites stärker abhängig von JavaScript erstellen, zahlen wir manchmal für das, was wir senden, auf eine Weise, die wir nicht immer sehen können. In diesem Artikel erfahren Sie, warum ein wenig Disziplin Ihnen dabei helfen kann, Ihre Website auf Mobilgeräten schnell zu laden und interaktiv zu gestalten. Wenn Sie weniger JavaScript ausliefern, reduziert sich der Zeitaufwand für die Netzwerkübertragung, das Dekomprimieren von Code und das Parsing und Kompilieren des JavaScript-Codes.

Die meisten Entwickler denken bei den Kosten für JavaScript an die Download- und Ausführungskosten. Je langsamer die Verbindung des Nutzers ist, desto länger dauert das Senden von mehr JavaScript-Byte.

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

Das kann zu einem Problem werden, da der effektive Netzwerkverbindungstyp eines Nutzers möglicherweise nicht 3G, 4G oder WLAN ist. Sie können im Café-WLAN, aber mit einem mobilen Hotspot mit 2G-Geschwindigkeiten verbunden sein.

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

  • Nur den Code senden, den der Nutzer benötigt:
    • Verwenden Sie die Codeaufteilung, um den JavaScript-Code in wichtige und nicht wichtige Elemente aufzuteilen. Modul-Bundler wie webpack unterstützen die Codeaufteilung.
    • Lazy Loading in nicht kritischem Code.
  • Reduzierung
  • Komprimierung
    • Verwenden Sie mindestens gzip, um textbasierte Ressourcen zu komprimieren.
    • Sie können beispielsweise Brotli verwenden (~q11). Brotli übertrifft gzip bei der Komprimierungsrate. CertSimple reduzierte damit die Größe komprimierter JS-Byte um 17% und LinkedIn spart 4% bei den Ladezeiten.
  • Entfernen Sie nicht verwendeten Code.
  • Code im Cache speichern, um Netzwerkfahrten zu minimieren.
    • Verwenden Sie HTTP-Caching, damit Browser Antworten effektiv im Cache speichern. Legen Sie die optimale Lebensdauer für Skripts fest (max-age) und geben Sie Validierungstokens (ETag) an, um die Übertragung unveränderter Byte zu vermeiden.
    • Das Service-Worker-Caching kann Ihr Anwendungsnetzwerk stabil machen und Ihnen einen schnellen Zugriff auf Funktionen wie den Code-Cache von V8 ermöglichen.
    • Verwenden Sie langfristiges Caching, damit nicht geänderte Ressourcen nicht noch einmal abgerufen werden müssen. Wenn Sie Webpack verwenden, finden Sie weitere Informationen unter Hashing von Dateinamen.

Parsen/Kompilieren

Nach dem Download verursacht eine JavaScript-Engine die schwersten Kosten für JavaScript, den Code zu parsen/zu kompilieren. In den Chrome-Entwicklertools ist das Parsen und Kompilieren Teil der gelben „Scripting“-Zeit im Bereich „Leistung“.

ALT_TEXT_HERE

Die Tabs Bottom-Up- und Aufruf-Baum zeigen die genauen Parse-/Compile-Zeiten an:

ALT_TEXT_HERE
Chrome-Entwicklertools: Bereich „Leistung“ > Bottom-up-Effekt. Wenn die Laufzeitaufrufstatistik in V8 aktiviert ist, sehen Sie die Zeit, die in Phasen wie dem Parsen und Kompilieren aufgewendet wurde.

Aber warum ist das wichtig?

ALT_TEXT_HERE

Wenn Sie viel Zeit mit dem Parsen oder Kompilieren von Code verbringen, kann dies dazu führen, dass ein Nutzer deutlich länger mit Ihrer Website interagieren kann. Je mehr JavaScript-Elemente Sie senden, desto länger dauert das Parsen und Kompilieren, bis Ihre Website interaktiv wird.

Byte für Byte ist die Verarbeitung von JavaScript durch den Browser teurer als die gleichwertige Größe eines Bildes oder einer Web-Schriftart – Tom Dale

Im Vergleich zu JavaScript fallen hohe Kosten für die Verarbeitung von Bildern gleicher Größe an (sie müssen immer noch decodiert werden!). Bei durchschnittlicher mobiler Hardware ist jedoch die Wahrscheinlichkeit höher, dass JavaScript die Interaktivität einer Seite beeinträchtigt.

ALT_TEXT_HERE
JavaScript und Bildbyte haben sehr unterschiedliche Kosten. Bilder blockieren in der Regel nicht den Hauptthread oder verhindern nicht, dass Schnittstellen interaktiv werden, während sie decodiert und gerastert werden. JS kann jedoch die Interaktivität aufgrund von Kosten für Parsen, Kompilierung und Ausführung verzögern.

Wenn das Parsen und Kompilieren langsam ist und der Kontext wichtig ist, sprechen wir hier über durchschnittliche Smartphones. Durchschnittliche Nutzer können Smartphones mit langsamen CPUs und GPUs, ohne L2/L3-Cache und sogar mit beschränktem Arbeitsspeicher haben.

Netzwerk- und Gerätefunktionen stimmen nicht immer überein. Ein Nutzer mit einer erstklassigen Glasfaserverbindung hat nicht unbedingt die optimale CPU, um den an sein Gerät gesendeten JavaScript-Code zu parsen und auszuwerten. Dies gilt auch für umgekehrte... eine schlechte Netzwerkverbindung, aber eine extrem schnelle CPU. – Kristofer Baxter, LinkedIn

Unten sind die Kosten für das Parsen von ca. 1 MB dekomprimierten (einfachen) JavaScript-Code auf Low-End- und High-End-Hardware aufgeführt. Zwischen den schnellsten Smartphones auf dem Markt und durchschnittlichen Smartphones besteht ein 2- bis 5-mal schnellerer zeitlicher Abstand zum Parsen/Kompilieren von Code.

ALT_TEXT_HERE
Dieses Diagramm zeigt die Parsing-Zeiten für ein 1 MB großes JavaScript-Bundle (~250 KB mit gzip) für Computer und Mobilgeräte unterschiedlicher Klassen. Bei den Parsing-Kosten sind es die dekomprimierten Zahlen, z. B. ~250 KB gzip-komprimiertes JS, die auf etwa 1 MB Code dekomprimiert wird.

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

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

ALT_TEXT_HERE
Oben sehen wir die Parsing-Zeiten, in denen die Leistung des Chips A11 Bionic von Apple mit der des Snapdragon 617 bei durchschnittlicher Android-Hardware verglichen wird.

Dies unterstreicht, wie wichtig es ist, Tests auf durchschnittlicher Hardware (z. B. Moto G4) statt nur auf dem Smartphone zu testen, das sich in der Hosentasche befindet. Der Kontext ist aber wichtig: Optimieren Sie Ihre App an die Geräte- und Netzwerkbedingungen Ihrer Nutzer.

ALT_TEXT_HERE
Google Analytics kann Einblicke in die Mobilgeräteklassen geben, mit denen echte Nutzer auf deine Website zugreifen. Dadurch können Sie die tatsächlichen CPU-/GPU-Einschränkungen, mit denen sie arbeiten, nachvollziehen.

Senden wir wirklich zu viel JavaScript nach unten? Eher, vielleicht :)

Mit HTTP Archive (die rund 500.000 Top-Websites) zur Analyse des Status von JavaScript auf Mobilgeräten haben wir herausgefunden, dass bei 50% der Websites mehr als 14 Sekunden benötigt werden, um interaktiv zu werden. Diese Websites benötigen bis zu vier Sekunden, um JS nur zu parsen und zu kompilieren.

ALT_TEXT_HERE

Wenn Sie die Zeit zum Abrufen und Verarbeiten von JavaScript und anderen Ressourcen berücksichtigen, überrascht es vielleicht nicht, dass Nutzer eine Weile warten müssen, bevor sie das Gefühl haben, dass die Seiten einsatzbereit sind. Das können wir hier definitiv besser machen.

Wenn du nicht kritisches JavaScript von deinen Seiten entfernst, kannst du die Übertragungszeiten, das CPU-intensives Parsen und Kompilieren sowie den potenziellen Speicheraufwand reduzieren. Dies trägt auch dazu bei, Ihre Seiten schneller interaktiver zu gestalten.

Ausführungszeit

Nicht nur das Parsen und Kompilieren kann Kosten verursachen. Die JavaScript-Ausführung (der Code wird nach dem Parsen/Kompilieren ausgeführt) ist einer der Vorgänge, der im Hauptthread ausgeführt werden muss. Lange Ausführungszeiten können auch dazu führen, wie schnell ein Nutzer mit Ihrer Website interagieren kann.

ALT_TEXT_HERE

Wenn die Ausführung des Skripts länger als 50 ms dauert, verzögert sich die Zeit bis zur Interaktivität um die gesamte Zeit, die für das Herunterladen, Kompilieren und Ausführen des JS erforderlich ist (Alex Russell).

Um dieses Problem zu beheben, sollte JavaScript in kleinen Blöcken enthalten sein, damit der Hauptthread nicht gesperrt wird. Prüfen Sie, ob Sie den Arbeitsaufwand während der Ausführung reduzieren können.

Sonstige Kosten

JavaScript kann die Seitenleistung auf andere Weise beeinflussen:

  • Erinnerung Seiten können aufgrund der automatischen Speicherbereinigung (GC) den Anschein erwecken oder pausieren. Wenn ein Browser Arbeitsspeicher freimacht, wird die JS-Ausführung angehalten. So kann ein Browser, der häufig Speicher sammelt, die Ausführung häufiger unterbrechen, als es uns gefallen könnte. Vermeiden Sie Speicherlecks und häufige GCS-Pausen, um Verzögerungen auf den Seiten zu vermeiden.
  • Während der Laufzeit kann JavaScript mit langer Ausführungszeit den Hauptthread blockieren und nicht mehr reagierende Seiten verursachen. Durch die Aufteilung der Arbeit in kleinere Teile (mit requestAnimationFrame() oder requestIdleCallback() für die Planung) können Reaktionsprobleme minimiert und Interaction to Next Paint (INP) verbessert werden.

Muster zur Reduzierung der JavaScript-Auslieferungskosten

Wenn du versuchst, das Parsen/Kompilieren und die Netzwerkübertragungszeit für JavaScript langsam zu halten, gibt es Muster, die dabei helfen können, wie das routenbasierte Aufteilen oder PRPL.

PRPL

PRPL (Push, Render, Pre-Cache, Lazy Load) ist ein Muster, das durch aggressives Code-Splitting und Caching für die Interaktivität optimiert wird:

ALT_TEXT_HERE

Sehen wir uns die möglichen Auswirkungen an.

Wir analysieren die Ladezeit beliebter mobiler Websites und progressiver Web-Apps mithilfe der Laufzeitaufrufstatistiken von V8. Wie Sie sehen, ist die orangefarbene Parsing-Zeit ein bedeutender Teil davon, wo viele dieser Websites ihre Zeit verbringen:

ALT_TEXT_HERE

Wego, eine Website, die PRPL verwendet, ermöglicht eine niedrige Parsing-Zeit für ihre Routen und wird sehr schnell interaktiv. Viele der anderen Websites oben haben Codeaufteilungs- und Leistungsbudgets eingeführt, um ihre JS-Kosten zu senken.

Progressives Bootstrapping

Viele Websites optimieren die Sichtbarkeit von Inhalten, allerdings auf Kosten der Interaktivität. Bei großen JavaScript-Bundles verwenden Entwickler manchmal serverseitiges Rendering, um bei großen JavaScript-Bundles einen schnellen First Paint zu erzielen. Dann „aktualisieren“ Sie ihn und hängen Event-Handler an, wenn der JavaScript-Code schließlich abgerufen wird.

Aber Vorsicht – das hat seine eigenen Kosten. 1) Sie senden in der Regel eine größere HTML-Antwort, was die Interaktivität erhöhen kann. 2) Sie können den Nutzer in ein überwältigendes Tal versetzen, in dem die Hälfte der Website erst nach Abschluss der JavaScript-Verarbeitung interaktiv sein kann.

Progressives Bootstrapping ist möglicherweise ein besserer Ansatz. Senden Sie eine Seite mit minimalem Funktionsumfang nach unten, die nur aus dem HTML/JS/CSS-Code besteht, der für die aktuelle Route benötigt wird. Je mehr Ressourcen verfügbar sind, desto mehr Funktionen kann die App per Lazy Loading laden.

ALT_TEXT_HERE
Progressive Bootstrapping von Paul Lewis

Das Laden von Code im Verhältnis zu dem, was zu sehen ist, ist der heilige Gral. PRPL und progressives Bootstrapping sind Muster, die dabei helfen können.

Ergebnisse

Die Übertragungsgröße ist für Low-End-Netzwerke von entscheidender Bedeutung. Die Analysezeit ist für CPU-gebundene Geräte wichtig. Eine einfache Wahrnehmung ist wichtig.

Teams konnten mit strengen Leistungsbudgets Erfolg haben, um die JavaScript-Übertragungs- und -Parsing-/Kompilierungszeiten niedrig zu halten. Alex Russell's Can You Afford It?: Reale Real-World-Budgets für die Webleistung finden Sie Hinweise zu Budgets für Mobilgeräte.

ALT_TEXT_HERE
Es ist hilfreich zu überlegen, wie viel JS-Spielraum wir bei unseren Architekturentscheidungen für die App-Logik lassen können.

Wenn du eine Website für Mobilgeräte erstellst, solltest du die Entwicklung auf repräsentativer Hardware vornehmen, deine JavaScript-Parsing-/Kompilierzeiten niedrig halten und ein Leistungsbudget einführen, damit dein Team die JavaScript-Kosten im Auge behalten kann.

Weitere Informationen