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

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

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

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

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

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; के तौर पर मार्क किया गया है.
File System Access API के लिए, ब्राउज़र के साथ काम करने की जानकारी देने वाली टेबल. (सोर्स)

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

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

फ़ाइल सिस्टम ऐक्सेस एपीआई में आने वाले समय में बदलाव हो सकता है. इसलिए, browser-fs-access API को इसके हिसाब से नहीं बनाया गया है. इसका मतलब है कि लाइब्रेरी, polyfill नहीं, बल्कि ponyfill है. अपने ऐप्लिकेशन को जितना हो सके उतना छोटा रखने के लिए, सिर्फ़ ज़रूरी फ़ंक्शन (स्टैटिक या डाइनैमिक तौर पर) इंपोर्ट किए जा सकते हैं. उपलब्ध तरीके, नाम के हिसाब से सही हैं: 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',
  });
})();

डेमो

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

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

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

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

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

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

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

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

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

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

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

आभार

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