Web-apps direct laden met een applicatie-shell-architectuur

Een applicatieshell is de minimale HTML, CSS en JavaScript die een gebruikersinterface aanstuurt. De applicatieshell moet:

  • laad snel
  • in de cache worden opgeslagen
  • inhoud dynamisch weergeven

Een applicatieshell is het geheim van betrouwbaar goede prestaties. Beschouw de schil van uw app als de bundel code die u in een app store zou publiceren als u een native app zou bouwen. Het is de lading die nodig is om van de grond te komen, maar dat is misschien niet het hele verhaal. Het houdt uw gebruikersinterface lokaal en haalt inhoud dynamisch binnen via een API.

App Shell Scheiding van HTML-, JS- en CSS-shell en de HTML-inhoud

Achtergrond

Het Progressive Web Apps- artikel van Alex Russell beschrijft hoe een webapp geleidelijk kan veranderen door gebruik en toestemming van de gebruiker om een ​​meer native-app-achtige ervaring te bieden, compleet met offline ondersteuning, pushmeldingen en de mogelijkheid om aan het startscherm te worden toegevoegd. Het hangt sterk af van de functionaliteit en prestatievoordelen van servicemedewerkers en hun cachingmogelijkheden. Hierdoor kunt u zich concentreren op snelheid , waardoor uw webapps hetzelfde onmiddellijke laden en dezelfde regelmatige updates krijgen die u gewend bent te zien in native applicaties.

Om deze mogelijkheden ten volle te kunnen benutten, hebben we een nieuwe manier van denken over websites nodig: de applicatieshell-architectuur .

Laten we eens kijken hoe u uw app kunt structureren met behulp van een service worker augmented application shell-architectuur . We bekijken zowel client- als server-side rendering en delen een end-to-end voorbeeld dat u vandaag nog kunt proberen.

Om dit punt te benadrukken, toont het onderstaande voorbeeld de eerste keer laden van een app die deze architectuur gebruikt. Let op de toast 'App is klaar voor offline gebruik' onderaan het scherm. Als er later een update van de shell beschikbaar komt, kunnen we de gebruiker informeren om te vernieuwen voor de nieuwe versie.

Afbeelding van een servicemedewerker die wordt uitgevoerd in DevTools voor de applicatieshell

Wat zijn servicemedewerkers ook alweer?

Een servicemedewerker is een script dat op de achtergrond draait, los van uw webpagina. Het reageert op gebeurtenissen, inclusief netwerkverzoeken van pagina's die het aanbiedt en pushmeldingen van uw server. Een servicemedewerker heeft een opzettelijk korte levensduur. Het wordt wakker wanneer het een gebeurtenis ontvangt en blijft slechts actief zolang het nodig is om het te verwerken.

Servicemedewerkers hebben ook een beperkte set API's in vergelijking met JavaScript in een normale browsercontext. Dit is standaard voor werknemers op internet. Een servicemedewerker heeft geen toegang tot de DOM, maar heeft wel toegang tot zaken als de Cache API en kan netwerkverzoeken indienen met behulp van de Fetch API . De IndexedDB API en postMessage() zijn ook beschikbaar voor gebruik voor gegevenspersistentie en berichtenuitwisseling tussen de servicemedewerker en de pagina's die deze beheert. Push-gebeurtenissen die vanaf uw server worden verzonden, kunnen de Notification API aanroepen om de gebruikersbetrokkenheid te vergroten.

Een servicemedewerker kan netwerkverzoeken onderscheppen die vanaf een pagina worden gedaan (wat een ophaalgebeurtenis voor de servicemedewerker activeert) en een antwoord retourneren dat is opgehaald van het netwerk, of is opgehaald uit een lokale cache, of zelfs programmatisch is opgebouwd. In feite is het een programmeerbare proxy in de browser. Het leuke is dat, ongeacht waar de reactie vandaan komt, het op de webpagina lijkt alsof er geen servicemedewerker bij betrokken is.

Voor meer diepgaande informatie over servicemedewerkers kunt u de Inleiding tot servicemedewerkers lezen.

Prestatievoordelen

Servicemedewerkers zijn krachtig voor offline caching, maar bieden ook aanzienlijke prestatieverbeteringen in de vorm van direct laden voor herhaalde bezoeken aan uw site of web-app. U kunt uw applicatieshell in de cache opslaan, zodat deze offline werkt en de inhoud ervan vullen met JavaScript.

Hierdoor kunt u bij herhaalde bezoeken betekenisvolle pixels op het scherm krijgen zonder het netwerk, zelfs als uw inhoud daar uiteindelijk vandaan komt. Zie het als het onmiddellijk weergeven van werkbalken en kaarten en het vervolgens geleidelijk laden van de rest van uw inhoud.

