Les navigateurs sont capables de traiter des fichiers et des répertoires depuis longtemps. API File fournit des fonctionnalités permettant de représenter des objets fichier dans des applications Web, les sélectionner de manière programmatique et accéder à leurs données. Pourtant, à partir du moment où l'on regarde de plus près, tout ce qui brille n'est plus que de l'or.
La méthode traditionnelle de gestion des fichiers
Ouverture des fichiers
En tant que développeur, vous pouvez ouvrir et lire des fichiers via le
<input type="file">
.
Dans sa forme la plus simple, l'ouverture d'un fichier ressemble à l'exemple de code ci-dessous.
L'objet input
vous donne un objet FileList
,
qui, dans le cas ci-dessous, ne comprend qu'un seul
File
Un File
est un type spécifique de Blob
,
et peuvent être utilisées dans n'importe quel contexte possible par un objet 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();
});
};
Ouverture des répertoires
Pour ouvrir des dossiers (ou répertoires), vous pouvez définir le paramètre
<input webkitdirectory>
.
À part cela, tout le reste fonctionne de la même manière que ci-dessus.
Malgré son nom préfixé par le fournisseur,
webkitdirectory
peut être utilisé non seulement dans les navigateurs Chromium et WebKit, mais aussi dans l'ancienne version Edge HTML, ainsi que dans Firefox.
Enregistrer (au lieu de télécharger) des fichiers
Pour enregistrer un fichier, vous êtes généralement limité au téléchargement d'un fichier,
qui fonctionne grâce aux
<a download>
.
Avec un objet Blob, vous pouvez définir l'attribut href
de l'ancre sur une URL blob:
que vous pouvez obtenir à partir de
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();
};
Problème
L'inconvénient majeur de l'approche de téléchargement est qu'il n'existe aucun moyen de créer une version classique Ouvrir → modifier → enregistrer le flux, c'est-à-dire qu'il n'y a aucun moyen d'écraser le fichier d'origine. Vous obtenez alors une nouvelle copie du fichier d'origine. dans le dossier "Téléchargements" par défaut du système d'exploitation chaque fois que vous "enregistrez".
API File System Access
L'API File System Access simplifie considérablement les opérations, l'ouverture et l'enregistrement. Elle active également l'enregistrement réel, c'est-à-dire que vous pouvez non seulement choisir l'emplacement où enregistrer un fichier, mais aussi écraser un fichier existant.
Ouverture des fichiers
Avec l'API File System Access,
Pour ouvrir un fichier, il suffit d'appeler la méthode window.showOpenFilePicker()
.
Cet appel renvoie un handle de fichier, à partir duquel vous pouvez obtenir le File
réel via la méthode 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);
}
};
Ouverture des répertoires
Ouvrez un répertoire en appelant
window.showDirectoryPicker()
, qui rend les répertoires sélectionnables dans la boîte de dialogue "File" (Fichier)
Enregistrement de fichiers
L'enregistrement de fichiers est tout aussi simple.
À partir d'un handle de fichier, vous créez un flux accessible en écriture via createWritable()
,
Ensuite, vous écrivez les données Blob en appelant la méthode write()
du flux,
Enfin, vous fermez le flux en appelant sa méthode 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);
}
};
Présentation de browser-fs-access
Aussi parfait que soit l’API File System Access, elle n'est pas encore disponible pour tous les utilisateurs.
<ph type="x-smartling-placeholder">C'est pourquoi je considère l'API File System Access comme une amélioration progressive. En tant que tel, je veux l’utiliser quand le navigateur le prend en charge, et utilisez l'approche traditionnelle si ce n'est pas le cas. tout en ne punissant jamais l'utilisateur avec des téléchargements inutiles de code JavaScript non pris en charge. browser-fs-access bibliothèque est ma réponse à ce défi.
Philosophie de conception
Étant donné que l'API File System Access
est encore susceptible de changer à l'avenir,
l'API browser-fs-access n'est pas modélisée d'après elle.
Autrement dit, la bibliothèque n'est pas un polyfill,
mais plutôt comme poney.
Vous pouvez importer (de manière statique ou dynamique) exclusivement les fonctionnalités dont vous avez besoin pour réduire au maximum la taille de votre application.
Les méthodes disponibles sont les API
fileOpen()
,
directoryOpen()
fileSave()
En interne, la fonctionnalité de bibliothèque détecte
si l'API File System Access est prise en charge
puis importe le chemin de code correspondant.
Utiliser la bibliothèque browser-fs-access
Ces trois méthodes sont intuitives à utiliser.
Vous pouvez spécifier le mimeTypes
ou le fichier extensions
accepté par votre application, et définir un indicateur multiple
.
pour autoriser ou interdire la sélection
de plusieurs fichiers ou répertoires.
Pour plus d'informations, consultez les
documentation de l'API browser-fs-access.
L'exemple de code ci-dessous montre comment ouvrir et enregistrer des fichiers image.
// 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',
});
})();
Démo
Vous pouvez voir le code ci-dessus en action dans une démonstration sur Glitch. Son code source y est également disponible. Étant donné que, pour des raisons de sécurité, les sous-frames multi-origines ne sont pas autorisés à afficher un sélecteur de fichier, la démo ne peut pas être intégrée dans cet article.
La bibliothèque browser-fs-access à l'œuvre
Pendant mon temps libre, je contribue légèrement à une pWA installable appelée Excalidraw, Un outil de tableau blanc qui vous permet d'esquisser facilement des diagrammes avec une sensation de dessin à la main. Entièrement réactif, il fonctionne bien sur une gamme d'appareils, des petits téléphones mobiles aux ordinateurs dotés d'un grand écran. Cela signifie qu'il doit traiter des fichiers sur les différentes plates-formes sont compatibles ou non avec l'API File System Access. Cela en fait un candidat idéal pour la bibliothèque browser-fs-access.
Je peux, par exemple, commencer un dessin sur mon iPhone, l'enregistrer (techniquement: le télécharger, car Safari n'est pas compatible avec l'API File System Access) ; dans le dossier "Téléchargements" de mon iPhone, ouvre le fichier sur mon bureau (après l'avoir transféré depuis mon téléphone), modifier le fichier et l’écraser avec mes modifications, ou même l’enregistrer en tant que nouveau fichier.
<ph type="x-smartling-placeholder"> <ph type="x-smartling-placeholder"> <ph type="x-smartling-placeholder"> <ph type="x-smartling-placeholder">Exemple de code réel
Vous trouverez ci-dessous un exemple d'utilisation de browser-fs-access dans Excalidraw.
Cet extrait est tiré de
/src/data/json.ts
Il est particulièrement intéressant de savoir comment la méthode saveAsJSON()
transmet un handle de fichier ou null
à browser-fs-access.
fileSave()
, qui l'écrase lorsqu'un handle est attribué,
ou enregistrer dans un nouveau fichier si ce n'est pas le cas.
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);
};
Considérations relatives à l'interface utilisateur
Que ce soit dans Excalidraw ou dans votre application,
l'interface utilisateur doit s'adapter à la situation
de prise en charge du navigateur.
Si l'API File System Access est compatible (if ('showOpenFilePicker' in window) {}
)
vous pouvez afficher un bouton Enregistrer sous en plus du bouton Enregistrer.
Les captures d'écran ci-dessous montrent la différence entre la barre d'outils responsive principale d'Excalidraw sur iPhone et sur Chrome pour ordinateur.
Notez que le bouton Save As (Enregistrer sous) n'est pas disponible sur iPhone.
Conclusions
Techniquement, l'utilisation des fichiers système fonctionne avec tous les navigateurs modernes. Dans les navigateurs compatibles avec l'API File System Access, vous pouvez améliorer votre expérience en autorisant pour véritable sauvegarde et écrasement (pas seulement le téléchargement) de fichiers et en permettant à vos utilisateurs de créer de nouveaux fichiers où ils le souhaitent, tout en restant fonctionnel sur les navigateurs qui ne prennent pas en charge l'API File System Access. browser-fs-access vous simplifie la vie. en traitant les subtilités de l'amélioration progressive et en simplifiant au maximum votre code.
Remerciements
Cet article a été lu par Joe Medley et Kayce Basques. Merci aux contributeurs d'Excalidraw pour leur travail sur le projet et pour avoir examiné mes demandes d’extraction. Image héros de Ilya Pavlov sur Unsplash.