ब्राउज़र-fs-access लाइब्रेरी की मदद से, फ़ाइलें और डायरेक्ट्री पढ़ना और लिखना

ब्राउज़र, फ़ाइलों और डायरेक्ट्री को लंबे समय से मैनेज कर रहे हैं. File API, वेब ऐप्लिकेशन में फ़ाइल ऑब्जेक्ट दिखाने की सुविधाएं उपलब्ध कराता है. साथ ही, प्रोग्राम के हिसाब से उन्हें चुनने और उनके डेटा को ऐक्सेस करने की सुविधाएं भी उपलब्ध कराता है. हालांकि, जब आप बारीकी से देखते हैं, तो आपको पता चलता है कि हर चमकती चीज़ सोना नहीं होती.

फ़ाइलों को मैनेज करने का पारंपरिक तरीका

फ़ाइलें खोलना

डेवलपर के तौर पर, <input type="file"> एलिमेंट की मदद से फ़ाइलें खोली और पढ़ी जा सकती हैं. फ़ाइल खोलने का सबसे आसान तरीका, नीचे दिए गए कोड सैंपल की तरह दिखता है. input ऑब्जेक्ट से आपको FileList मिलता है. यहां दिए गए उदाहरण में, इसमें सिर्फ़ एक File शामिल है. File, एक खास तरह का Blob होता है. इसका इस्तेमाल किसी भी ऐसे कॉन्टेक्स्ट में किया जा सकता है जहां 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();
  });
};

डायरेक्ट्री खोलना

फ़ोल्डर (या डायरेक्ट्री) खोलने के लिए, <input webkitdirectory> एट्रिब्यूट सेट किया जा सकता है. इसके अलावा, बाकी सभी सुविधाएं ऊपर बताई गई जानकारी के मुताबिक काम करती हैं. वेंडर के प्रीफ़िक्स वाले नाम के बावजूद, webkitdirectory का इस्तेमाल सिर्फ़ Chromium और WebKit ब्राउज़र में ही नहीं किया जा सकता. इसका इस्तेमाल EdgeHTML पर आधारित पुराने Edge के साथ-साथ Firefox में भी किया जा सकता है.

फ़ाइलें सेव करना (बल्कि: डाउनलोड करना)

फ़ाइल सेव करने के लिए, आम तौर पर फ़ाइल को डाउनलोड किया जाता है. यह <a download> एट्रिब्यूट की वजह से काम करता है. किसी BLOB के लिए, ऐंकर के href एट्रिब्यूट को ऐसे blob: यूआरएल पर सेट किया जा सकता है जिसे 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();
};

समस्या

डाउनलोड करें तरीके का एक बड़ा नुकसान यह है कि इसमें क्लासिक ओपन→बदलाव करें→सेव करें फ़्लो को लागू नहीं किया जा सकता. इसका मतलब है कि ओरिजनल फ़ाइल को ओवरराइट नहीं किया जा सकता. इसके बजाय, जब भी "सेव करें" विकल्प चुना जाता है, तो आपको ऑपरेटिंग सिस्टम के डिफ़ॉल्ट डाउनलोड फ़ोल्डर में, ओरिजनल फ़ाइल की नई कॉपी मिलती है.

File System Access API

File System Access API की मदद से, फ़ाइल खोलने और सेव करने, दोनों कार्रवाइयों को बहुत आसान बनाया जा सकता है. इससे सही तरीके से सेव करने की सुविधा भी मिलती है. इसका मतलब है कि किसी फ़ाइल को सेव करने के लिए, न सिर्फ़ जगह चुनी जा सकती है, बल्कि किसी मौजूदा फ़ाइल को बदला भी जा सकता है.

फ़ाइलें खोलना

File System Access API की मदद से, किसी फ़ाइल को खोलने के लिए window.showOpenFilePicker() तरीके को सिर्फ़ एक बार कॉल करना होता है. इस कॉल से एक फ़ाइल हैंडल मिलता है. इससे File तरीके का इस्तेमाल करके, असली File पाया जा सकता है.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);
  }
};

डायरेक्ट्री खोलना

window.showDirectoryPicker() को कॉल करके कोई डायरेक्ट्री खोलें. इससे फ़ाइल डायलॉग बॉक्स में डायरेक्ट्री चुनी जा सकती हैं.

फ़ाइलें सेव की जा रही हैं

फ़ाइलें सेव करना भी उतना ही आसान है. फ़ाइल हैंडल से, createWritable() की मदद से लिखने लायक स्ट्रीम बनाई जाती है. इसके बाद, स्ट्रीम के write() तरीके को कॉल करके, Blob डेटा लिखा जाता है. आखिर में, स्ट्रीम के 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);
  }
};

पेश है browser-fs-access

File System Access API बहुत अच्छा है, लेकिन यह अब तक ज़्यादातर लोगों के लिए उपलब्ध नहीं है.