Om deze architectuur op echte apparaten te testen, hebben we ons applicatie-shell-voorbeeld op WebPageTest.org uitgevoerd en de resultaten hieronder weergegeven.

Test 1: testen op kabel met een Nexus 5 met behulp van Chrome Dev

De eerste weergave van de app moet alle bronnen van het netwerk ophalen en levert pas na 1,2 seconde een zinvolle verf op. Dankzij caching van servicemedewerkers bereikt ons herhaald bezoek zinvolle verf en is het laden volledig voltooid in 0,5 seconde .

Webpaginatestverfdiagram voor kabelverbinding

Test 2: Testen op 3G met een Nexus 5 met behulp van Chrome Dev

We kunnen ons exemplaar ook testen met een iets langzamere 3G-verbinding. Dit keer duurt het bij het eerste bezoek 2,5 seconden voor onze eerste betekenisvolle verf. Het duurt 7,1 seconden om de pagina volledig te laden. Met het cachen van servicemedewerkers wordt bij ons herhaalbezoek zinvolle verf bereikt en wordt het laden volledig voltooid in 0,8 seconden .

Webpaginatestverfdiagram voor 3G-verbinding

Andere opvattingen vertellen een soortgelijk verhaal. Vergelijk de 3 seconden die nodig zijn om de eerste betekenisvolle verf in de applicatieshell te bereiken:

Teken de tijdlijn voor de eerste weergave van de webpaginatest

tot de 0,9 seconden die nodig zijn om dezelfde pagina te laden vanuit de cache van onze servicemedewerkers. Er wordt ruim 2 seconden tijd bespaard voor onze eindgebruikers.

Teken de tijdlijn voor herhaalde weergave vanuit Webpaginatest

Soortgelijke en betrouwbare prestatiewinsten zijn mogelijk voor uw eigen applicaties met behulp van de applicatieshell-architectuur.

Moeten servicemedewerkers van ons opnieuw nadenken over de manier waarop we apps structureren?

Servicemedewerkers impliceren enkele subtiele veranderingen in de applicatiearchitectuur. In plaats van uw hele applicatie in een HTML-string samen te persen, kan het nuttig zijn om dingen in AJAX-stijl te doen. Hier heb je een shell (die altijd in de cache wordt opgeslagen en altijd kan opstarten zonder het netwerk) en inhoud die regelmatig wordt vernieuwd en afzonderlijk wordt beheerd.

De gevolgen van deze splitsing zijn groot. Bij het eerste bezoek kunt u inhoud op de server weergeven en de servicemedewerker op de client installeren. Bij volgende bezoeken hoeft u alleen maar gegevens op te vragen.

Hoe zit het met progressieve verbetering?

Hoewel servicewerknemers momenteel niet door alle browsers worden ondersteund, maakt de shell-architectuur van de applicatie-inhoud gebruik van progressieve verbeteringen om ervoor te zorgen dat iedereen toegang heeft tot de inhoud. Neem bijvoorbeeld ons voorbeeldproject.

Hieronder ziet u de volledige versie weergegeven in Chrome, Firefox Nightly en Safari. Helemaal links ziet u de Safari-versie waarbij de inhoud zonder servicemedewerker op de server wordt weergegeven. Aan de rechterkant zien we de Chrome- en Firefox Nightly-versies powered by service worker.

Afbeelding van Application Shell geladen in Safari, Chrome en Firefox

Wanneer is het zinvol om deze architectuur te gebruiken?

De applicatieshell-architectuur is het meest logisch voor apps en sites die dynamisch zijn. Als uw site klein en statisch is, heeft u waarschijnlijk geen applicatieshell nodig en kunt u eenvoudig de hele site in de cache opslaan in een oninstall stap van een servicemedewerker. Gebruik de aanpak die het meest logisch is voor uw project. Een aantal JavaScript-frameworks moedigen al aan om uw applicatielogica te scheiden van de inhoud, waardoor dit patroon eenvoudiger toe te passen is.

Zijn er al productie-apps die dit patroon gebruiken?

De applicatieshell-architectuur is mogelijk met slechts een paar wijzigingen in de gebruikersinterface van uw algehele applicatie en heeft goed gewerkt voor grootschalige sites zoals Google's I/O 2015 Progressive Web App en Google's Inbox.

Afbeelding van het laden van Google Inbox. Illustreert Inbox met behulp van servicemedewerker.

Offline applicatieshells zijn een grote prestatiewinst en worden ook goed gedemonstreerd in de offline Wikipedia-app van Jake Archibald en de progressieve webapp van Flipkart Lite .

Screenshots van de Wikipedia-demo van Jake Archibald.

Uitleg van de architectuur

Tijdens de eerste laadervaring is het uw doel om zo snel mogelijk betekenisvolle inhoud op het scherm van de gebruiker te krijgen.

