Odczytywanie i zapisywanie plików oraz katalogów za pomocą biblioteki fs-access w przeglądarce

Przeglądarki już od dawna są w stanie radzić sobie z plikami i katalogami. File API udostępnia funkcje do reprezentowania obiektów plików w aplikacjach internetowych, a także zautomatyzowane wybieranie ich i uzyskiwanie dostępu do ich danych. Jednak gdy przyjrzysz się bliżej, nie wszystko, co się świeci, nie znaczy złoto.

Tradycyjne podejście do plików

Otwieranie plików

Jako deweloper możesz otwierać i odczytywać pliki za pomocą <input type="file"> . W najprostszej formie otwarcie pliku może wyglądać jak w przykładzie poniżej. Obiekt input daje Ci FileList, który w poniższym przypadku składa się tylko z jednego File File to specyficzny rodzaj Blob, i być używane w dowolnym kontekście, w jakim może funkcjonować 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();
  });
};

Otwieranie katalogów

Aby otwierać foldery (lub katalogi), możesz ustawić <input webkitdirectory> . Poza tym reszta działa tak samo jak powyżej. Pomimo nazwy z prefiksem dostawcy, Z webkitdirectory można korzystać nie tylko w przeglądarkach Chromium i WebKit, ale również w starszej wersji Edge opartej na EdgeHTML oraz w Firefoksie.

Zapisuję pliki (zamiast je pobierać)

Aby zapisać plik, zwykle wystarczy pobrać plik, który działa dzięki <a download>. . W przypadku obiektu blob możesz ustawić atrybut href kotwicy na adres URL blob:, który można uzyskać z metody 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();
};

Problem

Olbrzymią wadą metody pobierania jest to, że nie ma możliwości utworzenia klasycznej wersji, otwórz →edit→zapisz przepływ, czyli nie ma możliwości zastąpienia oryginalnego pliku. W rezultacie powstaje nowa kopia oryginalnego pliku. w domyślnym folderze Pobrane w systemie operacyjnym po „zapisaniu”.

Interfejs File System Access API

Interfejs File System Access API znacznie ułatwia wykonywanie operacji, a także zapisywanie. Umożliwia też prawdziwe zapisywanie, co oznacza, że nie tylko możesz wybrać, gdzie zapisać plik, ale także zastąpić istniejący plik.

Otwieranie plików

Za pomocą interfejsu File System Access API otwarcie pliku jest kwestią pojedynczego wywołania metody window.showOpenFilePicker(). To wywołanie zwraca uchwyt pliku, z którego można uzyskać rzeczywisty File za pomocą metody 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);
  }
};

Otwieranie katalogów

Otwórz katalog, dzwoniąc window.showDirectoryPicker(), który umożliwia wybieranie katalogów w oknie pliku.

Zapisuję pliki

Zapisywanie plików jest podobne. Na podstawie uchwytu pliku tworzysz strumień z możliwością zapisu w createWritable(). potem zapiszesz dane w obiekcie Blob, wywołując metodę write() strumienia, i zamknij strumień, wywołując jego metodę 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);
  }
};

Przedstawiamy dostęp do systemu plików w przeglądarce

Zapewne nie ma to jak w przypadku interfejsu File System Access API, nie jest jeszcze powszechnie dostępna.

Tabela pomocy dla interfejsu File System Access API. Wszystkie przeglądarki są oznaczone jako „brak obsługi” lub „za flagą”.
Tabela pomocy dla interfejsu File System Access API. (Źródło)

Dlatego uważam interfejs File System Access API za progresywne ulepszenie. Dlatego chcę go używać, gdy przeglądarka go obsługuje, a w przeciwnym razie stosować podejście tradycyjne. a jednocześnie nigdy nie karać użytkownika niepotrzebnymi plikami do pobrania z nieobsługiwanego kodu JavaScript. Interfejs browser-fs-access Biblioteka to odpowiedź na to wyzwanie.

Filozofia projektowania

Interfejs File System Access API prawdopodobnie ulegnie zmianie w przyszłości, interfejs API Browser-fs-access nie jest po nim modelowany. Oznacza to, że biblioteka nie jest polyfill, lecz raczej ponyfill. Możesz (statycznie lub dynamicznie) importować wyłącznie te funkcje, których potrzebujesz, by aplikacja była jak najmniejsza. Dostępne metody to fileOpen() directoryOpen() i fileSave(). Wewnętrznie funkcja biblioteki wykrywa, czy interfejs File System Access API jest obsługiwany. a potem zaimportuje odpowiednią ścieżkę kodu.

Korzystanie z biblioteki dostępnej w przeglądarce

Te 3 metody są intuicyjne w użyciu. Możesz określić akceptowaną przez aplikację mimeTypes lub plik extensions oraz ustawić flagę multiple aby zezwolić na wybór wielu plików lub katalogów albo zabronić tego wyboru. Szczegółowe informacje znajdziesz tutaj: dokumentacji interfejsubrowser-fs-access API. Poniższy przykładowy kod pokazuje, jak otwierać i zapisywać pliki graficzne.

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

