Offline-Daten

Damit Ihre PWA auch offline gut funktioniert, muss sie Speicher verwalten können. Im Kapitel zum Caching haben Sie erfahren, dass der Cache-Speicher eine Option zum Speichern von Daten auf einem Gerät ist. In diesem Kapitel erfahren Sie, wie Sie Offlinedaten verwalten, einschließlich Datenpersistenz, Limits und der verfügbaren Tools.

Speicher

Speicherplatz ist nicht nur für Dateien und Assets, sondern kann auch für andere Datentypen verwendet werden. In allen Browsern, die PWAs unterstützen, sind die folgenden APIs für die lokale Speicherung verfügbar:

  • IndexedDB: Eine NoSQL-Objektspeicheroption für strukturierte Daten und Blobs (binäre Daten).
  • WebStorage: Eine Möglichkeit, Schlüssel/Wert-Stringpaare mithilfe des lokalen Speichers oder des Sitzungsspeichers zu speichern. Sie ist nicht im Kontext eines Service Workers verfügbar. Diese API ist synchron und daher nicht für die komplexe Datenspeicherung geeignet.
  • Cache-Speicher: Wie im Caching-Modul beschrieben.

Auf unterstützten Plattformen können Sie den gesamten Gerätespeicher mit der Storage Manager API verwalten. Die Cache Storage API und IndexedDB bieten asynchronen Zugriff auf persistenten Speicher für PWAs und können über den Hauptthread, Webworker und Service-Worker aufgerufen werden. Beide spielen eine wichtige Rolle, damit PWAs auch bei einer instabilen oder nicht vorhandenen Netzwerkverbindung zuverlässig funktionieren. Wann sollten Sie die einzelnen Funktionen verwenden?

Verwenden Sie die Cache Storage API für Netzwerkressourcen, auf die Sie durch Anfordern ü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 auf NoSQL-ähnliche Weise durchsucht oder kombiniert werden müssen, oder andere Daten wie nutzerspezifische Daten, die nicht unbedingt einer URL-Anfrage entsprechen. IndexedDB ist nicht für die Volltextsuche konzipiert.

IndexedDB

Wenn Sie IndexedDB verwenden möchten, müssen Sie zuerst eine Datenbank öffnen. Dadurch wird eine neue Datenbank erstellt, falls noch keine vorhanden ist. IndexedDB ist eine asynchrone API, die jedoch einen Callback anstelle eines Promise zurückgibt. Im folgenden Beispiel wird die idb-Bibliothek von Jake Archibald verwendet, die ein kleiner Promise-Wrapper für IndexedDB ist. Hilfsbibliotheken sind für die Verwendung von IndexedDB nicht erforderlich. Wenn Sie jedoch die Promise-Syntax verwenden möchten, ist die idb-Bibliothek eine Option.

Im folgenden Beispiel wird eine Datenbank für Kochrezepte erstellt.

Datenbank erstellen und öffnen

So öffnen Sie eine Datenbank:

  1. Verwenden Sie die Funktion openDB, um eine neue IndexedDB-Datenbank mit dem Namen 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 sie auf 1 festgelegt.
  2. Ein Initialisierungsobjekt mit einem upgrade()-Callback 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. Diese Funktion ist der einzige 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. Normalerweise enthält die Funktion upgrade() eine switch-Anweisung ohne break-Anweisungen, damit jeder Schritt in der richtigen Reihenfolge ausgeführt werden kann, je nachdem, wie die alte Version der Datenbank aussieht.
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 in der Datenbank cookbook ein Objektspeicher namens recipes erstellt, wobei die Eigenschaft id als Indexschlüssel des Speichers festgelegt wird. Außerdem wird ein weiterer Index namens type auf Grundlage der Eigenschaft type erstellt.

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

Safari und Chrome mit IndexedDB-Inhalten

Daten hinzufügen

IndexedDB verwendet Transaktionen. Bei Transaktionen werden Aktionen gruppiert, sodass sie als Einheit ausgeführt werden. Sie sorgen dafür, dass die Datenbank immer in einem konsistenten Zustand ist. Sie sind auch wichtig, wenn mehrere Kopien Ihrer App ausgeführt werden, um gleichzeitiges Schreiben in dieselben Daten zu verhindern. So fügen Sie Daten hinzu:

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

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

