Explorez la puissante API File System Access, permettant aux applications web de lire, écrire et gérer des fichiers locaux en toute sécurité. Un guide complet pour les développeurs du monde entier.
Déverrouiller le système de fichiers local : une analyse approfondie de l'API File System Access
Pendant des décennies, le navigateur a été un environnement isolé (sandboxed), un espace sécurisé mais fondamentalement limité. L'une de ses frontières les plus rigides a été le système de fichiers local. Les applications web pouvaient vous demander de téléverser un fichier ou vous inviter à en télécharger un, mais l'idée d'un éditeur de texte basé sur le web ouvrant un fichier, vous laissant le modifier et l'enregistrant au même endroit relevait de la pure science-fiction. Cette limitation a été la raison principale pour laquelle les applications de bureau natives ont conservé leur avantage pour les tâches nécessitant une manipulation intensive des fichiers, comme le montage vidéo, le développement de logiciels et la conception graphique.
Ce paradigme est en train de changer. L'API File System Access, anciennement connue sous le nom d'API Native File System, brise cette barrière de longue date. Elle fournit aux développeurs web un mécanisme standardisé, sécurisé et puissant pour lire, écrire et gérer les fichiers et répertoires sur la machine locale de l'utilisateur. Il ne s'agit pas d'une vulnérabilité de sécurité ; c'est une évolution soigneusement conçue, qui donne à l'utilisateur un contrôle total grâce à des autorisations explicites.
Cette API est une pierre angulaire pour la prochaine génération d'Applications Web Progressives (PWA), leur conférant des capacités qui étaient autrefois exclusives aux logiciels natifs. Imaginez un IDE basé sur le web capable de gérer un dossier de projet local, un éditeur de photos qui travaille directement sur vos images haute résolution sans téléversement, ou une application de prise de notes qui enregistre des fichiers markdown directement dans votre dossier de documents. C'est l'avenir que l'API File System Access rend possible.
Dans ce guide complet, nous explorerons toutes les facettes de cette API transformatrice. Nous nous pencherons sur son histoire, comprendrons ses principes de sécurité fondamentaux, passerons en revue des exemples de code pratiques pour la lecture, l'écriture et la gestion de répertoires, et discuterons des techniques avancées et des cas d'utilisation concrets qui inspireront votre prochain projet.
L'évolution de la gestion des fichiers sur le Web
Pour vraiment apprécier l'importance de l'API File System Access, il est utile de revenir sur l'histoire de la manière dont les navigateurs ont géré les fichiers locaux. Le parcours a été celui d'une itération progressive, soucieuse de la sécurité.
L'approche classique : les inputs et les ancres
Les méthodes originales d'interaction avec les fichiers étaient simples et strictement contrôlées :
- Lecture de fichiers : L'élément
<input type="file">a été le cheval de bataille pour le téléversement de fichiers pendant des années. Lorsqu'un utilisateur sélectionne un fichier (ou plusieurs fichiers avec l'attributmultiple), l'application reçoit un objetFileList. Les développeurs peuvent alors utiliser l'APIFileReaderpour lire le contenu de ces fichiers en mémoire sous forme de chaîne de caractères, d'ArrayBuffer ou d'URL de données. Cependant, l'application ne connaît jamais le chemin d'origine du fichier et n'a aucun moyen de réécrire dessus. Chaque opération de 'sauvegarde' est en réalité un 'téléchargement'. - Sauvegarde de fichiers : La sauvegarde était encore plus indirecte. La technique courante consiste à créer une balise
<a>(ancre), à définir son attributhrefsur une URI de données ou une URL de Blob, à ajouter l'attributdownloadavec un nom de fichier suggéré, et à cliquer dessus par programmation. Cette action présente à l'utilisateur une boîte de dialogue 'Enregistrer sous...', qui pointe généralement vers son dossier 'Téléchargements'. L'utilisateur doit naviguer manuellement jusqu'au bon emplacement s'il souhaite écraser un fichier existant.
Les limites des anciennes méthodes
Bien que fonctionnel, ce modèle classique présentait des limitations importantes pour la création d'applications sophistiquées :
- Interaction sans état : La connexion au fichier est perdue immédiatement après sa lecture. Si un utilisateur modifie un document et souhaite l'enregistrer, l'application ne peut pas simplement écraser l'original. Elle doit télécharger une nouvelle copie, souvent avec un nom modifié (par ex., 'document(1).txt'), ce qui entraîne un encombrement de fichiers et une expérience utilisateur confuse.
- Pas d'accès aux répertoires : Le concept de dossier n'existait pas. Une application ne pouvait pas demander à un utilisateur d'ouvrir un répertoire de projet entier pour travailler avec son contenu, une exigence fondamentale pour tout IDE ou éditeur de code basé sur le web.
- Friction pour l'utilisateur : Le cycle constant 'Ouvrir...' -> 'Modifier' -> 'Enregistrer sous...' -> 'Naviguer...' -> 'Écraser ?' est lourd et inefficace par rapport à la simple expérience 'Ctrl + S' ou 'Cmd + S' dans les applications natives.
Ces contraintes ont relégué les applications web au rang de consommateurs et de créateurs de fichiers transitoires, et non d'éditeurs persistants des données locales d'un utilisateur. L'API File System Access a été conçue pour remédier directement à ces lacunes.
Présentation de l'API File System Access
L'API File System Access est une norme web moderne qui fournit un pont direct, bien que contrôlé par des autorisations, vers le système de fichiers local de l'utilisateur. Elle permet aux développeurs de créer des expériences riches, de type bureautique, où les fichiers et les répertoires sont traités comme des citoyens de première classe.
Concepts clés et terminologie
La compréhension de l'API commence par ses objets clés, qui agissent comme des handles (descripteurs) ou des références à des éléments du système de fichiers.
FileSystemHandle: C'est l'interface de base pour les fichiers et les répertoires. Elle représente une seule entrée dans le système de fichiers et possède des propriétés commenameetkind('file' ou 'directory').FileSystemFileHandle: Cette interface représente un fichier. Elle hérite deFileSystemHandleet fournit des méthodes pour interagir avec le contenu du fichier, telles quegetFile()pour obtenir un objetFilestandard (pour lire les métadonnées ou le contenu) etcreateWritable()pour obtenir un flux d'écriture de données.FileSystemDirectoryHandle: Ceci représente un répertoire. Elle vous permet de lister le contenu du répertoire ou d'obtenir des handles vers des fichiers ou sous-répertoires spécifiques en son sein à l'aide de méthodes commegetFileHandle()etgetDirectoryHandle(). Elle fournit également des itérateurs asynchrones pour parcourir ses entrées.FileSystemWritableFileStream: Il s'agit d'une puissante interface basée sur les flux pour écrire des données dans un fichier. Elle vous permet d'écrire efficacement des chaînes de caractères, des Blobs ou des Buffers et fournit des méthodes pour se déplacer à une position spécifique ou tronquer le fichier. Vous devez appeler sa méthodeclose()pour vous assurer que les modifications sont écrites sur le disque.
Le modèle de sécurité : centré sur l'utilisateur et sécurisé
Accorder à un site web un accès direct à votre système de fichiers est une considération de sécurité importante. Les concepteurs de cette API ont construit un modèle de sécurité robuste, basé sur les autorisations, qui privilégie le consentement et le contrôle de l'utilisateur.
- Actions initiées par l'utilisateur : Une application ne peut pas déclencher spontanément un sélecteur de fichiers. L'accès doit être initié par un geste direct de l'utilisateur, comme un clic sur un bouton. Cela empêche les scripts malveillants d'analyser silencieusement votre système de fichiers.
- Le sélecteur comme porte d'entrée : Les points d'entrée de l'API sont les méthodes de sélection :
window.showOpenFilePicker(),window.showSaveFilePicker(), etwindow.showDirectoryPicker(). Ces méthodes affichent l'interface utilisateur native du navigateur pour la sélection de fichiers/répertoires. La sélection de l'utilisateur constitue une autorisation explicite pour cet élément spécifique. - Invites d'autorisation : Après l'acquisition d'un handle, le navigateur peut demander à l'utilisateur des autorisations de 'lecture' ou de 'lecture-écriture' pour ce handle. L'utilisateur doit approuver cette invite avant que l'application ne puisse continuer.
- Persistance des autorisations : Pour une meilleure expérience utilisateur, les navigateurs peuvent conserver ces autorisations pour une origine (site web) donnée. Cela signifie qu'après qu'un utilisateur a accordé l'accès à un fichier une fois, il ne sera plus sollicité pendant la même session ou même lors de visites ultérieures. Le statut de l'autorisation peut être vérifié avec
handle.queryPermission()et redemandé avechandle.requestPermission(). Les utilisateurs peuvent révoquer ces autorisations à tout moment via les paramètres de leur navigateur. - Contextes sécurisés uniquement : Comme de nombreuses API web modernes, l'API File System Access n'est disponible que dans des contextes sécurisés, ce qui signifie que votre site web doit être servi via HTTPS ou depuis localhost.
Cette approche multi-couches garantit que l'utilisateur est toujours conscient et en contrôle, trouvant un équilibre entre de nouvelles capacités puissantes et une sécurité sans faille.
Implémentation pratique : un guide étape par étape
Passons de la théorie à la pratique. Voici comment vous pouvez commencer à utiliser l'API File System Access dans vos applications web. Toutes les méthodes de l'API sont asynchrones et retournent des Promesses, nous utiliserons donc la syntaxe moderne async/await pour un code plus propre.
Vérification du support par le navigateur
Avant d'utiliser l'API, vous devez vérifier si le navigateur de l'utilisateur la prend en charge. Une simple vérification de la détection de fonctionnalité est tout ce qui est nécessaire.
if ('showOpenFilePicker' in window) {
console.log('Parfait ! L\'API File System Access est prise en charge.');
} else {
console.log('Désolé, ce navigateur ne prend pas en charge l\'API.');
// Fournir une alternative avec <input type="file">
}
Lire un fichier
La lecture d'un fichier local est un point de départ courant. Le processus consiste à afficher le sélecteur de fichiers, à obtenir un handle de fichier, puis à lire son contenu.
const openFileButton = document.getElementById('open-file-btn');
openFileButton.addEventListener('click', async () => {
try {
// La méthode showOpenFilePicker() renvoie un tableau de handles,
// mais nous ne nous intéressons qu'au premier pour cet exemple.
const [fileHandle] = await window.showOpenFilePicker();
// Obtenir l'objet File Ă partir du handle.
const file = await fileHandle.getFile();
// Lire le contenu du fichier sous forme de texte.
const content = await file.text();
// Utiliser le contenu (par exemple, l'afficher dans un textarea).
document.getElementById('editor').value = content;
} catch (err) {
// Gérer les erreurs, comme l'annulation du sélecteur par l'utilisateur.
console.error('Erreur lors de l\'ouverture du fichier :', err);
}
});
Dans cet exemple, window.showOpenFilePicker() renvoie une promesse qui se résout avec un tableau d'objets FileSystemFileHandle. Nous déstructurons le premier élément dans notre variable fileHandle. À partir de là , fileHandle.getFile() fournit un objet File standard, qui possède des méthodes familières comme .text(), .arrayBuffer(), et .stream().
Écrire dans un fichier
C'est dans l'écriture que l'API brille vraiment, car elle permet à la fois de sauvegarder de nouveaux fichiers et d'écraser des fichiers existants de manière transparente.
Sauvegarder les modifications d'un fichier existant
Étendons notre exemple précédent. Nous devons stocker le fileHandle pour pouvoir l'utiliser plus tard pour enregistrer les modifications.
let currentFileHandle;
// ... à l'intérieur de l'écouteur de clic 'openFileButton' ...
// Après avoir obtenu le handle de showOpenFilePicker :
currentFileHandle = fileHandle;
// --- Maintenant, configurons le bouton de sauvegarde ---
const saveFileButton = document.getElementById('save-file-btn');
saveFileButton.addEventListener('click', async () => {
if (!currentFileHandle) {
alert('Veuillez d\'abord ouvrir un fichier !');
return;
}
try {
// Créer un FileSystemWritableFileStream pour écrire.
const writable = await currentFileHandle.createWritable();
// Obtenir le contenu de notre éditeur.
const content = document.getElementById('editor').value;
// Écrire le contenu dans le flux.
await writable.write(content);
// Fermer le fichier et écrire le contenu sur le disque.
// C'est une étape cruciale !
await writable.close();
alert('Fichier enregistré avec succès !');
} catch (err) {
console.error('Erreur lors de la sauvegarde du fichier :', err);
}
});
Les étapes clés sont createWritable(), qui prépare le fichier pour l'écriture, write(), qui envoie les données, et le critique close(), qui finalise l'opération et valide les changements sur le disque.
Enregistrer un nouveau fichier ('Enregistrer sous')
Pour enregistrer un nouveau fichier, vous utilisez window.showSaveFilePicker(). Cela présente une boîte de dialogue 'Enregistrer sous' et renvoie un nouveau FileSystemFileHandle pour l'emplacement choisi.
const saveAsButton = document.getElementById('save-as-btn');
saveAsButton.addEventListener('click', async () => {
try {
const newFileHandle = await window.showSaveFilePicker({
suggestedName: 'sans-titre.txt',
types: [{
description: 'Fichiers texte',
accept: {
'text/plain': ['.txt'],
},
}],
});
// Maintenant que nous avons un handle, nous pouvons utiliser la même logique d'écriture qu'auparavant.
const writable = await newFileHandle.createWritable();
const content = document.getElementById('editor').value;
await writable.write(content);
await writable.close();
// Optionnellement, mettre Ă jour notre handle actuel vers ce nouveau fichier.
currentFileHandle = newFileHandle;
alert('Fichier enregistré à un nouvel emplacement !');
} catch (err) {
console.error('Erreur lors de la sauvegarde du nouveau fichier :', err);
}
});
Travailler avec les répertoires
La capacité de travailler avec des répertoires entiers ouvre la voie à des cas d'utilisation puissants comme les IDE basés sur le web.
D'abord, nous laissons l'utilisateur sélectionner un répertoire :
const openDirButton = document.getElementById('open-dir-btn');
openDirButton.addEventListener('click', async () => {
try {
const dirHandle = await window.showDirectoryPicker();
// Nous pouvons maintenant traiter le contenu du répertoire.
await processDirectory(dirHandle);
} catch (err) {
console.error('Erreur lors de l\'ouverture du répertoire :', err);
}
});
Une fois que vous avez un FileSystemDirectoryHandle, vous pouvez parcourir son contenu en utilisant une boucle asynchrone for...of. La fonction suivante liste récursivement tous les fichiers et sous-répertoires.
async function processDirectory(dirHandle) {
const fileListElement = document.getElementById('file-list');
fileListElement.innerHTML = ''; // Vider la liste précédente
for await (const entry of dirHandle.values()) {
const listItem = document.createElement('li');
// La propriété 'kind' est soit 'file' soit 'directory'
listItem.textContent = `[${entry.kind}] ${entry.name}`;
fileListElement.appendChild(listItem);
if (entry.kind === 'directory') {
// Ceci montre qu'un simple appel récursif est possible,
// bien qu'une interface utilisateur complète gérerait l'imbrication différemment.
console.log(`Sous-répertoire trouvé : ${entry.name}`);
}
}
}
Créer de nouveaux fichiers et répertoires
Vous pouvez également créer par programmation de nouveaux fichiers et sous-répertoires dans un répertoire auquel vous avez accès. Cela se fait en passant l'option { create: true } aux méthodes getFileHandle() ou getDirectoryHandle().
async function createNewFile(dirHandle, fileName) {
try {
// Obtenir un handle pour un nouveau fichier, en le créant s'il n'existe pas.
const newFileHandle = await dirHandle.getFileHandle(fileName, { create: true });
console.log(`Handle créé ou obtenu pour le fichier : ${newFileHandle.name}`);
// Vous pouvez maintenant écrire dans ce handle.
} catch (err) {
console.error('Erreur lors de la création du fichier :', err);
}
}
async function createNewFolder(dirHandle, folderName) {
try {
// Obtenir un handle pour un nouveau répertoire, en le créant s'il n'existe pas.
const newDirHandle = await dirHandle.getDirectoryHandle(folderName, { create: true });
console.log(`Handle créé ou obtenu pour le répertoire : ${newDirHandle.name}`);
} catch (err) {
console.error('Erreur lors de la création du répertoire :', err);
}
}
Concepts avancés et cas d'utilisation
Une fois que vous maîtrisez les bases, vous pouvez explorer des fonctionnalités plus avancées pour créer des expériences utilisateur vraiment fluides.
Persistance avec IndexedDB
Un défi majeur est que les objets FileSystemHandle не sont pas conservés lorsque l'utilisateur rafraîchit la page. Pour résoudre ce problème, vous pouvez stocker les handles dans IndexedDB, la base de données côté client du navigateur. Cela permet à votre application de se souvenir des fichiers et dossiers avec lesquels l'utilisateur travaillait d'une session à l'autre.
Stocker un handle est aussi simple que de le placer dans un object store IndexedDB. Le récupérer est tout aussi facile. Cependant, les permissions ne sont pas stockées avec le handle. Lorsque votre application se recharge et récupère un handle depuis IndexedDB, vous devez d'abord vérifier si vous avez toujours la permission et la redemander si nécessaire.
// Fonction pour récupérer un handle stocké
async function getHandleFromDB(key) {
// (Code pour ouvrir IndexedDB et obtenir le handle)
const handle = await getFromDB(key);
if (!handle) return null;
// Vérifier si nous avons toujours la permission.
if (await handle.queryPermission({ mode: 'readwrite' }) === 'granted') {
return handle; // Permission déjà accordée.
}
// Sinon, nous devons redemander la permission.
if (await handle.requestPermission({ mode: 'readwrite' }) === 'granted') {
return handle; // Permission accordée par l'utilisateur.
}
// La permission a été refusée.
return null;
}
Ce modèle vous permet de créer une fonctionnalité 'Fichiers récents' ou 'Ouvrir un projet récent' qui ressemble à celle d'une application native.
Intégration du glisser-déposer (Drag and Drop)
L'API s'intègre parfaitement avec l'API native de glisser-déposer. Les utilisateurs peuvent glisser des fichiers ou des dossiers de leur bureau et les déposer sur votre application web pour accorder l'accès. Ceci est réalisé grâce à la méthode DataTransferItem.getAsFileSystemHandle().
const dropZone = document.getElementById('drop-zone');
dropZone.addEventListener('dragover', (event) => {
event.preventDefault(); // Nécessaire pour autoriser le dépôt
});
dropZone.addEventListener('drop', async (event) => {
event.preventDefault();
for (const item of event.dataTransfer.items) {
if (item.kind === 'file') {
const handle = await item.getAsFileSystemHandle();
if (handle.kind === 'directory') {
console.log(`Répertoire déposé : ${handle.name}`);
// Traiter le handle du répertoire
} else {
console.log(`Fichier déposé : ${handle.name}`);
// Traiter le handle du fichier
}
}
}
});
Applications concrètes
Les possibilités offertes par cette API sont vastes et s'adressent à un public mondial de créateurs et de professionnels :
- IDE et éditeurs de code basés sur le Web : Des outils comme VS Code pour le Web (vscode.dev) peuvent désormais ouvrir un dossier de projet local, permettant aux développeurs de modifier, créer et gérer l'ensemble de leur base de code directement dans le navigateur.
- Outils créatifs : Les éditeurs d'images, de vidéos et d'audio peuvent charger de gros fichiers multimédias directement depuis le disque dur de l'utilisateur, effectuer des modifications complexes et enregistrer le résultat sans le lent processus de téléversement et de téléchargement depuis un serveur.
- Productivité et analyse de données : Un utilisateur professionnel pourrait ouvrir un gros fichier CSV ou JSON dans un outil de visualisation de données basé sur le web, analyser les données et enregistrer des rapports, sans que les données ne quittent jamais sa machine, ce qui est excellent pour la confidentialité et les performances.
- Jeux vidéo : Les jeux basés sur le web pourraient permettre aux utilisateurs de gérer des parties sauvegardées ou d'installer des mods en accordant l'accès à un dossier de jeu spécifique.
Considérations et bonnes pratiques
Un grand pouvoir implique de grandes responsabilités. Voici quelques considérations clés pour les développeurs utilisant cette API.
Mettre l'accent sur l'expérience utilisateur (UX)
- La clarté est essentielle : Liez toujours les appels d'API à des actions utilisateur claires et explicites comme des boutons intitulés 'Ouvrir un fichier' ou 'Enregistrer les modifications'. Ne surprenez jamais l'utilisateur avec un sélecteur de fichiers.
- Fournir un retour d'information : Utilisez des éléments d'interface utilisateur pour informer l'utilisateur de l'état des opérations (par ex., 'Enregistrement...', 'Fichier enregistré avec succès', 'Permission refusée').
- Mécanismes de repli (fallbacks) : Comme l'API n'est pas encore universellement prise en charge, fournissez toujours un mécanisme de repli utilisant les méthodes traditionnelles de
<input type="file">et de téléchargement par ancre pour les navigateurs plus anciens.
Performance
L'API est conçue pour la performance. En éliminant le besoin de téléversements et de téléchargements sur le serveur, les applications peuvent devenir beaucoup plus rapides, surtout lorsqu'il s'agit de gros fichiers. Comme toutes les opérations sont asynchrones, elles ne bloqueront pas le thread principal du navigateur, gardant votre interface utilisateur réactive.
Limitations et compatibilité des navigateurs
La principale considération est le support des navigateurs. Fin 2023, l'API est entièrement prise en charge dans les navigateurs basés sur Chromium comme Google Chrome, Microsoft Edge et Opera. Le support dans Firefox est en développement derrière un drapeau, et Safari ne s'est pas encore engagé à l'implémenter. Pour un public mondial, cela signifie que vous ne pouvez pas compter sur cette API comme *seul* moyen de gérer les fichiers. Consultez toujours une source fiable comme CanIUse.com pour les dernières informations de compatibilité.
Conclusion : Une nouvelle ère pour les applications Web
L'API File System Access représente un bond en avant monumental pour la plateforme web. Elle comble directement l'une des lacunes fonctionnelles les plus importantes entre les applications web et natives, donnant aux développeurs les moyens de construire une nouvelle classe d'outils puissants, efficaces et conviviaux qui s'exécutent entièrement dans le navigateur.
En fournissant un pont sécurisé et contrôlé par l'utilisateur vers le système de fichiers local, elle améliore les capacités des applications, augmente les performances en réduisant la dépendance aux serveurs et rationalise les flux de travail pour les utilisateurs du monde entier. Bien que nous devions rester attentifs à la compatibilité des navigateurs et mettre en place des mécanismes de repli appropriés, la voie à suivre est claire. Le web évolue d'une plateforme de consommation de contenu à une plateforme mature pour sa création. Nous vous encourageons à explorer l'API File System Access, à expérimenter ses capacités et à commencer à construire la prochaine génération d'applications web dès aujourd'hui.