Ler e gravar arquivos e diretórios com a biblioteca browser-fs-access

Published on Updated on

Translated to: English, Español

Os navegadores já conseguem lidar com arquivos e diretórios há muito tempo. A API Arquivo fornece recursos para representar objetos de arquivo em aplicativos da web, bem como selecioná-los programaticamente e acessar seus dados. No momento em que você olha mais de perto, porém, nem tudo o que reluz é ouro.

A maneira tradicional de lidar com arquivos

Se você sabe como funcionava do jeito antigo, pode pular direto para o novo jeito.

Abrindo arquivos

Como desenvolvedor, você pode abrir e ler arquivos por meio do elemento <input type="file">. Em sua forma mais simples, abrir um arquivo pode ser parecido com o exemplo de código abaixo. O objeto de input FileList, que no caso a seguir consiste em apenas um File. Um File é um tipo específico de Blob e pode ser usado em qualquer contexto em que um Blob.

const openFile = async () => {
return new Promise((resolve) => {
const input = document.createElement('input');
input.type = 'file';
input.addEventListener('change', () => {
resolve(input.files[0]);
});
input.click();
});
};

Abrindo diretórios

Para abrir pastas (ou diretórios), você pode definir o atributo <input webkitdirectory> Tirando isso, todo o resto funciona da mesma forma que acima. Apesar de seu nome com o prefixo do fornecedor, webkitdirectory não pode ser usado apenas nos navegadores Chromium e WebKit, mas também no Edge baseado em EdgeHTML legado e no Firefox.

Salvando (em vez de baixando) arquivos

Para salvar um arquivo, tradicionalmente, você está limitado a baixar um arquivo, o que funciona graças ao atributo <a download> Dado um Blob, você pode definir o href da âncora como um blob: URL que pode ser obtido no método URL.createObjectURL()

Caution

Para evitar vazamentos de memória, sempre revogue o URL após o download.

const saveFile = async (blob) => {
const a = document.createElement('a');
a.download = 'my-file.txt';
a.href = URL.createObjectURL(blob);
a.addEventListener('click', (e) => {
setTimeout(() => URL.revokeObjectURL(a.href), 30 * 1000);
});
a.click();
};

O problema

Uma grande desvantagem da abordagem de download é que não há como fazer um fluxo clássico abrir → editar → salvar, ou seja, não há como sobrescrever o arquivo original. Em vez disso, você acaba com uma nova cópia do arquivo original na pasta de Downloads padrão do sistema operacional sempre que "salva".

A API de acesso ao sistema de arquivos

A API de acesso ao sistema de arquivos torna ambas as operações, abrir e salvar, muito mais simples. Também possibilita o salvamento real, ou seja, você não só pode escolher onde salvar um arquivo, mas também sobrescrever um arquivo existente.

Para obter uma introdução mais completa à API de acesso ao sistema de arquivos, consulte o artigo A API de acesso ao sistema de arquivos: simplificando o acesso a arquivos locais.

Abrindo arquivos

Com a API de acesso ao sistema de arquivos, abrir um arquivo é uma questão de chamar o método window.showOpenFilePicker(). Esta chamada retorna um identificador de arquivo, do qual você pode obter o File real por meio do método getFile().

const openFile = async () => {
try {
// Always returns an array.
const [handle] = await window.showOpenFilePicker();
return handle.getFile();
} catch (err) {
console.error(err.name, err.message);
}
};

Abrindo diretórios

Abra um diretório chamando window.showDirectoryPicker() que torna os diretórios selecionáveis na caixa de diálogo do arquivo.

Salvando arquivos

Salvar arquivos é igualmente simples. A partir de um identificador de arquivo, você cria um fluxo gravável por meio de createWritable(), depois grava os dados Blob chamando o write() do fluxo e, por fim, fecha o fluxo chamando seu método close().

const saveFile = async (blob) => {
try {
const handle = await window.showSaveFilePicker({
types: [{
accept: {
// Omitted
},
}],
});
const writable = await handle.createWritable();
await writable.write(blob);
await writable.close();
return handle;
} catch (err) {
console.error(err.name, err.message);
}
};

