Dateien und Verzeichnisse mit der Bibliothek „browser-fs-access“ lesen und schreiben

Browser können schon lange mit Dateien und Verzeichnissen umgehen. Die File API Funktionen zur Darstellung von Dateiobjekten in Webanwendungen, die programmatische Auswahl und den Zugriff auf ihre Daten. In dem Moment, in dem du genauer hinsiehst, ist jedoch nicht alles Gold, was glänzt.

Die traditionelle Art, mit Dateien zu arbeiten

Dateien werden geöffnet

Als Entwickler können Sie Dateien über die <input type="file"> -Elements. In der einfachsten Form kann das Öffnen einer Datei wie im Codebeispiel unten aussehen. Das input-Objekt gibt Ihnen ein FileList, die im folgenden Fall nur aus einem File Ein File ist eine bestimmte Art von Blob. und können in jedem Kontext verwendet werden, der mit einem Blob möglich ist.

const openFile = async () => {
  return new Promise((resolve) => {
    const input = document.createElement('input');
    input.type = 'file';
    input.addEventListener('change', () => {
      resolve(input.files[0]);
    });
    input.click();
  });
};

Verzeichnisse werden geöffnet

Für das Öffnen von Ordnern (oder Verzeichnissen) können Sie den <input webkitdirectory> . Ansonsten funktioniert alles wie oben beschrieben. Trotz des Namens mit Anbieterpräfix webkitdirectory kann nicht nur in Chromium- und WebKit-Browsern verwendet werden, sondern auch im alten EdgeHTML-basierten Edge sowie in Firefox.

Speichern (statt Herunterladen) von Dateien

Zum Speichern einer Datei können Sie sie normalerweise nur herunterladen, Das funktioniert dank des <a download> . Bei einem Blob können Sie das href-Attribut des Ankers auf eine blob:-URL setzen, die Sie über die URL.createObjectURL() .

const saveFile = async (blob) => {
  const a = document.createElement('a');
  a.download = 'my-file.txt';
  a.href = URL.createObjectURL(blob);
  a.addEventListener('click', (e) => {
    setTimeout(() => URL.revokeObjectURL(a.href), 30 * 1000);
  });
  a.click();
};

Das Problem

Ein großer Nachteil des Download-Ansatzes ist, dass es keine Möglichkeit gibt, Es gibt keine Möglichkeit, die Originaldatei zu überschreiben. Stattdessen erhalten Sie eine neue Kopie der Originaldatei. im Standardordner "Downloads" des Betriebssystems.

Die File System Access API

Die File System Access API vereinfacht sowohl das Öffnen als auch das Speichern erheblich. Außerdem wird damit das echte Speichern aktiviert, d. h. Sie können nicht nur auswählen, wo eine Datei gespeichert werden soll, aber auch eine vorhandene Datei überschreiben.

Dateien werden geöffnet

Mit der File System Access API Das Öffnen einer Datei ist nur mit einem Aufruf der window.showOpenFilePicker()-Methode erforderlich. Dieser Aufruf gibt ein Datei-Handle zurück, aus dem Sie den tatsächlichen File über die getFile()-Methode abrufen können.

const openFile = async () => {
  try {
    // Always returns an array.
    const [handle] = await window.showOpenFilePicker();
    return handle.getFile();
  } catch (err) {
    console.error(err.name, err.message);
  }
};

Verzeichnisse werden geöffnet

Öffnen Sie ein Verzeichnis durch Aufrufen von window.showDirectoryPicker(), die Verzeichnisse im Dialogfeld „Datei“ auswählbar macht.

Dateien werden gespeichert

Das Speichern von Dateien ist ähnlich einfach. Über createWritable() erstellen Sie aus einem Datei-Handle einen beschreibbaren Stream. Dann schreiben Sie die Blob-Daten, indem Sie die Methode write() des Streams aufrufen. Schließlich schließen Sie den Stream durch Aufrufen der close()-Methode.

const saveFile = async (blob) => {
  try {
    const handle = await window.showSaveFilePicker({
      types: [{
        accept: {
          // Omitted
        },
      }],
    });
    const writable = await handle.createWritable();
    await writable.write(blob);
    await writable.close();
    return handle;
  } catch (err) {
    console.error(err.name, err.message);
  }
};

Jetzt neu: browser-fs-access

So gut die File System Access API auch ist, sie ist noch nicht allgemein verfügbar.

