I browser sono in grado di gestire file e directory da molto tempo. L'API File fornisce funzionalità per la rappresentazione di oggetti file nelle applicazioni web, nonché per selezionarli e accedere ai relativi dati in modo programmatico. Tuttavia, se guardi più da vicino, scoprirai che non è tutto oro quel che luccica.
Il modo tradizionale di gestire i file
Apertura dei file
In qualità di sviluppatore, puoi aprire e leggere i file tramite l'elemento
<input type="file">
.
Nella sua forma più semplice, l'apertura di un file può essere simile all'esempio di codice riportato di seguito.
L'oggetto input
fornisce un FileList
,
che nel caso riportato di seguito è costituito da un solo
File
.
Un File
è un tipo specifico di Blob
e può essere utilizzato in qualsiasi contesto in cui è possibile utilizzare 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();
});
};
Apertura di directory
Per aprire cartelle (o directory), puoi impostare l'attributo
<input webkitdirectory>
.
A parte questo, tutto il resto funziona come sopra.
Nonostante il nome con prefisso del fornitore,
webkitdirectory
non è utilizzabile solo nei browser Chromium e WebKit, ma anche nell'Edge precedente basato su EdgeHTML e in Firefox.
Salvataggio (o download) di file
Per salvare un file, in genere, puoi solo scaricarlo,
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
Uno svantaggio enorme dell'approccio di download è che non è possibile eseguire un flusso classico di apertura, modifica e salvataggio, ovvero non è possibile sovrascrivere il file originale. Al contrario, ogni volta che selezioni "Salva", ottieni una nuova copia del file originale nella cartella Download predefinita del sistema operativo.
L'API File System Access
L'API File System Access semplifica notevolmente entrambe le operazioni, apertura e salvataggio. Consente inoltre il salvataggio effettivo, ovvero non solo puoi scegliere dove salvare un file, ma anche sovrascrivere un file esistente.
Apertura dei file
Con l'API File System Access,
l'apertura di un file è questione di una chiamata al metodo window.showOpenFilePicker()
.
Questa chiamata restituisce un handle file, da cui puoi ottenere il file 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 di directory
Apri una directory chiamando
window.showDirectoryPicker()
che rende le directory selezionabili nella finestra di dialogo del file.
Salvataggio dei file
Anche il salvataggio dei file è altrettanto semplice.
Da un handle file, crei uno stream scrivibile tramite createWritable()
, poi scrivi i dati del blob chiamando il metodo write()
dello stream e infine chiudi lo stream chiamando il relativo 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
L'API File System Access è perfettamente valida, ma non è ancora ampiamente disponibile.
Per questo motivo, considero l'API File System Access un miglioramento progressivo. Pertanto, voglio utilizzarlo quando il browser lo supporta e, in caso contrario, utilizzare l'approccio tradizionale, senza mai punire l'utente con download non necessari di codice JavaScript non supportato. La libreria browser-fs-access è la mia risposta a questa sfida.
Filosofia del design
Poiché l'API File System Access potrebbe ancora cambiare in futuro,
l'API browser-fs-access non è modellata su di essa.
In altre parole, la libreria non è un polyfill, ma un ponyfill.
Puoi importare (staticamente o dinamicamente) esclusivamente le funzionalità di cui hai bisogno per mantenere l'app il più piccola possibile.
I metodi disponibili sono fileOpen()
, directoryOpen()
e fileSave()
.
All'interno, la funzionalità della libreria rileva se l'API Accesso al file system è supportata, quindi importa il percorso del codice corrispondente.
Utilizzo della libreria browser-fs-access
I tre metodi sono intuitivi da utilizzare.
Puoi specificare il mimeTypes
o il file extensions
accettati dalla tua app e impostare un flag multiple
per consentire o meno la selezione di più file o directory.
Per tutti i dettagli, consulta la documentazione dell'API browser-fs-access.
L'esempio di codice seguente 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 codice sorgente è disponibile lì. Poiché per motivi di sicurezza i frame secondari di origine diversa non sono autorizzati a mostrare un selettore di file, la demo non può essere incorporata in questo articolo.
La libreria browser-fs-access in uso
Nel tempo libero, collaboro a una PWA installabile chiamata Excalidraw, uno strumento per lavagne che ti consente di disegnare facilmente diagrammi con un'esperienza simile a quella di un disegno a mano. È completamente adattabile e funziona bene su una serie 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 Accesso al file system. Questo 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 del mio iPhone, aprire il file sul mio computer (dopo averlo trasferito dal telefono), modificarlo e sovrascriverlo con le mie modifiche o addirittura salvarlo come nuovo file.
Esempio di codice reale
Di seguito è riportato un esempio pratico di browser-fs-access, così come viene utilizzato in Excalidraw.
Questo estratto è tratto da
/src/data/json.ts
.
Di particolare interesse è il modo in cui il metodo saveAsJSON()
passa un handle file o null
al metodo fileSave()
di browser-fs-access, che lo sovrascrive quando viene fornito un handle o lo salva in un nuovo file se non è presente.
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 all'interfaccia utente
Che si tratti di Excalidraw o della tua app,
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 come oltre a un pulsante Salva.
Gli screenshot di seguito mostrano la differenza tra la barra degli strumenti dell'app principale adattabile di Excalidraw su iPhone e su Chrome per computer.
Tieni presente che su iPhone non è presente il pulsante Salva come.
Conclusioni
Il lavoro con i file di sistema funziona tecnicamente su tutti i browser moderni. Sui browser che supportano l'API Accesso al file system, puoi migliorare l'esperienza consentendo un salvataggio e una sovrascrittura (non solo il download) effettivi dei file e consentendo agli utenti di creare nuovi file dove vogliono, il tutto rimanendo funzionale sui browser che non supportano l'API Accesso al file system. browser-fs-access semplifica la vita grazie al trattamento delle sfumature del miglioramento progressivo e alla semplificazione del codice.
Ringraziamenti
Questo articolo è stato esaminato da Joe Medley e Kayce Basques. Grazie ai collaboratori di Excalidraw per il loro lavoro sul progetto e per aver esaminato le mie richieste pull. Immagine hero di Ilya Pavlov su Unsplash.