Wie in der Dokumentation zur IDB-Bibliothek beschrieben, ist tx.done das Signal dafür, dass alles erfolgreich in die Datenbank übertragen wurde, wenn Sie in die Datenbank schreiben. Es ist jedoch von Vorteil, auf einzelne Vorgänge zu warten, damit Sie alle Fehler sehen können, die dazu führen, dass die Transaktion fehlschlägt.

// 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;
}

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

Daten werden abgerufen…

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. Rufen Sie objectStore() über diese Transaktion auf. Geben Sie den Namen des Objektspeichers an.
  3. Rufen Sie get() mit dem Schlüssel auf, den Sie abrufen möchten. Standardmäßig wird der Schlüssel des Speichers als Index verwendet.
// 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]);
}

Der Speichermanager

Es ist besonders wichtig, dass Sie wissen, wie Sie den Speicher Ihrer PWA verwalten, um Netzwerkantworten richtig zu speichern und zu streamen.

Die Speicherkapazität wird für alle Speicheroptionen freigegeben, einschließlich Cache Storage, IndexedDB, Web Storage und sogar der Service Worker-Datei und ihrer Abhängigkeiten. Die Menge des verfügbaren Speichers variiert jedoch von Browser zu Browser. Es ist unwahrscheinlich, dass Sie das Limit erreichen, da Websites in einigen Browsern Megabyte und sogar Gigabyte an Daten speichern können. In Chrome kann der Browser beispielsweise bis zu 80% des gesamten Festplattenspeichers belegen und ein einzelner Ursprung bis zu 60 %. In Browsern, die die Storage API unterstützen, können Sie sehen, wie viel Speicherplatz für Ihre App noch verfügbar ist, wie hoch das Kontingent ist und wie viel Speicherplatz bereits genutzt wird. Im folgenden Beispiel wird die Storage API verwendet, um das geschätzte Kontingent und die geschätzte Nutzung abzurufen. Anschließend wird der Prozentsatz der verwendeten und der verbleibenden Byte berechnet. navigator.storage gibt eine Instanz von StorageManager zurück. Es gibt eine separate Storage-Schnittstelle, die leicht mit der anderen verwechselt werden kann.

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 die Speichernutzung nach Art der Nutzung aufschlüsseln, indem Sie auf dem Tab Anwendung den Bereich Speicher öffnen.

Chrome-Entwicklertools im Bereich „Anwendung“, Abschnitt „Speicher löschen“

Firefox und Safari bieten keinen Übersichts-Bildschirm, auf dem alle Speicherkontingente und die Nutzung für den aktuellen Ursprung angezeigt werden.

Datenpersistenz

Sie können den Browser auf kompatiblen Plattformen um persistenten Speicher bitten, um das automatische Entfernen von Daten nach Inaktivität oder bei Speichermangel zu vermeiden. Wenn die Berechtigung erteilt wird, werden Daten im Browser nie aus dem Speicher entfernt. Dazu gehören die Registrierung des Service Workers, IndexedDB-Datenbanken und Dateien im Cache-Speicher. Nutzer haben immer die Kontrolle und können den Speicher jederzeit löschen, auch wenn der Browser persistenten Speicher gewährt hat.

Wenn Sie nichtflüchtigen Speicher anfordern möchten, rufen Sie StorageManager.persist() auf. Wie bisher wird über die navigator.storage-Property auf die StorageManager-Schnittstelle zugegriffen.

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 der aktuelle Ursprung bereits persistenten Speicher hat, indem Sie StorageManager.persisted() aufrufen. Firefox fordert den Nutzer auf, die Berechtigung zur Verwendung von persistentem Speicher zu erteilen. Chromium-basierte Browser gewähren oder verweigern die Persistenz basierend auf einer Heuristik, um die Wichtigkeit der Inhalte für den Nutzer zu bestimmen. Ein Kriterium für Google Chrome ist beispielsweise die PWA-Installation. Wenn der Nutzer ein Symbol für die PWA im Betriebssystem installiert hat, kann der Browser persistenten Speicher gewähren.

Mozilla Firefox fordert den Nutzer auf, die Berechtigung zum Speichern von Daten zu erteilen.

Unterstützung von Browsern für die API

Web Storage

Browser Support

  • Chrome: 4.
  • Edge: 12.
  • Firefox: 3.5.
  • Safari: 4.

Source

File System Access

Browser Support

  • Chrome: 86.
  • Edge: 86.
  • Firefox: 111.
  • Safari: 15.2.

Source

Speichermanager

Browser Support

  • Chrome: 55.
  • Edge: 79.
  • Firefox: 57.
  • Safari: 15.2.

Source

Ressourcen