अपने प्रोग्रेसिव वेब ऐप्लिकेशन को धीरे-धीरे और बेहतर बनाएं

आधुनिक ब्राउज़र के लिए निर्माण और 2003 की तरह धीरे-धीरे बेहतर बनाना

मार्च 2003 में, निक फ़िंक और स्टीव चैंपियन ने वेब डिज़ाइन की दुनिया को चौंका दिया इस्तेमाल किया जा सकता है. प्रोग्रेसिव एन्हैंसमेंट, वेब डिज़ाइन के लिए ऐसी रणनीति जो पहले मुख्य वेब पेज के कॉन्टेंट को लोड करने पर ज़ोर देती हो, और फिर, यह धीरे-धीरे और भी बारीकियां जोड़ता है और तकनीकी तौर पर सख्ती से पूरी तरह तैयार हैं. साथ ही, कॉन्टेंट में बेहतरीन सुविधाएं शामिल की गई हैं. साल 2003 में, प्रोग्रेसिव एन्हैंसमेंट का इस्तेमाल, उस समय के आधुनिक टूल के इस्तेमाल के बारे में किया गया था सीएसएस सुविधाएं, बिना रुकावट वाला JavaScript, और यहां तक कि सिर्फ़ स्केलेबल वेक्टर ग्राफ़िक. साल 2020 और उसके बाद, प्रोग्रेसिव एन्हैंसमेंट की सुविधा का इस्तेमाल आधुनिक ब्राउज़र सुविधाओं का उपयोग भी कर सकते हैं.

प्रोग्रेसिव एन्हैंसमेंट के साथ आने वाले समय के लिए, इनक्लूसिव वेब डिज़ाइन की सुविधा. फ़िंक और चैंपियन के ओरिजनल प्रज़ेंटेशन की टाइटल स्लाइड.
स्लाइड: प्रोग्रेसिव एन्हैंसमेंट के साथ आने वाले समय के लिए इन्क्लूसिव वेब डिज़ाइन. (सोर्स)

मॉडर्न JavaScript

JavaScript की बात करें, तो यह नए कोर ES 2015 JavaScript के लिए ब्राउज़र से जुड़ी सहायता देने वाली स्थिति है सुविधाएं बहुत अच्छी हैं. नए स्टैंडर्ड में प्रॉमिस, मॉड्यूल, क्लास, टेंप्लेट की लिटरल वैल्यू, ऐरो फ़ंक्शन, let, और const शामिल हैं, डिफ़ॉल्ट पैरामीटर, जनरेटर, डिस्ट्रक्चरिंग असाइनमेंट, रेस्ट और स्प्रेड, Map/Set, WeakMap/WeakSet और बहुत कुछ. सभी सुविधाएं काम करती हैं.

ES6 की सुविधाओं के लिए CanIUse सहायता टेबल, सभी मुख्य ब्राउज़र पर काम करती है.
ECMAScript 2015 (ES6) ब्राउज़र के लिए सहायता टेबल. (सोर्स)

एक साथ काम नहीं करने वाली सुविधा, ES 2017 की सुविधा, और मेरी पसंदीदा सुविधाओं में से एक, इस्तेमाल किया जा सकता है सभी प्रमुख ब्राउज़र पर उपलब्ध हैं. async और await कीवर्ड, एसिंक्रोनस और प्रॉमिस-आधारित व्यवहार को चालू करते हैं ताकि प्रॉमिस चेन को साफ़ तौर पर कॉन्फ़िगर करने की ज़रूरत न पड़े.

एक साथ काम नहीं करने वाले फ़ंक्शन के लिए CanIUse सहायता टेबल, जो सभी मुख्य ब्राउज़र पर काम करती है.
एसिंक्रोनस फ़ंक्शन ब्राउज़र की सहायता टेबल. (सोर्स)

यहां तक कि हाल ही में ES 2020 में जोड़ी गई नई भाषाएं, ज़रूरी नहीं चेन और नोलीश कोलेसिंग हम बहुत जल्द सहायता टीम से बात कर लेते हैं. यहां एक कोड सैंपल दिया गया है. JavaScript की मुख्य सुविधाओं की बात करें, तो इसका मतलब है कि यह हरी-भरी नहीं है आज है.

const adventurer = {
  name: 'Alice',
  cat: {
    name: 'Dinah',
  },
};
console.log(adventurer.dog?.name);
// Expected output: undefined
console.log(0 ?? 42);
// Expected output: 0
Windows XP में, हरी घास वाली मशहूर बैकग्राउंड इमेज.
JavaScript की मुख्य सुविधाओं की बात करें, तो हरी घास भी दिखती है. (Microsoft प्रॉडक्ट का स्क्रीनशॉट, जिसका इस्तेमाल इनके साथ किया जाता है अनुमति है.)

ऐप्लिकेशन का नमूना: Fugu Greetings

इस लेख के लिए, मैं एक आसान PWA के साथ काम करता/करती हूं, जिसे फ़ूगू की शुभकामनाएं (GitHub). इस ऐप्लिकेशन का नाम, Project Fugu 🐡 के लिए एक खास सुझाव है. यह वेब को सभी Android/iOS/डेस्कटॉप ऐप्लिकेशन का एक बेहतरीन इस्तेमाल भी किया जा सकता है. यहां पर प्रोजेक्ट के बारे में ज़्यादा जानकारी देखी जा सकती है लैंडिंग पेज पर आता है.

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