File System Access API के लिए, ब्राउज़र के साथ काम करने की सुविधा के बारे में जानकारी देने वाली टेबल. सभी ब्राउज़र को &#39;काम नहीं करता&#39; या &#39;फ़्लैग के पीछे&#39; के तौर पर मार्क किया गया है.
फ़ाइल सिस्टम को ऐक्सेस करने के एपीआई के लिए, ब्राउज़र के साथ काम करने की सुविधा की जानकारी देने वाली टेबल. (सोर्स)

इसलिए, मुझे फ़ाइल सिस्टम ऐक्सेस एपीआई, प्रोग्रेसिव एन्हांसमेंट के तौर पर दिखता है. इसलिए, मैं इसका इस्तेमाल तब करना चाहता हूं, जब ब्राउज़र इसे सपोर्ट करता हो. साथ ही, अगर ऐसा नहीं होता है, तो मैं पारंपरिक तरीके का इस्तेमाल करना चाहता हूं. हालांकि, मैं यह भी चाहता हूं कि उपयोगकर्ता को ऐसे JavaScript कोड को डाउनलोड करने के लिए मजबूर न किया जाए जो काम नहीं करता. browser-fs-access लाइब्रेरी, इस चुनौती का जवाब है.

डिज़ाइन फ़िलॉसफ़ी

File System Access API में आने वाले समय में बदलाव हो सकते हैं. इसलिए, browser-fs-access API को इसके हिसाब से नहीं बनाया गया है. इसका मतलब है कि लाइब्रेरी पॉलीफ़िल नहीं है, बल्कि पोनीफ़िल है. अपने ऐप्लिकेशन को कम से कम साइज़ का रखने के लिए, आपको जिस सुविधा की ज़रूरत है उसे सिर्फ़ इंपोर्ट किया जा सकता है. ऐसा स्टैटिक या डाइनैमिक तरीके से किया जा सकता है. इसके लिए, fileOpen(), directoryOpen(), और fileSave() जैसे तरीके उपलब्ध हैं. लाइब्रेरी, अंदरूनी तौर पर यह पता लगाती है कि File System Access API काम करता है या नहीं. इसके बाद, वह कोड के पाथ को इंपोर्ट करती है.

browser-fs-access लाइब्रेरी का इस्तेमाल करना

इन तीनों तरीकों को इस्तेमाल करना आसान है. अपने ऐप्लिकेशन के लिए, स्वीकार किए गए mimeTypes या फ़ाइल extensions के बारे में बताया जा सकता है. साथ ही, एक से ज़्यादा फ़ाइलों या डायरेक्ट्री को चुनने की अनुमति देने या न देने के लिए, multiple फ़्लैग सेट किया जा सकता है. पूरी जानकारी के लिए, browser-fs-access API से जुड़ा दस्तावेज़ देखें. नीचे दिए गए कोड के उदाहरण में, इमेज फ़ाइलों को खोलने और सेव करने का तरीका बताया गया है.

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

डेमो

ऊपर दिए गए कोड को GitHub पर डेमो में इस्तेमाल करके देखा जा सकता है. इसका सोर्स कोड भी वहां उपलब्ध है. सुरक्षा कारणों से, क्रॉस ऑरिजिन सब फ़्रेम को फ़ाइल पिकर दिखाने की अनुमति नहीं है. इसलिए, इस लेख में डेमो को एम्बेड नहीं किया जा सकता.

browser-fs-access लाइब्रेरी का इस्तेमाल

मुझे जब भी समय मिलता है, तब मैं Excalidraw नाम के इंस्टॉल किए जा सकने वाले PWA में थोड़ा-बहुत योगदान देता हूं. यह एक व्हाइटबोर्ड टूल है. इसकी मदद से, हाथ से बनाए गए डायग्राम आसानी से बनाए जा सकते हैं. यह पूरी तरह से रिस्पॉन्सिव है और छोटे मोबाइल फ़ोन से लेकर बड़ी स्क्रीन वाले कंप्यूटर तक, कई तरह के डिवाइसों पर अच्छी तरह से काम करता है. इसका मतलब है कि इसे अलग-अलग प्लैटफ़ॉर्म पर मौजूद फ़ाइलों को मैनेज करना होगा. भले ही, वे File System Access API के साथ काम करती हों या नहीं. इसलिए, यह ब्राउज़र-fs-ऐक्सेस लाइब्रेरी के लिए एक बेहतरीन विकल्प है.

उदाहरण के लिए, मैं अपने iPhone पर कोई ड्राइंग शुरू कर सकता/सकती हूं. इसके बाद, उसे सेव कर सकता/सकती हूं. तकनीकी तौर पर, इसे डाउनलोड करना कहा जाता है, क्योंकि Safari, File System Access API के साथ काम नहीं करता. इसके बाद, मैं उसे अपने iPhone के डाउनलोड फ़ोल्डर में सेव कर सकता/सकती हूं. इसके बाद, मैं उस फ़ाइल को अपने डेस्कटॉप पर खोल सकता/सकती हूं. इसके लिए, मुझे उसे अपने फ़ोन से डेस्कटॉप पर ट्रांसफ़र करना होगा. इसके बाद, मैं उस फ़ाइल में बदलाव कर सकता/सकती हूं और उसे अपने बदलावों के साथ ओवरराइट कर सकता/सकती हूं. इसके अलावा, मैं उसे नई फ़ाइल के तौर पर भी सेव कर सकता/सकती हूं.

