Offline-Daten

Damit Ihre PWA auch offline problemlos arbeiten kann, muss sie Speicherplatz verwalten. Im Kapitel zum Caching haben Sie gelernt, dass die Cache-Speicherung eine Option ist, um Daten auf einem Gerät zu speichern. In diesem Kapitel erfahren Sie, wie Sie Offlinedaten verwalten, einschließlich Datenpersistenz, Limits und verfügbaren Tools.

Speicher

Der Speicher bezieht sich nicht nur auf Dateien und Assets, sondern kann auch andere Datentypen umfassen. In allen Browsern, die PWAs unterstützen, sind die folgenden APIs für den On-Device-Speicher verfügbar:

  • IndexedDB: Eine NoSQL-Objektspeicheroption für strukturierte Daten und Blobs (Binärdaten).
  • WebStorage: Eine Möglichkeit zum Speichern von Schlüssel/Wert-String-Paaren unter Verwendung von lokalem Speicher oder Sitzungsspeicher. Er ist in einem Service Worker-Kontext nicht verfügbar. Diese API ist synchron und wird daher nicht für komplexe Datenspeicher empfohlen.
  • Cache-Speicher: wie im Caching-Modul beschrieben.

Sie können den gesamten Gerätespeicher mit der Storage Manager API auf unterstützten Plattformen verwalten. Die Cache Storage API und IndexedDB bieten asynchronen Zugriff auf nichtflüchtigen Speicher für PWAs und können über den Hauptthread, Web Worker und Service Worker aufgerufen werden. Beide tragen entscheidend dazu bei, dass PWAs auch dann zuverlässig funktionieren, wenn das Netzwerk instabil oder nicht vorhanden ist. Aber wann sollten Sie sie jeweils verwenden?

Verwenden Sie die Cache Storage API für Netzwerkressourcen, auf die Sie über eine URL zugreifen, z. B. HTML, CSS, JavaScript, Bilder, Videos und Audio.

Verwenden Sie IndexedDB, um strukturierte Daten zu speichern. Dazu gehören Daten, die in NoSQL-ähnlicher Form suchbar oder kombinierbar sein müssen, sowie andere Daten wie benutzerspezifische Daten, die nicht unbedingt mit einer URL-Anfrage übereinstimmen. Beachten Sie, dass IndexedDB nicht für die Volltextsuche vorgesehen ist.

IndexedDB

Öffnen Sie zuerst eine Datenbank, um IndexedDB zu verwenden. Wenn noch keine vorhanden ist, wird eine neue Datenbank erstellt. IndexedDB ist eine asynchrone API, aber sie erfordert einen Callback, anstatt ein Promise zurückzugeben. Im folgenden Beispiel wird die idb-Bibliothek von Jake Archibald verwendet, ein winziger Promise-Wrapper für IndexedDB. Hilfsbibliotheken sind für die Verwendung von IndexedDB nicht erforderlich, aber wenn Sie die Promise-Syntax verwenden möchten, ist die idb-Bibliothek eine Option.

Im folgenden Beispiel wird eine Datenbank zum Speichern von Kochrezepten erstellt.

Datenbank erstellen und öffnen

So öffnen Sie eine Datenbank:

  1. Verwenden Sie die Funktion openDB, um eine neue IndexedDB-Datenbank namens cookbook zu erstellen. Da IndexedDB-Datenbanken versioniert sind, müssen Sie die Versionsnummer erhöhen, wenn Sie Änderungen an der Datenbankstruktur vornehmen. Der zweite Parameter ist die Datenbankversion. Im Beispiel ist der Wert auf 1 festgelegt.
  2. Ein Initialisierungsobjekt, das einen upgrade()-Callback enthält, wird an openDB() übergeben. Die Callback-Funktion wird aufgerufen, wenn die Datenbank zum ersten Mal installiert wird oder wenn sie auf eine neue Version aktualisiert wird. Nur diese Funktion ist der Ort, an dem Aktionen ausgeführt werden können. Aktionen können das Erstellen neuer Objektspeicher (die Strukturen, die IndexedDB zum Organisieren von Daten verwendet) oder Indexe (die Sie durchsuchen möchten) umfassen. Hier sollte auch die Datenmigration erfolgen. In der Regel enthält die upgrade()-Funktion eine switch-Anweisung ohne break-Anweisungen, damit jeder Schritt basierend auf der alten Version der Datenbank der Reihe nach ausgeführt werden kann.
import { openDB } from 'idb';