फ़्यूगू का ग्रीटिंग पीडब्ल्यूए में बनाई गई ड्रॉइंग, जो पीडब्ल्यूए समुदाय के लोगो से मिलती-जुलती है.
Fugu Greetings सैंपल ऐप्लिकेशन.

प्रोग्रेसिव एन्हैंसमेंट

अब समय आ गया है कि आप प्रोग्रेसिव एन्हैंसमेंट के बारे में बात करें. MDN Web Docs ग्लॉसरी तय करती है इस तरह दिखाई गई है:

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

सुविधा का पता लगाना का इस्तेमाल आम तौर पर यह तय करने के लिए किया जाता है कि ब्राउज़र आधुनिक तरीके से काम कर सकते हैं या नहीं. पॉलीफ़िल होने पर का इस्तेमाल अक्सर JavaScript से छूटी हुई सुविधाओं को जोड़ने के लिए किया जाता है.

[…]

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

एमडीएन में योगदान देने वाले लोग

हर ग्रीटिंग कार्ड को शुरू से शुरू करना वाकई मुश्किल हो सकता है. तो क्यों न एक ऐसी सुविधा उपलब्ध कराई गई है जो उपयोगकर्ताओं को एक इमेज इंपोर्ट करने और वहां से शुरू करने की सुविधा देती हो? पारंपरिक दृष्टिकोण के साथ, आपने <input type=file> करने के लिए डिज़ाइन किया गया है. सबसे पहले, आपको एलिमेंट बनाना होगा, उसके type को 'file' पर सेट करना होगा, और accept प्रॉपर्टी में MIME टाइप जोड़ना होगा, और फिर प्रोग्राम के हिसाब से "क्लिक" करें इसे सुनना और बदलावों को सुनना. किसी इमेज को चुनने पर, वह सीधे कैनवस पर इंपोर्ट हो जाती है.

const importImage = async () => {
  return new Promise((resolve) => {
    const input = document.createElement('input');
    input.type = 'file';
    input.accept = 'image/*';
    input.addEventListener('change', () => {
      resolve(input.files[0]);
    });
    input.click();
  });
};

इंपोर्ट की सुविधा उपलब्ध होने पर, हो सकता है कि एक्सपोर्ट सुविधा मौजूद हो ताकि उपयोगकर्ता अपने ग्रीटिंग कार्ड को डिवाइस पर सेव कर सकें. फ़ाइलों को सेव करने का पारंपरिक तरीका, ऐंकर लिंक बनाना है download के साथ एट्रिब्यूट और इसके href के तौर पर BLOB का यूआरएल डालें. प्रोग्राम के हिसाब से, "क्लिक" को प्रोग्राम के हिसाब से क्रम में लगाने के लिए, डाउनलोड को ट्रिगर करना चाहिए, साथ ही, मेमोरी लीक होने से रोकने के लिए, BLOB ऑब्जेक्ट यूआरएल को रद्द करना न भूलें.

