Gepubliceerd: 27 juli 2020
Browsers kunnen al lange tijd bestanden en mappen verwerken. De File API biedt functies voor het weergeven van bestandsobjecten in webapplicaties, evenals voor het programmatisch selecteren ervan en het benaderen van hun gegevens. Maar als je beter kijkt, blijkt niet alles goud te zijn wat blinkt.
De traditionele manier om met bestanden om te gaan
Open bestanden
Je kunt bestanden openen en lezen met het element <input type="file"> . In de eenvoudigste vorm ziet het openen van een bestand er ongeveer zo uit als in het codevoorbeeld. Het input object geeft je een FileList , die in ons voorbeeld slechts één File bevat. Een ` File is een specifiek type Blob en kan in elke context worden gebruikt waarin een `Blob` kan worden gebruikt.
const openFile = async () => {
return new Promise((resolve) => {
const input = document.createElement('input');
input.type = 'file';
input.addEventListener('change', () => {
resolve(input.files[0]);
});
input.click();
});
};
Mappen openen
Om mappen (of directory's) te openen, kunt u het attribuut <input webkitdirectory> instellen. Verder werkt alles hetzelfde als hierboven beschreven. Ondanks de naam met vendor-prefix is webkitdirectory niet alleen bruikbaar in Chromium- en WebKit-browsers, maar ook in de oudere, op EdgeHTML gebaseerde browser Edge en in Firefox.
Bestanden opslaan en downloaden
Om een bestand op te slaan, ben je traditioneel beperkt tot het downloaden ervan, wat werkt dankzij het <a download> ` attribuut. Met een `Blob` kun je het ` href attribuut van de link instellen op een ` blob: URL` die je kunt verkrijgen met de URL.createObjectURL() methode.
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();
};
Het probleem
Een groot nadeel van de downloadmethode is dat er geen klassieke open→bewerken→opslaan-workflow mogelijk is, oftewel, het is niet mogelijk om het originele bestand te overschrijven . In plaats daarvan krijg je bij elke "opslaan" een nieuwe kopie van het originele bestand in de standaard downloadmap van het besturingssysteem.
De API voor toegang tot het bestandssysteem
De File System Access API vereenvoudigt zowel het openen als het opslaan van bestanden aanzienlijk. Het maakt ook echt opslaan mogelijk. Dit betekent dat u kunt kiezen waar u het bestand wilt opslaan en dat u een bestaand bestand kunt overschrijven.
Open bestanden
Met de File System Access API is het openen van een bestand een kwestie van één aanroep naar de methode window.showOpenFilePicker() . Deze aanroep retourneert een bestandshandle, waarmee je het daadwerkelijke File kunt ophalen via de methode 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);
}
};
Mappen openen
Open een map door window.showDirectoryPicker() aan te roepen; hiermee worden mappen selecteerbaar in het bestandsdialoogvenster.
Bestanden opslaan
Het opslaan van bestanden is eveneens eenvoudig. Vanuit een bestandsgreep maak je een beschrijfbare stream aan met createWritable() , vervolgens schrijf je de Blob-gegevens door de write() methode van de stream aan te roepen en tot slot sluit je de stream door de close() methode aan te roepen.
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);
}
};
Introductie van browser-fs-access
Hoewel de File System Access API uitstekend is, is deze nog niet algemeen beschikbaar .

Daarom zie ik de File System Access API als een progressieve verbetering . Ik wil deze API gebruiken wanneer de browser dit ondersteunt, en anders de traditionele aanpak hanteren; dit alles zonder de gebruiker te benadelen met onnodige downloads van niet-ondersteunde JavaScript-code. De browser-fs-access- bibliotheek is mijn antwoord op deze uitdaging.
Ontwerpfilosofie
Omdat de File System Access API in de toekomst waarschijnlijk nog zal veranderen, is de browser-fs-access API er niet op gebaseerd. Dat wil zeggen, de bibliotheek is geen polyfill , maar een ponyfill . Je kunt (statisch of dynamisch) exclusief de functionaliteit importeren die je nodig hebt om je app zo klein mogelijk te houden. De beschikbare methoden zijn de toepasselijk genaamde fileOpen() , directoryOpen() en fileSave() . Intern detecteert de bibliotheek of de File System Access API wordt ondersteund en importeert vervolgens het bijbehorende codepad.
Gebruik de bibliotheek
De drie methoden zijn intuïtief in gebruik. U kunt de door uw app geaccepteerde mimeTypes of extensions specificeren en een multiple instellen om de selectie van meerdere bestanden of mappen toe te staan of te verbieden. Zie de API-documentatie van browser-fs-access voor meer informatie. Het codevoorbeeld laat zien hoe u afbeeldingsbestanden kunt openen en opslaan.
// 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
Je kunt de code in actie zien in een GitHub-demo . De broncode is daar ook beschikbaar.
De browser-fs-access-bibliotheek in de praktijk
In mijn vrije tijd draag ik een klein beetje bij aan een installeerbare PWA genaamd Excalidraw , een whiteboardtool waarmee je diagrammen kunt schetsen met een handgetekende uitstraling. Het is volledig responsief en werkt goed op een breed scala aan apparaten, van kleine mobiele telefoons tot computers met grote schermen. Dit betekent dat het bestanden op al die verschillende platforms moet kunnen verwerken, ongeacht of ze de File System Access API ondersteunen. Daardoor is het een uitstekende kandidaat voor de browser-fs-access-bibliotheek.
Ik kan bijvoorbeeld een tekening op mijn iPhone beginnen, deze opslaan (technisch gezien: downloaden, aangezien Safari de File System Access API niet ondersteunt) in mijn map Downloads op mijn iPhone, het bestand op mijn computer openen (nadat ik het van mijn telefoon heb overgezet), het bestand bewerken en overschrijven met mijn wijzigingen, of het zelfs opslaan als een nieuw bestand.




Praktisch codevoorbeeld
Hieronder ziet u een concreet voorbeeld van hoe browser-fs-access wordt gebruikt in Excalidraw. Dit fragment is afkomstig uit /src/data/json.ts . Bijzonder interessant is hoe de saveAsJSON() methode een bestandsdescriptor of null doorgeeft aan fileSave() -methode van browser-fs-access. Dit zorgt ervoor dat het bestand wordt overschreven wanneer een descriptor wordt meegegeven, of dat er een nieuw bestand wordt aangemaakt als dat niet het geval is.
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);
};
Overwegingen met betrekking tot de gebruikersinterface
Of het nu in Excalidraw of in uw app is, de gebruikersinterface moet zich aanpassen aan de ondersteuningssituatie van de browser. Als de File System Access API wordt ondersteund ( if ('showOpenFilePicker' in window) {} ), kunt u naast een ' Opslaan' -knop ook een 'Opslaan als' -knop weergeven. De onderstaande schermafbeeldingen tonen het verschil tussen de responsieve hoofdwerkbalk van Excalidraw op een iPhone en op een Chrome-desktop. Let op dat de 'Opslaan als' -knop op de iPhone ontbreekt.


Conclusies
Het werken met systeembestanden werkt in principe in alle moderne browsers. In browsers die de File System Access API ondersteunen, kunt u de gebruikerservaring verbeteren door het daadwerkelijk opslaan en overschrijven (niet alleen downloaden) van bestanden mogelijk te maken en door gebruikers nieuwe bestanden te laten aanmaken waar ze maar willen. Dit alles blijft functioneel in browsers die de File System Access API niet ondersteunen. De browser-fs-access-module maakt het u gemakkelijk door de subtiliteiten van progressieve verbetering af te handelen en uw code zo eenvoudig mogelijk te houden.
Dankbetuigingen
Dit is beoordeeld door Joe Medley en Kayce Basques . Met dank aan de bijdragers aan Excalidraw voor hun werk aan het project en voor het beoordelen van mijn pull requests.