Bei einigen Websites muss möglicherweise mit dem Service Worker kommuniziert werden, ohne dass das Ergebnis mitgeteilt werden muss. Hier einige Beispiele:
- Eine Seite sendet dem Service Worker eine Liste von URLs, die vorab abgerufen werden sollen. Wenn der Nutzer auf einen Link klickt, sind die Dokument- oder Seitenunterressourcen also bereits im Cache verfügbar, was die nachfolgende Navigation erheblich beschleunigt.
- Die Seite fordert den Service Worker auf, eine Reihe von Top-Artikeln abzurufen und im Cache zu speichern, damit sie für Offlinezwecke verfügbar sind.
Wenn Sie diese Arten von nicht kritischen Aufgaben an den Service Worker delegieren, wird der Haupt-Thread freigegeben, um dringendere Aufgaben wie die Reaktion auf Nutzerinteraktionen besser zu bewältigen.
In diesem Leitfaden erfahren Sie, wie Sie mithilfe von Standardbrowser-APIs und der Workbox-Bibliothek eine einseitige Kommunikationstechnik von der Seite zum Service Worker implementieren. Wir nennen diese Anwendungsfälle Imperatives Caching.
Produktionsfall
1-800-Flowers.com hat erzwingendes Caching (Vorab-Caching) mithilfe von Service Workern über postMessage()
implementiert, um die Top-Artikel auf Kategorieseiten vorab zu laden und so die nachfolgende Navigation zu Produktdetailseiten zu beschleunigen.
Dabei wird ein gemischter Ansatz verwendet, um zu entscheiden, welche Elemente vorab abgerufen werden sollen:
- Beim Laden der Seite bittet er den Service Worker, die JSON-Daten für die Top 9-Artikel abzurufen und die resultierenden Antwortobjekte in den Cache aufzunehmen.
- Bei den verbleibenden Elementen wird das Ereignis
mouseover
überwacht. Wenn ein Nutzer den Mauszeiger auf ein Element bewegt, kann ein Abruf der Ressource auf „Nachfrage“ ausgelöst werden.
Sie verwenden die Cache API, um JSON-Antworten zu speichern:
Wenn der Nutzer auf ein Element klickt, können die zugehörigen JSON-Daten aus dem Cache abgerufen werden, ohne dass eine Netzwerkverbindung erforderlich ist. Dadurch wird die Navigation beschleunigt.
Workbox verwenden
Workbox bietet eine einfache Möglichkeit, Nachrichten über das Paket workbox-window
an einen Service Worker zu senden. Dieses Paket besteht aus einer Reihe von Modulen, die im Fensterkontext ausgeführt werden sollen. Sie ergänzen die anderen Workbox-Pakete, die im Service Worker ausgeführt werden.
Um die Seite mit dem Service Worker zu kommunizieren, rufen Sie zuerst eine Workbox-Objektreferenz an den registrierten Service Worker ab:
const wb = new Workbox('/sw.js');
wb.register();
Anschließend können Sie die Nachricht direkt deklarativ senden, ohne sich um die Registrierung, die Aktivierungsprüfung oder die zugrunde liegende Kommunikations-API kümmern zu müssen:
wb.messageSW({"type": "PREFETCH", "payload": {"urls": ["/data1.json", "data2.json"]}}); });
Der Dienst-Worker implementiert einen message
-Handler, um diese Nachrichten zu empfangen. Optional kann eine Antwort zurückgegeben werden, in folgenden Fällen ist dies jedoch nicht erforderlich:
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'PREFETCH') {
// do something
}
});
Browser-APIs verwenden
Wenn die Workbox-Bibliothek nicht Ihren Anforderungen entspricht, können Sie die Kommunikation zwischen Fenster und Dienstmitarbeiter mithilfe von Browser-APIs implementieren.
Mit der postMessage API kann ein einseitiger Kommunikationsmechanismus zwischen der Seite und dem Service Worker eingerichtet werden.
Die Seite ruft postMessage()
auf der Service Worker-Benutzeroberfläche auf:
navigator.serviceWorker.controller.postMessage({
type: 'MSG_ID',
payload: 'some data to perform the task',
});
Der Dienst-Worker implementiert einen message
-Handler, um diese Nachrichten zu empfangen.
self.addEventListener('message', (event) => {
if (event.data && event.data.type === MSG_ID) {
// do something
}
});
Das {type : 'MSG_ID'}
-Attribut ist nicht unbedingt erforderlich, aber es ist eine Möglichkeit, der Seite zu ermöglichen, dem Service Worker unterschiedliche Arten von Anweisungen zu senden, z. B. „to prefetch“ (vorab laden) oder „to clear storage“ (Speicher löschen). Der Dienst-Worker kann anhand dieses Flags in verschiedene Ausführungspfade verzweigen.
Wenn der Vorgang erfolgreich war, kann der Nutzer davon profitieren. Andernfalls ändert sich der grundlegende Nutzerfluss nicht. Wenn 1-800-Flowers.com beispielsweise versucht, Inhalte vorab zu cachen, muss die Seite nicht wissen, ob der Service Worker erfolgreich war. Ist dies der Fall, profitieren die Nutzenden von einer schnelleren Navigation. Andernfalls muss die Seite zur neuen Seite weitergeleitet werden. Das dauert nur noch ein bisschen.
Ein einfaches Beispiel für das Vorabladen
Eine der häufigsten Anwendungen des imperativen Cachings ist das Prefetching, also das Abrufen von Ressourcen für eine bestimmte URL, bevor der Nutzer sie aufruft, um die Navigation zu beschleunigen.
Es gibt verschiedene Möglichkeiten, das Prefetching auf Websites zu implementieren:
- Verwendung von Link-Prefetch-Tags auf Seiten: Ressourcen werden fünf Minuten lang im Browsercache aufbewahrt. Danach gelten die normalen
Cache-Control
-Regeln für die Ressource. - Ergänzung der vorherigen Methode durch eine Laufzeit-Caching-Strategie im Dienstworker, um die Lebensdauer der Prefetch-Ressource über dieses Limit hinaus zu verlängern.
Bei relativ einfachen Prefetch-Szenarien wie dem Prefetching von Dokumenten oder bestimmten Assets (JS, CSS usw.) sind diese Techniken die beste Lösung.
Wenn zusätzliche Logik erforderlich ist, z. B. das Parsen der prefetch-Ressource (eine JSON-Datei oder -Seite), um die internen URLs abzurufen, sollten Sie diese Aufgabe vollständig an den Dienst-Worker delegieren.
Die Delegierung dieser Arten von Vorgängen an den Dienst-Worker bietet folgende Vorteile:
- Die Hauptlast des Abrufens und der Verarbeitung nach dem Abrufen (wird später vorgestellt) auf einen sekundären Thread auslagern. Dadurch wird der Hauptthread für wichtigere Aufgaben freigegeben, z. B. für die Reaktion auf Nutzerinteraktionen.
- Mehrere Clients (z.B. Tabs) erlauben, eine gemeinsame Funktion wiederzuverwenden, und den Dienst sogar gleichzeitig aufrufen, ohne den Hauptthread zu blockieren.
Produktdetailseiten vorabrufen
Verwenden Sie zuerst postMessage()
in der Service Worker-Schnittstelle und übergeben Sie ein Array von URLs im Cache:
navigator.serviceWorker.controller.postMessage({
type: 'PREFETCH',
payload: {
urls: [
'www.exmaple.com/apis/data_1.json',
'www.exmaple.com/apis/data_2.json',
],
},
});
Implementieren Sie im Service Worker einen message
-Handler, um Nachrichten abzufangen und zu verarbeiten, die von einem aktiven Tab gesendet werden:
addEventListener('message', (event) => {
let data = event.data;
if (data && data.type === 'PREFETCH') {
let urls = data.payload.urls;
for (let i in urls) {
fetchAsync(urls[i]);
}
}
});
Im vorherigen Code wurde eine kleine Hilfsfunktion namens fetchAsync()
eingeführt, um das URL-Array zu iterieren und für jede URL eine Abrufanfrage zu senden:
async function fetchAsync(url) {
// await response of fetch call
let prefetched = await fetch(url);
// (optionally) cache resources in the service worker storage
}
Wenn die Antwort eingeht, können Sie sich auf die Caching-Header der Ressource verlassen. In vielen Fällen werden Ressourcen jedoch nicht im Cache gespeichert, z. B. auf Produktdetailseiten. In solchen Fällen können Sie dieses Verhalten überschreiben, indem Sie die abgerufene Ressource im Service Worker-Cache speichern. Dies hat den zusätzlichen Vorteil, dass die Datei auch in Offlineszenarien bereitgestellt werden kann.
Jenseits von JSON-Daten
Sobald die JSON-Daten von einem Serverendpunkt abgerufen wurden, enthalten sie häufig andere URLs, die ebenfalls einen Vorabruf wert sind, z. B. ein Bild oder andere Endpunktdaten, die diesen Daten der ersten Ebene zugeordnet sind.
Angenommen, in unserem Beispiel sind die zurückgegebenen JSON-Daten die Informationen einer Website zum Einkaufen von Lebensmitteln:
{
"productName": "banana",
"productPic": "https://cdn.example.com/product_images/banana.jpeg",
"unitPrice": "1.99"
}
Ändern Sie den fetchAsync()
-Code so, dass die Liste der Produkte durchgegangen und das Hero-Image für jedes Produkt im Cache gespeichert wird:
async function fetchAsync(url, postProcess) {
// await response of fetch call
let prefetched = await fetch(url);
//(optionally) cache resource in the service worker cache
// carry out the post fetch process if supplied
if (postProcess) {
await postProcess(prefetched);
}
}
async function postProcess(prefetched) {
let productJson = await prefetched.json();
if (productJson && productJson.product_pic) {
fetchAsync(productJson.product_pic);
}
}
Sie können für Situationen wie 404-Fehler eine Ausnahmebehandlung für diesen Code hinzufügen. Der Vorteil der Verwendung eines Service Workers für das Vorabladen besteht jedoch darin, dass ein Fehler ohne große Auswirkungen auf die Seite und den Hauptthread hat. Sie können auch eine ausgefeiltere Logik für die Nachverarbeitung der vorab abgerufenen Inhalte verwenden, um sie flexibler zu gestalten und von den zu verarbeitenden Daten zu entkoppeln. Alles ist möglich.
Fazit
In diesem Artikel haben wir einen gängigen Anwendungsfall für die einseitige Kommunikation zwischen Seite und Service Worker behandelt: Imperatives Caching. Die vorgestellten Beispiele dienen nur dazu, eine Möglichkeit zu demonstrieren, wie dieses Muster verwendet werden kann. Derselbe Ansatz kann auch auf andere Anwendungsfälle angewendet werden, z. B. das On-Demand-Caching von Top-Artikeln für die Offline-Nutzung oder das Speichern von Lesezeichen.
Weitere Kommunikationsmuster für Seiten und Service Worker finden Sie hier:
- Updates übertragen: Die Seite wird vom Service Worker aufgerufen, um über wichtige Updates zu informieren, z. B. dass eine neue Version der Webanwendung verfügbar ist.
- Zwei-Wege-Kommunikation: Sie können eine Aufgabe an einen Dienst-Worker delegieren (z. B. einen umfangreichen Download) und die Seite über den Fortschritt informieren.