Чтение и запись файлов и каталогов с помощью библиотеки браузера-fs-access.

Браузеры уже давно умеют работать с файлами и каталогами. File API предоставляет функции для представления файловых объектов в веб-приложениях, а также их программного выбора и доступа к их данным. Однако стоит присмотреться поближе, и становится ясно, что не все то золото, что блестит.

Традиционный способ работы с файлами

Открытие файлов

Как разработчик, вы можете открывать и читать файлы с помощью элемента <input type="file"> . В простейшей форме открытие файла может выглядеть примерно так, как показано в примере кода ниже. input объект предоставляет вам FileList , который в приведенном ниже случае состоит только из одного File . File — это особый вид Blob , который можно использовать в любом контексте, в котором может использоваться 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();
 
});
};

Открытие каталогов

Для открытия папок (или каталогов) вы можете установить атрибут <input webkitdirectory> . В остальном все работает так же, как указано выше. Несмотря на свое имя с префиксом поставщика, webkitdirectory можно использовать не только в браузерах Chromium и WebKit, но также в устаревшем Edge на основе EdgeHTML, а также в Firefox.

Сохранение (вернее: скачивание) файлов

Традиционно для сохранения файла вы ограничены загрузкой файла, что работает благодаря атрибуту <a download> . Учитывая Blob, вы можете установить атрибут href привязки для URL-адреса blob: который вы можете получить из метода 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();
};

Проблема

Серьезным недостатком подхода к загрузке является отсутствие возможности реализовать классический процесс открытия → редактирования → сохранения, то есть невозможно перезаписать исходный файл. Вместо этого при каждом «сохранении» вы получаете новую копию исходного файла в папке «Загрузки» операционной системы по умолчанию.

API доступа к файловой системе

API доступа к файловой системе значительно упрощает обе операции: открытие и сохранение. Он также обеспечивает истинное сохранение , то есть вы можете не только выбрать, где сохранить файл, но и перезаписать существующий файл.

Открытие файлов

С помощью API доступа к файловой системе открытие файла осуществляется одним вызовом метода window.showOpenFilePicker() . Этот вызов возвращает дескриптор файла, из которого вы можете получить сам File с помощью метода 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);
 
}
};

Открытие каталогов

Откройте каталог, вызвав метод 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);
 
}
};

Представляем браузер-fs-доступ

Каким бы прекрасным ни был API доступа к файловой системе, он еще не широко доступен .

Таблица поддержки браузером API доступа к файловой системе. Все браузеры имеют пометку «нет поддержки» или «за флажком».
Таблица поддержки браузером API доступа к файловой системе. ( Источник )

Вот почему я рассматриваю API доступа к файловой системе как прогрессивное усовершенствование . Таким образом, я хочу использовать его, когда браузер его поддерживает, и использовать традиционный подход, если нет; и при этом никогда не наказывать пользователя ненужной загрузкой неподдерживаемого кода JavaScript. Библиотека браузера-fs-access — мой ответ на этот вызов.

Философия дизайна

Поскольку API доступа к файловой системе, скорее всего, изменится в будущем, API доступа к браузеру-fs-access не моделируется по его образцу. То есть библиотека — это не полифилл , а скорее понифилл . Вы можете (статически или динамически) импортировать только те функции, которые вам нужны, чтобы ваше приложение было как можно меньше. Доступные методы — это правильно названные fileOpen() , directoryOpen() и fileSave() . Внутри функция библиотеки определяет, поддерживается ли API доступа к файловой системе, а затем импортирует соответствующий путь к коду.

Использование библиотеки браузера-fs-access

Эти три метода интуитивно понятны в использовании. Вы можете указать принятые в вашем приложении mimeTypes или extensions файлов, а также установить multiple флаг, чтобы разрешить или запретить выбор нескольких файлов или каталогов. Полную информацию см. в документации API браузера-fs-access . В приведенном ниже примере кода показано, как можно открывать и сохранять файлы изображений.

// 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',
 
});
})();

Демо

Вы можете увидеть приведенный выше код в действии в демо-версии Glitch. Его исходный код также доступен там. Поскольку по соображениям безопасности в подкадрах перекрестного происхождения не разрешено отображать средство выбора файлов, демонстрационную версию нельзя встроить в эту статью.

Библиотека браузера-fs-access в дикой природе

В свободное время я вношу небольшой вклад в устанавливаемый PWA под названием Excalidraw , инструмент для доски, который позволяет легко рисовать диаграммы, как будто они нарисованы от руки. Он полностью отзывчив и хорошо работает на самых разных устройствах: от небольших мобильных телефонов до компьютеров с большими экранами. Это означает, что ему необходимо иметь дело с файлами на всех различных платформах, независимо от того, поддерживают ли они API доступа к файловой системе. Это делает его отличным кандидатом на использование библиотеки браузера-fs-access.

Я могу, например, запустить рисунок на iPhone, сохранить его (технически: загрузить, поскольку Safari не поддерживает API доступа к файловой системе) в папку «Загрузки» iPhone, открыть файл на рабочем столе (предварительно перенеся его с моего компьютера). телефон), измените файл и перезапишите его моими изменениями или даже сохраните как новый файл.

Рисунок Excalidraw на iPhone.
Запуск рисунка Excalidraw на iPhone, где API доступа к файловой системе не поддерживается, но файл можно сохранить (загрузить) в папку «Загрузки».
Модифицированный рисунок Excalidraw в Chrome на рабочем столе.
Открытие и изменение чертежа Excalidraw на рабочем столе, где поддерживается API доступа к файловой системе, и, следовательно, доступ к файлу можно получить через API.
Перезапись исходного файла с изменениями.
Перезапись исходного файла с изменениями исходного файла чертежа Excalidraw. Браузер показывает диалоговое окно с вопросом, все ли в порядке.
Сохранение изменений в новом файле чертежа Excalidraw.
Сохранение изменений в новый файл Excalidraw. Исходный файл остается нетронутым.

Пример кода из реальной жизни

Ниже вы можете увидеть реальный пример браузерного доступа к fs, который используется в Excalidraw. Этот отрывок взят из /src/data/json.ts . Особый интерес представляет то, как метод saveAsJSON() передает дескриптор файла или null методу fileSave() браузера-fs-access, что приводит к его перезаписи, когда дескриптор задан, или к сохранению в новый файл, если нет.

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 отсутствует кнопка «Сохранить как» .

Панель инструментов приложения Excalidraw на iPhone с единственной кнопкой «Сохранить».
Панель инструментов приложения Excalidraw на iPhone с помощью одной кнопки «Сохранить» .
Панель инструментов приложения Excalidraw на рабочем столе Chrome с кнопками «Сохранить» и «Сохранить как».
Панель инструментов приложения Excalidraw в Chrome с кнопкой «Сохранить» и выделенной кнопкой «Сохранить как» .

Выводы

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

Благодарности

Эта статья была рецензирована Джо Медли и Кейси Баскс . Спасибо участникам Excalidraw за их работу над проектом и за рассмотрение моих запросов на включение. Изображение героя Ильи Павлова на Unsplash.