Kaynak gizli dosya sistemi

Dosya Sistemi Standardı, sayfanın kaynağına özel olan ve performans için son derece optimize edilmiş özel bir dosya türüne isteğe bağlı erişim sağlayan kullanıcı tarafından görülmeyen depolama uç noktası olarak bir kaynak gizli dosya sistemi (OPFS) sunar.

Tarayıcı desteği

Kaynak gizli dosya sistemi, modern tarayıcılar tarafından desteklenir ve File System Living Standard'da (Dosya Sistemi Yaşam Standardı) Web Hypertext Application Technology Working Group (WHEREWG) tarafından standart hâle getirilmiştir.

Tarayıcı Desteği

  • Chrome: 86..
  • Kenar: 86..
  • Firefox: 111..
  • Safari: 15.2..

Kaynak

Motivasyon

Bilgisayarınızdaki dosyaları düşündüğünüzde aklınıza bir dosya hiyerarşisi gelir. Bu hiyerarşi, işletim sisteminizin dosya gezginini kullanarak keşfedebileceğiniz klasörler halinde düzenlenmiş dosyalardır. Örneğin, Windows'da Tarık adlı bir kullanıcının Yapılacaklar listesi C:\Users\Tom\Documents\ToDo.txt konumunda yayında olabilir. Bu örnekte, ToDo.txt dosya adı, Users, Tom ve Documents ise klasör adlarıdır. Windows'da "C:", sürücünün kök dizinini temsil eder.

Web'deki dosyalarla çalışmanın geleneksel yolu

Bir web uygulamasında Yapılacaklar listesini düzenlemek için her zamanki akış şu şekildedir:

  1. Kullanıcı dosyayı bir sunucuya yükler veya <input type="file"> ile istemcide açır.
  2. Kullanıcı gerekli değişiklikleri yapar, ardından JavaScript aracılığıyla programatik olarak click() yüklediğiniz bir dosya yerleştirilmiş <a download="ToDo.txt> ile oluşturulan dosyayı indirir.
  3. Klasörleri açmak için <input type="file" webkitdirectory> ürününde özel bir özellik kullanırsınız. Bu özellik, kendi adına olmasına rağmen neredeyse evrensel tarayıcı desteğine sahiptir.

Web'deki dosyalarla çalışmanın modern yolu

Bu akış, kullanıcıların dosyaları düzenlemeyi nasıl düşündüğünü yansıtmaz ve kullanıcıların giriş dosyalarının kopyalarını indirdikleri anlamına gelir. Bu nedenle File System Access API, tam olarak adından da anlaşılacağı üzere üç seçici yöntemi (showOpenFilePicker(), showSaveFilePicker() ve showDirectoryPicker()) kullanıma sundu. Aşağıdaki şekilde bir akışı etkinleştirirler:

  1. ToDo.txt uygulamasını showOpenFilePicker() ile açıp bir FileSystemFileHandle nesnesi alın.
  2. Dosya herkese açık kullanıcı adının getFile() yöntemini çağırarak FileSystemFileHandle nesnesinden bir File alın.
  3. Dosyayı değiştirin ve herkese açık kullanıcı adında requestPermission({mode: 'readwrite'}) çağrısı yapın.
  4. Kullanıcı izin isteğini kabul ederse değişiklikleri orijinal dosyaya geri kaydedin.
  5. Alternatif olarak, showSaveFilePicker() dosyasını çağırın ve kullanıcının yeni bir dosya seçmesine izin verin. (Kullanıcı daha önce açılmış bir dosyayı seçerse dosya içeriğinin üzerine yazılır.) Tekrarlanan kayıtlarda, dosya tutma yerini sabit tutabilirsiniz. Böylece, dosya kaydetme iletişim kutusunu tekrar görüntülemek zorunda kalmazsınız.

Web üzerindeki dosyalarla çalışmaya ilişkin kısıtlamalar

