Przeglądarki od dawna poradziły sobie z plikami i katalogami. Interfejs File API zapewnia funkcje umożliwiające prezentowanie obiektów plików w aplikacjach internetowych oraz automatyczne ich wybieranie i uzyskiwanie dostępu do ich danych. Jednak gdy spoglądasz bliżej, nie wszystko, co się świeci, nie jest złote.
Tradycyjny sposób obsługi plików
Otwieranie plików
Jako programista możesz otwierać i odczytywać pliki za pomocą elementu <input type="file">
.
W najprostszej formie otwarcie pliku może przypominać przykładowy kod widoczny poniżej.
Obiekt input
udostępnia obiekt FileList
, który w tym przypadku zawiera tylko 1 File
.
File
to konkretny rodzaj elementu Blob
, którego można używać w dowolnym kontekście dostępnym dla blobów.
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
Do otwierania folderów (lub katalogów) możesz ustawić atrybut <input webkitdirectory>
.
Poza tym wszystkie pozostałe funkcje działają tak samo jak powyżej.
Pomimo nazwy, która jest prefiksem dostawcy, z webkitdirectory
można korzystać nie tylko w przeglądarkach Chromium i WebKit, ale także w starszych, opartych na interfejsie EdgeHTML i Firefoksie.
Zapisywanie (a nie: pobieranie) plików
Aby zapisać plik, tradycyjnie wystarczy go pobrać, co jest możliwe dzięki atrybutowi <a download>
.
Dla obiektu blob możesz ustawić atrybut href
kotwicy na adres URL blob:
, który można uzyskać za pomocą 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
Główną wadą metody pobierania jest to, że nie można wykonać klasycznego procesu otwartego →edytuj →zapisz, tzn. nie ma możliwości zastępowania oryginalnego pliku. Zamiast tego przy każdym zapisie tworzona jest nowa kopia oryginalnego pliku w domyślnym folderze Pobrane pliki systemu operacyjnego.
Interfejs File System Access API
Interfejs File System Access API znacznie ułatwia wykonywanie operacji, a także otwieranie i zapisywanie. Umożliwia też prawdziwe zapisywanie, co oznacza, że możesz nie tylko wybrać miejsce zapisania pliku, ale także nadpisać istniejący plik.
Otwieranie plików
W przypadku interfejsu File System Access API otwarcie pliku wymaga jednego wywołania metody window.showOpenFilePicker()
.
To wywołanie zwraca uchwyt pliku, z którego można uzyskać faktyczny 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, wywołując metodę window.showDirectoryPicker()
, która umożliwia wybór katalogów w oknie dialogowym pliku.
Zapisuję pliki
Zapisywanie plików wygląda podobnie.
Na podstawie nicku pliku tworzysz strumień z możliwością zapisu przez createWritable()
, następnie zapisujesz dane blobów, wywołując metodę write()
strumienia, a na koniec zamykasz 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 funkcję Browser-fs-access
Interfejs File System Access API jest w porządku, ale nie jest jeszcze powszechnie dostępny.
Dlatego uważam interfejs File System Access API za progresywne ulepszenie. Dlatego chcę używać go, gdy przeglądarka go obsługuje, a w razie potrzeby – tradycyjnego podejścia – nigdy nie karać użytkowników niepotrzebnym pobieraniem nieobsługiwanego kodu JavaScript. Biblioteka browser-fs-access pomaga mi rozwiązać ten problem.
Filozofia projektowania
Interfejs File System Access API prawdopodobnie jeszcze się zmieni w przyszłości, więc interfejs API Browser-fs-access nie jest modelowany na jego podstawie.
Oznacza to, że biblioteka to nie polyfill, a ponyfill;
Możesz (statycznie lub dynamicznie) importować tylko te funkcje, które są niezbędne do utrzymania jak najmniejszego rozmiaru aplikacji.
Dostępne metody to: fileOpen()
, directoryOpen()
i fileSave()
.
Wewnętrznie funkcja biblioteki wykrywa, czy interfejs File System Access API jest obsługiwany, a następnie importuje odpowiednią ścieżkę kodu.
Korzystanie z biblioteki fs-access przeglądarki
Trzy metody są intuicyjne w użyciu.
Możesz określić akceptowany przez aplikację mimeTypes
lub plik extensions
oraz ustawić flagę multiple
, aby zezwolić na wybór wielu plików lub katalogów lub go zabronić.
Więcej informacji znajdziesz w dokumentacji interfejsu Browser-fs-access API.
Przykładowy kod poniżej 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',
});
})();
Pokaz
Powyższy kod możesz zobaczyć w prezentacji dotyczącej Glitch. Jej kod źródłowy również jest tam dostępny. Ze względów bezpieczeństwa podramki podrzędne z innych domen nie mogą wyświetlać selektora plików, więc nie można umieścić wersji demonstracyjnej w tym artykule.
Dostępna w przeglądarce biblioteka dostępu do plików FS
W wolnym czasie mogę pomóc w tworzeniu dostępnej do instalacji PWA o nazwie Excalidraw. Jest to narzędzie do wirtualnej tablicy umożliwiające łatwe szkicowanie diagramów rysowanych odręcznie. Jest w pełni responsywny i działa dobrze na różnych urządzeniach, od małych telefonów komórkowych po komputery z dużymi ekranami. Oznacza to, że musi obsługiwać pliki na różnych platformach niezależnie od tego, czy obsługują interfejs File System Access API. Jest to więc świetna propozycja dla biblioteki dostępu do FS w przeglądarce.
Mogę na przykład zacząć rysować na iPhonie, zapisać go (technicznie: pobrać, ponieważ Safari nie obsługuje interfejsu File System Access API) w folderze pobierania na iPhonie, otworzyć plik na pulpicie (po przeniesieniu z telefonu), zmodyfikować plik i zastąpić go moimi zmianami, a nawet zapisać jako nowy plik.
Przykładowy kod w rzeczywistości
Poniżej możesz zobaczyć przykładowy kod przeglądarki fs-access 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 nick pliku lub null
do metody fileSave()
fs-access, co powoduje zastępowanie jej po podaniu nicka lub zapisywanie w nowym pliku, jeśli nie jest.
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 użytkownika
Interfejs powinien dostosowywać się do poziomu obsługi przeglądarki.
Jeśli interfejs File System Access API jest obsługiwany (if ('showOpenFilePicker' in window) {}
), oprócz przycisku Zapisz możesz wyświetlić przycisk Zapisz jako.
Na poniższych zrzutach ekranu widać różnicę między elastycznym głównym paskiem narzędzi aplikacji Excalidraw na iPhonie a Chrome na komputery.
Zwróć uwagę, że na iPhonie brakuje przycisku Zapisz jako.
Podsumowanie
Technicznie praca z plikami systemowymi działa we wszystkich nowoczesnych przeglądarkach. W przeglądarkach, które obsługują interfejs File System Access API, możesz polepszyć ich wrażenia, zezwalając na rzeczywiste zapisywanie i zastępowanie plików (a nie tylko na ich pobieranie) oraz umożliwiając użytkownikom tworzenie nowych plików w dowolnym miejscu i przy zachowaniu działania w przeglądarkach, które nie obsługują interfejsu File System Access API. Pole browser-fs-access ułatwia życie, eliminując subtelności stopniowego ulepszania i upraszczając tworzenie kodu.
Podziękowania
Ten artykuł napisali Joe Medley i Kayce Basques. Dziękujemy współtwórcom Excalidraw za pracę nad projektem i sprawdzanie moich żądań pull. Baner powitalny autorstwa Ilyi Pavlov w serwisie Unsplash.