Eerst laden en andere pagina's laden

Diagram van de eerste laadbeurt met de App Shell

Over het algemeen zal de applicatieshell-architectuur:

  • Geef prioriteit aan de initiële belasting, maar laat de servicemedewerker de applicatieshell in de cache opslaan, zodat herhaalde bezoeken niet vereisen dat de shell opnieuw van het netwerk wordt opgehaald.

  • Lazy-load of achtergrondlaad al het andere. Een goede optie is het gebruik van read-through-caching voor dynamische inhoud.

  • Gebruik hulpprogramma's voor servicemedewerkers, zoals sw-precache , om bijvoorbeeld de servicemedewerker die uw statische inhoud beheert, op betrouwbare wijze in de cache op te slaan en bij te werken. (Later meer over sw-precache.)

Om dit te behalen:

  • De server verzendt HTML-inhoud die de client kan weergeven en gebruikt toekomstige HTTP-cache-vervalheaders om rekening te houden met browsers zonder ondersteuning van servicemedewerkers. Het zal bestandsnamen weergeven met behulp van hashes om zowel 'versiebeheer' als eenvoudige updates voor later in de levenscyclus van de applicatie mogelijk te maken.

  • Pagina('s) zullen inline CSS-stijlen opnemen in een <style> -tag in het document <head> om een ​​snelle eerste weergave van de applicatieshell te bieden. Elke pagina laadt asynchroon het JavaScript dat nodig is voor de huidige weergave. Omdat CSS niet asynchroon kan worden geladen, kunnen we stijlen opvragen met behulp van JavaScript, omdat het asynchroon IS in plaats van parsergestuurd en synchroon. We kunnen ook profiteren van requestAnimationFrame() om gevallen te voorkomen waarin we een snelle cachehit krijgen en eindigen met stijlen die per ongeluk onderdeel worden van het kritieke weergavepad. requestAnimationFrame() zorgt ervoor dat het eerste frame wordt geverfd voordat de stijlen worden geladen. Een andere optie is om projecten zoals loadCSS van Filament Group te gebruiken om CSS asynchroon aan te vragen met behulp van JavaScript.

  • De servicemedewerker slaat een vermelding van de applicatieshell in de cache op, zodat bij herhaalde bezoeken de shell volledig uit de cache van de servicemedewerker kan worden geladen, tenzij er een update beschikbaar is op het netwerk.

App-shell voor inhoud

Een praktische uitvoering

We hebben een volledig werkend voorbeeld geschreven met behulp van de applicatieshell-architectuur, standaard ES2015 JavaScript voor de client en Express.js voor de server. Niets houdt u uiteraard tegen om uw eigen stack te gebruiken voor zowel het client- als het servergedeelte (bijvoorbeeld PHP, Ruby, Python).

Levenscyclus van servicemedewerkers

Voor ons applicatieshell-project gebruiken we sw-precache , die de volgende levenscyclus van servicemedewerkers biedt:

Evenement Actie
Installeren Cache de applicatieshell en andere app-bronnen met één pagina.
Activeren Ruim oude caches op.
Ophalen Bied een web-app van één pagina aan voor URL's en gebruik de cache voor assets en vooraf gedefinieerde gedeeltelijke onderdelen. Gebruik netwerk voor andere verzoeken.

Serverbits

In deze architectuur zou een server-side component (in ons geval geschreven in Express) inhoud en presentatie afzonderlijk moeten kunnen behandelen. Inhoud kan worden toegevoegd aan een HTML-lay-out die resulteert in een statische weergave van de pagina, of deze kan afzonderlijk worden aangeboden en dynamisch worden geladen.

Het is begrijpelijk dat uw serverconfiguratie drastisch kan verschillen van de configuratie die we gebruiken voor onze demo-app. Dit patroon van web-apps is haalbaar met de meeste serverconfiguraties, hoewel er wel enige herinrichting voor nodig is. We hebben ontdekt dat het volgende model redelijk goed werkt:

Diagram van de App Shell-architectuur
  • Eindpunten worden gedefinieerd voor drie delen van uw applicatie: de gebruikersgerichte URL's (index/wildcard), de applicatieshell (servicewerker) en uw HTML-gedeelten.

  • Elk eindpunt heeft een controller die een stuurindeling binnenhaalt die op zijn beurt stuurgedeelten en weergaven kan binnenhalen. Simpel gezegd zijn gedeeltelijke weergaven stukjes HTML die naar de laatste pagina worden gekopieerd. Opmerking: JavaScript-frameworks die geavanceerdere gegevenssynchronisatie uitvoeren, zijn vaak veel eenvoudiger over te zetten naar een Application Shell-architectuur. Ze hebben de neiging om databinding en synchronisatie te gebruiken in plaats van gedeeltelijke gegevens.

  • De gebruiker krijgt in eerste instantie een statische pagina met inhoud te zien. Deze pagina registreert een servicemedewerker, als deze wordt ondersteund, die de applicatieshell en alles waarvan deze afhankelijk is (CSS, JS enz.) in de cache opslaat.

  • De app-shell fungeert dan als een web-app met één pagina, waarbij javascript naar XHR wordt gebruikt in de inhoud voor een specifieke URL. De XHR-aanroepen worden gedaan naar een /partials*-eindpunt dat het kleine stukje HTML, CSS en JS retourneert dat nodig is om die inhoud weer te geven. Let op: Er zijn veel manieren om dit te benaderen en XHR is er slechts één van. Sommige applicaties zullen hun gegevens inline plaatsen (misschien met behulp van JSON) voor de eerste weergave en zijn daarom niet "statisch" in de platte HTML-zin.

  • Browsers zonder ondersteuning van servicemedewerkers moeten altijd een fall-back-ervaring krijgen. In onze demo vallen we terug op de standaard statische server-side rendering, maar dit is slechts een van de vele opties. Het servicemedewerkeraspect biedt u nieuwe mogelijkheden om de prestaties van uw app in applicatiestijl met één pagina te verbeteren met behulp van de in de cache opgeslagen applicatieshell.

Bestandsversiebeheer

Een vraag die opkomt is hoe om te gaan met het beheren van bestandsversies en het bijwerken ervan. Dit is toepassingsspecifiek en de opties zijn:

  • Netwerk eerst en gebruik anders de cacheversie.

  • Alleen netwerk en mislukt als u offline bent.

  • Cache de oude versie en update deze later.

Voor de applicatieshell zelf moet een cache-first-benadering worden gevolgd voor de configuratie van uw servicewerknemer. Als u de applicatieshell niet in de cache opslaat, heeft u de architectuur niet goed overgenomen.

Gereedschap

We onderhouden een aantal verschillende helperbibliotheken voor servicemedewerkers die het proces van het vooraf cachen van de shell van uw applicatie of het verwerken van algemene cachingpatronen eenvoudiger maken.

Schermafbeelding van de Service Worker Library-site op Web Fundamentals

Gebruik sw-precache voor uw applicatieshell

Het gebruik van sw-precache om de applicatieshell in het cachegeheugen op te slaan zou de zorgen rond bestandsrevisies, de installatie-/activeringsvragen en het ophaalscenario voor de app-shell moeten wegnemen. Plaats sw-precache in het bouwproces van uw applicatie en gebruik configureerbare jokertekens om uw statische bronnen op te halen. In plaats van uw service worker-script handmatig te maken, kunt u sw-precache er een laten genereren die uw cache op een veilige en efficiënte manier beheert, met behulp van een cache-first fetch-handler.

Bij de eerste bezoeken aan uw app wordt de precaching van de volledige set benodigde bronnen geactiveerd. Dit is vergelijkbaar met de ervaring van het installeren van een native app vanuit een app store. Wanneer gebruikers terugkeren naar uw app, worden alleen bijgewerkte bronnen gedownload. In onze demo informeren we gebruikers wanneer er een nieuwe shell beschikbaar is met het bericht 'App-updates. Vernieuwen voor de nieuwe versie.' Dit patroon is een eenvoudige manier om gebruikers te laten weten dat ze kunnen vernieuwen voor de nieuwste versie.

Gebruik sw-toolbox voor runtime-caching

Gebruik sw-toolbox voor runtime caching met verschillende strategieën, afhankelijk van de bron:

  • cacheFirst voor afbeeldingen, samen met een speciale benoemde cache met een aangepast vervalbeleid van N maxEntries.

  • netwerkEerste of snelste voor API-verzoeken, afhankelijk van de gewenste versheid van de inhoud. De snelste is misschien prima, maar als er een specifieke API-feed is die regelmatig wordt bijgewerkt, gebruik dan networkFirst.

Conclusie

Applicatieshell-architecturen bieden verschillende voordelen, maar zijn alleen zinvol voor bepaalde soorten applicaties. Het model is nog jong en het zal de moeite waard zijn om de inspanning en de algemene prestatievoordelen van deze architectuur te evalueren.

In onze experimenten hebben we gebruik gemaakt van het delen van sjablonen tussen de client en de server om het werk van het bouwen van twee applicatielagen tot een minimum te beperken. Dit zorgt ervoor dat progressieve verbetering nog steeds een kernfunctie is.

Als u al overweegt serviceworkers in uw app te gebruiken, bekijk dan de architectuur en beoordeel of deze zinvol is voor uw eigen projecten.

Met dank aan onze reviewers: Jeff Posnick, Paul Lewis, Alex Russell, Seth Thompson, Rob Dodson, Taylor Savage en Joe Medley.