<ph type="x-smartling-placeholder">
</ph> Tabelle mit Browserunterstützung für die File System Access API Alle Browser sind mit „Keine Unterstützung“ gekennzeichnet oder „hinter einer Kennzeichnung“. <ph type="x-smartling-placeholder">
</ph> Tabelle mit Browserunterstützung für die File System Access API (Quelle)

Deshalb sehe ich die File System Access API als Progressive Verbesserung. Daher möchte ich sie nutzen, wenn der Browser sie unterstützt. und andernfalls den herkömmlichen Ansatz verwenden. ohne dass Nutzer mit unnötigem Herunterladen von nicht unterstütztem JavaScript-Code bestraft werden. Den Parameter browser-fs-access ist meine Antwort auf diese Herausforderung.

Designphilosophie

Da sich die File System Access API in Zukunft wahrscheinlich noch ändern wird, die browser-fs-access API nicht danach modelliert wird. Das heißt, die Bibliothek ist kein Polyfill, sondern mit einem Ponyfill. Sie können (statisch oder dynamisch) exklusiv alle Funktionen importieren, die Sie benötigen, um Ihre App so klein wie möglich zu halten. Die verfügbaren Methoden sind die passend benannten fileOpen(), directoryOpen() und fileSave() Die Bibliotheksfunktion erkennt intern, ob die File System Access API unterstützt wird. und importiert dann den entsprechenden Codepfad.

Bibliothek des Typs browser-fs-access verwenden

Die drei Methoden sind intuitiv. Sie können den akzeptierten mimeTypes oder die Datei extensions Ihrer Anwendung angeben und ein multiple-Flag festlegen , um die Auswahl mehrerer Dateien oder Verzeichnisse zuzulassen oder zu verbieten. Detaillierte Informationen finden Sie in der Dokumentation zur API Browser-fs-access Das folgende Codebeispiel zeigt, wie Sie Bilddateien öffnen und speichern können.

// The imported methods will use the File
// System Access API or a fallback implementation.
import {
  fileOpen,
  directoryOpen,
  fileSave,
} from 'https://unpkg.com/browser-fs-access';

(async () => {
  // Open an image file.
  const blob = await fileOpen({
    mimeTypes: ['image/*'],
  });

  // Open multiple image files.
  const blobs = await fileOpen({
    mimeTypes: ['image/*'],
    multiple: true,
  });

  // Open all files in a directory,
  // recursively including subdirectories.
  const blobsInDirectory = await directoryOpen({
    recursive: true
  });

  // Save a file.
  await fileSave(blob, {
    fileName: 'Untitled.png',
  });
})();

Demo

Du kannst den obigen Code in einer Demo zu Glitch sehen. Der Quellcode ist ebenfalls dort verfügbar. Da aus Sicherheitsgründen ursprungsübergreifende Subframes keine Dateiauswahl anzeigen dürfen, Die Demo kann nicht in diesen Artikel eingebettet werden.

Die Bibliothek „browser-fs-access“ in der Praxis

In meiner Freizeit trage ich einen kleinen Beitrag zu installierbare PWA namens Excalidraw, ein Whiteboard-Tool, mit dem Sie ganz einfach von Hand gezeichnete Diagramme skizzieren können. Sie ist vollständig responsiv und funktioniert auf zahlreichen Geräten gut – von kleinen Smartphones bis hin zu Computern mit großen Bildschirmen. Das bedeutet, dass es Dateien auf allen verschiedenen Plattformen verarbeiten muss, ob sie die File System Access API unterstützen. Daher eignet sie sich hervorragend für die Bibliothek browser-fs-access.

Ich kann zum Beispiel eine Zeichnung auf meinem iPhone erstellen, Speichern (technisch: Download, da Safari die File System Access API nicht unterstützt) im Ordner "Downloads" auf dem iPhone, öffne die Datei auf meinem Desktop (nach der Übertragung von meinem Smartphone), die Datei ändern und sie mit meinen Änderungen überschreiben oder sie sogar als neue Datei speichern.

