Membaca dan menulis file serta direktori dengan library browser-fs-access

Browser telah mampu menangani file dan direktori untuk waktu yang lama. File API menyediakan fitur untuk merepresentasikan objek file dalam aplikasi web, serta memilih dan mengakses datanya secara terprogram. Namun, saat Anda melihat lebih dekat, semua yang berkilau itu belum tentu emas.

Cara tradisional menangani file

Membuka file

Sebagai pengembang, Anda dapat membuka dan membaca file melalui <input type="file"> . Dalam bentuk yang paling sederhana, membuka file bisa terlihat seperti contoh kode di bawah ini. Objek input memberi Anda FileList, yang dalam kasus di bawah ini hanya terdiri dari satu File. File adalah jenis Blob spesifik, dan dapat digunakan dalam konteks apa pun seperti yang bisa dilakukan oleh 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();
  });
};

Membuka direktori

Untuk membuka folder (atau direktori), Anda dapat mengatur atribut <input webkitdirectory> . Selain itu, yang lainnya berfungsi sama seperti di atas. Meskipun namanya awalan vendor, webkitdirectory tidak hanya dapat digunakan di browser Chromium dan WebKit, tetapi juga di Edge berbasis EdgeHTML lama serta di Firefox.

Menyimpan (bukan: mendownload) file

Untuk menyimpan file, biasanya, Anda dibatasi untuk mendownload file, yang berfungsi berkat <a download> . Dengan adanya Blob, Anda dapat menetapkan atribut href anchor ke URL blob: yang bisa Anda dapatkan dari 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();
};

Permasalahan

Kelemahan besar dari pendekatan download adalah tidak ada cara untuk membuat open→edit→simpan alur terjadi, yaitu, tidak ada cara untuk menimpa file asli. Sebagai gantinya, Anda akan mendapatkan salinan baru dari file asli di folder {i>Downloads<i} {i>default <i}pada sistem operasi kapan pun Anda "menyimpan".

File System Access API

File System Access API membuat operasi, membuka dan menyimpan, menjadi jauh lebih sederhana. Alat ini juga memungkinkan penyimpanan benar, sehingga Anda tidak hanya dapat memilih tempat menyimpan file, tetapi juga menimpa file yang ada.

Membuka file

Dengan File System Access API, membuka file hanya memerlukan satu panggilan ke metode window.showOpenFilePicker(). Panggilan ini menampilkan handle file, yang memungkinkan Anda mendapatkan File sebenarnya melalui metode 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);
  }
};

Membuka direktori

Buka direktori dengan memanggil window.showDirectoryPicker() yang membuat direktori dapat dipilih di kotak dialog file.

Menyimpan file

Cara menyimpan file juga sama mudahnya. Dari handle file, Anda membuat streaming yang dapat ditulis melalui createWritable(), lalu tulis data Blob dengan memanggil metode write() aliran data, dan terakhir, Anda menutup aliran data dengan memanggil metode 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);
  }
};

Memperkenalkan browser-fs-access

Sebaik apa pun File System Access API, teknologi ini belum tersedia secara luas.

Tabel dukungan browser untuk File System Access API. Semua browser ditandai sebagai &#39;tidak didukung&#39; atau &#39;di balik bendera&#39;.
Tabel dukungan browser untuk File System Access API. (Sumber)

Inilah alasan saya melihat File System Access API sebagai progressive enhancement. Karena itu, saya ingin menggunakannya ketika {i>browser<i} mendukungnya, dan menggunakan pendekatan tradisional jika tidak; semua tanpa menghukum pengguna dengan mengunduh kode JavaScript yang tidak perlu dan tidak didukung. Class browser-fs-access perpustakaan adalah jawaban saya untuk tantangan ini.

Filosofi desain

Karena {i>File System Access API <i} masih mungkin berubah di masa mendatang, API browser-fs-access tidak dimodelkan. Artinya, library bukan polyfill, melainkan ponyfill. Anda dapat (secara statis atau dinamis) mengimpor fungsi apa pun yang diperlukan untuk menjaga aplikasi Anda sekecil mungkin. Metode yang tersedia adalah fileOpen(), directoryOpen(), dan fileSave(). Secara internal, fitur library mendeteksi apakah File System Access API didukung, lalu mengimpor jalur kode yang sesuai.

Menggunakan library browser-fs-access

Ketiga metode tersebut intuitif untuk digunakan. Anda dapat menentukan mimeTypes aplikasi yang diterima atau file extensions, dan menyetel tanda multiple untuk mengizinkan atau melarang pemilihan beberapa file atau direktori. Untuk detail selengkapnya, lihat dokumentasi API browser-fs-access. Contoh kode di bawah menunjukkan cara membuka dan menyimpan file gambar.

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

