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

Browser telah lama dapat menangani file dan direktori. File API menyediakan fitur untuk merepresentasikan objek file di aplikasi web, serta memilihnya secara terprogram dan mengakses datanya. Namun, saat Anda melihat lebih dekat, semua yang berkilau bukanlah emas.

Cara tradisional dalam menangani file

Membuka file

Sebagai developer, Anda dapat membuka dan membaca file melalui elemen <input type="file">. Dalam bentuknya yang paling sederhana, membuka file dapat terlihat seperti contoh kode di bawah. Objek input memberi Anda FileList, yang dalam kasus di bawah hanya terdiri dari satu File. File adalah jenis Blob tertentu, dan dapat digunakan dalam konteks apa pun yang dapat 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 menetapkan atribut <input webkitdirectory>. Selain itu, semuanya 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 (lebih tepatnya: mendownload) file

Untuk menyimpan file, biasanya Anda hanya dapat mendownload file, yang berfungsi berkat atribut <a download>. Dengan Blob, Anda dapat menyetel atribut href penanda ke URL blob: yang dapat Anda peroleh 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 tidak ada cara untuk membuat alur buka→edit→simpan klasik terjadi, yaitu 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

File System Access API membuat kedua operasi, membuka dan menyimpan, menjadi jauh lebih sederhana. Fitur ini juga memungkinkan penyimpanan sebenarnya, yaitu Anda tidak hanya dapat memilih tempat untuk 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 dapat Anda gunakan untuk 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 sederhananya. Dari handle file, Anda membuat stream yang dapat ditulis melalui createWritable(), lalu Anda menulis data Blob dengan memanggil metode write() stream, dan terakhir Anda menutup stream dengan memanggil metode close()-nya.

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 sangat bagus, API ini belum tersedia secara luas.

Tabel dukungan browser untuk File System Access API. Semua browser ditandai sebagai &#39;tidak mendukung&#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 saat 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 masih mungkin berubah di masa mendatang, browser-fs-access API tidak dimodelkan setelahnya. Artinya, library ini bukan polyfill, melainkan ponyfill. Anda dapat (secara statis atau dinamis) mengimpor secara eksklusif fungsi apa pun yang Anda butuhkan untuk menjaga ukuran aplikasi sekecil mungkin. Metode yang tersedia adalah fileOpen(), directoryOpen(), dan fileSave(). Secara internal, library mendeteksi fitur jika 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 extensions file yang diterima aplikasi, dan menetapkan tanda multiple untuk mengizinkan atau tidak mengizinkan pemilihan beberapa file atau direktori. Untuk mengetahui detail selengkapnya, lihat dokumentasi browser-fs-access API. 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 GitHub. Kode sumber-nya juga tersedia di sana. Karena alasan keamanan, sub-frame lintas origin tidak diizinkan untuk menampilkan pemilih file, sehingga demo tidak dapat disematkan dalam artikel ini.

Library browser-fs-access di alam bebas

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

Misalnya, saya dapat memulai gambar di iPhone, menyimpannya (secara teknis: mendownloadnya, karena Safari tidak mendukung File System Access API) ke folder Download iPhone saya, membuka file di desktop (setelah mentransfernya dari ponsel), memodifikasi 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 file dapat disimpan (didownload) ke folder Downloads.
Gambar Excalidraw yang dimodifikasi di Chrome di desktop.
Membuka dan mengubah gambar Excalidraw di desktop tempat File System Access API didukung 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 saya setuju.
Menyimpan modifikasi ke file gambar Excalidraw baru.
Menyimpan modifikasi ke file Excalidraw baru. File asli tetap tidak tersentuh.

Contoh kode dalam kehidupan nyata

Di bawah ini, Anda dapat melihat contoh sebenarnya browser-fs-access saat digunakan di Excalidraw. Kutipan ini diambil dari /src/data/json.ts. Yang menarik adalah bagaimana metode saveAsJSON() meneruskan handle file atau null ke metode fileSave() browser-fs-access, yang menyebabkannya ditimpa saat handle diberikan, atau disimpan 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 aplikasi Anda, UI harus beradaptasi dengan situasi dukungan browser. Jika File System Access API didukung (if ('showOpenFilePicker' in window) {}), Anda dapat menampilkan tombol Simpan Sebagai selain tombol Simpan. Screenshot di bawah menunjukkan perbedaan antara toolbar aplikasi utama responsif Excalidraw di iPhone dan di desktop Chrome. Perhatikan bahwa tombol Simpan Sebagai tidak ada di iPhone.

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 Simpan dan Simpan Sebagai yang difokuskan.

Kesimpulan

Bekerja dengan file sistem secara teknis berfungsi di semua browser modern. Di browser yang mendukung File System Access API, Anda dapat meningkatkan kualitas pengalaman dengan mengizinkan penyimpanan dan penimpaan file yang sebenarnya (bukan hanya mendownload) dan dengan mengizinkan pengguna membuat file baru di mana pun mereka inginkan, sekaligus tetap berfungsi di browser yang tidak mendukung File System Access API. browser-fs-access mempermudah hidup Anda dengan menangani kehalusan peningkatan progresif dan membuat kode Anda sesederhana mungkin.

Ucapan terima kasih

Artikel ini ditinjau oleh Joe Medley dan Kayce Basques. Terima kasih kepada kontributor Excalidraw atas pekerjaan mereka dalam proyek ini dan atas peninjauan Permintaan Tarikan saya. Gambar banner besar oleh Ilya Pavlov di Unsplash.