Как сохранить файл на локальные устройства пользователя с помощью нового API доступа к файловой системе и резервного варианта для несовместимых браузеров.

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

Введение

API доступа к файловой системе позволяет читать, записывать и управлять файлами в вашем браузере. Это позволяет разработчикам создавать мощные веб-приложения, взаимодействующие с файлами на локальном устройстве пользователя.

У команды web.dev есть руководство, в котором представлены и выделены все функции.​

Это относительно новый API, поэтому он еще не принят всеми поставщиками браузеров.​

Например, одна из ключевых функций, которую мы собираемся использовать — showSaveFilePicker, которая открывает диалоговое окно для выбора места назначения файла, который будет записан на локальный диск пользователя, — поддерживается только Edge, Chrome и Opera (февраль 2018 г.). 2022- источник Caniuse).

Начиная

Вообще говоря, я использую TypeScript. Это решение также обеспечивается безопасностью типов. Вот почему сначала необходимо установить определения типов для API доступа к файловой системе.

npm i -D @types/wicg-file-system-access

Руки вверх

Для экспорта файла я использую Blob — то есть содержимое файла, который я хочу экспортировать — и filename. Я создаю одну функцию, которая сохраняется на локальном устройстве пользователя и которую можно использовать в моем приложении.

export const save = (data: {blob: Blob, filename: string}) => {
    if ('showSaveFilePicker' in window) {
        return exportNativeFileSystem(data);
    }

    return download(data);
};

API доступа к файловой системе — Сохранить как

Вышеуказанная функция проверяет, доступен ли showSaveFilePicker в объекте window, т. е. проверяет, поддерживает ли браузер API доступа к файловой системе или нет.

Чтобы сохранить файл с новым API, мы сначала показываем пользователю диалог в режиме «сохранения». Используя его, пользователь может выбрать место, где будет сохранен файл. После установки пути файл можно эффективно записать на локальный диск.

const exportNativeFileSystem =
        async ({blob, filename}: {blob: Blob, filename: string}) => {
    const fileHandle: FileSystemFileHandle =
        await getNewFileHandle({filename});

    if (!fileHandle) {
        throw new Error('Cannot access filesystem');
    }

    await writeFile({fileHandle, blob});
};

Во многих случаях я хочу, чтобы мое приложение предлагало имя файла по умолчанию. Этого можно добиться, установив suggestedName​. Кроме того, я также ограничиваю типы файлов, которые можно выбрать, предоставляя MIME-типы и соответствующие расширения файлов.

const getNewFileHandle = 
    ({filename}: {filename: string}): Promise<FileSystemFileHandle> => {
  const opts: SaveFilePickerOptions = {
    suggestedName: filename,
    types: [
      {
        description: 'Markdown file',
        accept: {
          'text/plain': ['.md']
        }
      }
    ]
  };

  return showSaveFilePicker(opts);
};

Наконец, файл можно эффективно записать с помощью writeFile — еще одной функции API​. Он использует дескриптор файла, который я ранее запросил, чтобы узнать, куда экспортировать данные в файловой системе.

const writeFile = 
    async ({fileHandle, blob}: {fileHandle: FileSystemFileHandle, blob: Blob}) => {
  const writer = await fileHandle.createWritable();
  await writer.write(blob);
  await writer.close();
};

Резервный вариант — Скачать

В качестве запасного варианта я добавляю в DOM временный элемент привязки, который автоматически щелкается. Чтобы экспортировать файл в папку загрузки пользователя по умолчанию, я предоставляю объект в качестве URL-адреса для файла blob​.

const download = async ({filename, blob}: {filename: string; blob: Blob}) => {
  const a: HTMLAnchorElement = document.createElement('a');
  a.style.display = 'none';
  document.body.appendChild(a);

  const url: string = window.URL.createObjectURL(blob);

  a.href = url;
  a.download = `${filename}.md`;

  a.click();

  window.URL.revokeObjectURL(url);
  a.parentElement?.removeChild(a);
};

Получить код

​Вы можете найти весь код, представленный в этой статье, в недавнем плагине Chrome, который я опубликовал на GitHub 👉 save-utils.ts

Краткое содержание

Это был довольно короткий пост, который, я надеюсь, был хоть немного интересным 🤪. Если вы хотите глубже изучить API доступа к файловой системе, я еще раз советую вам взглянуть на хороший пост команды web.dev.

​В бесконечность и дальше
Дэвид​