Bu yöntemlerle erişilebilen dosya ve klasörler, kullanıcı tarafından görülebilir dosya sisteminde bulunur. Web'den kaydedilen dosyalar ve özel olarak yürütülebilir dosyalar web işareti ile işaretlenir. Bu nedenle, tehlikeli olabilecek bir dosya yürütülmeden önce işletim sisteminin gösterebileceği ek bir uyarı olur. Ek bir güvenlik özelliği olarak, web'den edinilen dosyalar da Güvenli Tarama ile korunur. Bu Güvenli Tarama, kolaylık sağlamak amacıyla ve bu makale bağlamında bulut tabanlı bir virüs taraması gibi düşünebilirsiniz. File System Access API'yi kullanarak bir dosyaya veri yazarken yazma işlemleri yerinde değildir ancak geçici bir dosya kullanır. Dosyanın kendisi tüm bu güvenlik kontrollerini geçmediği sürece değiştirilmez. Tahmin edebileceğiniz gibi bu çalışma, örneğin macOS'te gibi, mümkün olduğunda iyileştirmelerin uygulanmasına rağmen dosya işlemlerini nispeten yavaşlatıyor. Yine de her write() çağrısı bağımsız olduğundan, arka planda dosyayı açar, belirtilen ofsete doğru arama yapar ve son olarak veri yazar.

İşlemenin temeli olarak dosyalar

Aynı zamanda dosyalar, veri kaydetmek için mükemmel bir yoldur. Örneğin, SQLite tüm veritabanlarını tek bir dosyada depolar. Diğer bir örnek olarak resim işlemede kullanılan mipmaps verilebilir. Mipmaps'ler, önceden hesaplanmış ve optimize edilmiş görüntü dizileridir. Bunların her biri, bir öncekinin giderek daha düşük çözünürlüklü temsilidir. Bu da, daha hızlı yakınlaştırma gibi birçok işlemi beraberinde getirir. Peki web uygulamaları, web tabanlı dosya işlemenin performans maliyetleri olmadan dosyaların avantajlarından nasıl yararlanabilir? Cevap, kaynak gizli dosya sistemidir.

Kullanıcı tarafından görülebilen kaynak gizli dosya sistemi ile kaynaktaki gizli dosya sistemi

İşletim sisteminin dosya gezginini kullanarak göz atılan, kullanıcı tarafından görülebilen dosya sisteminin aksine; okuyabileceğiniz, yazabileceğiniz, taşıyabileceğiniz ve yeniden adlandırabileceğiniz dosya ve klasörlere sahip olan kaynak gizli dosya sistemi kullanıcılar tarafından görülmez. Kaynak gizli dosya sistemindeki dosya ve klasörler, adından da anlaşılacağı üzere gizlidir ve daha somut bir şekilde, bir sitenin kaynağına özeldir. Geliştirici Araçları Konsolu'nda location.origin yazarak bir sayfanın kaynağını keşfedin. Örneğin, https://developer.chrome.com/articles/ sayfasının kaynağı https://developer.chrome.com şeklindedir (yani /articles bölümü, kaynağın parçası değildir). "Aynı site"yi anlama bölümünde ise kaynak teorisi hakkında daha fazla bilgi edinebilirsiniz. ve "same-origin" yer alır. Aynı kaynağı paylaşan tüm sayfalar, aynı kaynak gizli dosya sistemi verilerini görebilir. Bu nedenle https://developer.chrome.com/docs/extensions/mv3/getstarted/extensions-101/, önceki örnekle aynı ayrıntıları görebilir. Her kaynağın kendi bağımsız kaynak gizli dosya sistemi vardır. Yani, https://developer.chrome.com kaynağının gizli dosya sistemi, https://web.dev gibi kaynaklardan tamamen farklıdır. Windows'da, kullanıcının görebildiği dosya sisteminin kök dizini C:\\ şeklindedir. Kaynak gizli dosya sisteminin eşdeğeri, eşzamansız yöntemin çağrılmasıyla erişilen kaynak başına başlangıçta boş bir kök dizindir navigator.storage.getDirectory(). Kullanıcı tarafından görülebilen dosya sistemi ile kaynak gizli dosya sisteminin karşılaştırmasını görmek için aşağıdaki şemaya bakın. Şemada, kök dizinden ayrı olarak diğer her şeyin kavramsal olarak aynı olduğu gösterilmektedir. Buna ek olarak, verileriniz ve depolama ihtiyaçlarınız için gerektiğinde organize edilecek ve düzenlenecek bir dosya ve klasör hiyerarşisi vardır.

İki örnek dosya hiyerarşisine sahip, kullanıcı tarafından görülebilen dosya sisteminin ve kaynak gizli dosya sisteminin şeması. Kullanıcı tarafından görülebilen dosya sisteminin giriş noktası, sembolik bir sabit disktir. Kaynak gizli dosya sisteminin giriş noktası, &quot;navigator.storage.getDirectory&quot; yöntemini çağırmaktadır.

