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