Demo

Anda dapat melihat cara kerja kode di atas dalam demo di Glitch. Kode sumbernya juga tersedia di sana. Karena alasan keamanan, sub frame lintas origin tidak diizinkan untuk menampilkan pemilih file, demo tidak dapat disematkan dalam artikel ini.

Library browser-fs-access secara umum

Di waktu luang saya, saya sedikit berkontribusi untuk PWA yang dapat diinstal yang disebut Excalidraw, {i>tool<i} papan tulis yang memungkinkan Anda membuat sketsa diagram dengan mudah dan terasa seperti gambar tangan. Aplikasi ini sepenuhnya responsif dan berfungsi dengan baik di berbagai perangkat, mulai dari ponsel kecil hingga komputer dengan layar besar. Artinya, sistem tersebut perlu menangani file di berbagai platform apakah mereka mendukung API Akses Sistem File atau tidak. Hal ini menjadikannya kandidat yang tepat untuk library browser-fs-access.

Saya dapat, misalnya, mulai menggambar di iPhone, simpan (secara teknis: download, karena Safari tidak mendukung File System Access API) ke folder Download di iPhone, buka file di desktop (setelah mentransfernya dari ponsel), mengubah file, dan menimpanya dengan perubahan saya, atau bahkan menyimpannya sebagai file baru.

Gambar Excalidraw di iPhone.
Memulai gambar Excalidraw di iPhone yang tidak mendukung File System Access API, tetapi tempat file dapat disimpan (didownload) ke folder Download.
Gambar Excalidraw yang dimodifikasi di Chrome pada desktop.
Membuka dan mengubah gambar Excalidraw di desktop yang mendukung File System Access API, sehingga file dapat diakses melalui API.
Menimpa file asli dengan modifikasi.
Menimpa file asli dengan modifikasi pada file gambar Excalidraw asli. Browser akan menampilkan dialog yang menanyakan apakah ini baik-baik saja.
Menyimpan modifikasi ke file gambar Excalidraw baru.
Menyimpan perubahan ke file Excalidraw baru. File asli tetap tidak disentuh.

Contoh kode di kehidupan nyata

Di bawah ini, Anda dapat melihat contoh aktual dari browser-fs-access saat digunakan di Excalidraw. Cuplikan ini diambil dari /src/data/json.ts Yang perlu diperhatikan adalah cara metode saveAsJSON() meneruskan nama sebutan file atau null ke browser-fs-access' fileSave(), yang menyebabkannya menimpa saat handle diberikan, atau untuk menyimpan ke file baru jika belum.

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

Pertimbangan UI

Baik di Excalidraw atau aplikasi Anda, UI harus beradaptasi dengan situasi dukungan browser. Jika File System Access API didukung (if ('showOpenFilePicker' in window) {}) Anda dapat menampilkan tombol Save As selain tombol Save. Screenshot di bawah menunjukkan perbedaan antara toolbar aplikasi utama responsif Excalidraw di iPhone dan Chrome di desktop. Perhatikan bagaimana tombol Save As tidak ada di iPhone.

Toolbar aplikasi Excalidraw di iPhone hanya dengan &#39;Save&#39; tombol.
Toolbar aplikasi Excalidraw di iPhone hanya dengan tombol Save.
Toolbar aplikasi Excalidraw di desktop Chrome dengan tombol &#39;Save&#39; dan tab &quot;Simpan Sebagai&quot; tombol.
Toolbar aplikasi Excalidraw di Chrome dengan Save dan tombol Save As yang difokuskan.

Kesimpulan

Secara teknis, bekerja dengan file sistem berfungsi pada semua browser modern. Pada browser yang mendukung File System Access API, Anda bisa membuat pengalaman lebih baik dengan mengizinkan untuk menyimpan dan menimpa file yang sebenarnya (tidak hanya mengunduh) file dan dengan memungkinkan pengguna membuat file baru di mana pun mereka inginkan, semuanya namun tetap berfungsi pada browser yang tidak mendukung File System Access API. browser-fs-access memudahkan hidup Anda dengan menangani seluk-beluk {i>progressive enhancement<i} dan membuat kode Anda sesederhana mungkin.

Ucapan terima kasih

Artikel ini ditinjau oleh Joe Medley dan Kayce Basques. Terima kasih kepada kontributor Excalidraw untuk pekerjaan mereka pada project dan untuk meninjau Permintaan Pull saya. Banner besar oleh Ilya Pavlov di Unsplash.