async function createDB() {
  // Using https://github.com/jakearchibald/idb
  const db = await openDB('cookbook', 1, {
    upgrade(db, oldVersion, newVersion, transaction) {
      // Switch over the oldVersion, *without breaks*, to allow the database to be incrementally upgraded.
    switch(oldVersion) {
     case 0:
       // Placeholder to execute when database is created (oldVersion is 0)
     case 1:
       // Create a store of objects
       const store = db.createObjectStore('recipes', {
         // The `id` property of the object will be the key, and be incremented automatically
           autoIncrement: true,
           keyPath: 'id'
       });
       // Create an index called `name` based on the `type` property of objects in the store
       store.createIndex('type', 'type');
     }
   }
  });
}

In diesem Beispiel wird ein Objektspeicher in der cookbook-Datenbank namens recipes erstellt, wobei das Attribut id als Indexschlüssel des Speichers festgelegt ist. Außerdem wird ein weiterer Index mit dem Namen type erstellt, der auf dem Attribut type basiert.

Sehen wir uns den gerade erstellten Objektspeicher an. Nachdem Sie Rezepte zum Objektspeicher hinzugefügt und die Entwicklertools in Chromium-basierten Browsern oder Web Inspector in Safari geöffnet haben, sollten Sie Folgendes erwarten:

Safari und Chrome mit IndexedDB-Inhalten.

Daten hinzufügen

IndexedDB verwendet Transaktionen. In Transaktionen werden Aktionen gruppiert, sodass sie als eine Einheit ausgeführt werden. Sie tragen dazu bei, dass die Datenbank immer in einem einheitlichen Zustand ist. Sie sind auch wichtig, wenn mehrere Kopien Ihrer Anwendung ausgeführt werden, um das gleichzeitige Schreiben in dieselben Daten zu verhindern. So fügen Sie Daten hinzu:

  1. Starten Sie eine Transaktion, wobei mode auf readwrite gesetzt ist.
  2. Rufen Sie den Objektspeicher ab, in dem Sie Daten hinzufügen.
  3. Rufen Sie add() mit den Daten auf, die Sie speichern. Die Methode empfängt Daten in Wörterbuchform (als Schlüssel/Wert-Paare) und fügt sie dem Objektspeicher hinzu. Das Wörterbuch muss mit strukturiertem Klonen geklont werden können. Wenn Sie ein vorhandenes Objekt aktualisieren möchten, rufen Sie stattdessen die Methode put() auf.

Transaktionen haben ein Promise (done), das aufgelöst wird, wenn die Transaktion erfolgreich abgeschlossen wird, oder mit einem Transaktionsfehler abgelehnt werden.

Wie in der Dokumentation zur IDB-Bibliothek erläutert, gibt tx.done beim Schreiben in die Datenbank an, dass alles erfolgreich in die Datenbank übertragen wurde. Es ist jedoch vorteilhaft, einzelne Vorgänge abzuwarten, damit Sie alle Fehler sehen können, die zum Fehlschlagen der Transaktion führen.

// Using https://github.com/jakearchibald/idb
async function addData() {
  const cookies = {
      name: "Chocolate chips cookies",
      type: "dessert"
        cook_time_minutes: 25
  };
  const tx = await db.transaction('recipes', 'readwrite');
  const store = tx.objectStore('recipes');
  store.add(cookies);
  await tx.done;
}

Nachdem Sie die Cookies hinzugefügt haben, befindet sich das Rezept in der Datenbank mit den anderen Schemas. Die ID wird automatisch festgelegt und von indexedDB erhöht. Wenn Sie diesen Code zweimal ausführen, erhalten Sie zwei identische Cookie-Einträge.

Daten abrufen

So rufen Sie Daten aus IndexedDB ab:

  1. Starten Sie eine Transaktion und geben Sie den oder die Objektspeicher und optional den Transaktionstyp an.
  2. Rufe objectStore() aus dieser Transaktion auf. Sie müssen den Namen des Objektspeichers angeben.
  3. Rufen Sie get() mit dem gewünschten Schlüssel auf. Standardmäßig verwendet der Speicher seinen Schlüssel als Index.
// Using https://github.com/jakearchibald/idb
async function getData() {
  const tx = await db.transaction('recipes', 'readonly')
  const store = tx.objectStore('recipes');
// Because in our case the `id` is the key, we would
// have to know in advance the value of the id to
// retrieve the record
  const value = await store.get([id]);
}

Speichermanager

Damit Sie Netzwerkantworten richtig speichern und streamen können, ist es besonders wichtig, dass Sie den Speicherplatz Ihrer PWA verwalten.