const exportImage = async (blob) => {
  const a = document.createElement('a');
  a.download = 'fugu-greeting.png';
  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.chooseFileSystemEntries() दिखाया है. इस वजह से, मुझे अलग-अलग इंपोर्ट और एक्सपोर्ट मॉड्यूल को शर्तों के साथ लोड करना होगा. हालांकि, यह इस बात पर निर्भर करता है कि यह तरीका उपलब्ध है या नहीं. इसे करने का तरीका नीचे बताया गया है.

const loadImportAndExport = () => {
  if ('chooseFileSystemEntries' in window) {
    Promise.all([
      import('./import_image.mjs'),
      import('./export_image.mjs'),
    ]);
  } else {
    Promise.all([
      import('./import_image_legacy.mjs'),
      import('./export_image_legacy.mjs'),
    ]);
  }
};

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

Safari वेब इंस्पेक्टर, लोड हो रही लेगसी फ़ाइलों को दिखा रहा है.
Safari Web Inspector नेटवर्क टैब.
Firefox डेवलपर टूल, लोड हो रही लेगसी फ़ाइलों को दिखा रहे हैं.
Firefox डेवलपर टूल नेटवर्क टैब.

हालांकि, Chrome पर सिर्फ़ नई स्क्रिप्ट लोड होती हैं. यह ऐसा ब्राउज़र होता है जिस पर एपीआई की सुविधा काम करती है. यह बहुत बढ़िया तरीके से मुमकिन हो पाया है, क्योंकि डाइनैमिक import(), जिसमें सभी मॉडर्न ब्राउज़र सहायता. जैसा कि मैंने पहले बताया था, इन दिनों घास काफ़ी हरी-भरी है.

Chrome DevTools, लोड हो रही मॉडर्न फ़ाइलें दिखा रहा है.
Chrome DevTools नेटवर्क टैब.

फ़ाइल सिस्टम ऐक्सेस एपीआई

इसलिए अब जब मैंने इसका समाधान कर लिया है, तो फ़ाइल सिस्टम ऐक्सेस API (एपीआई) के आधार पर लागू किए जाने वाले असल काम पर नज़र डालने का समय आ गया है. इमेज इंपोर्ट करने के लिए, मैं window.chooseFileSystemEntries() को कॉल करता/करती हूं और उसे एक accepts प्रॉपर्टी पास करें जहां मैं कहूं कि मुझे इमेज फ़ाइलें चाहिए. फ़ाइल एक्सटेंशन और MIME टाइप, दोनों का इस्तेमाल किया जा सकता है. इससे एक फ़ाइल हैंडल बन जाएगा, जिससे मैं getFile() को कॉल करके असली फ़ाइल ले सकता हूं.

const importImage = async () => {
  try {
    const handle = await window.chooseFileSystemEntries({
      accepts: [
        {
          description: 'Image files',
          mimeTypes: ['image/*'],
          extensions: ['jpg', 'jpeg', 'png', 'webp', 'svg'],
        },
      ],
    });
    return handle.getFile();
  } catch (err) {
    console.error(err.name, err.message);
  }
};

इमेज एक्सपोर्ट करने की प्रोसेस करीब-करीब वैसी ही है, लेकिन इस बार मुझे chooseFileSystemEntries() तरीके में, 'save-file' का टाइप पैरामीटर पास करना होगा. इससे मुझे फ़ाइल सेव करने का डायलॉग बॉक्स मिलता है. फ़ाइल खुली होने पर, यह ज़रूरी नहीं है, क्योंकि 'open-file' डिफ़ॉल्ट है. मैंने accepts पैरामीटर को पहले की तरह ही सेट कर दिया है, लेकिन इस समय में सिर्फ़ PNG इमेज तक ही सीमित है. फिर से मुझे एक फ़ाइल हैंडल मिलता है, लेकिन फ़ाइल पाने के बजाय, इस बार मैं createWritable() को कॉल करके लिखने योग्य स्ट्रीम बनाऊँगा. इसके बाद, मैंने फ़ाइल पर एक ब्लॉब लिखा, जो मेरे ग्रीटिंग कार्ड की इमेज है. आख़िर में, लिखने लायक स्ट्रीम को बंद कर दिया.

हर बार कुछ नया नहीं हो पाता है: हो सकता है कि डिस्क में स्टोरेज खत्म हो, लिखने या पढ़ने में कोई गड़बड़ी हो सकती है या हो सकता है कि उपयोगकर्ता फ़ाइल के डायलॉग को रद्द कर दे. इसलिए, मैं हमेशा कॉल को try...catch स्टेटमेंट में रैप करता/करती हूं.

const exportImage = async (blob) => {
  try {
    const handle = await window.chooseFileSystemEntries({
      type: 'save-file',
      accepts: [
        {
          description: 'Image file',
          extensions: ['png'],
          mimeTypes: ['image/png'],
        },
      ],
    });
    const writable = await handle.createWritable();
    await writable.write(blob);
    await writable.close();
  } catch (err) {
    console.error(err.name, err.message);
  }
};

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

Fugu Greetings ऐप्लिकेशन, जिसमें एक डायलॉग बॉक्स खुला है.
फ़ाइल खुलने का डायलॉग.
अब इंपोर्ट की गई इमेज के साथ Fugu Greetings ऐप्लिकेशन.
इंपोर्ट की गई इमेज.
बदली गई इमेज के साथ Fugu Greetings ऐप्लिकेशन.
बदली गई इमेज को नई फ़ाइल में सेव किया जा रहा है.

वेब शेयर और वेब शेयर टारगेट एपीआई

हमेशा के लिए सेव करने के अलावा, शायद मुझे अपना ग्रीटिंग कार्ड शेयर करना हो. यह वेब शेयर एपीआई और वेब शेयर टारगेट एपीआई की मदद से, मुझे यह काम करने की अनुमति मिली है. मोबाइल और हाल ही में डेस्कटॉप ऑपरेटिंग सिस्टम ने पहले से ही फ़ाइलें शेयर करने की सुविधा जोड़ी है मैकेनिज़्म. उदाहरण के लिए, नीचे macOS पर डेस्कटॉप Safari की शेयर शीट दी गई है, जो मेरा ब्लॉग. लेख शेयर करें बटन पर क्लिक करके, लेख का लिंक अपने दोस्त के साथ शेयर किया जा सकता है. जैसे, macOS Messages ऐप्लिकेशन से.

macOS पर डेस्कटॉप Safari की शेयर शीट, लेख के &#39;शेयर करें&#39; बटन से ट्रिगर हुई
macOS पर डेस्कटॉप Safari पर Web Share API.

इसे करने के लिए दिया गया कोड काफ़ी आसान है. मैंने navigator.share() को कॉल किया है और इसे किसी ऑब्जेक्ट में वैकल्पिक title, text, और url पास करें. अगर मुझे कोई इमेज अटैच करनी हो, तो क्या होगा? फ़िलहाल, Web Share API का लेवल पहला लेवल पर यह सुविधा नहीं देता है. अच्छी खबर यह है कि वेब शेयर के लेवल 2 में फ़ाइल शेयर करने की सुविधाएं जोड़ दी गई हैं.

try {
  await navigator.share({
    title: 'Check out this article:',
    text: `"${document.title}" by @tomayac:`,
    url: document.querySelector('link[rel=canonical]').href,
  });
} catch (err) {
  console.warn(err.name, err.message);
}

हम आपको बताते हैं कि Fugu Greeting कार्ड ऐप्लिकेशन की मदद से, यह काम कैसे करना है. सबसे पहले, मुझे एक BLOB वाले files कलेक्शन के साथ data ऑब्जेक्ट तैयार करना होगा. इसके बाद a title और text. इसके बाद, सबसे सही तरीका यह है कि मैं नए navigator.canShare() तरीके का इस्तेमाल करूं, जो इसका नाम क्या है: इससे मुझे पता चलता है कि जिस data ऑब्जेक्ट को शेयर करने की कोशिश की जा रही है वह ब्राउज़र तकनीकी रूप से शेयर कर सकता है या नहीं. अगर navigator.canShare() मुझे बताता है कि डेटा शेयर किया जा सकता है, तो मैं इसके लिए तैयार हूं navigator.share() को पहले की तरह कॉल करें. किसी भी गड़बड़ी की वजह से, मैं फिर से try...catch ब्लॉक इस्तेमाल कर रही हूं.

const share = async (title, text, blob) => {
  const data = {
    files: [
      new File([blob], 'fugu-greeting.png', {
        type: blob.type,
      }),
    ],
    title: title,
    text: text,
  };
  try {
    if (!(navigator.canShare(data))) {
      throw new Error("Can't share data.", data);
    }
    await navigator.share(data);
  } catch (err) {
    console.error(err.name, err.message);
  }
};

पहले की तरह ही, मैं प्रोग्रेसिव एन्हैंसमेंट का इस्तेमाल करता हूं. अगर navigator ऑब्जेक्ट पर 'share' और 'canShare', दोनों मौजूद हैं, तो सिर्फ़ तब ही मैं आगे बढ़ती हूं और डाइनैमिक import() के ज़रिए share.mjs लोड करें. मोबाइल Safari जैसे ब्राउज़र पर, जो केवल दो शर्तों में से एक को पूरा करते हैं, मैं लोड नहीं करता सुविधाएं उपलब्ध कराई हैं.

const loadShare = () => {
  if ('share' in navigator && 'canShare' in navigator) {
    import('./share.mjs');
  }
};

Fugu Greetings में, अगर मैं Android पर Chrome जैसे काम करने वाले ब्राउज़र पर शेयर करें बटन को टैप करूँ, पहले से मौजूद शेयर शीट खुलती है. उदाहरण के लिए, मैं Gmail चुन सकता/सकती हूं और ईमेल कंपोज़र विजेट चित्र अटैच किया गया.

ओएस लेवल की शेयर शीट, जिसमें इमेज शेयर करने के लिए अलग-अलग ऐप्लिकेशन दिखाए गए हैं.
फ़ाइल शेयर करने के लिए ऐप्लिकेशन चुना जा रहा है.
Gmail का ईमेल लिखने वाला विजेट, अटैच की गई इमेज के साथ.
फ़ाइल, Gmail के कंपोज़र में किसी नए ईमेल के साथ अटैच हो जाती है.

संपर्क पिकर एपीआई

अब मुझे संपर्कों के बारे में बात करनी है, मतलब डिवाइस की अड्रेस बुक या संपर्क मैनेजर ऐप्लिकेशन में जाकर. जब आप कोई ग्रीटिंग कार्ड लिखते हैं, तो हो सकता है कि इसे सही तरीके से लिखना हमेशा आसान न हो किसी दूसरे व्यक्ति का नाम. उदाहरण के लिए, मेरे एक दोस्त सर्गेई हैं जो अपने नाम को सिरिलिक अक्षरों में लिखना पसंद करते हैं. मैं जो जर्मन QWERTZ कीबोर्ड का उपयोग कर रहे हैं और उन्हें अपना नाम लिखने का तरीका नहीं पता है. इस समस्या को Contact Picker API की मदद से हल किया जा सकता है. मेरे दोस्त ने मेरे फ़ोन के संपर्क ऐप्लिकेशन में सेव किया है, इसलिए संपर्क पिकर API के ज़रिए, मैं वेब से अपने संपर्क टैप कर सकता/सकती हूं.

सबसे पहले, मुझे उन प्रॉपर्टी की सूची बनानी होगी जिन्हें मुझे ऐक्सेस करना है. इस मामले में, मुझे सिर्फ़ नाम, लेकिन अन्य काम के मामलों में शायद मेरी दिलचस्पी टेलीफ़ोन नंबर, ईमेल, अवतार आइकॉन या घर या ऑफ़िस का पता. इसके बाद, मैं एक options ऑब्जेक्ट को कॉन्फ़िगर करता हूं और multiple को true पर सेट करता हूं, ताकि ज़्यादा विकल्प चुना जा सके एक से ज़्यादा एंट्री. आखिर में, navigator.contacts.select() को कॉल किया जा सकता है, जो आपकी पसंद की प्रॉपर्टी दिखाता है चुनिंदा संपर्कों के लिए.

const getContacts = async () => {
  const properties = ['name'];
  const options = { multiple: true };
  try {
    return await navigator.contacts.select(properties, options);
  } catch (err) {
    console.error(err.name, err.message);
  }
};

अब तक, आपको शायद पैटर्न के बारे में पता चल गया होगा: मैं फ़ाइल सिर्फ़ तब लोड करता हूं, जब एपीआई असल में काम करता है.

if ('contacts' in navigator) {
  import('./contacts.mjs');
}

'फ़ुगु ग्रीटिंग' में, जब मैं संपर्क बटन पर टैप करूं और अपने दो सबसे अच्छे दोस्त चुनूं, सेरगेई मिखाईलवोविच पर्याप्तन और 劳伦斯·爱德华·"拉里"·佩奇, देखा जा सकता है कि संपर्क पिकर में सिर्फ़ उनके नाम दिखाए जा सकते हैं. लेकिन उनके ईमेल पते या उनके फ़ोन नंबर जैसी अन्य जानकारी नहीं. इसके बाद, मेरे ग्रीटिंग कार्ड पर उनके नाम जुड़ जाएंगे.

संपर्क पिकर, जिसमें पता बुक में मौजूद दो संपर्कों के नाम दिख रहे हैं.
पता पुस्तिका से संपर्क पिकर के साथ दो नाम चुनना.
पहले चुने गए दो संपर्कों के नाम, ग्रीटिंग कार्ड पर दिखाए जाएंगे.
इसके बाद, दोनों नाम ग्रीटिंग कार्ड पर आ जाते हैं.

एसिंक्रोनस क्लिपबोर्ड एपीआई

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

सिस्टम के क्लिपबोर्ड पर कुछ कॉपी करने के लिए, मुझे उस पर कुछ लिखना होगा. navigator.clipboard.write() तरीका, क्लिपबोर्ड पर मौजूद आइटम के कलेक्शन को पैरामीटर. क्लिपबोर्ड का हर आइटम एक ऐसा ऑब्जेक्ट होता है जिसकी वैल्यू के तौर पर BLOB होता है और BLOB का टाइप होता है का इस्तेमाल किया जा सकता है.

const copy = async (blob) => {
  try {
    await navigator.clipboard.write([
      new ClipboardItem({
        [blob.type]: blob,
      }),
    ]);
  } catch (err) {
    console.error(err.name, err.message);
  }
};

चिपकाने के लिए, मुझे कॉल करने पर मिले क्लिपबोर्ड आइटम पर लूप करना होगा navigator.clipboard.read(). इसकी वजह यह है कि क्लिपबोर्ड पर कई आइटम अलग-अलग प्रतिनिधित्व कर रहे हैं. क्लिपबोर्ड के हर आइटम में एक types फ़ील्ड होता है. इससे मुझे पता चलता है कि उपलब्ध रिपोर्ट में किस तरह के MIME टाइप दिए गए हैं संसाधन. मैंने क्लिपबोर्ड आइटम पर getType() तरीका इस्तेमाल किया MIME टाइप जो मुझे पहले मिला था.

const paste = async () => {
  try {
    const clipboardItems = await navigator.clipboard.read();
    for (const clipboardItem of clipboardItems) {
      try {
        for (const type of clipboardItem.types) {
          const blob = await clipboardItem.getType(type);
          return blob;
        }
      } catch (err) {
        console.error(err.name, err.message);
      }
    }
  } catch (err) {
    console.error(err.name, err.message);
  }
};

अब यह कहना मुश्किल ही है. मैं ऐसा सिर्फ़ सपोर्ट करने वाले ब्राउज़र पर करता हूं.

if ('clipboard' in navigator && 'write' in navigator.clipboard) {
  import('./clipboard.mjs');
}

यह कैसे काम करता है? मेरे पास macOS Preview ऐप्लिकेशन में एक इमेज खुली है और क्लिपबोर्ड पर कॉपी करें. जब मैं चिपकाएं पर क्लिक करता/करती हूं, तब Fugu Greetings ऐप्लिकेशन मुझसे पूछता है चुनें कि ऐप्लिकेशन को क्लिपबोर्ड पर टेक्स्ट और इमेज देखने की अनुमति देनी है या नहीं.

Fugu Greetings ऐप्लिकेशन, क्लिपबोर्ड की अनुमति वाला अनुरोध दिखा रहा है.
क्लिपबोर्ड की अनुमति वाला अनुरोध.

आखिर में, अनुमति स्वीकार करने के बाद इमेज को ऐप्लिकेशन में चिपकाया जाता है. जवाब देने का एक और तरीका भी काम करता है. मुझे ग्रीटिंग कार्ड को क्लिपबोर्ड पर कॉपी करने दें. इसके बाद, 'झलक देखें' खोलकर फ़ाइल और फिर क्लिपबोर्ड से नया पर क्लिक करने पर ग्रीटिंग कार्ड बिना टाइटल वाली नई इमेज में चिपका दिया जाएगा.

macOS का प्रीव्यू ऐप्लिकेशन, जिसमें बिना टाइटल वाली इमेज चिपकाई गई है.
macOS Preview ऐप्लिकेशन में चिपकाई गई इमेज.

बैजिंग एपीआई

Badging API, एक और काम का एपीआई है. इंस्टॉल किए जा सकने वाले PWA के तौर पर, Fugu Greetings में एक ऐप्लिकेशन आइकॉन मौजूद है जिसे लोग ऐप्लिकेशन डॉक या होम स्क्रीन पर लगा सकें. API को दिखाने का एक मज़ेदार और आसान तरीका यह है कि उसे Fugu Greetings में इस्तेमाल (ab) कर दिया जाए एक नई तरह की होती है. मैंने एक इवेंट लिसनर जोड़ा है, जो pointerdown इवेंट के होने पर पेन स्ट्रोक काउंटर को बढ़ा देता है और फिर अपडेट किया गया आइकॉन बैज सेट करता है. जब भी कैनवस साफ़ हो जाता है, तो काउंटर रीसेट हो जाता है और बैज निकाल दिया जाता है.

let strokes = 0;

canvas.addEventListener('pointerdown', () => {
  navigator.setAppBadge(++strokes);
});

clearButton.addEventListener('click', () => {
  strokes = 0;
  navigator.setAppBadge(strokes);
});

यह सुविधा धीरे-धीरे बेहतर काम करती है. इसलिए, कॉन्टेंट लोड होने का लॉजिक पहले की तरह ही होता है.

if ('setAppBadge' in navigator) {
  import('./badge.mjs');
}

इस उदाहरण में, मैंने एक पेन स्ट्रोक का इस्तेमाल करके, एक से सात तक की संख्याएं बनाई हैं प्रति संख्या. आइकन पर मौजूद बैज काउंटर अब सात है.

एक से सात तक की संख्या ग्रीटिंग कार्ड पर बनाई गई, जिसमें से हर एक को सिर्फ़ एक पेन स्ट्रोक से बनाया गया था.
सात पेन स्ट्रोक का इस्तेमाल करके, 1 से 7 तक की संख्या बनाना.
Fugu Greetings ऐप्लिकेशन पर मौजूद बैज का आइकॉन, जिसमें नंबर 7 दिख रहा है.
पेन स्ट्रोक काउंटर, ऐप्लिकेशन आइकॉन बैज के रूप में दिखता है.

समय-समय पर चलने वाला बैकग्राउंड सिंक एपीआई

क्या आपको अपने दिन की शुरुआत कुछ नई चीज़ों के साथ करनी है? Fugu Greetings ऐप्लिकेशन की एक शानदार सुविधा यह है कि यह आपको हर सुबह प्रेरणा दे सकता है अपना ग्रीटिंग कार्ड शुरू करने के लिए एक नई बैकग्राउंड इमेज के साथ. यह ऐप्लिकेशन, पीरियडिक बैकग्राउंड सिंक एपीआई का इस्तेमाल करता है लक्ष्य हासिल किया है.

पहला चरण सर्विस वर्कर रजिस्ट्रेशन में समय-समय पर होने वाले सिंक इवेंट रजिस्टर करना है. यह 'image-of-the-day' नाम के सिंक टैग को सुनता है और इसमें कम से कम एक दिन का इंटरवल हो, ताकि उपयोगकर्ता को हर 24 घंटे में एक नई बैकग्राउंड इमेज मिल सके.

const registerPeriodicBackgroundSync = async () => {
  const registration = await navigator.serviceWorker.ready;
  try {
    registration.periodicSync.register('image-of-the-day-sync', {
      // An interval of one day.
      minInterval: 24 * 60 * 60 * 1000,
    });
  } catch (err) {
    console.error(err.name, err.message);
  }
};

दूसरा चरण है, सर्विस वर्कर में periodicsync इवेंट के लिए सुनना. अगर इवेंट टैग 'image-of-the-day' है, यानी कि पहले इवेंट टैग को रजिस्टर किया गया था, दिन की इमेज, getImageOfTheDay() फ़ंक्शन की मदद से वापस लाई गई है, और नतीजों को सभी क्लाइंट के लिए लागू कर दिया जाता है, ताकि वे अपने कैनवस अपडेट कर सकें और कैश मेमोरी में सेव करता है.

self.addEventListener('periodicsync', (syncEvent) => {
  if (syncEvent.tag === 'image-of-the-day-sync') {
    syncEvent.waitUntil(
      (async () => {
        const blob = await getImageOfTheDay();
        const clients = await self.clients.matchAll();
        clients.forEach((client) => {
          client.postMessage({
            image: blob,
          });
        });
      })()
    );
  }
});

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

// In the client:
const registration = await navigator.serviceWorker.ready;
if (registration && 'periodicSync' in registration) {
  import('./periodic_background_sync.mjs');
}
// In the service worker:
if ('periodicSync' in self.registration) {
  importScripts('./image_of_the_day.mjs');
}

Fugu ग्रीटिंग में, वॉलपेपर बटन दबाने से, दिन के ग्रीटिंग कार्ड की इमेज दिखती है इसे 'पीरियॉडिक बैकग्राउंड सिंक एपीआई' की मदद से हर दिन अपडेट किया जाता है.

फ़्युगु ग्रीटिंग ऐप्लिकेशन, जिसमें दिन के नए ग्रीटिंग कार्ड की इमेज है.
वॉलपेपर बटन को दबाने से, उस दिन की इमेज दिखती है.

सूचना को ट्रिगर करने वाला एपीआई

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

टारगेट किए गए समय के लिए प्रॉम्प्ट देने के बाद, ऐप्लिकेशन, सूचना को showTrigger के साथ शेड्यूल करता है. यह पहले चुनी गई टारगेट तारीख वाला TimestampTrigger हो सकता है. रिमाइंडर की सूचना स्थानीय तौर पर ट्रिगर होगी. इसके लिए, किसी नेटवर्क या सर्वर साइड की ज़रूरत नहीं है.

const targetDate = promptTargetDate();
if (targetDate) {
  const registration = await navigator.serviceWorker.ready;
  registration.showNotification('Reminder', {
    tag: 'reminder',
    body: "It's time to finish your greeting card!",
    showTrigger: new TimestampTrigger(targetDate),
  });
}

मैंने अभी तक जो कुछ भी दिखाया है उसकी तरह ही, इसमें लगातार सुधार होता रहता है. इसलिए कोड सिर्फ़ शर्त के साथ लोड होता है.

if ('Notification' in window && 'showTrigger' in Notification.prototype) {
  import('./notification_triggers.mjs');
}

जब मैं Fugu ग्रीटिंग मैसेज में रिमाइंडर चेकबॉक्स को सही का निशान लगाता हूँ, तो एक प्रॉम्प्ट दिखता है जब मैं अपना ग्रीटिंग कार्ड पूरा करने के लिए याद दिलाना चाहूं.

Fugu Greetings ऐप्लिकेशन, जिसमें एक प्रॉम्प्ट दिख रहा है. इसमें लोगों से पूछा जा रहा है कि उन्हें अपना ग्रीटिंग कार्ड पूरा करने के लिए, कब याद दिलाना है.
ग्रीटिंग कार्ड पूरा करने के लिए, रिमाइंडर पाने के लिए स्थानीय सूचना शेड्यूल करना.

जब Fugu ग्रीटिंग में शेड्यूल की गई सूचना ट्रिगर होती है, यह किसी अन्य सूचना की तरह ही दिखाया जाता है, लेकिन जैसा कि मैंने पहले लिखा है, इसके लिए इंटरनेट की ज़रूरत नहीं पड़ी.

macOS सूचना केंद्र, Fugu Greetings से ट्रिगर होने वाली सूचना दिखा रहा है.
ट्रिगर हुई सूचना, macOS के सूचना केंद्र में दिखती है.

वेक लॉक एपीआई

साथ ही, मुझे वेक लॉक एपीआई को भी शामिल करना है. कभी-कभी आपको वीडियो देखने की प्रेरणा मिलने तक स्क्रीन को देर तक घूरने की ज़रूरत होती है चुंबन देता है. ऐसी स्थिति में, स्क्रीन बंद हो जाना सबसे खराब स्थिति हो सकती है. वेक लॉक एपीआई की मदद से, ऐसा होने से रोका जा सकता है.

सबसे पहले, navigator.wakelock.request method() की मदद से वेक लॉक की सुविधा चालू करें. स्क्रीन वेक लॉक पाने के लिए, मैंने इसे 'screen' स्ट्रिंग पास की है. इसके बाद, मैं वेक लॉक के रिलीज़ होने पर, इवेंट लिसनर को सूचना देती हूँ. ऐसा तब हो सकता है, जब टैब की 'किसको दिखे' सेटिंग में बदलाव हो. अगर ऐसा होता है, तो जब टैब फिर से दिखने लगेगा, तब वेक लॉक फिर से लागू किया जा सकेगा.

let wakeLock = null;
const requestWakeLock = async () => {
  wakeLock = await navigator.wakeLock.request('screen');
  wakeLock.addEventListener('release', () => {
    console.log('Wake Lock was released');
  });
  console.log('Wake Lock is active');
};

const handleVisibilityChange = () => {
  if (wakeLock !== null && document.visibilityState === 'visible') {
    requestWakeLock();
  }
};

document.addEventListener('visibilitychange', handleVisibilityChange);
document.addEventListener('fullscreenchange', handleVisibilityChange);

हां, यह एक प्रोग्रेसिव एन्हैंसमेंट है, इसलिए मुझे इसे सिर्फ़ तब लोड करना है, जब ब्राउज़र API का इस्तेमाल करता है.

if ('wakeLock' in navigator && 'request' in navigator.wakeLock) {
  import('./wake_lock.mjs');
}

Fugu Greetings में, इन्सोम्निया का एक चेकबॉक्स होता है. चेकबॉक्स पर सही का निशान लगाने पर, यह चेकबॉक्स स्क्रीन चालू करना.

नींद न आने की जानकारी वाले चेकबॉक्स पर सही का निशान लगाने पर, स्क्रीन चालू रहती है.
अनिद्रा चेकबॉक्स की मदद से ऐप्लिकेशन को बंद रखा जाता है.

कुछ समय से इस्तेमाल में न होने का पता लगाने वाला एपीआई

कभी-कभी, भले ही आप स्क्रीन को घंटों तक घूरते रहें, यह बस बेकार है और आपको कुछ समझ नहीं आ रहा है कि अपने ग्रीटिंग कार्ड से क्या करना है. कुछ समय से इस्तेमाल में न होने की पहचान करने वाले एपीआई की मदद से, ऐप्लिकेशन उपयोगकर्ता के डिवाइस के इस्तेमाल में न रहने के समय का पता लगाता है. अगर उपयोगकर्ता लंबे समय से कुछ समय से इस्तेमाल में नहीं है, तो ऐप्लिकेशन अपनी शुरुआती स्थिति में रीसेट हो जाता है और कैनवस साफ़ करता है. फ़िलहाल, यह एपीआई सूचनाओं की अनुमति, क्योंकि डिवाइस का कुछ समय तक इस्तेमाल न किए जाने का पता लगाने के लिए, प्रोडक्शन के ज़्यादातर इस्तेमाल में सूचनाओं से जुड़े होते हैं. उदाहरण के लिए, किसी ऐसे डिवाइस पर सिर्फ़ सूचना भेजने के लिए जिसे उपयोगकर्ता फ़िलहाल इस्तेमाल कर रहा हो.

यह सुनिश्चित करने के बाद कि सूचना की अनुमति दी गई है, मैं डिवाइस का कुछ समय तक इस्तेमाल न करने वाला डिटेक्टर. मैंने एक इवेंट लिसनर को रजिस्टर किया है जो इनऐक्टिव बदलावों को सुनता है. इन बदलावों में उपयोगकर्ता और स्क्रीन स्थिति. भले ही, उपयोगकर्ता ऐक्टिव हो या कुछ समय से इस्तेमाल में न हो, और स्क्रीन को अनलॉक या लॉक किया जा सके. अगर उपयोगकर्ता कुछ समय से इस्तेमाल में नहीं है, तो कैनवस हट जाता है. आइडल डिटेक्टर का समय 60 सेकंड का है.

const idleDetector = new IdleDetector();
idleDetector.addEventListener('change', () => {
  const userState = idleDetector.userState;
  const screenState = idleDetector.screenState;
  console.log(`Idle change: ${userState}, ${screenState}.`);
  if (userState === 'idle') {
    clearCanvas();
  }
});

await idleDetector.start({
  threshold: 60000,
  signal,
});

हमेशा की तरह, मैं यह कोड सिर्फ़ तब लोड करता हूं, जब ब्राउज़र पर यह कोड काम करता है.

if ('IdleDetector' in window) {
  import('./idle_detection.mjs');
}

Fugu Greetings ऐप्लिकेशन में, Ephemeral चेकबॉक्स के चुने जाने पर, कैनवस हट जाता है चेक किया गया है और उपयोगकर्ता लंबे समय से निष्क्रिय है.

उपयोगकर्ता के देर तक डिवाइस का इस्तेमाल न करने पर, साफ़ कैनवस के साथ Fugu Greetings ऐप्लिकेशन.
जब कुछ समय के लिए चेकबॉक्स पर सही का निशान लगा हो और उपयोगकर्ता लंबे समय से इस्तेमाल में न हो, तो कैनवस मिटा दिया जाता है.

आखिरी हिस्सा

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

Chrome DevTools नेटवर्क पैनल, सिर्फ़ ऐसे कोड वाली फ़ाइलों के अनुरोध दिखा रहा है जो मौजूदा ब्राउज़र पर काम करता है.
Chrome DevTools नेटवर्क टैब, सिर्फ़ ऐसे कोड वाली फ़ाइलों के अनुरोध दिखा रहा है जो मौजूदा ब्राउज़र पर काम करता है.

यह ऐप्लिकेशन हर ब्राउज़र पर कुछ अलग दिख सकता है, क्योंकि सभी प्लैटफ़ॉर्म पर सभी सुविधाएं काम नहीं करतीं, हालांकि, मुख्य फ़ंक्शन हमेशा मौजूद रहता है—जो ब्राउज़र की क्षमताओं के हिसाब से धीरे-धीरे बेहतर हो जाता है. ध्यान दें कि ये क्षमताएं एक ही ब्राउज़र में बदल भी सकती हैं, यह इस बात पर निर्भर करता है कि ऐप्लिकेशन इंस्टॉल किए गए ऐप्लिकेशन के तौर पर चल रहा है या ब्राउज़र टैब में.

Android Chrome पर चल रहा फ़गू ग्रीटिंग, कई उपलब्ध सुविधाएं दिखा रहा है.
Android Chrome पर चल रहा Fugu Greetings.
डेस्कटॉप Safari पर चल रहा Fugu Greetings, कम उपलब्ध फ़ीचर दिखा रहा है.
डेस्कटॉप Safari पर चल रही Fugu Greetings.
डेस्कटॉप Chrome पर चल रहा Fugu वेलकम मैसेज, जिसमें कई उपलब्ध सुविधाएं दिख रही हैं.
Chrome के डेस्कटॉप वर्शन पर चल रहा Fugu Greetings.

अगर Fugu Greetings ऐप्लिकेशन में आपकी दिलचस्पी है, ढूंढें और इसे GitHub पर फ़ोर्क करें.

GitHub पर Fugu Greetings रेपो.
GitHub पर Fugu Greetings ऐप्लिकेशन.

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

स्वीकार की गई

मैं क्रिश्चन लीबेल और हीमैंथ एचएम इन दोनों ने फ़ूगू ग्रीटिंग्स में अपना योगदान दिया है. इस लेख की समीक्षा जो मेडली ने की है और कायस बास्क. जेक आर्किबाल्ड ने मुझे हालात के बारे में जानने में मदद की को डाइनैमिक import() के साथ सेट किया जा सकता है.