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 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 utilizzabile da 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 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 "salvi" viene creata 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 reale, 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 è 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);
}
};
Aprire le 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.
Dall'handle di un file, crei un flusso in scrittura tramite createWritable()
,
quindi scrivi i dati BLOB richiamando il metodo write()
del flusso,
quindi chiudi il flusso 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é è probabile che l'API File System Access subisca delle modifiche in futuro,
l'API browser-fs-access non viene modellata in base a questa.
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 quelli denominati correttamente
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
Si tratta di tre metodi intuitivi da usare.
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, do un piccolo contributo a una PWA installabile chiamata Excalidraw, uno strumento di lavagna che consente di disegnare facilmente diagrammi con la sensazione 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, dato che Safari non supporta l'API File System Access) nella cartella Download dell'iPhone, aprire il file sul desktop (dopo averlo trasferito dal telefono), modificarlo e sovrascriverlo con le mie modifiche, o persino salvarlo come nuovo file.
Esempio di codice reale
Di seguito, puoi vedere 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 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 viene fornito.
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 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 manca 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 al massimo.
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.