API de Acesso ao Sistema de Arquivos na Prática: Arquitetura de Leitura/Escrita de Arquivos Locais no Navegador
A Evolução do Acesso a Arquivos no Navegador
| Abordagem | Leitura/Escrita | Persistência | Modelo de Permissões | Desempenho |
|---|---|---|---|---|
<input type="file"> |
Somente leitura | ❌ | Nenhum | Médio |
| File + FileReader | Somente leitura | ❌ | Nenhum | Médio |
| IndexedDB | Ler/escrever bytes | ✅ | Mesma origem | Baixo (serialização) |
| File System Access | Ler/escrever | ✅ | Concedido pelo usuário | Alto |
| OPFS | Ler/escrever | ✅ | Sandbox de mesma origem | Mais alto |
A API de Acesso ao Sistema de Arquivos oferece aos navegadores verdadeiras capacidades de leitura/escrita de arquivos em nível de aplicativo nativo.
Seletores de Arquivos: showOpenFilePicker / showSaveFilePicker
Abrir um Arquivo
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();
Salvar um Arquivo
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: Leitura/Escrita e Permissões
Lendo o Conteúdo de um Arquivo
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
Escrevendo em um Arquivo
async function writeFile(handle: FileSystemFileHandle, content: Blob | string | BufferSource) {
const writable = await handle.createWritable();
await writable.write(content);
await writable.close();
}
Consulta e Solicitação de Permissões
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;
}
Modelo de permissões: Handles obtidos via seletor recebem automaticamente permissão de leitura. A permissão de escrita requer autorização explícita do usuário (requestPermission exibe um prompt).
Acesso a Diretórios e Travessia
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}`);
}
}
Busca Recursiva de Arquivos
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 é um sistema de arquivos privado e isolado fornecido pelo navegador para cada origem—sem necessidade de autorização do usuário:
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
| Recurso | IndexedDB | OPFS |
|---|---|---|
| Tipo de armazenamento | Dados estruturados | Arquivos (binário) |
| Modo de leitura/escrita | Transações + serialização | E/S de arquivo direta |
| Desempenho com arquivos grandes | Ruim (sobrecarga de serialização) | Excelente |
| Acesso aleatório | ❌ | ✅ (Access Handle) |
| Capacidade | Limite de mesma origem | Limite de mesma origem |
| Suporte a Workers | ✅ | ✅ |
Access Handle: Leitura/Escrita Aleatória de Alto Desempenho
Arquivos OPFS suportam createSyncAccessHandle(), fornecendo leitura/escrita de arquivos síncrona e baseada em posição—desempenho próximo ao sistema de arquivos nativo:
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();
Vantagem principal: Access Handle é síncrono, utilizável em Workers sem async/await—10-100x mais rápido que WritableStream.
Prática: Arquitetura de Leitura/Escrita de Arquivos para Mesclagem de PDF
Mesclagem de PDF do ToolsKu usa a API de Acesso ao Sistema de Arquivos para um fluxo de trabalho completo de "abrir → processar → salvar":
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();
}
Persistindo Handles de Arquivos
Salve handles no IndexedDB para restaurar o acesso na próxima visita:
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;
}
Perguntas Frequentes
Suporte do navegador para a API de Acesso ao Sistema de Arquivos?
Chrome/Edge 86+ têm suporte completo. Firefox e Safari não suportam. É necessária detecção de recursos e tratamento de fallback.
Limites de capacidade de armazenamento do OPFS?
OPFS compartilha a cota de armazenamento de mesma origem com a Cache API e o IndexedDB (tipicamente 50%+ do espaço em disco disponível). Consulte com navigator.storage.estimate().
O Access Handle pode ser usado na thread principal?
Sim, mas E/S síncrona bloqueia a thread principal. Melhor prática: use Access Handle em um Worker para evitar bloquear a interface do usuário.
Como lidar com a escrita de arquivos grandes?
Use escrita fragmentada com WritableStream para evitar carregar o arquivo inteiro na memória:
const writable = await handle.createWritable();
for await (const chunk of readableStream) {
await writable.write(chunk);
}
await writable.close();
Resumo
A API de Acesso ao Sistema de Arquivos oferece aos navegadores capacidades de leitura/escrita de arquivos em nível de aplicativo nativo. showOpenFilePicker/showSaveFilePicker fornecem acesso a arquivos autorizado pelo usuário, FileSystemFileHandle gerencia permissões de leitura/escrita, OPFS fornece armazenamento isolado sem autorização, e Access Handle permite leitura/escrita aleatória síncrona de alto desempenho. Esta é a infraestrutura central para construir ferramentas de processamento de arquivos no navegador.
Experimente estas ferramentas executadas localmente no navegador — nenhum cadastro necessário →