ब्राउज़र, फ़ाइलों और डायरेक्ट्री के साथ काम करते आ रहे हैं. 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 का इस्तेमाल करना बहुत आसान है. हालांकि, यह अभी तक ज़्यादातर डिवाइसों पर उपलब्ध नहीं है.
इसलिए, मुझे फ़ाइल सिस्टम ऐक्सेस एपीआई एक प्रगतिशील बेहतर बनाने की सुविधा के तौर पर दिखता है. इसलिए, जब ब्राउज़र पर यह सुविधा काम करती है, तब मैं इसका इस्तेमाल करना चाहता/चाहती हूं. अगर यह सुविधा काम नहीं करती है, तो मैं पुराने तरीके का इस्तेमाल करना चाहता/चाहती हूं. साथ ही, मैं उपयोगकर्ता को ऐसे 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 काम नहीं करता. इसके बाद, फ़ाइल को अपने फ़ोन से ट्रांसफ़र करके, अपने डेस्कटॉप पर खोला जा सकता है. इसके बाद, फ़ाइल में बदलाव किया जा सकता है और अपने बदलावों के साथ उसे ओवरराइट किया जा सकता है. इसके अलावा, उसे नई फ़ाइल के तौर पर भी सेव किया जा सकता है.
असल ज़िंदगी से जुड़ा कोड सैंपल
नीचे, 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 पर इस रूप में सेव करें बटन मौजूद नहीं है.
मीटिंग में सामने आए नतीजे
सिस्टम फ़ाइलों के साथ काम करना, तकनीकी तौर पर सभी आधुनिक ब्राउज़र पर काम करता है. File System Access API के साथ काम करने वाले ब्राउज़र पर, फ़ाइलों को डाउनलोड करने के साथ-साथ, उन्हें सेव करने और उनमें बदलाव करने की अनुमति देकर, उपयोगकर्ताओं को बेहतर अनुभव दिया जा सकता है. साथ ही, उन्हें अपनी पसंद के मुताबिक नई फ़ाइलें बनाने की अनुमति दी जा सकती है. यह सुविधा, File System Access API के साथ काम न करने वाले ब्राउज़र पर भी काम करती है. browser-fs-access, प्रोग्रेसिव बेहतर बनाने की बारीकियों को समझकर और आपके कोड को ज़्यादा से ज़्यादा आसान बनाकर, आपके काम को आसान बनाता है.
आभार
इस लेख की समीक्षा जो मेडली और केस बेस्केस ने की है. प्रोजेक्ट पर काम करने और मेरे पुल रिक्वेस्ट की समीक्षा करने के लिए, Excalidraw के योगदान देने वालों का धन्यवाद. Unsplash पर इल्या पावलोव की हीरो इमेज.