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

Browser telah mampu menangani file dan direktori sejak lama. File API menyediakan fitur untuk merepresentasikan objek file di aplikasi web, serta memilih dan mengakses objek file secara terprogram. Namun, begitu Anda melihat lebih dekat, yang berkilauan belum tentu emas.

Cara tradisional untuk menangani file

Membuka file

Sebagai developer, Anda dapat membuka dan membaca file melalui elemen <input type="file">. Dalam bentuknya yang paling sederhana, membuka file mungkin terlihat seperti contoh kode di bawah ini. Objek input memberi Anda FileList, yang dalam kasus di bawah hanya terdiri dari satu File. File adalah jenis Blob khusus, dan dapat digunakan dalam konteks apa pun yang dapat dilakukan 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 menetapkan atribut <input webkitdirectory>. Selain itu, fitur lainnya berfungsi sama seperti di atas. Meskipun memiliki nama berawalan 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 hanya dapat mendownload file, yang berfungsi berkat atribut <a download>. Dengan adanya Blob, Anda dapat menetapkan atribut href anchor ke URL blob: yang bisa diperoleh dari metode 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 bahwa tidak ada cara untuk membuat alur terbuka →edit→simpan klasik terjadi. Artinya, tidak ada cara untuk menimpa file asli. Sebagai gantinya, Anda akan mendapatkan salinan baru dari file asli di folder Download default sistem operasi setiap kali Anda "menyimpan".

File System Access API

API Akses Sistem File membuat operasi, membuka dan menyimpan, menjadi jauh lebih sederhana. Tindakan ini juga memungkinkan simpanan benar, yaitu, Anda tidak hanya dapat memilih lokasi penyimpanan file, tetapi juga menimpa file yang sudah ada.

Membuka file

Dengan File System Access API, membuka file hanya perlu satu panggilan ke metode window.showOpenFilePicker(). Panggilan ini menampilkan handle file, tempat Anda bisa 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

Menyimpan file juga sama mudahnya. Dari handle file, Anda membuat stream yang dapat ditulis melalui createWritable(), lalu menulis data Blob dengan memanggil metode write() aliran data, dan terakhir 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

Meskipun file System Access API tidak bermasalah, API ini belum tersedia secara luas.

Tabel dukungan browser untuk File System Access API. Semua browser ditandai sebagai &#39;tidak ada dukungan&#39; atau &#39;di belakang tanda&#39;.
Tabel dukungan browser untuk File System Access API. (Sumber)

Itulah sebabnya saya melihat File System Access API sebagai progressive enhancement. Oleh karena itu, saya ingin menggunakannya ketika browser mendukungnya, dan menggunakan pendekatan tradisional jika tidak; semuanya tanpa menghukum pengguna dengan download kode JavaScript yang tidak didukung yang tidak perlu. Library browser-fs-access adalah jawaban saya untuk tantangan ini.

Filosofi desain

Karena File System Access API kemungkinan masih akan berubah di masa mendatang, API browser-fs-access tidak dibuat berdasarkan modelnya. Artinya, library ini bukan polyfill, melainkan ponyfill. Anda dapat (secara statis atau dinamis) secara eksklusif mengimpor fungsi apa pun yang diperlukan untuk menjaga aplikasi Anda sekecil mungkin. Metode yang tersedia adalah fileOpen(), directoryOpen(), dan fileSave() yang diberi nama tepat. Secara internal, fitur library akan mendeteksi apakah File System Access API didukung, lalu mengimpor jalur kode yang sesuai.

Menggunakan library browser-fs-access

Ketiga metode ini intuitif untuk digunakan. Anda dapat menentukan mimeTypes atau file extensions yang diterima aplikasi Anda, dan menetapkan tanda multiple untuk mengizinkan atau melarang pemilihan beberapa file atau direktori. Untuk mengetahui 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.

Pustaka browser-fs-access di mana-mana

Di waktu luang saya, saya sedikit berkontribusi untuk PWA yang dapat diinstal yang disebut Excalidraw, alat papan tulis virtual yang memungkinkan Anda dengan mudah membuat sketsa diagram dengan nuansa yang digambar tangan. Layanan ini sepenuhnya responsif dan berfungsi dengan baik di berbagai perangkat, mulai dari ponsel kecil hingga komputer dengan layar besar. Artinya, sistem perlu menangani file di berbagai platform baik mendukung File System Access API maupun tidak. Hal ini menjadikannya kandidat yang bagus untuk library browser-fs-access.

Misalnya, saya dapat membuat gambar di iPhone, menyimpannya (secara teknis: mendownloadnya, karena Safari tidak mendukung File System Access API) ke folder Download iPhone, membuka 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 penggambaran 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 memodifikasi 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 menampilkan dialog yang menanyakan apakah ini baik-baik saja.
Menyimpan modifikasi ke file gambar Excalidraw baru.
Menyimpan modifikasi ke file Excalidraw baru. File asli tetap tidak disentuh.

Contoh kode kehidupan nyata

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

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 maupun di 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 ini menunjukkan perbedaan antara toolbar aplikasi utama responsif Excalidraw di iPhone dan desktop Chrome. Perhatikan bahwa tombol Save As di iPhone tidak ada.

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

Kesimpulan

Bekerja dengan file sistem secara teknis berfungsi pada semua browser modern. Pada browser yang mendukung File System Access API, Anda dapat membuat pengalaman lebih baik dengan memungkinkan penyimpanan dan penimpaan file yang sebenarnya (tidak hanya mendownload) file, dan dengan mengizinkan pengguna membuat file baru di mana pun mereka mau, selagi tetap berfungsi pada browser yang tidak mendukung File System Access API. browser-fs-access memudahkan hidup Anda dengan menangani seluk-beluk progressive enhancement 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 dalam project dan untuk meninjau Permintaan Pull saya. Banner besar oleh Ilya Pavlov di Unsplash.