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 →

#File System Access#OPFS#文件句柄#本地文件#读写权限