Tarayıcı-fs erişim kitaplığı ile dosya ve dizinleri okuma ve yazma

Tarayıcılar uzun süredir dosyalar ve dizinlerle çalışabiliyor. File API, web uygulamalarında dosya nesnelerini temsil etme, programatik olarak seçme ve verilerine erişme özellikleri sağlar. Ancak yakından baktığınızda her parlayan şeyin altın olmadığını görürsünüz.

Dosyalarla ilgili geleneksel yöntem

Dosyaları açma

Geliştirici olarak, dosyaları <input type="file"> öğesi aracılığıyla açıp okuyabilirsiniz. En basit haliyle, bir dosyayı açmak aşağıdaki kod örneğine benzer. input nesnesi size bir FileList verir. Aşağıdaki örnekte bu, yalnızca bir File'dan oluşur. File, belirli bir Blob türüdür ve Blob'un kullanılabildiği her bağlamda kullanılabilir.

const openFile = async () => {
  return new Promise((resolve) => {
    const input = document.createElement('input');
    input.type = 'file';
    input.addEventListener('change', () => {
      resolve(input.files[0]);
    });
    input.click();
  });
};

Dizinleri açma

Klasörleri (veya dizinleri) açmak için <input webkitdirectory> özelliğini ayarlayabilirsiniz. Bunun dışında her şey yukarıdakiyle aynı şekilde çalışır. Tedarikçi ön ekli adına rağmen, webkitdirectory yalnızca Chromium ve WebKit tarayıcılarda değil, aynı zamanda eski EdgeHTML tabanlı Edge'de ve Firefox'ta da kullanılabilir.

Dosyaları kaydetme (daha doğrusu indirme)

Geleneksel olarak bir dosyayı kaydetmek için indirme işlemi yapmanız gerekir. Bu işlem, <a download> özelliğinden yararlanır. Bir Blob verildiğinde, bağlantının href özelliğini URL.createObjectURL() yönteminden alabileceğiniz bir blob: URL'sine ayarlayabilirsiniz.

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

Sorun

İndirme yaklaşımının büyük bir dezavantajı, klasik açma→düzenleme→kaydetme akışının gerçekleştirilememesidir. Yani orijinal dosyanın üzerine yazmak mümkün değildir. Bunun yerine, "kaydet" işlemini her yaptığınızda işletim sisteminin varsayılan İndirilenler klasöründe orijinal dosyanın yeni bir kopyası oluşturulur.

File System Access API

File System Access API, hem açma hem de kaydetme işlemlerini çok daha basit hale getirir. Ayrıca, gerçek kaydetme özelliğini de etkinleştirir. Yani, bir dosyayı nereye kaydedeceğinizi seçmenin yanı sıra mevcut bir dosyanın üzerine de yazabilirsiniz.

Dosyaları açma

File System Access API ile bir dosyayı açmak için window.showOpenFilePicker() yöntemine tek bir çağrı yapmanız yeterlidir. Bu çağrı, getFile() yöntemiyle gerçek File değerini alabileceğiniz bir dosya tutucusu döndürür.

const openFile = async () => {
  try {
    // Always returns an array.
    const [handle] = await window.showOpenFilePicker();
    return handle.getFile();
  } catch (err) {
    console.error(err.name, err.message);
  }
};

Dizinleri açma

Dosya iletişim kutusunda dizinlerin seçilebilir olmasını sağlayan window.showDirectoryPicker() işlevini çağırarak bir dizin açın.

Dosyaları kaydetme

Dosyaları kaydetmek de aynı şekilde kolaydır. Dosya tutma yerinden createWritable() ile yazılabilir bir akış oluşturursunuz, ardından akışın write() yöntemini çağırarak Blob verilerini yazarsınız ve son olarak akışın close() yöntemini çağırarak akışı kapatırsınız.

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

browser-fs-access ile tanışın

File System Access API mükemmel olsa da henüz yaygın olarak kullanılamamaktadır.

File System Access API için tarayıcı desteği tablosu. Tüm tarayıcılar &quot;desteklenmiyor&quot; veya &quot;bir işaretin arkasında&quot; olarak işaretlenir.
File System Access API için tarayıcı destek tablosu. (Kaynak)

Bu nedenle, File System Access API'yi ilerleyici geliştirme olarak görüyorum. Bu nedenle, tarayıcı desteklediğinde bunu kullanmak, desteklemediğinde ise geleneksel yaklaşımı kullanmak istiyorum. Bu sırada, kullanıcıyı desteklenmeyen JavaScript kodunun gereksiz indirilmesiyle cezalandırmamak da önemli. Bu zorluğa yanıtım browser-fs-access kitaplığıdır.

Tasarım felsefesi

File System Access API'nin gelecekte değişme ihtimali olduğundan browser-fs-access API, bu API'ye göre modellenmemiştir. Yani kitaplık bir polyfill değil, ponyfill'dir. Uygulamanızı olabildiğince küçük tutmak için ihtiyacınız olan işlevleri (statik veya dinamik olarak) özel olarak içe aktarabilirsiniz. Kullanılabilir yöntemler, uygun şekilde adlandırılmış fileOpen(), directoryOpen() ve fileSave()'dır. Kitaplık, File System Access API'nin desteklenip desteklenmediğini dahili olarak algılar ve ardından ilgili kod yolunu içe aktarır.

browser-fs-access kitaplığını kullanma

