Lettura e scrittura di file e directory con la libreria browser-fs-access

I browser sono in grado di gestire file e directory da molto tempo. L'API File offre funzionalità per la rappresentazione di oggetti file nelle applicazioni web, selezionare e accedere ai dati in modo programmatico. Nel momento in cui guardi più da vicino, però, tutto ciò che luccica non è oro.

Il modo tradizionale di gestire i file

Apertura dei file

In qualità di sviluppatore, puoi aprire e leggere file tramite il <input type="file"> . Nella sua forma più semplice, l'apertura di un file può avere un aspetto simile all'esempio di codice riportato di seguito. L'oggetto input ti fornisce un valore FileList, che nel seguente caso è costituito da un solo File. Un File è un tipo specifico di Blob, e può essere utilizzato in tutti i contesti possibili.

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

Apertura directory

Per aprire cartelle (o directory), puoi impostare il <input webkitdirectory> . A parte questo, tutto il resto funziona come sopra. Nonostante il nome con il prefisso del fornitore, webkitdirectory non è utilizzabile solo nei browser Chromium e WebKit, ma anche nel legacy Edge basato su EdgeHTML e in Firefox.

Salvataggio (anziché download) dei file

Per salvare un file, tradizionalmente, devi limitarti al download di un file, che funziona grazie <a download>: . Dato un BLOB, puoi impostare l'attributo href dell'ancoraggio su un URL blob: che puoi ottenere dal 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();
};

Il problema

Un enorme svantaggio dell'approccio al download è che non è possibile creare un modello flusso di apertura→modifica→salvataggio, ovvero non c'è modo di sovrascrivere il file originale. Viene invece creata una nuova copia del file originale nella cartella Download predefinita del sistema operativo ogni volta che "salvi".

API File System Access

L'API File System Access rende molto più semplici sia le operazioni, l'apertura che il salvataggio. Consente inoltre il salvataggio reale, ovvero consente di scegliere non solo dove salvare un file, ma anche sovrascrivere un file esistente.

Apertura dei file

Con l'API File System Access, l'apertura di un file è una questione di una chiamata al metodo window.showOpenFilePicker(). Questa chiamata restituisce un handle di file, dal quale è possibile ottenere l'elemento File effettivo tramite il metodo getFile().

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

Apertura directory

Apri una directory chiamando window.showDirectoryPicker() che rende selezionabili le directory nella finestra di dialogo del file.

Salvataggio dei file in corso...

Anche il salvataggio dei file è semplice. Da un handle di file, crei uno stream scrivibile tramite createWritable(), quindi scrivi i dati BLOB richiamando il metodo write() del flusso, Infine, chiudi lo stream chiamando il metodo close().

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

Introduzione a browser-fs-access

Perfettamente come l'API File System Access, ma non è ancora disponibile al pubblico.

Tabella relativa al supporto dei browser per l&#39;API File System Access. Tutti i browser sono contrassegnati come &quot;nessun supporto&quot; o &quot;dietro una bandiera&quot;.
. Tabella relativa al supporto dei browser per l'API File System Access. (Fonte)

Questo è il motivo per cui vedo l'API File System Access come un miglioramento progressivo. Di conseguenza, voglio utilizzarlo quando il browser lo supporta, e utilizzare l'approccio tradizionale in caso contrario; senza mai penalizzare l'utente con download non necessari di codice JavaScript non supportato. L'app browser-fs-access raccolta è la mia risposta a questa sfida.

Filosofia di progettazione

Poiché l'API File System Access potrebbe ancora cambiare in futuro, l'API browser-fs-access non è modellata in base a questa. Vale a dire che la libreria non è un polyfill, ma piuttosto un ponyfill. Puoi (in modo statico o dinamico) importare esclusivamente le funzionalità di cui hai bisogno per ridurre il più possibile le dimensioni della tua app. I metodi disponibili sono quelli con il nome appropriato fileOpen(), directoryOpen() e fileSave() Internamente, la funzionalità libreria rileva se l'API File System Access è supportata, e importa il percorso del codice corrispondente.

Utilizzo della libreria browser-fs-access

Si tratta di tre metodi intuitivi da usare. Puoi specificare i valori mimeTypes o il file extensions accettati dell'app e impostare un flag multiple per consentire o impedire la selezione di più file o directory. Per i dettagli completi, consulta documentazione relativa all'API browser-fs-access. L'esempio di codice riportato di seguito mostra come aprire e salvare i file immagine.

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

