Os navegadores conseguem lidar com arquivos e diretórios há muito tempo. A API File oferece recursos para representar objetos de arquivo em aplicativos da Web, além de selecionar e acessar os dados deles de maneira programática. Mas, quando você olha mais de perto, nem tudo que reluz é ouro.
A maneira tradicional de lidar com arquivos
Abrir arquivos
Como desenvolvedor, você pode abrir e ler arquivos usando o elemento
<input type="file">
.
Na forma mais simples, abrir um arquivo pode ser parecido com o exemplo de código abaixo.
O objeto input
fornece um FileList
, que, no caso abaixo, consiste em apenas um File
.
Um File
é um tipo específico de Blob
e pode ser usado em qualquer contexto em que um Blob pode.
const openFile = async () => {
return new Promise((resolve) => {
const input = document.createElement('input');
input.type = 'file';
input.addEventListener('change', () => {
resolve(input.files[0]);
});
input.click();
});
};
Abrir diretórios
Para abrir pastas (ou diretórios), defina o atributo
<input webkitdirectory>
.
Fora isso, tudo funciona da mesma forma que acima.
Apesar do nome com prefixo do fornecedor, webkitdirectory
pode ser usado não apenas em navegadores Chromium e WebKit, mas também no Edge legado baseado em EdgeHTML e no Firefox.
Como salvar (ou melhor, baixar) arquivos
Para salvar um arquivo, tradicionalmente, você precisa fazer o download, o que funciona graças ao atributo <a download>
.
Com um blob, é possível definir o atributo href
da âncora como um URL blob:
que pode ser obtido com o método
URL.createObjectURL()
.
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 de abrir→editar→salvar, ou seja, não há como substituir 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 você "salva".
A API File System Access
A API File System Access simplifica muito as duas operações, abrir e salvar. Ele também permite o salvamento verdadeiro, ou seja, você pode escolher onde salvar um arquivo e substituir um arquivo existente.
Abrir arquivos
Com a API File System Access,
abrir um arquivo é uma questão de uma chamada para o método window.showOpenFilePicker()
.
Essa chamada retorna um identificador de arquivo, com o qual você pode receber o File
real pelo 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);
}
};
Abrir diretórios
Abra um diretório chamando
window.showDirectoryPicker()
, que torna os diretórios selecionáveis na caixa de diálogo de arquivos.
Como salvar arquivos
Salvar arquivos também é simples.
De um identificador de arquivo, crie um stream gravável via createWritable()
,
grave os dados do blob chamando o método write()
do stream
e, por fim, feche o stream chamando o 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);
}
};
Conheça o browser-fs-access
Por mais perfeita que seja a API File System Access, ela ainda não está disponível para todos.

Por isso, considero a API File System Access um aprimoramento progressivo. Por isso, quero usá-lo quando o navegador for compatível e usar a abordagem tradicional caso contrário. Tudo isso sem punir o usuário com downloads desnecessários de código JavaScript não compatível. A biblioteca browser-fs-access é minha resposta a esse desafio.
Filosofia de design
Como a API File System Access ainda pode mudar no futuro, a API browser-fs-access não é modelada com base nela.
Ou seja, a biblioteca não é um polyfill,
mas sim um ponyfill.
Você pode importar (de forma estática ou dinâmica) exclusivamente a funcionalidade necessária para manter o app o menor possível.
Os métodos disponíveis são fileOpen()
, directoryOpen()
e fileSave()
.
Internamente, a biblioteca detecta se a API File System Access é compatível
e importa o caminho de código correspondente.
Como usar a biblioteca browser-fs-access
Os três métodos são intuitivos de usar.
É possível especificar o mimeTypes
ou o arquivo extensions
aceito do app e definir uma flag multiple
para permitir ou não a seleção de vários arquivos ou diretórios.
Para mais detalhes, consulte a
documentação da API browser-fs-access.
O exemplo de código abaixo mostra como 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',
});
})();
Demonstração
Confira o código acima em ação em uma demonstração no GitHub. O código-fonte também está disponível lá. Por motivos de segurança, os subframes de origem cruzada não podem mostrar um seletor de arquivos. Por isso, não é possível incorporar a demonstração neste artigo.
A biblioteca browser-fs-access em uso
No meu tempo livre, contribuo um pouco para um PWA instalável chamado Excalidraw, uma ferramenta de lousa interativa que permite esboçar diagramas com facilidade e um toque de desenho à mão. Ele é totalmente responsivo e funciona bem em vários dispositivos, desde smartphones pequenos até computadores com telas grandes. Isso significa que ele precisa lidar com arquivos em todas as plataformas, com ou sem suporte à API File System Access. Isso o torna um ótimo candidato para a biblioteca browser-fs-access.
Por exemplo, posso começar um desenho no iPhone, salvar (tecnicamente: fazer o download, já que o Safari não é compatível com a API File System Access) na pasta de downloads do iPhone, abrir o arquivo no computador (depois de transferir do smartphone), modificar o arquivo e substituir com minhas mudanças ou até mesmo salvar como um novo arquivo.




Exemplo de código da vida real
Confira abaixo um exemplo real de browser-fs-access usado no Excalidraw.
Este trecho foi extraído de
/src/data/json.ts
.
É interessante saber como o método saveAsJSON()
transmite um identificador de arquivo ou null
para o método fileSave()
do browser-fs-access. Isso faz com que ele substitua quando um identificador é fornecido ou salve em um novo arquivo, caso contrário.
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 sobre a interface
No Excalidraw ou no seu app,
a interface precisa se adaptar à situação de suporte do navegador.
Se a API File System Access for compatível (if ('showOpenFilePicker' in window) {}
),
você poderá mostrar um botão Salvar como além de um botão Salvar.
As capturas de tela abaixo mostram a diferença entre a barra de ferramentas principal responsiva do app Excalidraw no iPhone e no Chrome para computador.
No iPhone, o botão Salvar como não aparece.


Conclusões
Tecnicamente, trabalhar com arquivos de sistema funciona em todos os navegadores modernos. Em navegadores que oferecem suporte à API File System Access, é possível melhorar a experiência permitindo o salvamento e a substituição (não apenas o download) de arquivos e deixando que os usuários criem novos arquivos onde quiserem, tudo isso sem perder a funcionalidade em navegadores que não oferecem suporte à API File System Access. A browser-fs-access facilita sua vida ao lidar com as sutilezas do aprimoramento progressivo e simplificar ao máximo seu código.
Agradecimentos
Este artigo foi revisado por Joe Medley e Kayce Basques. Agradeço aos colaboradores do Excalidraw pelo trabalho no projeto e pela revisão dos meus pull requests. Imagem principal de Ilya Pavlov no Unsplash.