File System Access API en pratique : Architecture de lecture/écriture de fichiers locaux côté navigateur
L'évolution de l'accès aux fichiers dans le navigateur
| Approche | Lecture/Écriture | Persistance | Modèle de permissions | Performance |
|---|---|---|---|---|
<input type="file"> |
Lecture seule | ❌ | Aucun | Moyenne |
| File + FileReader | Lecture seule | ❌ | Aucun | Moyenne |
| IndexedDB | Lire/écrire des octets | ✅ | Même origine | Faible (sérialisation) |
| File System Access | Lire/écrire | ✅ | Accordée par l'utilisateur | Élevée |
| OPFS | Lire/écrire | ✅ | Bac à sable de même origine | La plus élevée |
La File System Access API confère aux navigateurs de véritables capacités de lecture/écriture de fichiers au niveau des applications natives.
Sélecteurs de fichiers : showOpenFilePicker / showSaveFilePicker
Ouvrir un fichier
const [fileHandle] = await window.showOpenFilePicker({
types: [{
description: 'Image files',
accept: { 'image/*': ['.png', '.jpg', '.webp'] },
}],
multiple: false,
});
const file = await fileHandle.getFile();
const arrayBuffer = await file.arrayBuffer();
Enregistrer un fichier
const handle = await window.showSaveFilePicker({
suggestedName: 'compressed.webp',
types: [{
description: 'WebP Image',
accept: { 'image/webp': ['.webp'] },
}],
});
const writable = await handle.createWritable();
await writable.write(compressedBlob);
await writable.close();
FileSystemFileHandle : Lecture/écriture et permissions
Lecture du contenu d'un fichier
const fileHandle: FileSystemFileHandle = handle;
const file = await fileHandle.getFile();
// Multiple read methods
const text = await file.text(); // Text
const buffer = await file.arrayBuffer(); // Binary
const stream = file.stream(); // Streaming
Écriture dans un fichier
async function writeFile(handle: FileSystemFileHandle, content: Blob | string | BufferSource) {
const writable = await handle.createWritable();
await writable.write(content);
await writable.close();
}
Requête et demande de permissions
async function verifyPermission(handle: FileSystemFileHandle, readWrite = true) {
const options: FileSystemHandlePermissionDescriptor = { mode: readWrite ? 'readwrite' : 'read' };
if ((await handle.queryPermission(options)) === 'granted') return true;
if ((await handle.requestPermission(options)) === 'granted') return true;
return false;
}
Modèle de permissions : Les handles obtenus via le sélecteur reçoivent automatiquement la permission de lecture. La permission d'écriture nécessite une autorisation explicite de l'utilisateur (requestPermission affiche une invite).
Accès aux répertoires et traversée
const dirHandle = await window.showDirectoryPicker();
for await (const [name, handle] of dirHandle.entries()) {
if (handle.kind === 'file') {
const file = await handle.getFile();
console.log(`File: ${name}, Size: ${file.size}`);
} else if (handle.kind === 'directory') {
console.log(`Directory: ${name}`);
}
}
Recherche récursive de fichiers
async function* findFiles(dirHandle: FileSystemDirectoryHandle, pattern: RegExp) {
for await (const [name, handle] of dirHandle.entries()) {
if (handle.kind === 'file' && pattern.test(name)) {
yield { name, handle };
} else if (handle.kind === 'directory') {
yield* findFiles(handle, pattern);
}
}
}
// Find all PDF files
for await (const pdf of findFiles(dirHandle, /\.pdf$/)) {
console.log(pdf.name);
}
Origin Private File System (OPFS)
OPFS est un système de fichiers privé et bac à sable fourni par le navigateur pour chaque origine—aucune autorisation utilisateur nécessaire :
const opfsRoot = await navigator.storage.getDirectory();
// Create a file
const fileHandle = await opfsRoot.getFileHandle('work-data.bin', { create: true });
// Create a subdirectory
const subDir = await opfsRoot.getDirectoryHandle('cache', { create: true });
// Delete a file
await opfsRoot.removeEntry('work-data.bin');
OPFS vs IndexedDB
| Caractéristique | IndexedDB | OPFS |
|---|---|---|
| Type de stockage | Données structurées | Fichiers (binaire) |
| Mode lecture/écriture | Transactions + sérialisation | E/S de fichier directe |
| Performance avec les gros fichiers | Médiocre (surcoût de sérialisation) | Excellente |
| Accès aléatoire | ❌ | ✅ (Access Handle) |
| Capacité | Limite de même origine | Limite de même origine |
| Support des Workers | ✅ | ✅ |
Access Handle : Lecture/écriture aléatoire haute performance
Les fichiers OPFS prennent en charge createSyncAccessHandle(), fournissant une lecture/écriture de fichiers synchrone et basée sur la position—performance proche du système de fichiers natif :
const fileHandle = await opfsRoot.getFileHandle('large-data.bin', { create: true });
const accessHandle = await fileHandle.createSyncAccessHandle();
// Write data at a specific position
const writeBuffer = new Uint8Array([1, 2, 3, 4, 5]);
accessHandle.write(writeBuffer, { at: 0 });
// Read data at a specific position
const readBuffer = new Uint8Array(5);
accessHandle.read(readBuffer, { at: 0 });
// Get file size
const fileSize = accessHandle.getSize();
// Flush to disk
accessHandle.flush();
// Close handle
accessHandle.close();
Avantage clé : Access Handle est synchrone, utilisable dans les Workers sans async/await—10 à 100 fois plus rapide que WritableStream.
Pratique : Architecture de lecture/écriture de fichiers pour la fusion PDF
Fusion PDF de ToolsKu utilise la File System Access API pour un flux de travail complet « ouvrir → traiter → enregistrer » :
async function mergePdfWorkflow() {
// 1. User selects multiple PDFs
const handles = await window.showOpenFilePicker({
types: [{ accept: { 'application/pdf': ['.pdf'] } }],
multiple: true,
});
// 2. Read all files
const buffers: ArrayBuffer[] = [];
for (const handle of handles) {
const file = await handle.getFile();
buffers.push(await file.arrayBuffer());
}
// 3. Merge processing
const mergedPdf = await mergePdfs(buffers);
// 4. Save result
const saveHandle = await window.showSaveFilePicker({
suggestedName: 'merged.pdf',
types: [{ accept: { 'application/pdf': ['.pdf'] } }],
});
const writable = await saveHandle.createWritable();
await writable.write(mergedPdf);
await writable.close();
}
Persistance des handles de fichiers
Enregistrez les handles dans IndexedDB pour restaurer l'accès lors de la prochaine visite :
async function saveHandle(key: string, handle: FileSystemFileHandle) {
const db = await openDB('file-handles', 1, {
upgrade(db) { db.createObjectStore('handles'); },
});
await db.put('handles', handle, key);
}
async function loadHandle(key: string) {
const db = await openDB('file-handles', 1);
const handle: FileSystemFileHandle = await db.get('handles', key);
if (await verifyPermission(handle, true)) {
return handle;
}
return null;
}
Questions fréquentes
Support des navigateurs pour la File System Access API ?
Chrome/Edge 86+ offrent un support complet. Firefox et Safari ne la prennent pas en charge. La détection de fonctionnalités et le traitement de repli sont nécessaires.
Limites de capacité de stockage OPFS ?
OPFS partage le quota de stockage de même origine avec Cache API et IndexedDB (généralement 50%+ de l'espace disque disponible). Interrogez avec navigator.storage.estimate().
Access Handle peut-il être utilisé sur le thread principal ?
Oui, mais les E/S synchrones bloquent le thread principal. Meilleure pratique : utilisez Access Handle dans un Worker pour éviter de bloquer l'interface utilisateur.
Comment gérer l'écriture de gros fichiers ?
Utilisez l'écriture fragmentée avec WritableStream pour éviter de charger tout le fichier en mémoire :
const writable = await handle.createWritable();
for await (const chunk of readableStream) {
await writable.write(chunk);
}
await writable.close();
Résumé
La File System Access API confère aux navigateurs des capacités de lecture/écriture de fichiers au niveau des applications natives. showOpenFilePicker/showSaveFilePicker fournissent un accès aux fichiers autorisé par l'utilisateur, FileSystemFileHandle gère les permissions de lecture/écriture, OPFS offre un stockage bac à sable sans autorisation, et Access Handle permet une lecture/écriture aléatoire synchrone haute performance. Il s'agit de l'infrastructure centrale pour construire des outils de traitement de fichiers côté navigateur.
Essayez ces outils exécutés localement dans le navigateur — aucune inscription requise →