Kaynak gizli dosya sistemine ilişkin bilgiler

Tarayıcıdaki diğer depolama mekanizmalarında (örneğin, localStorage veya IndexedDB) olduğu gibi, kaynak gizli dosya sistemi de tarayıcı kota kısıtlamalarına tabidir. Kullanıcı tüm tarama verilerini veya tüm site verilerini temizlediğinde kaynak gizli dosya sistemi de silinir. navigator.storage.estimate() komutunu çağırın ve sonuçta ortaya çıkan yanıt nesnesinde, uygulamanızın halihazırda kullandığı depolama alanı miktarını görmek için usage girişine bakın. Bu giriş, fileSystem girişine özel olarak bakmak istediğiniz usageDetails nesnesindeki depolama mekanizmasına göre ayrılmıştır. Kaynak gizli dosya sistemi kullanıcı tarafından görülemediğinden izin istemi ve Güvenli Tarama kontrolü yoktur.

Kök dizine erişim elde etme

Kök dizine erişim elde etmek için aşağıdaki komutu çalıştırın. Sonuçta boş bir dizin tutma yeri (daha açık bir ifadeyle, bir FileSystemDirectoryHandle) elde edersiniz.

const opfsRoot = await navigator.storage.getDirectory();
// A FileSystemDirectoryHandle whose type is "directory"
// and whose name is "".
console.log(opfsRoot);

Ana iş parçacığı veya Web çalışanı

Kaynak gizli dosya sistemini kullanmanın iki yolu vardır: ana iş parçacığında veya Web çalışanı'nda. Web İşçileri ana iş parçacığını engelleyemez. Bu nedenle, bu bağlamda API'ler eşzamanlı olabilir. Bu, genellikle ana iş parçacığında izin verilmeyen bir kalıptır. Eşzamanlı API'ler vaatlerle uğraşmak zorunda kalmadıkları için daha hızlı olabilir ve dosya işlemleri genellikle WebAssembly'de derlenebilen C gibi dillerde eşzamanlıdır.

// This is synchronous C code.
FILE *f;
f = fopen("example.txt", "w+");
fputs("Some text\n", f);
fclose(f);

Mümkün olan en hızlı dosya işlemlerine ihtiyacınız varsa veya WebAssembly ile çalışıyorsanız Bir web çalışanında kaynak gizli dosya sistemini kullanma bölümüne geçin. Aksi takdirde okumaya devam edebilirsiniz.

Ana iş parçacığında kaynak gizli dosya sistemini kullanın

Yeni dosya ve klasörler oluşturma

Kök klasörü oluşturduktan sonra getFileHandle() ve getDirectoryHandle() yöntemlerini kullanarak dosya ve klasör oluşturun. {create: true} iletilirken mevcut değilse dosya veya klasör oluşturulur. Başlangıç noktası olarak yeni oluşturulan bir dizini kullanıp bu işlevleri çağırarak bir dosya hiyerarşisi oluşturun.

const fileHandle = await opfsRoot
    .getFileHandle('my first file', {create: true});
const directoryHandle = await opfsRoot
    .getDirectoryHandle('my first folder', {create: true});
const nestedFileHandle = await directoryHandle
    .getFileHandle('my first nested file', {create: true});
const nestedDirectoryHandle = await directoryHandle
    .getDirectoryHandle('my first nested folder', {create: true});

Önceki kod örneğinde oluşturulan dosya hiyerarşisi.

Mevcut dosyalara ve klasörlere erişme

Kullanıcının adını biliyorsanız getFileHandle() veya getDirectoryHandle() yöntemlerini çağırıp dosya ya da klasörün adını ileterek önceden oluşturulmuş dosya ve klasörlere erişin.

const existingFileHandle = await opfsRoot.getFileHandle('my first file');
const existingDirectoryHandle = await opfsRoot
    .getDirectoryHandle('my first folder');

Okuma için bir dosya tutma yeriyle ilişkilendirilmiş dosyayı alma

