Опубликовано: 27 июля 2020 г.
Браузеры уже давно умеют работать с файлами и каталогами. File API предоставляет возможности для представления файловых объектов в веб-приложениях, а также для программного выбора файлов и доступа к их данным. Однако, если присмотреться, оказывается, что не всё то золото, что блестит.
Традиционный способ работы с файлами
Открыть файлы
You can open and read files with the <input type="file"> element. In its simplest form, opening a file can look something like the code sample. The input object gives you a FileList , which in the case of our example, consists of just one File . A File is a specific kind of Blob , and can be used in any context that a Blob can.
const openFile = async () => {
return new Promise((resolve) => {
const input = document.createElement('input');
input.type = 'file';
input.addEventListener('change', () => {
resolve(input.files[0]);
});
input.click();
});
};
Открытие каталогов
Для открытия папок (или каталогов) можно установить атрибут <input webkitdirectory> . В остальном все работает так же, как описано выше. Несмотря на префикс поставщика, webkitdirectory можно использовать не только в браузерах Chromium и WebKit, но и в устаревшем браузере Edge на основе EdgeHTML, а также в Firefox.
Сохраняйте и скачивайте файлы
Традиционно для сохранения файла достаточно загрузить его, что работает благодаря атрибуту <a download> . Имея объект Blob, вы можете установить атрибут href для привязки к blob: URL, который можно получить из метода 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();
};
Проблема
A massive downside of the download approach is that there is no way to make a classic open→edit→save flow happen, that is, there is no way to overwrite the original file. Instead, you end up with a new copy of the original file in the operating system's default Downloads folder whenever you "save".
API доступа к файловой системе
The File System Access API makes both operations, opening and saving, a lot simpler. It also enables true saving . This means you can choose where to save the file and to overwrite an existing file.
Открыть файлы
With the File System Access API , opening a file is a matter of one call to the window.showOpenFilePicker() method. This call returns a file handle, from which you can get the actual File via the getFile() method.
const openFile = async () => {
try {
// Always returns an array.
const [handle] = await window.showOpenFilePicker();
return handle.getFile();
} catch (err) {
console.error(err.name, err.message);
}
};
Открытые каталоги
Чтобы открыть каталог, вызовите метод window.showDirectoryPicker() , который позволяет выбирать каталоги в диалоговом окне выбора файла.
Сохранение файлов
Сохранение файлов также довольно просто. Из файлового дескриптора вы создаете записываемый поток с помощью createWritable() , затем записываете данные Blob, вызывая метод write() потока, и, наконец, закрываете поток, вызывая метод 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);
}
};
Представляем browser-fs-access
Несмотря на то, что API доступа к файловой системе безупречен, он пока не получил широкого распространения .

This is why I see the File System Access API as a progressive enhancement . As such, I want to use it when the browser supports it, and use the traditional approach if not; all while never punishing the user with unnecessary downloads of unsupported JavaScript code. The browser-fs-access library is my answer to this challenge.
философия дизайна
Поскольку API доступа к файловой системе, вероятно, еще изменится в будущем, API browser-fs-access не создан по его образцу. То есть, библиотека не является полифилом , а представляет собой понифил . Вы можете (статически или динамически) импортировать только необходимую вам функциональность, чтобы ваше приложение оставалось как можно меньше. Доступные методы — это методы с соответствующими названиями fileOpen() , directoryOpen() и fileSave() . Внутри библиотека определяет, поддерживается ли API доступа к файловой системе, и затем импортирует соответствующий фрагмент кода.
Воспользуйтесь библиотекой
The three methods are intuitive to use. You can specify your app's accepted mimeTypes or file extensions , and set a multiple flag to allow or disallow selection of multiple files or directories. For full details, see the browser-fs-access API documentation . The code sample shows how you can open and save image files.
// 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',
});
})();
Демо
Вы можете увидеть код в действии в демонстрационном примере на GitHub . Его исходный код также доступен там.
Библиотека browser-fs-access в реальных условиях
В свободное время я немного помогаю в разработке устанавливаемого PWA- приложения Excalidraw , инструмента для рисования на доске, который позволяет создавать диаграммы, имитирующие рисунок от руки. Оно полностью адаптивно и хорошо работает на самых разных устройствах, от небольших мобильных телефонов до компьютеров с большими экранами. Это означает, что ему необходимо работать с файлами на всех платформах, независимо от того, поддерживают ли они API доступа к файловой системе. Это делает его отличным кандидатом для библиотеки browser-fs-access.
Например, я могу начать рисовать на своем iPhone, сохранить его (технически: загрузить, поскольку Safari не поддерживает API доступа к файловой системе) в папку «Загрузки» на iPhone, открыть файл на компьютере (после переноса с телефона), изменить файл и перезаписать его своими изменениями или даже сохранить как новый файл.




Реальный пример кода
Below, you can see an actual example of browser-fs-access as it is used in Excalidraw. This excerpt is taken from /src/data/json.ts . Of special interest is how the saveAsJSON() method passes either a file handle or null to browser-fs-access' fileSave() method, which causes it to overwrite when a handle is given, or to save to a new file if not.
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);
};
соображения пользовательского интерфейса
Независимо от того, используется ли Excalidraw или ваше приложение, пользовательский интерфейс должен адаптироваться к поддержке браузера. Если поддерживается API доступа к файловой системе ( if ('showOpenFilePicker' in window) {} ), вы можете отобразить кнопку «Сохранить как» в дополнение к кнопке «Сохранить» . На скриншотах ниже показана разница между адаптивной панелью инструментов главного приложения Excalidraw на iPhone и на настольном компьютере Chrome. Обратите внимание, что на iPhone кнопка «Сохранить как» отсутствует.


Выводы
Работа с системными файлами технически работает во всех современных браузерах. В браузерах, поддерживающих API доступа к файловой системе, вы можете улучшить пользовательский опыт, разрешив истинное сохранение и перезапись файлов (а не только загрузку), а также позволив пользователям создавать новые файлы в любом удобном для них месте, при этом сохраняя функциональность в браузерах, не поддерживающих API доступа к файловой системе. Browser-fs-access упрощает вашу работу, учитывая тонкости прогрессивного улучшения и максимально упрощая ваш код.
Благодарности
Этот код был проверен Джо Медли и Кейси Баскес . Спасибо участникам проекта Excalidraw за их работу и за проверку моих запросов на слияние (Pull Requests).