iPhone पर Excalidraw की ड्रॉइंग.
iPhone पर Excalidraw ड्राइंग शुरू करना. इस पर File System Access API काम नहीं करता है. हालांकि, फ़ाइल को डाउनलोड फ़ोल्डर में सेव (डाउनलोड) किया जा सकता है.
डेस्कटॉप पर Chrome पर बदली गई Excalidraw ड्राइंग.
डेस्कटॉप पर Excalidraw ड्राइंग खोलना और उसमें बदलाव करना. इस डेस्कटॉप पर, फ़ाइल सिस्टम को ऐक्सेस करने का एपीआई काम करता है. इसलिए, एपीआई के ज़रिए फ़ाइल को ऐक्सेस किया जा सकता है.
बदलावों के साथ ओरिजनल फ़ाइल को ओवरराइट करना.
ओरिजनल Excalidraw ड्राइंग फ़ाइल में किए गए बदलावों को ओरिजनल फ़ाइल में सेव करना. ब्राउज़र एक डायलॉग दिखाता है, जिसमें मुझसे पूछा जाता है कि क्या यह ठीक है.
बदलावों को नई Excalidraw ड्रॉइंग फ़ाइल में सेव किया जा रहा है.
बदलावों को नई Excalidraw फ़ाइल में सेव किया जा रहा है. ओरिजनल फ़ाइल में कोई बदलाव नहीं होता.

असल ज़िंदगी का कोड सैंपल

नीचे, browser-fs-access का एक उदाहरण दिया गया है. इसमें दिखाया गया है कि इसका इस्तेमाल Excalidraw में कैसे किया जाता है. यह उद्धरण, /src/data/json.ts से लिया गया है. खास तौर पर, यह देखना ज़रूरी है कि saveAsJSON() फ़ंक्शन, फ़ाइल हैंडल या null को browser-fs-access' fileSave() फ़ंक्शन को कैसे पास करता है. इससे, हैंडल दिए जाने पर फ़ाइल ओवरराइट हो जाती है या हैंडल नहीं दिए जाने पर नई फ़ाइल में सेव हो जाती है.

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

यूज़र इंटरफ़ेस (यूआई) से जुड़ी ज़रूरी बातें

Excalidraw या आपके ऐप्लिकेशन में, यूज़र इंटरफ़ेस (यूआई) को ब्राउज़र के साथ काम करने की स्थिति के हिसाब से अडजस्ट होना चाहिए. अगर File System Access API काम करता है (if ('showOpenFilePicker' in window) {}), तो सेव करें बटन के साथ-साथ इस रूप में सेव करें बटन भी दिखाया जा सकता है. नीचे दिए गए स्क्रीनशॉट में, iPhone और Chrome डेस्कटॉप पर Excalidraw के रिस्पॉन्सिव मुख्य ऐप्लिकेशन टूलबार के बीच का अंतर दिखाया गया है. ध्यान दें कि iPhone पर Save As बटन मौजूद नहीं है.

iPhone पर Excalidraw ऐप्लिकेशन का टूलबार. इसमें सिर्फ़ &#39;सेव करें&#39; बटन है.
iPhone पर Excalidraw ऐप्लिकेशन का टूलबार. इसमें सिर्फ़ Save बटन है.
Chrome डेस्कटॉप पर Excalidraw ऐप्लिकेशन का टूलबार. इसमें &#39;सेव करें&#39; और &#39;इस नाम से सेव करें&#39; बटन दिख रहा है.
Chrome पर Excalidraw ऐप्लिकेशन का टूलबार. इसमें सेव करें और इस नाम से सेव करें बटन को हाइलाइट किया गया है.

मीटिंग में सामने आए नतीजे

सिस्टम फ़ाइलों के साथ काम करने की सुविधा, तकनीकी तौर पर सभी नए ब्राउज़र पर काम करती है. File System Access API के साथ काम करने वाले ब्राउज़र पर, फ़ाइलों को सही तरीके से सेव करने और बदलने (सिर्फ़ डाउनलोड करने के बजाय) की अनुमति देकर, उपयोगकर्ता अनुभव को बेहतर बनाया जा सकता है. साथ ही, उपयोगकर्ताओं को अपनी पसंद के मुताबिक नई फ़ाइलें बनाने की अनुमति दी जा सकती है. यह सब, File System Access API के साथ काम न करने वाले ब्राउज़र पर भी किया जा सकता है. browser-fs-access, प्रोग्रेसिव एन्हांसमेंट की बारीकियों को मैनेज करके और आपके कोड को जितना हो सके उतना आसान बनाकर, आपके काम को आसान बनाता है.

Acknowledgements

इस लेख की समीक्षा जो मेडली और केसी बास्क ने की है. Excalidraw में योगदान देने वाले लोगों को धन्यवाद. उन्होंने इस प्रोजेक्ट पर काम किया और मेरे पुल अनुरोधों की समीक्षा की. Unsplash पर Ilya Pavlov की हीरो इमेज.