Prezentacja

Powyższy kod możesz zobaczyć w wersji demonstracyjnej w usłudze Glitch. Jej kod źródłowy również jest dostępny w tym miejscu. Ze względów bezpieczeństwa ramki podrzędne z innych domen nie mogą wyświetlać selektora plików, wersji demonstracyjnej nie można umieścić w tym artykule.

Biblioteka FS z dostępem do przeglądarki

W wolnym czasie uczestniczę w programie PWA do zainstalowania o nazwie Excalidraw, narzędzie do wirtualnej tablicy, które pozwala łatwo szkicować diagramy rysunkowe. Jest w pełni elastyczny i dobrze działa na różnych urządzeniach – od małych telefonów komórkowych po komputery z dużymi ekranami. Oznacza to, że musi zajmować się plikami na różnych platformach. niezależnie od tego, czy obsługują interfejs File System Access API. Dzięki temu świetnie nadaje się do wdrożenia biblioteki z dostępem do systemu plików przeglądarki.

Mogę na przykład zacząć rysować na iPhonie, zapisz go (technicznie: pobierz, ponieważ Safari nie obsługuje interfejsu File System Access API). do folderu pobranych plików na iPhonie, otwórz plik na pulpicie (po przesłaniu go z telefonu) modyfikować plik, zastąpić go moimi zmianami, a nawet zapisać jako nowy plik.

Rysunek Excalidraw na iPhonie.
Uruchamianie rysunku Excalidraw na iPhonie, który nie jest obsługiwany przez interfejs File System Access API, ale na którym można zapisać (pobrać) plik w folderze Pobrane.
.
Zmodyfikowany rysunek Excalidraw w Chrome na komputerze.
Otwieranie i modyfikowanie rysunku Excalidraw na komputerze, na którym obsługiwany jest interfejs File System Access API, dzięki czemu można uzyskać dostęp do pliku przez API.
.
Zastąpienie pierwotnego pliku wprowadzonymi modyfikacjami.
Zastąp pierwotny plik zmianami w oryginalnym pliku rysunku Excalidraw. W przeglądarce pojawi się okno z pytaniem, czy wszystko w porządku.
.
Zapisywanie zmian w nowym pliku rysunków Excalidraw.
Zapisywanie zmian w nowym pliku Excalidraw. Oryginalny plik pozostaje niezmieniony.

Przykładowy kod w rzeczywistości

Poniżej znajdziesz przykładowy kod dostępu z fs przeglądarki używany w Excalidraw. Ten fragment pochodzi z /src/data/json.ts Szczególnie interesujące jest to, w jaki sposób metoda saveAsJSON() przekazuje uchwyt pliku lub null na dostęp do funkcji przeglądarki z fs. fileSave(), która powoduje zastąpienie po podaniu nicka, lub zapisać w nowym pliku.

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);
};

Uwagi na temat interfejsu

Zarówno w Excalidraw, jak i w aplikacji interfejs użytkownika powinien dostosowywać się do obsługi używanej przeglądarki. Jeśli interfejs File System Access API jest obsługiwany (if ('showOpenFilePicker' in window) {}) oprócz przycisku Zapisz jako możesz wyświetlać przycisk Zapisz jako. Zrzuty ekranu poniżej pokazują różnicę między elastycznym głównym paskiem narzędzi aplikacji Excalidraw na iPhonie i w Chrome na komputerze. Zwróć uwagę, że na iPhonie brakuje przycisku Zapisz jako.

Pasek narzędzi aplikacji Excalidraw na iPhonie za pomocą przycisku „Zapisz” Przycisk
Pasek narzędzi aplikacji Excalidraw na iPhonie za pomocą przycisku Zapisz.
.
Pasek narzędzi aplikacji Excalidraw na komputerze z przyciskiem „Zapisz” oraz przycisk Zapisz jako Przycisk
Pasek narzędzi aplikacji Excalidraw w Chrome z zaznaczonym przyciskiem Zapisz i zaznaczonym przyciskiem Zapisz jako.

Podsumowanie

Praca z plikami systemowymi jest zasadniczo sprawdzana we wszystkich nowoczesnych przeglądarkach. W przeglądarkach, które obsługują interfejs File System Access API, możesz zwiększyć wygodę użytkowania, zezwalając na do rzeczywistego zapisywania i zastępowania (nie tylko pobierania) plików oraz umożliwiając użytkownikom tworzenie nowych plików w dowolnym miejscu, a jednocześnie funkcjonować w przeglądarkach, które nie obsługują interfejsu File System Access API. browser-fs-access ułatwia życie stawiając na niuanse stopniowego ulepszania i jak najprostsze tworzenie kodu.

Podziękowania

Ten artykuł został zrecenzowany przez Joe Medley i Kayce Basques Dziękujemy współtwórcom Excalidraw za pracę nad projektem i za sprawdzenie moich żądań pull. Baner powitalny – autor: Ilia Pawłow o Unsplash.