Das VitePWA Plugin von Anthony Fu ist ein fantastisches Werkzeug für Ihre Vite-basierten Seiten. Es hilft Ihnen, einen Service Worker hinzuzufügen, der
- Offline-Unterstützung
- Assets und Inhalte cacht
- den Benutzer benachrichtigt, wenn neue Inhalte verfügbar sind
- ...und weitere Goodies!
Wir werden gemeinsam das Konzept von Service Workern durchgehen und dann direkt damit beginnen, einen mit dem VitePWA-Plugin zu erstellen.
Neu bei Vite? Schauen Sie sich meinen vorherigen Beitrag für eine Einführung an.
Inhaltsverzeichnis
- Service Worker, vorgestellt
- Versionierung und Manifeste
- Unser erster Service Worker
- Was ist mit Offline-Funktionalität?
- Wie Service Worker aktualisiert werden
- Eine bessere Möglichkeit, Inhalte zu aktualisieren
- Laufzeit-Caching
- Eigene Service-Worker-Inhalte hinzufügen
- Zusammenfassung
Service Worker, vorgestellt
Bevor wir uns mit dem VitePWA-Plugin befassen, sprechen wir kurz über den Service Worker selbst.
Ein Service Worker ist ein Hintergrundprozess, der in einem separaten Thread Ihrer Webanwendung ausgeführt wird. Service Worker haben die Fähigkeit, Netzwerkanfragen abzufangen und… alles zu tun. Die Möglichkeiten sind überraschend vielfältig. Sie könnten beispielsweise Anfragen nach TypeScript-Dateien abfangen und diese im laufenden Betrieb kompilieren. Oder Sie könnten Anfragen nach Videodateien abfangen und eine fortgeschrittene Transkodierung durchführen, die der Browser derzeit nicht unterstützt. Häufiger wird ein Service Worker jedoch zum Caching von Assets verwendet, sowohl zur Verbesserung der Leistung einer Website als auch um ihr zu ermöglichen, etwas zu tun, wenn sie offline ist.
Wenn jemand Ihre Website zum ersten Mal besucht, wird der vom VitePWA-Plugin erstellte Service Worker installiert und alle Ihre HTML-, CSS- und JavaScript-Dateien werden mithilfe der Cache Storage API gecached. Das Ergebnis ist, dass der Browser bei nachfolgenden Besuchen Ihrer Website diese Ressourcen aus dem Cache lädt, anstatt Netzwerkanfragen stellen zu müssen. Und selbst beim ersten Besuch Ihrer Website, da der Service Worker gerade alles vorab gecached hat, wird der nächste Link, auf den Ihr Benutzer klickt, wahrscheinlich bereits vorab gecached sein, sodass der Browser eine Netzwerkanfrage komplett umgehen kann.
Versionierung und Manifeste
Sie fragen sich vielleicht, was mit einem Service Worker passiert, wenn Ihr Code aktualisiert wird. Wenn Ihr Service Worker beispielsweise eine Datei foo.js cached und Sie diese Datei ändern, möchten Sie, dass der Service Worker die aktualisierte Version herunterlädt, wenn ein Benutzer die Website das nächste Mal besucht.
Aber in der Praxis haben Sie keine Datei foo.js. Normalerweise erstellt ein Build-System etwas wie foo-ABC123.js, wobei „ABC123“ ein Hash der Datei ist. Wenn Sie foo.js aktualisieren, sendet die nächste Bereitstellung Ihrer Website möglicherweise foo-XYZ987.js. Wie geht der Service Worker damit um?
Es stellt sich heraus, dass die Service Worker API ein *extrem* Low-Level-Primitive ist. Wenn Sie nach einer nativen schlüsselfertigen Lösung zwischen ihr und der Cache-API suchen, werden Sie enttäuscht sein. **Grundsätzlich muss die Erstellung Ihres Service Workers teilweise automatisiert und mit dem Build-System verbunden werden.** Sie müssten alle Assets sehen, die Ihr Build erstellt hat, diese Dateinamen fest in den Service Worker codieren, Code zum Vorab-Caching haben und, was noch wichtiger ist, *die gecachten Dateien verfolgen*.
Wenn sich der Code aktualisiert, ändert sich auch die Service-Worker-Datei, die die *neuen* Dateinamen mit Hashes enthält. Wenn ein Benutzer die App das nächste Mal besucht, muss der neue Service Worker installiert werden und das neue Dateimanifest mit dem aktuell im Cache befindlichen Manifest vergleichen, nicht mehr benötigte Dateien auswerfen und gleichzeitig neue Inhalte cachen.
Das ist ein absurder Arbeitsaufwand und unglaublich schwierig richtig zu machen. Auch wenn es ein unterhaltsames Projekt sein kann, möchten Sie in der Praxis ein etabliertes Produkt zur Generierung Ihres Service Workers verwenden – und das beste Produkt ist Workbox, das von den Leuten bei Google stammt.
Selbst Workbox ist ein eher Low-Level-Primitive. Es benötigt detaillierte Informationen über die Dateien, die Sie vorab cachen, welche in Ihrem Build-Tool versteckt sind. Deshalb verwenden wir das VitePWA-Plugin. Es nutzt Workbox im Hintergrund und konfiguriert es mit allen benötigten Informationen über die Bundles, die Vite erstellt. Wenig überraschend gibt es auch webpack- und Rollup-Plugins, falls Sie es bevorzugen, mit diesen Bundlern zu arbeiten.
Unser erster Service Worker
Ich gehe davon aus, dass Sie bereits eine Vite-basierte Website haben. Wenn nicht, können Sie gerne eine erstellen aus einer der verfügbaren Vorlagen.
Zuerst installieren wir das VitePWA-Plugin
npm i vite-plugin-pwa
Wir importieren das Plugin in unsere Vite-Konfiguration
import { VitePWA } from "vite-plugin-pwa"
Dann setzen wir es auch in der Konfiguration ein
plugins: [
VitePWA()
Wir werden gleich weitere Optionen hinzufügen, aber das ist alles, was wir brauchen, um einen überraschend nützlichen Service Worker zu erstellen. Registrieren wir ihn nun irgendwo im Einstiegspunkt unserer Anwendung mit diesem Code
import { registerSW } from "virtual:pwa-register";
if ("serviceWorker" in navigator) {
// && !/localhost/.test(window.location)) {
registerSW();
}
Lassen Sie sich vom auskommentierten Code nicht irritieren. Er ist tatsächlich äußerst wichtig, da er verhindert, dass der Service Worker in der Entwicklung ausgeführt wird. Wir wollen den Service Worker nur überall installieren, wo wir nicht auf localhost entwickeln, es sei denn, wir entwickeln den Service Worker selbst, in diesem Fall können wir diese Prüfung auskommentieren (und vor dem Pushen von Code in den Hauptzweig rückgängig machen).
Öffnen wir nun einen neuen Browser, starten wir DevTools, navigieren wir zum Tab Netzwerk und starten wir die Web-App. Alles sollte wie erwartet geladen werden. Der Unterschied ist, dass Sie in DevTools eine ganze Reihe von Netzwerkanfragen sehen sollten.

Das ist Workbox, das die Bundles vorab cacht. Es funktioniert!
Was ist mit Offline-Funktionalität?
Unser Service Worker cached also alle unsere gebündelten Assets vor. Das bedeutet, er liefert diese Assets aus dem Cache, ohne überhaupt das Netzwerk ansprechen zu müssen. Bedeutet das, dass unser Service Worker auch dann Assets liefern kann, wenn der Benutzer keine Netzwerkverbindung hat? Ja, das tut er!
Und, ob Sie es glauben oder nicht, das ist bereits geschehen. Probieren Sie es aus, indem Sie den Tab "Netzwerk" in DevTools öffnen und Chrome anweisen, den Offline-Modus zu simulieren, wie hier gezeigt.

Lassen Sie uns die Seite aktualisieren. Sie *sollten* sehen, wie alles geladen wird. Natürlich werden alle Netzwerkanfragen, die Sie ausführen, für immer hängen bleiben, da Sie offline sind. Aber selbst hier gibt es Dinge, die Sie tun können. Moderne Browser verfügen über eine eigene interne, persistente Datenbank namens IndexedDB. Es hindert Sie nichts daran, eigenen Code zu schreiben, um einige Daten dort zu synchronisieren, dann benutzerdefinierten Service-Worker-Code zu schreiben, um Netzwerkanfragen abzufangen, zu bestimmen, ob der Benutzer offline ist, und dann äquivalente Inhalte aus IndexedDB bereitzustellen, falls sie dort vorhanden sind.
Eine viel einfachere Option ist jedoch, zu erkennen, ob der Benutzer offline ist, eine Nachricht über die Offline-Verbindung anzuzeigen und dann die Datenanfragen zu umgehen. Dies ist ein Thema für sich, über das ich in viel größerer Detailtiefe geschrieben habe.
Bevor wir Ihnen zeigen, wie Sie Ihre eigenen Service-Worker-Inhalte schreiben und integrieren können, werfen wir einen genaueren Blick auf unseren bestehenden Service Worker. Insbesondere sehen wir uns an, wie er die Aktualisierung/Änderung von Inhalten verwaltet. Das ist überraschend schwierig und leicht zu vermasseln, selbst mit dem VitePWA-Plugin.
Bevor wir weitermachen, stellen Sie sicher, dass Sie Chrome DevTools angewiesen haben, Sie wieder online zu schalten.
Wie Service Worker aktualisiert werden
Schauen wir uns genauer an, was mit unserer Website passiert, wenn wir den Inhalt ändern. Wir werden unseren bestehenden Service Worker entfernen, was wir im Tab "Anwendung" von DevTools unter "Speicher" tun können.

Klicken Sie auf die Schaltfläche "Website-Daten löschen", um einen sauberen Start zu erhalten. Während ich dabei bin, werde ich die meisten Routen meiner eigenen Website entfernen, damit es weniger Ressourcen gibt, und dann Vite die App neu erstellen lassen.
Schauen Sie in die generierte Datei sw.js, um den generierten Workbox-Service-Worker zu sehen. Darin sollte sich ein Pre-Cache-Manifest befinden. Meines sieht so aus

Wenn sw.js minifiziert ist, führen Sie es durch Prettier, um es leichter lesbar zu machen.
Lassen Sie uns nun die Website ausführen und sehen, was sich in unserem Cache befindet

Konzentrieren wir uns auf die Datei settings.js. Vite hat assets/settings.ccb080c2.js basierend auf dem Hash seines Inhalts generiert. Workbox, unabhängig von Vite, hat seinen *eigenen* Hash derselben Datei generiert. Wenn derselbe Dateiname mit unterschiedlichem Inhalt generiert würde, dann würde ein neuer Service Worker neu generiert, mit einem anderen Pre-Cache-Manifest (gleiche Datei, aber andere Revision) und Workbox wüsste, wie es die neue Version cachen und die alte entfernen soll, wenn sie nicht mehr benötigt wird.
Auch hier werden die Dateinamen immer unterschiedlich sein, da wir einen Bundler verwenden, der Hash-Codes in unsere Dateinamen einfügt, aber Workbox unterstützt Entwicklungsumgebungen, die das nicht tun.
Seit dem Zeitpunkt des Schreibens wurde das VitePWA-Plugin aktualisiert und fügt diese Revisions-Hashes nicht mehr ein. Wenn Sie versuchen, die Schritte in diesem Artikel nachzuvollziehen, kann dieser spezifische Schritt leicht von Ihrer tatsächlichen Erfahrung abweichen. Weitere Informationen finden Sie in diesem GitHub-Issue.
Wenn wir unsere Datei settings.js aktualisieren, erstellt Vite eine neue Datei in unserem Build mit einem neuen Hash-Code, den Workbox als neue Datei behandelt. Sehen wir uns das in Aktion an. Nach dem Ändern der Datei und dem erneuten Ausführen des Vite-Builds sieht unser Pre-Cache-Manifest so aus

Wenn wir nun die Seite aktualisieren, wird der vorherige Service Worker *immer noch* ausgeführt und lädt die *vorherige* Datei. Dann wird der *neue* Service Worker mit dem *neuen* Pre-Cache-Manifest heruntergeladen und vorab gecached.

Beachten Sie die Schlussfolgerung hier: Unsere alten Inhalte werden dem Benutzer immer noch bereitgestellt, da der alte Service Worker immer noch läuft. Der Benutzer kann die gerade vorgenommene Änderung nicht sehen, selbst wenn er aktualisiert, da der Service Worker standardmäßig garantiert, dass alle Tabs mit dieser Web-App die *gleiche* Version ausführen. Wenn Sie möchten, dass der Browser die aktualisierte Version anzeigt, schließen Sie Ihren Tab (und alle anderen Tabs mit der Website) und öffnen Sie ihn erneut.

Workbox hat die ganze Arbeit geleistet, damit das alles richtig funktioniert! Wir haben wenig dazu beigetragen, dies zum Laufen zu bringen.
Eine bessere Möglichkeit, Inhalte zu aktualisieren
Es ist unwahrscheinlich, dass Sie es vermeiden können, Ihren Benutzern veraltete Inhalte anzuzeigen, bis sie zufällig alle ihre Browser-Tabs schließen. Glücklicherweise bietet das VitePWA-Plugin eine bessere Möglichkeit. Die Funktion registerSW akzeptiert ein Objekt mit einer Methode onNeedRefresh. Diese Methode wird aufgerufen, wenn ein neuer Service Worker darauf wartet, die Kontrolle zu übernehmen. registerSW gibt auch eine Funktion zurück, die Sie aufrufen können, um die Seite neu zu laden und dabei den neuen Service Worker zu aktivieren.
Das ist viel, also sehen wir uns etwas Code an
if ("serviceWorker" in navigator) {
// && !/localhost/.test(window.location) && !/lvh.me/.test(window.location)) {
const updateSW = registerSW({
onNeedRefresh() {
Toastify({
text: `<h4 style='display: inline'>An update is available!</h4>
<br><br>
<a class='do-sw-update'>Click to update and reload</a> `,
escapeMarkup: false,
gravity: "bottom",
onClick() {
updateSW(true);
}
}).showToast();
}
});
}
Ich verwende die Bibliothek toastify-js, um eine Toast-UI-Komponente anzuzeigen, die den Benutzern mitteilt, wann eine neue Version des Service Workers verfügbar ist und darauf wartet. Wenn der Benutzer auf den Toast klickt, rufe ich die Funktion auf, die VitePWA mir gibt, um die Seite neu zu laden, mit dem neuen Service Worker im Einsatz.

Eine Sache, die Sie hier beachten sollten, ist, dass die Toast-Komponente beim nächsten Laden Ihrer Website nicht angezeigt wird, nachdem Sie den Code zum Anzeigen des Toasts bereitgestellt haben. Das liegt daran, dass der alte Service Worker (der vor dem Hinzufügen der Toast-Komponente) immer noch ausgeführt wird. Dies erfordert, dass Sie alle Tabs manuell schließen und die Web-App erneut öffnen, damit der neue Service Worker die Kontrolle übernimmt. Dann, *beim nächsten Mal*, wenn Sie einen Code aktualisieren, sollte der Service Worker den Toast anzeigen und Sie auffordern, zu aktualisieren.
Warum wird der Service Worker nicht aktualisiert, wenn die Seite aktualisiert wird? Ich habe bereits erwähnt, dass das Aktualisieren der Seite den wartenden Service Worker nicht aktualisiert oder aktiviert. Warum funktioniert *dies* also? Das Aufrufen dieser Methode aktualisiert nicht nur die Seite, sondern ruft auch einige Low-Level-Service-Worker-APIs auf (insbesondere skipWaiting) und liefert uns damit das gewünschte Ergebnis.
Laufzeit-Caching
Wir haben das Bundle-Pre-Caching gesehen, das wir mit VitePWA für unsere Build-Assets kostenlos erhalten. Was ist mit dem Caching anderer Inhalte, die wir zur Laufzeit anfordern könnten? Workbox unterstützt dies über seine Funktion runtimeCaching.
So geht's. Das VitePWA-Plugin kann ein Objekt entgegennehmen, dessen eine Eigenschaft workbox ist, die Workbox-Eigenschaften übernimmt.
const getCache = ({ name, pattern }: any) => ({
urlPattern: pattern,
handler: "CacheFirst" as const,
options: {
cacheName: name,
expiration: {
maxEntries: 500,
maxAgeSeconds: 60 * 60 * 24 * 365 * 2 // 2 years
},
cacheableResponse: {
statuses: [200]
}
}
});
// ...
plugins: [
VitePWA({
workbox: {
runtimeCaching: [
getCache({
pattern: /^https:\/\/s3.amazonaws.com\/my-library-cover-uploads/,
name: "local-images1"
}),
getCache({
pattern: /^https:\/\/my-library-cover-uploads.s3.amazonaws.com/,
name: "local-images2"
})
]
}
})
],
// ...
Ich weiß, das ist viel Code. Aber alles, was es wirklich tut, ist Workbox anzuweisen, alles zu cachen, was es findet und mit diesen URL-Mustern übereinstimmt. Die Dokumentation bietet viel mehr Informationen, wenn Sie tief in die Details eintauchen möchten.
Nachdem diese Aktualisierung wirksam geworden ist, können wir sehen, wie diese Ressourcen von unserem Service Worker bereitgestellt werden.

Und wir können den entsprechenden Cache sehen, der erstellt wurde.

Eigene Service-Worker-Inhalte hinzufügen
Nehmen wir an, Sie möchten mit Ihrem Service Worker fortgeschrittene Dinge tun. Sie möchten Code hinzufügen, um Daten mit IndexedDB zu synchronisieren, Fetch-Handler hinzuzufügen und bei Offline-Verbindungen mit IndexedDB-Daten zu antworten (nochmals, mein vorheriger Beitrag erklärt die Ein- und Ausstiege von IndexedDB). Aber wie fügen Sie Ihren eigenen Code in den Service Worker ein, den Vite für uns erstellt?
Es gibt eine weitere Workbox-Option, die wir dafür verwenden können: importScripts.
VitePWA({
workbox: {
importScripts: ["sw-code.js"],
Hier fordert der Service Worker zur Laufzeit sw-code.js an. Stellen Sie in diesem Fall sicher, dass eine Datei sw-code.js vorhanden ist, die von Ihrer Anwendung bereitgestellt werden kann. Der einfachste Weg, dies zu erreichen, ist, sie in den public-Ordner zu legen (siehe die Vite Docs für detaillierte Anweisungen).
Wenn diese Datei zu groß wird, sodass Sie sie mit JavaScript-Imports aufteilen müssen, stellen Sie sicher, dass Sie sie bündeln, um zu verhindern, dass Ihr Service Worker Importanweisungen ausführt (was er möglicherweise kann oder auch nicht). Sie können stattdessen einen separaten Vite-Build erstellen.
Zusammenfassung
Ende 2021 fragte CSS-Tricks eine Reihe von Frontend-Leuten, was man tun könne, um seine Website besser zu machen. Chris Ferdinandi schlug einen Service Worker vor. Nun, genau das haben wir in diesem Artikel erreicht, und es war relativ einfach, nicht wahr? Das ist dem VitePWA mit Dank an Workbox und die Cache API zu verdanken.
Service Worker, die die Cache API nutzen, können die Leistung Ihrer Web-App erheblich verbessern. Und auch wenn es anfangs etwas beängstigend oder verwirrend erscheinen mag, ist es gut zu wissen, dass wir Werkzeuge wie das VitePWA-Plugin haben, um die Dinge erheblich zu vereinfachen. Installieren Sie das Plugin und lassen Sie es die schwere Arbeit erledigen. Sicher, es gibt fortgeschrittenere Dinge, die ein Service Worker tun kann, und VitePWA kann für komplexere Funktionalitäten verwendet werden, aber eine Offline-Website ist ein fantastischer Ausgangspunkt!
Adam, danke für den Beitrag. Mach weiter so.