Puoi vedere il codice riportato sopra in azione in una demo su Glitch. Anche il suo codice sorgente è disponibile lì. Per motivi di sicurezza, i frame secondari multiorigine non possono mostrare un selettore file, la demo non può essere incorporata in questo articolo.

La libreria browser-fs-access in circolazione

Nel mio tempo libero, do un piccolo contributo a un PWA installabile chiamato Excalidraw, uno strumento di lavagna che consente di disegnare facilmente diagrammi in un formato disegnato a mano. È completamente reattivo e funziona bene su una vasta gamma di dispositivi, dai piccoli telefoni cellulari ai computer con schermi di grandi dimensioni. Ciò significa che deve gestire file su tutte le varie piattaforme. se supportano o meno l'API File System Access. Questo lo rende un ottimo candidato per la libreria browser-fs-access.

Ad esempio posso iniziare a disegnare sull'iPhone, salvarla (tecnicamente: scaricala, poiché Safari non supporta l'API File System Access) nella cartella Download dell'iPhone, apri il file sul desktop (dopo averlo trasferito dal telefono), modificare il file, sovrascriverlo con le mie modifiche o persino salvarlo come nuovo file.

Un disegno Excalidraw su un iPhone.
Avvio di un disegno Excalidraw su un iPhone in cui l'API File System Access non è supportata, ma in cui è possibile salvare (scaricare) un file nella cartella Download.
di Gemini Advanced.
Il disegno Excalidraw modificato su Chrome sul desktop.
Apertura e modifica del disegno Excalidraw sul desktop in cui è supportata l'API File System Access, in modo da poter accedere al file tramite l'API.
di Gemini Advanced.
Sovrascrivere il file originale con le modifiche.
Sovrascrivi il file originale con le modifiche al file di disegno originale di Excalidraw. Il browser mostra una finestra di dialogo che mi chiede se va tutto bene.
di Gemini Advanced.
Salvataggio delle modifiche in un nuovo file di disegno di Excalidraw.
Salvataggio delle modifiche in un nuovo file Excalidraw. Il file originale rimane invariato.

Esempio di codice reale

Di seguito, puoi vedere un esempio reale di browser-fs-access utilizzato in Excalidraw. Questo brano è tratto da /src/data/json.ts Di particolare interesse è il modo in cui il metodo saveAsJSON() passa un handle di file o null a browser-fs-access' fileSave(), che ne fa sì che venga sovrascritta quando viene assegnato un handle, o salvarli in un nuovo file.

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

Considerazioni sull'interfaccia utente

Che si tratti di Excalidraw o nella tua app, l'interfaccia utente deve adattarsi alla situazione di assistenza del browser. Se l'API File System Access è supportata (if ('showOpenFilePicker' in window) {}) puoi mostrare un pulsante Salva con nome oltre a un pulsante Salva. Gli screenshot riportati di seguito mostrano la differenza tra la barra degli strumenti principale dell'app adattabile di Excalidraw su iPhone e su Chrome per desktop. Nota che su iPhone il pulsante Salva con nome non è presente.

Barra degli strumenti dell&#39;app Excalidraw su iPhone con un semplice &quot;Salva&quot; .
Barra degli strumenti dell'app Excalidraw su iPhone con un solo pulsante Salva.
di Gemini Advanced.
Barra degli strumenti dell&#39;app Excalidraw sulla versione desktop di Chrome con un &quot;Salva&quot; e &quot;Salva con nome&quot; .
Barra degli strumenti dell'app Excalidraw su Chrome con un pulsante Salva e un pulsante Salva con nome attivo.

Conclusioni

L'utilizzo dei file di sistema funziona tecnicamente su tutti i browser moderni. Sui browser che supportano l'API File System Access, puoi migliorare l'esperienza consentendo per il salvataggio e la sovrascrittura (non solo per il download) dei file, consentendo agli utenti di creare nuovi file dove vogliono, rimanendo comunque funzionante sui browser che non supportano l'API File System Access. browser-fs-access semplifica la tua vita gestire le sottigliezze del miglioramento progressivo e rendere il codice il più semplice possibile.

Ringraziamenti

Questo articolo è stato esaminato da Joe Medley e Kayce Basques. Grazie ai collaboratori di Excalidraw per il lavoro che ha svolto sul progetto e per l'esame delle mie richieste di pull. Immagine hero di Ilya Pavlov su Unsplash.