Üç yöntemin de kullanımı kolaydır. Uygulamanızın kabul ettiği mimeTypes veya dosya extensions türlerini belirleyebilir ve birden fazla dosya ya da dizin seçimine izin vermek veya izin vermemek için multiple işareti ayarlayabilirsiniz. Tüm ayrıntılar için browser-fs-access API belgelerine bakın. Aşağıdaki kod örneğinde, resim dosyalarını nasıl açıp kaydedebileceğiniz gösterilmektedir.

// 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

Yukarıdaki kodun nasıl çalıştığını GitHub'daki demoda görebilirsiniz. Kaynak kodu da aynı şekilde burada mevcuttur. Güvenlik nedeniyle, kaynaklar arası alt çerçevelerin dosya seçici göstermesine izin verilmediğinden demo bu makaleye yerleştirilemez.

The browser-fs-access library in the wild

Boş zamanlarımda, Excalidraw adlı yüklenebilir PWA'ya küçük bir katkıda bulunuyorum. Excalidraw, elle çizilmiş gibi görünen diyagramları kolayca taslak hâline getirmenizi sağlayan bir beyaz tahta aracıdır. Tamamen duyarlı olan bu tema, küçük cep telefonlarından büyük ekranlı bilgisayarlara kadar çeşitli cihazlarda iyi çalışır. Bu nedenle, File System Access API'yi destekleseler de desteklemeseler de çeşitli platformlardaki dosyalarla ilgilenmesi gerekir. Bu nedenle, browser-fs-access kitaplığı için harika bir adaydır.

Örneğin, iPhone'umda bir çizim başlatabilir, Safari, File System Access API'yi desteklemediğinden teknik olarak indirerek iPhone'umun İndirilenler klasörüne kaydedebilir, dosyayı masaüstü bilgisayarımda açabilir (telefondan aktardıktan sonra), dosyayı değiştirebilir ve değişikliklerimle üzerine yazabilir veya yeni bir dosya olarak kaydedebilirim.

iPhone&#39;da Excalidraw çizimi.
File System Access API'nin desteklenmediği ancak dosyaların İndirilenler klasörüne kaydedilebildiği (indirilebildiği) bir iPhone'da Excalidraw çizimi başlatma.
Masaüstündeki Chrome&#39;da değiştirilmiş Excalidraw çizimi.
Dosyaya API üzerinden erişilebildiği için File System Access API'nin desteklendiği masaüstünde Excalidraw çizimini açma ve değiştirme.
Orijinal dosyanın üzerine değişiklikleri yazma
Orijinal Excalidraw çizim dosyasındaki değişikliklerle orijinal dosyanın üzerine yazma. Tarayıcıda, bunun uygun olup olmadığını soran bir iletişim kutusu gösteriliyor.
Değişiklikleri yeni bir Excalidraw çizim dosyasına kaydetme
Değişiklikler yeni bir Excalidraw dosyasına kaydedilir. Orijinal dosya değiştirilmez.

Gerçek hayattan kod örneği

Aşağıda, Excalidraw'da kullanılan browser-fs-access'in gerçek bir örneğini görebilirsiniz. Bu alıntı, /src/data/json.ts kaynağından alınmıştır. saveAsJSON() yönteminin, browser-fs-access'in fileSave() yöntemine bir dosya tutucusu veya null iletmesi özellikle önemlidir. Bu, tutucu verildiğinde üzerine yazılmasına, verilmediğinde ise yeni bir dosyaya kaydedilmesine neden olur.

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

Kullanıcı arayüzüyle ilgili dikkat edilmesi gereken noktalar

Excalidraw'da veya uygulamanızda kullanıcı arayüzü, tarayıcının destek durumuna uyum sağlamalıdır. File System Access API destekleniyorsa (if ('showOpenFilePicker' in window) {}) Kaydet düğmesinin yanı sıra Farklı Kaydet düğmesi de gösterebilirsiniz. Aşağıdaki ekran görüntülerinde, Excalidraw'ın iPhone'daki ve Chrome masaüstündeki duyarlı ana uygulama araç çubuğu arasındaki fark gösterilmektedir. iPhone'da Farklı Kaydet düğmesinin olmadığını unutmayın.

iPhone&#39;da yalnızca &quot;Kaydet&quot; düğmesi bulunan Excalidraw uygulama araç çubuğu.
iPhone'da yalnızca Kaydet düğmesi bulunan Excalidraw uygulama araç çubuğu.
Chrome masaüstündeki Excalidraw uygulama araç çubuğunda &quot;Kaydet&quot; ve &quot;Farklı Kaydet&quot; düğmeleri.
Chrome'da Excalidraw uygulama araç çubuğu. Kaydet ve odaklanılmış Farklı Kaydet düğmesi.

Sonuçlar

Sistem dosyalarıyla çalışma, teknik olarak tüm modern tarayıcılarda mümkündür. File System Access API'yi destekleyen tarayıcılarda, dosyaların gerçek anlamda kaydedilmesine ve üzerine yazılmasına (yalnızca indirilmesine değil) izin vererek ve kullanıcılarınızın istedikleri yerde yeni dosyalar oluşturmasına olanak tanıyarak deneyimi iyileştirebilirsiniz. Bu sırada, File System Access API'yi desteklemeyen tarayıcılarda işlevselliği koruyabilirsiniz. browser-fs-access, aşamalı iyileştirmenin incelikleriyle ilgilenerek ve kodunuzu olabildiğince basit hale getirerek hayatınızı kolaylaştırır.

Teşekkür

Bu makale Joe Medley ve Kayce Basques tarafından incelenmiştir. Projeye katkıda bulunan ve çekme isteklerimi inceleyen Excalidraw katkıda bulunanlarına teşekkür ederim. Unsplash'teki Ilya Pavlov'un hero resmi.