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

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

नीचे, browser-fs-access का एक असल उदाहरण दिया गया है, जिसका इस्तेमाल Excalidraw में किया जाता है. यह जानकारी /src/data/json.ts से ली गई है. खास बात यह है कि saveAsJSON() तरीका, फ़ाइल हैंडल या ब्राउज़र-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 ऐप्लिकेशन टूलबार.

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

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

धन्यवाद

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