Lettura e scrittura di file e directory

Data di pubblicazione: 27 luglio 2020

I browser sono in grado di gestire file e directory da molto tempo. L'API File fornisce funzionalità per rappresentare gli oggetti file nelle applicazioni web, nonché per selezionarli in modo programmatico e accedere ai relativi dati. Ma se guardi più da vicino, non è tutto oro quel che luccica.

Il modo tradizionale di gestire i file

Apri i file

Puoi aprire e leggere i file con l'elemento <input type="file">. Nella sua forma più semplice, l'apertura di un file può essere simile al codice campione. L'oggetto input fornisce un FileList, che nel nostro esempio è costituito da un solo File. Un File è un tipo specifico di Blob e può essere utilizzato in qualsiasi contesto in cui può essere utilizzato un Blob.

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

Aprire directory

Per l'apertura di cartelle (o directory), puoi impostare l'attributo <input webkitdirectory>. A parte questo, tutto il resto funziona come descritto sopra. Nonostante il nome con prefisso del fornitore, webkitdirectory è utilizzabile non solo nei browser Chromium e WebKit, ma anche nella versione precedente di Edge basata su EdgeHTML e in Firefox.

Salvare e scaricare file

Per il salvataggio di un file, tradizionalmente, puoi solo scaricare un file, che funziona grazie all'attributo <a download>. Dato un blob, puoi impostare l'attributo href dell'ancora su un URL blob: che puoi ottenere dal metodo 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 di download è che non è possibile eseguire un flusso classico apri→modifica→salva, ovvero non è possibile sovrascrivere il file originale. Invece, ogni volta che fai clic su "Salva", ottieni una nuova copia del file originale nella cartella Download predefinita del sistema operativo.

API File System Access

L'API File System Access semplifica notevolmente entrambe le operazioni, apertura e salvataggio. Consente inoltre il salvataggio effettivo. Ciò significa che puoi scegliere dove salvare il file e sovrascrivere un file esistente.

Apri i file

Con l'API File System Access, l'apertura di un file richiede una sola chiamata al metodo window.showOpenFilePicker(). Questa chiamata restituisce un handle del file, da cui puoi ottenere l'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);
  }
};

Apri directory

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

Salvare i file

Il salvataggio dei file è altrettanto semplice. Da un handle di file, crei un flusso scrivibile tramite createWritable(), quindi scrivi i dati del blob chiamando il metodo write() del flusso e infine chiudi il flusso 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 di browser-fs-access

L'API File System Access è perfetta, ma non è ancora ampiamente disponibile.

Tabella di supporto dei browser per l&#39;API File System Access. Tutti i browser sono contrassegnati come &quot;non supportato&quot; o &quot;dietro un flag&quot;.
Tabella del supporto del browser per l'API File System Access. (Fonte)

Per questo motivo, considero l'API File System Access un miglioramento progressivo. Pertanto, 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. La libreria browser-fs-access è la mia risposta a questa sfida.

Filosofia di progettazione

Poiché è probabile che l'API File System Access cambi in futuro, l'API browser-fs-access non è modellata su di essa. ovvero non è un polyfill, ma un ponyfill. Puoi importare (staticamente o dinamicamente) esclusivamente le funzionalità che ti servono per mantenere l'app il più piccola possibile. I metodi disponibili sono fileOpen(), directoryOpen() e fileSave(). Internamente, la libreria rileva se l'API File System Access è supportata e poi importa il percorso del codice corrispondente.

Utilizzare la libreria

I tre metodi sono intuitivi da usare. Puoi specificare il mimeTypes o il file extensions accettati dalla tua app e impostare un flag multiple per consentire o impedire la selezione di più file o directory. Per tutti i dettagli, consulta la documentazione dell'API browser-fs-access. L'esempio di codice 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 in azione in una demo di GitHub. Anche il suo codice sorgente è disponibile.

La libreria browser-fs-access in natura

Nel tempo libero, contribuisco in minima parte a una PWA installabile chiamata Excalidraw, uno strumento di lavagna che ti consente di disegnare diagrammi con un aspetto disegnato a mano. È completamente reattivo e funziona bene su una vasta gamma di dispositivi, dai piccoli cellulari ai computer con schermi di grandi dimensioni. Ciò significa che deve gestire i file su tutte le varie piattaforme, indipendentemente dal fatto che supportino o meno l'API File System Access. Ciò lo rende un ottimo candidato per la libreria browser-fs-access.

Ad esempio, posso iniziare un disegno sul mio iPhone, salvarlo (tecnicamente: scaricarlo, poiché Safari non supporta l'API File System Access) nella cartella Download dell'iPhone, aprire il file sul mio computer (dopo averlo trasferito dallo smartphone), modificarlo e sovrascriverlo con le mie modifiche o persino salvarlo come nuovo file.

Un disegno di Excalidraw su un iPhone.
Avvio di un disegno di Excalidraw su un iPhone in cui l'API File System Access non è supportata, ma in cui un file può essere salvato (scaricato) nella cartella Download.
Il disegno di Excalidraw modificato su Chrome sul computer.
Apertura e modifica del disegno di Excalidraw sul computer in cui è supportata l'API File System Access e quindi è possibile accedere al file tramite l'API.
Sovrascrivendo il file originale con le modifiche.
Sovrascrittura del file originale con le modifiche apportate al file di disegno Excalidraw originale. Il browser mostra una finestra di dialogo che mi chiede se va bene.
Salvataggio delle modifiche in un nuovo file di disegno Excalidraw.
Salvataggio delle modifiche in un nuovo file Excalidraw. Il file originale rimane invariato.

Esempio di codice reale

Di seguito è riportato un esempio reale di browser-fs-access utilizzato in Excalidraw. Questo estratto è tratto da /src/data/json.ts. Di particolare interesse è il modo in cui il metodo saveAsJSON() passa un handle di file o null al metodo fileSave() di browser-fs-access, che lo fa sovrascrivere quando viene fornito un handle o salvare in un nuovo file in caso contrario.

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 relative alla UI

Nell'app o in Excalidraw, l'interfaccia utente deve adattarsi alla situazione di supporto 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 reattiva dell'app Excalidraw su iPhone e su Chrome da computer. Nota che su iPhone manca il pulsante Salva come.

Barra degli strumenti dell&#39;app Excalidraw su iPhone con un solo pulsante &quot;Salva&quot;.
Barra degli strumenti dell'app Excalidraw su iPhone con un solo pulsante Salva.
Barra degli strumenti dell'app Excalidraw su Chrome con un pulsante Salva e un pulsante Salva con nome messo in evidenza.

Conclusioni

Tecnicamente, l'utilizzo dei file di sistema funziona su tutti i browser moderni. Sui browser che supportano l'API File System Access, puoi migliorare l'esperienza consentendo il salvataggio e la sovrascrittura (non solo il download) dei file e permettendo agli utenti di creare nuovi file ovunque vogliano, il tutto mantenendo la funzionalità sui browser che non supportano l'API File System Access. browser-fs-access ti semplifica la vita gestendo le sottigliezze del miglioramento progressivo e rendendo il codice il più semplice possibile.

Ringraziamenti

Questo articolo è stato rivisto da Joe Medley e Kayce Basques. Grazie ai contributori di Excalidraw per il loro lavoro sul progetto e per aver esaminato le mie richieste di pull.