नेविगेशन पहले से लोड होने की वजह से सर्विस वर्कर की रफ़्तार बढ़ाना

नेविगेशन प्रीलोड की मदद से, एक साथ कई अनुरोध करके, सेवा वर्कर के शुरू होने में लगने वाले समय को कम किया जा सकता है.

ब्राउज़र के इस्तेमाल से जुड़ी सहायता

  • Chrome: 59.
  • Edge: 18.
  • Firefox: 99.
  • Safari: 15.4.

सोर्स

खास जानकारी

समस्या

जब किसी ऐसी साइट पर नेविगेट किया जाता है जो फ़ेच इवेंट को हैंडल करने के लिए सेवा वर्कर का इस्तेमाल करती है, तो ब्राउज़र सेवा वर्कर से जवाब मांगता है. इसमें, सेवा वर्कर को बूट करना (अगर वह पहले से नहीं चल रहा है) और फ़ेच इवेंट को डिस्पैच करना शामिल है.

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

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

SW बूट
नेविगेशन का अनुरोध

सेवा वर्कर के बूट होने में लगने वाले समय की वजह से, नेटवर्क अनुरोध में देरी होती है.

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

Facebook ने इस समस्या के असर के बारे में हमें बताया और नेविगेशन अनुरोधों को एक साथ पूरा करने का तरीका पूछा:

SW बूट
नेविगेशन का अनुरोध

नेविगेशन को पहले से लोड करने की सुविधा

नेविगेशन प्रीलोड की सुविधा की मदद से, यह तय किया जा सकता है कि "जब उपयोगकर्ता नेविगेशन के लिए GET अनुरोध करता है, तो सेवा वर्कर के बूट अप होने के दौरान नेटवर्क अनुरोध शुरू करें".

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

यहां इसकी जानकारी देने वाला एक वीडियो दिया गया है. इसमें, while-loop का इस्तेमाल करके, सेवा वर्कर को शुरू होने में 500 मिलीसेकंड की देरी दी गई है:

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

नेविगेशन प्रीलोड की सुविधा चालू करना

addEventListener('activate', event => {
  event.waitUntil(async function() {
    // Feature-detect
    if (self.registration.navigationPreload) {
      // Enable navigation preloads!
      await self.registration.navigationPreload.enable();
    }
  }());
});

navigationPreload.enable() को कभी भी कॉल किया जा सकता है या navigationPreload.disable() की मदद से इसे बंद किया जा सकता है. हालांकि, आपके fetch इवेंट को इसका इस्तेमाल करना होगा. इसलिए, इसे अपने सेवा वर्कर के activate इवेंट में चालू और बंद करना सबसे सही है.

पहले से लोड किए गए जवाब का इस्तेमाल करना

अब ब्राउज़र नेविगेशन के लिए प्रीलोड करेगा. हालांकि, आपको जवाब का इस्तेमाल करना होगा:

addEventListener('fetch', event => {
  event.respondWith(async function() {
    // Respond from the cache if we can
    const cachedResponse = await caches.match(event.request);
    if (cachedResponse) return cachedResponse;

    // Else, use the preloaded response, if it's there
    const response = await event.preloadResponse;
    if (response) return response;

    // Else try the network.
    return fetch(event.request);
  }());
});

event.preloadResponse एक वादा है, जो रिस्पॉन्स के साथ पूरा होता है. ऐसा तब होता है, जब:

  • नेविगेशन प्रीलोड की सुविधा चालू हो.
  • अनुरोध, GET अनुरोध है.
  • यह अनुरोध, नेविगेशन अनुरोध है. ब्राउज़र, iframes के साथ-साथ पेजों को लोड करते समय यह अनुरोध जनरेट करते हैं.

अगर ऐसा नहीं है, तो event.preloadResponse अब भी मौजूद है, लेकिन यह undefined के साथ हल हो जाता है.

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

मान लें कि हमें कोई लेख दिखाना है:

addEventListener('fetch', event => {
  const url = new URL(event.request.url);
  const includeURL = new URL(url);
  includeURL.pathname += 'include';

  if (isArticleURL(url)) {
    event.respondWith(async function() {
      // We're going to build a single request from multiple parts.
      const parts = [
        // The top of the page.
        caches.match('/article-top.include'),
        // The primary content
        fetch(includeURL)
          // A fallback if the network fails.
          .catch(() => caches.match('/article-offline.include')),
        // The bottom of the page
        caches.match('/article-bottom.include')
      ];

      // Merge them all together.
      const {done, response} = await mergeResponses(parts);

      // Wait until the stream is complete.
      event.waitUntil(done);

      // Return the merged response.
      return response;
    }());
  }
});

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

यह "ऐप्लिकेशन शेल" मॉडल से ज़्यादा तेज़ है, क्योंकि पेज के अनुरोध के साथ-साथ नेटवर्क का अनुरोध भी किया जाता है. साथ ही, कॉन्टेंट को बड़ी हैक के बिना स्ट्रीम किया जा सकता है.

हालांकि, includeURL के लिए किए गए अनुरोध को पूरा होने में, सर्विस वर्कर के शुरू होने में लगने वाला समय लगेगा. हम इस समस्या को ठीक करने के लिए, नेविगेशन प्रीलोड का भी इस्तेमाल कर सकते हैं. हालांकि, इस मामले में हमें पूरे पेज को प्रीलोड नहीं करना है, बल्कि हमें किसी इंक्लूड को प्रीलोड करना है.

इस सुविधा के साथ काम करने के लिए, हर प्रीलोड अनुरोध के साथ एक हेडर भेजा जाता है:

Service-Worker-Navigation-Preload: true

सर्वर इस जानकारी का इस्तेमाल, नेविगेशन के लिए प्रीलोड किए जाने वाले अनुरोधों के लिए अलग कॉन्टेंट भेजने के लिए कर सकता है. यह कॉन्टेंट, नेविगेशन के लिए सामान्य अनुरोध के लिए भेजे जाने वाले कॉन्टेंट से अलग होता है. बस Vary: Service-Worker-Navigation-Preload हेडर जोड़ना न भूलें, ताकि कैश मेमोरी को पता चल सके कि आपके जवाब अलग-अलग हैं.

अब हम प्रीलोड अनुरोध का इस्तेमाल इन कामों के लिए कर सकते हैं:

// Try to use the preload
const networkContent = Promise.resolve(event.preloadResponse)
  // Else do a normal fetch
  .then(r => r || fetch(includeURL))
  // A fallback if the network fails.
  .catch(() => caches.match('/article-offline.include'));

const parts = [
  caches.match('/article-top.include'),
  networkContent,
  caches.match('/article-bottom')
];

हेडर बदलना

डिफ़ॉल्ट रूप से, Service-Worker-Navigation-Preload हेडर की वैल्यू true होती है. हालांकि, इसे अपनी पसंद के मुताबिक सेट किया जा सकता है:

navigator.serviceWorker.ready.then(registration => {
  return registration.navigationPreload.setHeaderValue(newValue);
}).then(() => {
  console.log('Done!');
});

उदाहरण के लिए, इसे उस आखिरी पोस्ट के आईडी पर सेट किया जा सकता है जिसे आपने स्थानीय तौर पर कैश किया है, ताकि सर्वर सिर्फ़ नया डेटा दिखा सके.

स्थिति देखना

getState का इस्तेमाल करके, नेविगेशन प्रीलोड की स्थिति देखी जा सकती है:

navigator.serviceWorker.ready.then(registration => {
  return registration.navigationPreload.getState();
}).then(state => {
  console.log(state.enabled); // boolean
  console.log(state.headerValue); // string
});

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