File System Access API in der Praxis: Browserseitige Architektur für lokales Lesen/Schreiben von Dateien
Die Evolution des Dateizugriffs im Browser
| Ansatz | Lesen/Schreiben | Persistenz | Berechtigungsmodell | Leistung |
|---|---|---|---|---|
<input type="file"> |
Nur Lesen | ❌ | Keines | Mittel |
| File + FileReader | Nur Lesen | ❌ | Keines | Mittel |
| IndexedDB | Lesen/Schreiben von Bytes | ✅ | Gleiche Herkunft | Niedrig (Serialisierung) |
| File System Access | Lesen/Schreiben | ✅ | Vom Benutzer gewährt | Hoch |
| OPFS | Lesen/Schreiben | ✅ | Sandbox gleicher Herkunft | Höchste |
Die File System Access API verleiht Browsern echte Datei-Lese-/Schreibfähigkeiten auf Anwendungsebene.
Dateiauswahl: showOpenFilePicker / showSaveFilePicker
Eine Datei öffnen
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();
Eine Datei speichern
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: Lesen/Schreiben und Berechtigungen
Dateiinhalt lesen
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
In eine Datei schreiben
async function writeFile(handle: FileSystemFileHandle, content: Blob | string | BufferSource) {
const writable = await handle.createWritable();
await writable.write(content);
await writable.close();
}
Berechtigungsabfrage und -anforderung
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;
}
Berechtigungsmodell: Handles, die über die Auswahl erhalten wurden, erhalten automatisch Leseberechtigung. Schreibberechtigung erfordert eine ausdrückliche Benutzerautorisierung (requestPermission zeigt einen Dialog an).
Verzeichniszugriff und Traversierung
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}`);
}
}
Rekursive Dateisuche
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 ist ein privates, Sandbox-basiertes Dateisystem, das der Browser für jede Herkunft bereitstellt—keine Benutzerautorisierung erforderlich:
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
| Merkmal | IndexedDB | OPFS |
|---|---|---|
| Speichertyp | Strukturierte Daten | Dateien (binär) |
| Lese-/Schreibmodus | Transaktionen + Serialisierung | Direkte Datei-E/A |
| Leistung bei großen Dateien | Schlecht (Serialisierungsaufwand) | Ausgezeichnet |
| Wahlfreier Zugriff | ❌ | ✅ (Access Handle) |
| Kapazität | Grenze gleicher Herkunft | Grenze gleicher Herkunft |
| Worker-Unterstützung | ✅ | ✅ |
Access Handle: Hochperformanter wahlfreier Lese-/Schreibzugriff
OPFS-Dateien unterstützen createSyncAccessHandle(), der synchronen, positionsbasierten Datei-Lese-/Schreibzugriff bietet—Leistung nahe dem nativen Dateisystem:
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();
Hauptvorteil: Access Handle ist synchron, in Workern ohne async/await verwendbar—10-100x schneller als WritableStream.
Praxis: Lese-/Schreibarchitektur für PDF-Zusammenführung
PDF-Zusammenführung von ToolsKu verwendet die File System Access API für einen vollständigen Arbeitsablauf „Öffnen → Verarbeiten → Speichern":
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();
}
Datei-Handles persistieren
Speichern Sie Handles in IndexedDB, um den Zugriff beim nächsten Besuch wiederherzustellen:
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;
}
Häufige Fragen
Browserunterstützung für die File System Access API?
Chrome/Edge 86+ bieten volle Unterstützung. Firefox und Safari unterstützen sie nicht. Feature-Erkennung und Fallback-Behandlung sind erforderlich.
OPFS-Speicherkapazitätsgrenzen?
OPFS teilt sich das Speicherkontingent gleicher Herkunft mit der Cache API und IndexedDB (typischerweise 50%+ des verfügbaren Speicherplatzes). Abfragen mit navigator.storage.estimate().
Kann Access Handle im Hauptthread verwendet werden?
Ja, aber synchrone E/A blockiert den Hauptthread. Best Practice: Verwenden Sie Access Handle in einem Worker, um die Benutzeroberfläche nicht zu blockieren.
Wie geht man mit dem Schreiben großer Dateien um?
Verwenden Sie gestückeltes Schreiben mit WritableStream, um zu vermeiden, dass die gesamte Datei in den Speicher geladen wird:
const writable = await handle.createWritable();
for await (const chunk of readableStream) {
await writable.write(chunk);
}
await writable.close();
Zusammenfassung
Die File System Access API verleiht Browsern Datei-Lese-/Schreibfähigkeiten auf Anwendungsebene. showOpenFilePicker/showSaveFilePicker bieten benutzerautorisierten Dateizugriff, FileSystemFileHandle verwaltet Lese-/Schreibberechtigungen, OPFS bietet autorisierungsfreien Sandbox-Speicher und Access Handle ermöglicht hochperformanten synchronen wahlfreien Lese-/Schreibzugriff. Dies ist die Kerninfrastruktur für den Aufbau browserseitiger Dateiverarbeitungstools.
Probiere diese browser-lokalen Tools aus — keine Registrierung erforderlich →