Eine Anwendungs-Shell ist der minimale HTML-, CSS- und JavaScript-Code, der eine Benutzeroberfläche unterstützt. Die Anwendungs-Shell sollte:
- schnell geladen werden.
- im Cache gespeichert werden
- Inhalte dynamisch anzeigen
Eine Anwendungs-Shell ist das Geheimnis für eine zuverlässig gute Leistung. Stellen Sie sich den Shell Ihrer App als das Code-Bundle vor, das Sie in einem App-Shop veröffentlichen würden, wenn Sie eine native App entwickeln würden. Es ist die Last, die zum Starten erforderlich ist, aber möglicherweise nicht die ganze Geschichte. Die Benutzeroberfläche bleibt lokal und Inhalte werden dynamisch über eine API abgerufen.
Hintergrund
Im Artikel Progressive Web Apps von Alex Russell wird beschrieben, wie sich eine Web-App schrittweise durch die Nutzung und die Einwilligung der Nutzer ändern kann, um eine ähnliche Nutzererfahrung wie bei einer nativen App zu bieten, einschließlich Offlineunterstützung, Push-Benachrichtigungen und der Möglichkeit, zur Startseite hinzugefügt zu werden. Das hängt stark von den Funktionen und Leistungsvorteilen von Dienstworkern und ihren Caching-Funktionen ab. So können Sie sich auf die Geschwindigkeit konzentrieren und Ihren Webanwendungen dieselben sofortigen Ladezeiten und regelmäßigen Updates bieten, die Sie von nativen Anwendungen gewohnt sind.
Um diese Funktionen optimal nutzen zu können, müssen wir unsere Websites neu überdenken: die Anwendungs-Shell-Architektur.
Sehen wir uns an, wie Sie Ihre App mit einer Service Worker-gestützten Anwendungs-Shell-Architektur strukturieren. Wir sehen uns sowohl das client- als auch das serverseitige Rendering an und stellen Ihnen ein End-to-End-Beispiel zur Verfügung, das Sie gleich ausprobieren können.
Zur Veranschaulichung zeigt das Beispiel unten die erste Ladezeit einer App mit dieser Architektur. Unten auf dem Bildschirm wird die Meldung „App ist offline verfügbar“ angezeigt. Wenn später ein Update für die Shell verfügbar wird, können wir den Nutzer bitten, die neue Version zu verwenden.
Was sind Service Worker?
Ein Service Worker ist ein Script, das unabhängig von Ihrer Webseite im Hintergrund ausgeführt wird. Es reagiert auf Ereignisse, einschließlich Netzwerkanfragen von Seiten, die es ausliefert, und Push-Benachrichtigungen von Ihrem Server. Ein Dienst-Worker hat eine absichtlich kurze Lebensdauer. Sie wird aktiviert, wenn ein Ereignis eintritt, und läuft nur so lange, wie es für die Verarbeitung des Ereignisses erforderlich ist.
Im Vergleich zu JavaScript in einem normalen Browserkontext haben Service Worker auch eine begrenzte Anzahl von APIs. Das ist Standard für Worker im Web. Ein Dienst-Worker kann nicht auf das DOM zugreifen, aber auf Dinge wie die Cache API und kann Netzwerkanfragen mit der Fetch API stellen. Die IndexedDB API und postMessage() können auch für die Datenspeicherung und die Nachrichtenübermittlung zwischen dem Dienstarbeiter und den von ihm gesteuerten Seiten verwendet werden. Push-Ereignisse, die von Ihrem Server gesendet werden, können die Notification API aufrufen, um das Nutzer-Engagement zu erhöhen.
Ein Service Worker kann Netzwerkanfragen abfangen, die von einer Seite gesendet werden (was ein Abrufereignis auf dem Service Worker auslöst), und eine Antwort zurückgeben, die aus dem Netzwerk oder aus einem lokalen Cache abgerufen oder sogar programmatisch erstellt wurde. Es ist im Grunde ein programmierbarer Proxy im Browser. Das Tolle daran ist, dass es für die Webseite unabhängig davon, woher die Antwort kommt, so aussieht, als wäre kein Service Worker beteiligt.
Weitere Informationen zu Dienstmitarbeitern finden Sie in der Einführung in Dienstmitarbeiter.
Leistungsvorteile
Service Worker sind leistungsstark für das Offline-Caching, bieten aber auch erhebliche Leistungsvorteile in Form des sofortigen Ladens bei wiederholten Besuchen Ihrer Website oder Webanwendung. Sie können die Anwendungs-Shell im Cache speichern, damit sie offline funktioniert, und die Inhalte mit JavaScript ausfüllen.
So können Sie bei wiederholten Besuchen aussagekräftige Pixel auf dem Bildschirm ohne das Netzwerk erzielen, auch wenn Ihre Inhalte letztendlich von dort stammen. Stellen Sie sich vor, dass Symbolleisten und Karten sofort angezeigt und der Rest der Inhalte dann schrittweise geladen wird.
Um diese Architektur auf echten Geräten zu testen, haben wir unser Beispiel für eine Anwendungs-Shell auf WebPageTest.org ausgeführt. Die Ergebnisse finden Sie unten.
Test 1: Test über Kabel mit einem Nexus 5 mit Chrome Dev
Für die erste Ansicht der App müssen alle Ressourcen aus dem Netzwerk abgerufen werden.Eine sinnvolle Darstellung ist erst nach 1,2 Sekunden möglich. Dank des Service Worker-Cachings wird beim wiederholten Besuch die Website in 0, 5 Sekunden vollständig gerendert und geladen.
Test 2: Tests mit 3G auf einem Nexus 5 mit Chrome Dev
Wir können unser Beispiel auch mit einer etwas langsameren 3G-Verbindung testen. Diesmal dauert es beim ersten Besuch 2,5 Sekunden, bis die Inhalte weitgehend gezeichnet sind. Das Laden der Seite dauert 7,1 Sekunden. Mit dem Service Worker-Caching wird beim wiederholten Besuch eine sinnvolle Darstellung erreicht und das Laden ist in 0, 8 Sekunden abgeschlossen.
Andere Ansichten zeichnen ein ähnliches Bild. Vergleichen Sie die 3 Sekunden, die für die erste aussagekräftige Darstellung in der Anwendungs-Shell benötigt werden:
auf 0,9 Sekunden, wenn dieselbe Seite aus dem Service Worker-Cache geladen wird. So sparen unsere Endnutzer über zwei Sekunden.
Mit der Anwendungs-Shell-Architektur sind ähnliche und zuverlässige Leistungssteigerungen für Ihre eigenen Anwendungen möglich.
Müssen wir die Struktur von Apps überdenken, wenn wir Service Worker verwenden?
Service Worker erfordern einige subtile Änderungen an der Anwendungsarchitektur. Anstatt die gesamte Anwendung in einen HTML-String zu quetschen, kann es von Vorteil sein, AJAX zu verwenden. Hier haben Sie eine Shell (die immer im Cache gespeichert ist und immer ohne Netzwerk gestartet werden kann) und Inhalte, die regelmäßig aktualisiert und separat verwaltet werden.
Die Auswirkungen dieser Aufteilung sind enorm. Beim ersten Besuch können Sie Inhalte auf dem Server rendern und den Service Worker auf dem Client installieren. Bei nachfolgenden Besuchen müssen Sie nur Daten anfordern.
Was ist mit der progressiven Verbesserung?
Service Worker werden derzeit nicht von allen Browsern unterstützt. Die Architektur der Anwendungs-Content-Shell verwendet jedoch progressive Verbesserungen, damit alle Nutzer auf die Inhalte zugreifen können. Nehmen wir zum Beispiel unser Beispielprojekt.
Unten sehen Sie die Vollversion, die in Chrome, Firefox Nightly und Safari gerendert wurde. Ganz links sehen Sie die Safari-Version, bei der die Inhalte ohne einen Service Worker auf dem Server gerendert werden. Rechts sehen wir die Chrome- und Firefox-Nightly-Versionen, die von Service Workern unterstützt werden.
Wann ist es sinnvoll, diese Architektur zu verwenden?
Die Anwendungs-Shell-Architektur eignet sich am besten für dynamische Apps und Websites. Wenn Ihre Website klein und statisch ist, benötigen Sie wahrscheinlich keine Anwendungs-Shell und können die gesamte Website einfach in einem Service Worker-oninstall
-Schritt im Cache speichern. Verwenden Sie den Ansatz, der für Ihr Projekt am sinnvollsten ist. Einige JavaScript-Frameworks empfehlen bereits, die Anwendungslogik von den Inhalten zu trennen, was die Anwendung dieses Musters vereinfacht.
Gibt es bereits Produktions-Apps, in denen dieses Muster verwendet wird?
Die Anwendungs-Shell-Architektur ist mit nur wenigen Änderungen an der Benutzeroberfläche Ihrer gesamten Anwendung möglich und hat sich für große Websites wie die I/O 2015 Progressive Web App von Google und Google Inbox bewährt.
Offline-Anwendungs-Shells bieten eine enorme Leistungssteigerung und werden auch in der offlinefähigen Wikipedia-App von Jake Archibald und in der progressiven Webanwendung Flipkart Lite gut veranschaulicht.
Architektur erläutern
Beim ersten Laden sollten möglichst schnell aussagekräftige Inhalte auf dem Bildschirm des Nutzers angezeigt werden.
Erster Ladevorgang und Laden anderer Seiten
Im Allgemeinen hat die Anwendungs-Shell-Architektur folgende Eigenschaften:
Priorisieren Sie das erste Laden, lassen Sie den Dienst-Worker aber die Anwendungs-Shell im Cache speichern, damit bei wiederholten Besuchen die Shell nicht noch einmal aus dem Netzwerk abgerufen werden muss.
Laden Sie alles andere per Lazy Load oder im Hintergrund. Eine gute Option ist die Verwendung von Durchlese-Caching für dynamische Inhalte.
Verwenden Sie Dienstmitarbeiter-Tools wie sw-precache, um den Dienstmitarbeiter, der Ihre statischen Inhalte verwaltet, zuverlässig im Cache zu speichern und zu aktualisieren. (Weitere Informationen zu sw-precache finden Sie später.)
So gehts:
Der Server sendet HTML-Inhalte, die der Client rendern kann, und verwendet HTTP-Cache-Ablaufsheader für die ferne Zukunft, um Browser ohne Service Worker-Unterstützung zu berücksichtigen. Dateinamen werden mit Hashes bereitgestellt, um sowohl eine Versionierung als auch einfache Updates für später im Anwendungslebenszyklus zu ermöglichen.
Seite(n) enthält Inline-CSS-Stile in einem
<style>
-Tag im Dokument<head>
, um eine schnelle erste Darstellung der Anwendungs-Shell zu ermöglichen. Auf jeder Seite wird das für die aktuelle Ansicht erforderliche JavaScript asynchron geladen. Da CSS nicht asynchron geladen werden kann, können wir Stile mit JavaScript anfordern, da es asynchron und nicht parsergesteuert und synchron ist. Wir können auchrequestAnimationFrame()
verwenden, um Fälle zu vermeiden, in denen wir einen schnellen Cache-Treffer erhalten und Stile versehentlich Teil des kritischen Rendering-Pfads werden.requestAnimationFrame()
erzwingt, dass der erste Frame gerendert wird, bevor die Stile geladen werden. Eine weitere Möglichkeit besteht darin, Projekte wie loadCSS von Filament Group zu verwenden, um CSS asynchron über JavaScript anzufordern.Der Dienstworker speichert einen Cache-Eintrag der Anwendungs-Shell, damit die Shell bei wiederholten Besuchen vollständig aus dem Dienstworker-Cache geladen werden kann, es sei denn, im Netzwerk ist ein Update verfügbar.
Eine praktische Implementierung
Wir haben ein voll funktionsfähiges Beispiel mit der Anwendungs-Shell-Architektur, Vanilla-ES2015-JavaScript für den Client und Express.js für den Server geschrieben. Natürlich steht es Ihnen frei, für den Client- oder Serverteil Ihren eigenen Stack zu verwenden (z. B. PHP, Ruby oder Python).
Service Worker-Lebenszyklus
Für unser Anwendungs-Shell-Projekt verwenden wir sw-precache, das den folgenden Dienstworker-Lebenszyklus bietet:
Ereignis | Aktion |
---|---|
Installieren | Anwendungs-Shell und andere Ressourcen der Single-Page-Anwendung im Cache speichern |
Aktivieren | Alte Caches löschen. |
Abrufen | Eine Single-Page-Web-App für URLs bereitstellen und den Cache für Assets und vordefinierte Teilansichten verwenden. Verwenden Sie das Netzwerk für andere Anfragen. |
Server-Bits
In dieser Architektur sollte eine serverseitige Komponente (in unserem Fall in Express geschrieben) Inhalte und Präsentation getrennt behandeln können. Inhalte können einem HTML-Layout hinzugefügt werden, was zu einem statischen Rendern der Seite führt, oder sie können separat bereitgestellt und dynamisch geladen werden.
Verständlicherweise kann sich Ihre serverseitige Einrichtung stark von der unterscheiden, die wir für unsere Demo-App verwenden. Dieses Muster von Webanwendungen ist mit den meisten Serverkonfigurationen möglich, erfordert aber einige Umstrukturierungen. Wir haben festgestellt, dass das folgende Modell ziemlich gut funktioniert:
Endpunkte werden für drei Teile Ihrer Anwendung definiert: die für Nutzer sichtbaren URLs (Index/Wildcard), die Anwendungs-Shell (Dienst-Worker) und Ihre HTML-Teile.
Jeder Endpunkt hat einen Controller, der ein Handlebars-Layout einzieht, das wiederum Handlebar-Teile und ‑Ansichten einziehen kann. Einfach ausgedrückt sind Teilansichten HTML-Abschnitte, die in die endgültige Seite kopiert werden. Hinweis: JavaScript-Frameworks mit erweiterter Datensynchronisierung lassen sich oft viel einfacher auf eine Application Shell-Architektur umstellen. Sie verwenden eher Datenbindung und Synchronisierung als Teilansichten.
Dem Nutzer wird zuerst eine statische Seite mit Inhalten angezeigt. Auf dieser Seite wird ein Service Worker registriert, sofern unterstützt, der die Anwendungs-Shell und alle zugehörigen Elemente (CSS, JS usw.) im Cache speichert.
Die App-Shell fungiert dann als Single-Page-Webanwendung, die den Inhalt für eine bestimmte URL mit JavaScript per XHR abruft. Die XHR-Aufrufe werden an einen Endpunkt vom Typ „/partials*“ gesendet, der den kleinen HTML-, CSS- und JS-Code zurückgibt, der zum Anzeigen dieser Inhalte erforderlich ist. Hinweis: Es gibt viele Möglichkeiten, dies zu erreichen, und XHR ist nur eine davon. Einige Anwendungen fügen ihre Daten für das erste Rendern inline ein (möglicherweise mit JSON) und sind daher nicht „statisch“ im Sinne von flachem HTML.
Für Browser, die keinen Dienstworker unterstützen, sollte immer ein Fallback-Angebot bereitgestellt werden. In unserer Demo greifen wir auf einfaches statisches serverseitiges Rendering zurück. Dies ist jedoch nur eine von vielen Optionen. Der Service Worker-Aspekt bietet Ihnen neue Möglichkeiten, die Leistung Ihrer Single-Page-Anwendung mithilfe der im Cache gespeicherten Anwendungs-Shell zu verbessern.
Versionsverwaltung für Dateien
Eine Frage, die sich stellt, ist, wie mit der Dateiversionierung und -aktualisierung umgegangen werden soll. Das ist anwendungsspezifisch und die Optionen sind:
Zuerst das Netzwerk und andernfalls die im Cache gespeicherte Version verwenden.
Nur Netzwerk, funktioniert nicht offline.
Die alte Version im Cache speichern und später aktualisieren.
Für die Anwendungs-Shell selbst sollte bei der Einrichtung des Service Workers ein Cache-first-Ansatz verwendet werden. Wenn Sie die Anwendungs-Shell nicht im Cache speichern, haben Sie die Architektur nicht richtig angepasst.
Tools
Wir bieten eine Reihe verschiedener Service Worker-Hilfsbibliotheken, die die Einrichtung des Precachings der Shell Ihrer Anwendung oder die Verarbeitung gängiger Caching-Muster vereinfachen.
sw-precache für die Anwendungs-Shell verwenden
Wenn Sie sw-precache zum Caching der Anwendungs-Shell verwenden, sollten die Bedenken hinsichtlich Dateiüberprüfungen, Installations-/Aktivierungsfragen und des Abrufszenarios für die App-Shell ausgeräumt werden. Fügen Sie „sw-precache“ in den Build-Prozess Ihrer Anwendung ein und verwenden Sie konfigurierbare Platzhalter, um Ihre statischen Ressourcen abzurufen. Anstatt Ihr Service Worker-Script manuell zu erstellen, können Sie mit sw-precache ein Script generieren lassen, das Ihren Cache mithilfe eines Cache-first-Abruf-Handlers sicher und effizient verwaltet.
Bei den ersten Besuchen Ihrer App wird das gesamte erforderliche Caching ausgelöst. Das ist vergleichbar mit der Installation einer nativen App über einen App-Shop. Wenn Nutzer zu Ihrer App zurückkehren, werden nur aktualisierte Ressourcen heruntergeladen. In unserer Demo informieren wir Nutzer mit der Meldung „App-Updates verfügbar“ darüber, dass eine neue Shell verfügbar ist. Aktualisieren Sie die Seite, um die neue Version zu sehen.“ Mit diesem Muster können Nutzer auf einfache Weise darüber informiert werden, dass sie die neueste Version aufrufen können.
sw-toolbox für das Laufzeit-Caching verwenden
Verwenden Sie sw-toolbox für das Laufzeit-Caching mit unterschiedlichen Strategien, je nach Ressource:
cacheFirst für Bilder zusammen mit einem speziellen benannten Cache mit einer benutzerdefinierten Ablaufrichtlinie von N maxEntries.
networkFirst oder „schnellste“ für API-Anfragen, je nach gewünschter Aktualität der Inhalte. „Schnellste“ ist in Ordnung, aber wenn es einen bestimmten API-Feed gibt, der häufig aktualisiert wird, verwenden Sie „networkFirst“.
Fazit
Anwendungs-Shell-Architekturen bieten mehrere Vorteile, sind aber nur für einige Anwendungsklassen sinnvoll. Das Modell ist noch relativ neu und es lohnt sich, den Aufwand und die Vorteile dieser Architektur im Hinblick auf die Gesamtleistung zu bewerten.
Bei unseren Tests haben wir die Vorlagenfreigabe zwischen Client und Server genutzt, um den Aufwand für die Erstellung von zwei Anwendungsschichten zu minimieren. So bleibt die progressive Verbesserung eine Kernfunktion.
Wenn Sie bereits die Verwendung von Service Workern in Ihrer App in Betracht ziehen, sehen Sie sich die Architektur an und überlegen Sie, ob sie für Ihre eigenen Projekte sinnvoll ist.
Mit Dank an unsere Rezensenten: Jeff Posnick, Paul Lewis, Alex Russell, Seth Thompson, Rob Dodson, Taylor Savage und Joe Medley.