Die Speicherkapazität wird von allen Speicheroptionen gemeinsam genutzt, einschließlich Cache Storage, IndexedDB, Web Storage und sogar der Service Worker-Datei und deren Abhängigkeiten. Der verfügbare Speicherplatz variiert jedoch von Browser zu Browser. Es ist unwahrscheinlich, dass Ihnen die Daten ausgehen. Websites könnten in einigen Browsern Megabyte und sogar Gigabyte an Daten speichern. In Chrome beispielsweise kann der Browser bis zu 80% des gesamten Speicherplatzes nutzen und ein einzelner Ursprung kann bis zu 60% des gesamten Speicherplatzes nutzen. Bei Browsern, die die Storage API unterstützen, können Sie sehen, wie viel Speicherplatz noch für Ihre Anwendung verfügbar ist, welches Kontingent und wie viel Speicherplatz noch verfügbar ist. Im folgenden Beispiel werden mithilfe der Storage API Kontingent und Nutzung geschätzt. Anschließend werden der Prozentsatz der verwendeten und verbleibenden Byte berechnet. navigator.storage gibt eine Instanz von StorageManager zurück. Es gibt eine separate Storage-Oberfläche und dies ist leicht verwirrend.

if (navigator.storage && navigator.storage.estimate) {
  const quota = await navigator.storage.estimate();
  // quota.usage -> Number of bytes used.
  // quota.quota -> Maximum number of bytes available.
  const percentageUsed = (quota.usage / quota.quota) * 100;
  console.log(`You've used ${percentageUsed}% of the available storage.`);
  const remaining = quota.quota - quota.usage;
  console.log(`You can write up to ${remaining} more bytes.`);
}

In den Chromium-Entwicklertools können Sie das Kontingent Ihrer Website und wie viel Speicherplatz belegt ist je nachdem, was genutzt wird, sehen. Öffnen Sie dazu auf dem Tab Anwendung den Abschnitt Speicher.

Chrome-Entwicklertools in der Anwendung, Abschnitt „Speicherinhalt löschen“

Firefox und Safari bieten keine Zusammenfassungsseite für die Anzeige des gesamten Speicherkontingents und der Speichernutzung für den aktuellen Ursprung.

Datenpersistenz

Sie können den Browser um nichtflüchtigen Speicher auf kompatiblen Plattformen bitten, um eine automatische Datenbereinigung bei Inaktivität oder Speicherauslastung zu vermeiden. Wenn die Berechtigung gewährt wird, wird der Browser niemals Daten aus dem Speicher entfernen. Dieser Schutz umfasst die Service Worker-Registrierung, IndexedDB-Datenbanken und Dateien im Cache-Speicher. Hinweis: Die Nutzer haben immer die Verantwortung und können den Speicher jederzeit löschen, auch wenn der Browser dauerhaften Speicher gewährt hat.

Rufen Sie StorageManager.persist() auf, um nichtflüchtigen Speicher anzufordern. Wie zuvor ist der Zugriff auf die StorageManager-Oberfläche über die navigator.storage-Property möglich.

async function persistData() {
  if (navigator.storage && navigator.storage.persist) {
    const result = await navigator.storage.persist();
    console.log(`Data persisted: ${result}`);
}

Sie können auch prüfen, ob im aktuellen Ursprung bereits nichtflüchtiger Speicher gewährt wurde. Rufen Sie dazu StorageManager.persisted() auf. Firefox fordert vom Nutzer die Berechtigung zur Verwendung des nichtflüchtigen Speichers an. Chromium-basierte Browser bieten oder lehnen Persistenz basierend auf einer Heuristik ab, um die Wichtigkeit von Inhalten für den Nutzer zu bestimmen. Ein Kriterium für Google Chrome ist beispielsweise die Installation einer PWA. Wenn der Nutzer im Betriebssystem ein Symbol für die PWA installiert hat, kann im Browser dauerhafter Speicher zugelassen werden.

Mozilla Firefox bittet den Nutzer um die Berechtigung zur Speicherpersistenz.

Unterstützung für API-Browser

Webspeicher

Unterstützte Browser

  • 4
  • 12
  • 3,5
  • 4

Quelle

Zugriff auf Dateisystem

Unterstützte Browser

  • 86
  • 86
  • 111
  • 15.2

Quelle

Speichermanager

Unterstützte Browser

  • 55
  • 79
  • 57
  • 15.2

Quelle

Ressourcen