FileSystemFileHandle, dosya sistemindeki bir dosyayı temsil eder. İlişkili File yöntemini almak için getFile() yöntemini kullanın. File nesnesi belirli bir Blob türüdür ve Blob öğesinin yapabileceği herhangi bir bağlamda kullanılabilir. Özellikle, FileReader, URL.createObjectURL(), createImageBitmap() ve XMLHttpRequest.send() hem Blobs hem de Files kabul ediliyor. Yapacaksanız, FileSystemFileHandle "ücretsiz"den bir File alma Böylece verilere erişebilir ve kullanıcı tarafından görülebilen dosya sisteminin kullanımına sunabilirsiniz.

const file = await fileHandle.getFile();
console.log(await file.text());

Dosyaya akışla yazma

createWritable() komutunu çağırarak bir dosyaya veri akışı sağlayın. Bu durumda FileSystemWritableFileStream oluşturulur, ardından içerik write() oluşturulur. Sonunda akışı close() yapmanız gerekir.

const contents = 'Some text';
// Get a writable stream.
const writable = await fileHandle.createWritable();
// Write the contents of the file to the stream.
await writable.write(contents);
// Close the stream, which persists the contents.
await writable.close();

Dosya ve klasör silme

Dosya veya dizin tanıtıcılarına özel remove() yöntemini çağırarak dosyaları ve klasörleri silin. Bir klasörü tüm alt klasörleri içerecek şekilde silmek için {recursive: true} seçeneğini iletin.

await fileHandle.remove();
await directoryHandle.remove({recursive: true});
.

Alternatif olarak, bir dizindeki silinecek dosyanın veya klasörün adını biliyorsanız removeEntry() yöntemini kullanın.

directoryHandle.removeEntry('my first nested file');
.

Dosya ve klasörleri taşıma ve yeniden adlandırma

move() yöntemini kullanarak dosya ve klasörleri yeniden adlandırabilir ve taşıyabilirsiniz. Taşıma ve yeniden adlandırma işlemleri birlikte veya ayrı ayrı yapılabilir.

// Rename a file.
await fileHandle.move('my first renamed file');
// Move a file to another directory.
await fileHandle.move(nestedDirectoryHandle);
// Move a file to another directory and rename it.
await fileHandle
    .move(nestedDirectoryHandle, 'my first renamed and now nested file');
.

Dosya veya klasörün yolunu çözümleme

Belirli bir dosyanın veya klasörün referans dizinle ilişkili olarak nerede bulunduğunu öğrenmek için resolve() yöntemini kullanın ve bağımsız değişken olarak bir FileSystemHandle ileterek. Kaynak gizli dosya sistemindeki bir dosya veya klasörün tam yolunu almak için navigator.storage.getDirectory() aracılığıyla alınan referans dizin olarak kök dizini kullanın.

const relativePath = await opfsRoot.resolve(nestedDirectoryHandle);
// `relativePath` is `['my first folder', 'my first nested folder']`.

İki dosya veya klasör tutma yerinin aynı dosyaya veya klasöre işaret edip etmediğini kontrol edin

Bazen iki herkese açık kullanıcı adınız olur ve bunların aynı dosyayı mı yoksa klasörü mü gösterdiğini bilemezsiniz. Böyle bir durumun olup olmadığını kontrol etmek için isSameEntry() yöntemini kullanın.

fileHandle.isSameEntry(nestedFileHandle);
// Returns `false`.

Klasörün içeriğini listeleme

FileSystemDirectoryHandle, for await…of döngüsüyle yineleme yaptığınız bir eşzamansız yinelemedir. Eşzamansız iterasyon aracı olarak, ihtiyacınız olan bilgilere bağlı olarak seçebileceğiniz entries(), values() ve keys() yöntemlerini de destekler:

for await (let [name, handle] of directoryHandle) {}
for await (let [name, handle] of directoryHandle.entries()) {}
for await (let handle of directoryHandle.values()) {}
for await (let name of directoryHandle.keys()) {}

Bir klasörün ve tüm alt klasörlerin içeriğini yinelemeli olarak listeleme