<ph type="x-smartling-placeholder">
</ph> Eine Excalidraw-Zeichnung auf einem iPhone.
Excalidraw-Zeichnung auf einem iPhone starten, auf dem die File System Access API nicht unterstützt wird, aber eine Datei im Ordner „Downloads“ gespeichert (heruntergeladen) werden kann
<ph type="x-smartling-placeholder">
</ph> Die modifizierte Excalidraw-Zeichnung in Chrome auf dem Desktop.
Öffnen und Ändern der Excalidraw-Zeichnung auf dem Desktop, wo die File System Access API unterstützt wird und somit über die API auf die Datei zugegriffen werden kann.
<ph type="x-smartling-placeholder">
</ph> Originaldatei mit den Änderungen überschreiben
Die Originaldatei wird mit den Änderungen an der ursprünglichen Excalidraw-Zeichnungsdatei überschrieben. Im Browser wird ein Dialogfeld angezeigt, in dem Sie gefragt werden, ob das in Ordnung ist.
<ph type="x-smartling-placeholder">
</ph> Speichern der Änderungen in einer neuen Excalidraw-Zeichnungsdatei.
Speichern der Änderungen in einer neuen Excalidraw-Datei. Die Originaldatei bleibt unverändert.

Codebeispiel aus der Praxis

Unten sehen Sie ein Beispiel für die Verwendung von „browser-fs-access“ in Excalidraw. Dieser Auszug stammt aus /src/data/json.ts Von besonderem Interesse ist, wie die saveAsJSON()-Methode entweder ein Datei-Handle oder null an browser-fs-access übergibt. fileSave()-Methode, mit der es überschrieben wird, wenn ein Handle angegeben wird, oder in einer neuen Datei speichern.

export const saveAsJSON = async (
  elements: readonly ExcalidrawElement[],
  appState: AppState,
  fileHandle: any,
) => {
  const serialized = serializeAsJSON(elements, appState);
  const blob = new Blob([serialized], {
    type: "application/json",
  });
  const name = `${appState.name}.excalidraw`;
  (window as any).handle = await fileSave(
    blob,
    {
      fileName: name,
      description: "Excalidraw file",
      extensions: ["excalidraw"],
    },
    fileHandle || null,
  );
};

export const loadFromJSON = async () => {
  const blob = await fileOpen({
    description: "Excalidraw files",
    extensions: ["json", "excalidraw"],
    mimeTypes: ["application/json"],
  });
  return loadFromBlob(blob);
};

Hinweise zur Benutzeroberfläche

Ob in Excalidraw oder in Ihrer App, sollte sich die Benutzeroberfläche an die Support-Situation des Browsers anpassen. Wenn die File System Access API unterstützt wird (if ('showOpenFilePicker' in window) {}) können Sie neben Speichern auch die Schaltfläche Speichern unter verwenden. Die folgenden Screenshots zeigen den Unterschied zwischen der responsiven Haupt-App-Symbolleiste von Excalidraw auf einem iPhone und in der Chrome-Desktop-App. Beachte, dass auf dem iPhone die Schaltfläche Speichern unter fehlt.

<ph type="x-smartling-placeholder">
</ph> Symbolleiste der App „Excalidraw“ auf einem iPhone nur mit „Speichern“ Schaltfläche.
Symbolleiste der App „Excalidraw“ auf einem iPhone über die Schaltfläche Speichern
<ph type="x-smartling-placeholder">
</ph> Symbolleiste der App „Excalidraw“ in der Chrome-Desktop-App mit der Schaltfläche „Speichern“ und die Option &quot;Speichern unter&quot; Schaltfläche.
Symbolleiste der App „Excalidraw“ in Chrome mit der Schaltfläche Speichern und der hervorgehobenen Schaltfläche Speichern unter

Ergebnisse

Die Arbeit mit Systemdateien funktioniert technisch in allen modernen Browsern. Für Browser, die die File System Access API unterstützen, können Sie die Nutzung optimieren, indem Sie zum echten Speichern und Überschreiben (nicht nur für das Herunterladen) von Dateien und indem Sie Ihren Nutzern die Möglichkeit geben, an jedem beliebigen Ort neue Dateien zu erstellen, in Browsern, die die File System Access API nicht unterstützen, weiterhin funktionieren. browser-fs-access erleichtert Ihnen das Leben indem Sie mit den Feinheiten Progressive Enhancement umzugehen und Ihren Code so einfach wie möglich zu gestalten.

Danksagungen

Dieser Artikel wurde von Joe Medley geprüft und Kayce Basques Vielen Dank an die Mitwirkenden an Excalidraw für ihre Arbeit am Projekt und für die Überprüfung meiner Pull-Anfragen. Hero-Image von Ilya Pawlow auf Unsplash.