Apresentando o navegador-fs-access

Por mais perfeita que seja a API de acesso ao sistema de arquivos, ela ainda não está amplamente disponível.

Tabela de suporte do navegador para a API de acesso ao sistema de arquivos. Todos os navegadores são marcados como 'sem suporte' ou 'atrás de um sinalizador'.
Tabela de suporte do navegador para a API de acesso ao sistema de arquivos. (Fonte)

É por isso que vejo a API de acesso ao sistema de arquivos como um aprimoramento progressivo. Como tal, quero usá-lo quando o navegador oferecer suporte e, se não for, usar a abordagem tradicional; ao mesmo tempo, nunca pune o usuário com downloads desnecessários de código JavaScript não suportado. A biblioteca browser-fs-access é minha resposta para esse desafio.

Filosofia de design

Como a API de acesso ao sistema de arquivos provavelmente ainda mudará no futuro, a API browser-fs-access não é modelada a partir dela. Ou seja, a biblioteca não é um polyfill, mas sim um ponyfill. Você pode (estaticamente ou dinamicamente) importar exclusivamente qualquer funcionalidade necessária para manter seu aplicativo o menor possível. Os métodos disponíveis são os nomeados apropriadamente fileOpen(), directoryOpen() e fileSave(). Internamente, o recurso de biblioteca detecta se a API de acesso ao sistema de arquivos é compatível e, a seguir, importa o caminho do código correspondente.

Usando a biblioteca browser-fs-access

Os três métodos são intuitivos de usar. Você pode especificar os mimeTypes ou extensions arquivo aceitos do seu aplicativo e definir um multiple para permitir ou proibir a seleção de vários arquivos ou diretórios. Para obter detalhes completos, consulte a documentação da API browser-fs-access. O exemplo de código abaixo mostra como você pode abrir e salvar arquivos de imagem.

// The imported methods will use the File
// System Access API or a fallback implementation.
import {
fileOpen,
directoryOpen,
fileSave,
} from 'https://unpkg.com/browser-fs-access';

(async () => {
// Open an image file.
const blob = await fileOpen({
mimeTypes: ['image/*'],
});

// Open multiple image files.
const blobs = await fileOpen({
mimeTypes: ['image/*'],
multiple: true,
});

// Open all files in a directory,
// recursively including subdirectories.
const blobsInDirectory = await directoryOpen({
recursive: true
});

// Save a file.
await fileSave(blob, {
fileName: 'Untitled.png',
});
})();

Demo

Você pode ver o código acima em ação em uma demonstração no Glitch. Seu código-fonte também está disponível lá. Como, por razões de segurança, os subquadros de origem cruzada não têm permissão para mostrar um seletor de arquivos, a demonstração não pode ser incorporada neste artigo.

A biblioteca browser-fs-access em liberdade

Em meu tempo livre, contribuo um pouquinho para um PWA instalável chamado Excalidraw, uma ferramenta de quadro branco que permite esboçar diagramas facilmente com uma sensação de desenho à mão. É totalmente responsivo e funciona bem em uma variedade de dispositivos, desde pequenos telefones celulares a computadores com telas grandes. Isso significa que ele precisa lidar com arquivos em todas as várias plataformas, independentemente de serem ou não compatíveis com a API de acesso ao sistema de arquivos. Isso o torna um ótimo candidato para a biblioteca browser-fs-access.

Posso, por exemplo, iniciar um desenho no meu iPhone, salvá-lo (tecnicamente: baixe-o, pois o Safari não oferece suporte à API de acesso ao sistema de arquivos) na pasta Downloads do meu iPhone, abra o arquivo no meu desktop (após transferi-lo do meu telefone), modifique o arquivo e substitua-o com minhas alterações ou mesmo salve-o como um novo arquivo.