Eşzamansız döngüler ve özyinelemeyle eşlenmiş işlevlerle çalışırken hata yapmak kolaydır. Aşağıdaki işlev, tüm dosyalar ve boyutları dahil olmak üzere bir klasörün içeriğini ve tüm alt klasörlerini listelemek için başlangıç noktası olarak kullanılabilir. Dosya boyutlarına ihtiyacınız yoksa, handle.getFile() vaadini değil, doğrudan handle öğesini aktararak directoryEntryPromises.push ifadesini kullanarak işlevi basitleştirebilirsiniz.

  const getDirectoryEntriesRecursive = async (
    directoryHandle,
    relativePath = '.',
  ) => {
    const fileHandles = [];
    const directoryHandles = [];
    const entries = {};
    // Get an iterator of the files and folders in the directory.
    const directoryIterator = directoryHandle.values();
    const directoryEntryPromises = [];
    for await (const handle of directoryIterator) {
      const nestedPath = `${relativePath}/${handle.name}`;
      if (handle.kind === 'file') {
        fileHandles.push({ handle, nestedPath });
        directoryEntryPromises.push(
          handle.getFile().then((file) => {
            return {
              name: handle.name,
              kind: handle.kind,
              size: file.size,
              type: file.type,
              lastModified: file.lastModified,
              relativePath: nestedPath,
              handle
            };
          }),
        );
      } else if (handle.kind === 'directory') {
        directoryHandles.push({ handle, nestedPath });
        directoryEntryPromises.push(
          (async () => {
            return {
              name: handle.name,
              kind: handle.kind,
              relativePath: nestedPath,
              entries:
                  await getDirectoryEntriesRecursive(handle, nestedPath),
              handle,
            };
          })(),
        );
      }
    }
    const directoryEntries = await Promise.all(directoryEntryPromises);
    directoryEntries.forEach((directoryEntry) => {
      entries[directoryEntry.name] = directoryEntry;
    });
    return entries;
  };

Bir web çalışanında kaynak gizli dosya sistemini kullan

Daha önce belirtildiği gibi, Web İşçileri ana iş parçacığını engelleyemez. Bu nedenle, bu bağlamda eşzamanlı yöntemlere izin verilir.

Eşzamanlı erişim herkese açık kullanıcı adı alma

Mümkün olan en hızlı dosya işlemlerine giriş noktası, createSyncAccessHandle() çağrısı yapılarak normal bir FileSystemFileHandle öğesinden alınan FileSystemSyncAccessHandle değeridir.

const fileHandle = await opfsRoot
    .getFileHandle('my highspeed file.txt', {create: true});
const syncAccessHandle = await fileHandle.createSyncAccessHandle();
.

Eşzamanlı yerinde dosya yöntemleri

Eşzamanlı erişim sahibi olduktan sonra, tümü eşzamanlı olan ve yerinde, hızlı dosya yöntemlerine erişim elde edersiniz.

  • getSize(): Dosyanın bayt cinsinden boyutunu döndürür.
  • write(): Arabelleğin içeriğini dosyaya isteğe bağlı olarak belirli bir uzaklıkta yazar ve yazılan bayt sayısını döndürür. Döndürülen yazılı bayt sayısını kontrol etmek, çağrıda bulunanların hataları ve kısmi yazma işlemlerini tespit edip işlemesine olanak tanır.
  • read(): Dosyanın içeriğini isteğe bağlı olarak belirli bir ofsette bir arabelleğe okur.
  • truncate(): Dosyayı belirtilen boyuta göre yeniden boyutlandırır.
  • flush(): Dosya içeriğinin write() üzerinden yapılan tüm değişiklikleri içermesini sağlar.
  • close(): Erişim herkese açık kullanıcı adını kapatır.

Yukarıda belirtilen tüm yöntemlerin kullanıldığı bir örneği aşağıda bulabilirsiniz.

const opfsRoot = await navigator.storage.getDirectory();
const fileHandle = await opfsRoot.getFileHandle('fast', {create: true});
const accessHandle = await fileHandle.createSyncAccessHandle();

const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();

// Initialize this variable for the size of the file.
let size;
// The current size of the file, initially `0`.
size = accessHandle.getSize();
// Encode content to write to the file.
const content = textEncoder.encode('Some text');
// Write the content at the beginning of the file.
accessHandle.write(content, {at: size});
// Flush the changes.
accessHandle.flush();
// The current size of the file, now `9` (the length of "Some text").
size = accessHandle.getSize();

// Encode more content to write to the file.
const moreContent = textEncoder.encode('More content');
// Write the content at the end of the file.
accessHandle.write(moreContent, {at: size});
// Flush the changes.
accessHandle.flush();
// The current size of the file, now `21` (the length of
// "Some textMore content").
size = accessHandle.getSize();

// Prepare a data view of the length of the file.
const dataView = new DataView(new ArrayBuffer(size));

// Read the entire file into the data view.
accessHandle.read(dataView);
// Logs `"Some textMore content"`.
console.log(textDecoder.decode(dataView));

// Read starting at offset 9 into the data view.
accessHandle.read(dataView, {at: 9});
// Logs `"More content"`.
console.log(textDecoder.decode(dataView));

// Truncate the file after 4 bytes.
accessHandle.truncate(4);
.

Kaynak gizli dosya sistemindeki bir dosyayı kullanıcıların görebildiği dosya sistemine kopyalama

Yukarıda belirtildiği gibi, kaynak gizli dosya sisteminden kullanıcıların görebildiği dosya sistemine dosya taşımak mümkün değildir ancak dosyaları kopyalayabilirsiniz. showSaveFilePicker() yalnızca ana iş parçacığında gösterildiği, Çalışan iş parçacığında gösterilmediği için kodu orada çalıştırdığınızdan emin olun.

// On the main thread, not in the Worker. This assumes
// `fileHandle` is the `FileSystemFileHandle` you obtained
// the `FileSystemSyncAccessHandle` from in the Worker
// thread. Be sure to close the file in the Worker thread first.
const fileHandle = await opfsRoot.getFileHandle('fast');
try {
  // Obtain a file handle to a new file in the user-visible file system
  // with the same name as the file in the origin private file system.
  const saveHandle = await showSaveFilePicker({
    suggestedName: fileHandle.name || ''
  });
  const writable = await saveHandle.createWritable();
  await writable.write(await fileHandle.getFile());
  await writable.close();
} catch (err) {
  console.error(err.name, err.message);
}

Kaynak gizli dosya sisteminde hata ayıklama

Yerleşik Geliştirici Araçları desteği eklenene kadar (bkz. crbug/1284595), kaynak özel dosya sisteminde hata ayıklamak için OPFS Explorer Chrome uzantısını kullanın. Yukarıdaki Yeni dosya ve klasör oluşturma bölümünde yer alan ekran görüntüsü doğrudan uzantıdan alınmıştır.

Chrome Web Mağazası&#39;ndaki OPFS Explorer Chrome DevTools uzantısı.

Uzantıyı yükledikten sonra Chrome Geliştirici Araçları'nı açın ve OPFS Gezgini sekmesini seçin. Artık dosya hiyerarşisini incelemeye hazır olursunuz. Kaynak gizli dosya sistemindeki dosyaları, dosya adını tıklayarak kullanıcının görebildiği dosya sistemine kaydedin ve çöp kutusu simgesini tıklayarak dosya ve klasörleri silin.

Demo

Kaynak özel dosya sistemini (OPFS Explorer uzantısını yüklüyorsanız) iş başında görün. Bu demoda, sistem WebAssembly'de derlenmiş SQLite veritabanı için arka uç olarak kullanılır. Glitch'teki kaynak kodu kontrol etmeyi unutmayın. Aşağıdaki yerleştirilmiş sürümün, kaynak gizli dosya sistemi arka ucunu kullanmadığını (iframe'in kaynaklar arası olması nedeniyle) ancak demoyu ayrı bir sekmede açtığınızda kullandığına dikkat edin.

Sonuçlar

WHEREWG tarafından belirtilen kaynak gizli dosya sistemi, web'deki dosyaları kullanma ve bunlarla etkileşim kurma şeklimizi şekillendirmiştir. Kullanıcıların görebildiği dosya sistemiyle ulaşılması mümkün olmayan yeni kullanım alanları ortaya çıkardı. Tüm büyük tarayıcı tedarikçileri (Apple, Mozilla ve Google) bu sürece dahildir ve ortak bir vizyonu paylaşır. Kaynak gizli dosya sisteminin geliştirilmesi büyük ölçüde ortak bir çalışmadır ve geliştiricilerden ve kullanıcılardan gelen geri bildirimler, sürecin ilerlemesi için büyük önem taşımaktadır. Standardı hassaslaştırıp iyileştirmeye devam ederken whatwg/fs deposu ile ilgili sorunlar veya çekme istekleri biçiminde geri bildirimlerinizi bekliyoruz.

Teşekkür

Bu makale Austin Sully, Etienne Noël ve Rachel Andrew tarafından incelenmiştir. Christina Rumpf'ın Unsplash'teki hero resim.