Um desenho Excalidraw em um iPhone.
Iniciando um desenho Excalidraw em um iPhone onde a API de acesso ao sistema de arquivos não é suportada, mas onde um arquivo pode ser salvo (baixado) na pasta Downloads.
O desenho Excalidraw modificado no Chrome na área de trabalho.
Abrindo e modificando o desenho Excalidraw na área de trabalho onde a API de acesso ao sistema de arquivos é suportada e, portanto, o arquivo pode ser acessado por meio da API.
Substituindo o arquivo original com as modificações.
Substituindo o arquivo original com as modificações no arquivo de desenho original Excalidraw. O navegador mostra uma caixa de diálogo perguntando se está tudo bem.
Salvando as modificações em um novo arquivo de desenho Excalidraw.
Salvando as modificações em um novo arquivo Excalidraw. O arquivo original permanece intocado.

Amostra de código de aplicação real

Abaixo, você pode ver um exemplo real de navegador-fs-access como ele é usado no Excalidraw. Este trecho foi retirado de /src/data/json.ts. É de interesse especial como o saveAsJSON() passa um identificador de arquivo ou null para o método browser-fs-access ' fileSave(), o que faz com que ele seja sobrescrito quando um identificador é fornecido, ou salva em um novo arquivo se não for.

export const saveAsJSON = async (
elements: readonly ExcalidrawElement[],
appState: AppState,
fileHandle: any,

) => {
const serialized = serializeAsJSON(elements, appState);
const blob = new Blob([serialized], {
type: "application/json",
});
const name = `${appState.name}.excalidraw`;
(window as any).handle = await fileSave(
blob,
{
fileName: name,
description: "Excalidraw file",
extensions: ["excalidraw"],
},
fileHandle || null,
);
};

export const loadFromJSON = async () => {
const blob = await fileOpen({
description: "Excalidraw files",
extensions: ["json", "excalidraw"],
mimeTypes: ["application/json"],
});
return loadFromBlob(blob);
};

Considerações de interface do usuário

Seja no Excalidraw ou em seu aplicativo, a IU deve se adaptar à situação de suporte do navegador. Se a API de acesso ao sistema de arquivos for suportada (if ('showOpenFilePicker' in window) {}), você pode mostrar um botão Salvar como além de um botão Salvar. As imagens abaixo mostram a diferença entre a barra de ferramentas responsiva do aplicativo principal do Excalidraw no iPhone e na área de trabalho do Chrome. Observe como no iPhone o botão Salvar como está faltando.

Excalidraw app toolbar no iPhone com apenas um botão 'Salvar'.
Barra de ferramentas do aplicativo Excalidraw no iPhone com apenas um botão Salvar.
Barra de ferramentas do aplicativo Excalidraw na área de trabalho do Chrome com um botão 'Salvar' e 'Salvar como'.
Barra de ferramentas do aplicativo Excalidraw no Chrome com um botão Salvar e um botão Salvar em foco.

Conclusões

Trabalhar com arquivos de sistema funciona tecnicamente em todos os navegadores modernos. Em navegadores que suportam a API de acesso ao sistema de arquivos, você pode tornar a experiência melhor permitindo o verdadeiro salvamento e sobrescrita (não apenas o download) de arquivos e permitindo que seus usuários criem novos arquivos onde quiserem, ao mesmo tempo em que permanecem funcionais em navegadores que não suporta a API de acesso ao sistema de arquivos. O navegador-fs-access torna sua vida mais fácil, lidando com as sutilezas do aprimoramento progressivo e tornando seu código o mais simples possível.

Reconhecimentos

Este artigo foi revisado por Joe Medley e Kayce Basques. Agradeço aos colaboradores da Excalidraw por seu trabalho no projeto e por revisar minhas solicitações de pull. Imagem do herói por Ilya Pavlov em Unsplash.

Updated on Improve article

This site uses cookies to deliver and enhance the quality of its services and to analyze traffic. If you agree, cookies are also used to serve advertising and to personalize the content and advertisements that you see. Learn more about our use of cookies.