बारीकी से अलग-अलग सेक्शन में बांटने के साथ-साथ Next.js और Gatsby पेज लोड होने की प्रोसेस को बेहतर बनाया गया

Next.js और Gatsby में, Webpack Chunking की नई रणनीति का इस्तेमाल किया जाता है. इससे डुप्लीकेट कोड कम होता है और पेज लोड होने की परफ़ॉर्मेंस बेहतर होती है.

Chrome, JavaScript के ओपन-सोर्स इकोसिस्टम में टूलिंग और फ़्रेमवर्क के साथ मिलकर काम कर रहा है. हाल ही में, Next.js और Gatsby की लोडिंग परफ़ॉर्मेंस को बेहतर बनाने के लिए, कई नए ऑप्टिमाइज़ेशन जोड़े गए हैं. इस लेख में, ज़्यादा बारीकी से चंक करने की बेहतर रणनीति के बारे में बताया गया है. यह रणनीति, अब दोनों फ़्रेमवर्क में डिफ़ॉल्ट रूप से उपलब्ध है.

परिचय

कई वेब फ़्रेमवर्क की तरह, Next.js और Gatsby अपने मुख्य बंडलर के तौर पर webpack का इस्तेमाल करते हैं. webpack v3 में CommonsChunkPlugin को पेश किया गया था. इससे, एक ही "कॉमन" चंक (या चंक) में अलग-अलग एंट्री पॉइंट के बीच शेयर किए गए मॉड्यूल को आउटपुट करना मुमकिन हो गया. शेयर किए गए कोड को अलग से डाउनलोड किया जा सकता है. साथ ही, इसे ब्राउज़र की कैश मेमोरी में सेव किया जा सकता है. इससे पेज के लोड होने की परफ़ॉर्मेंस बेहतर हो सकती है.

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

सामान्य एंट्रीपॉइंट और बंडल कॉन्फ़िगरेशन

शेयर किए गए सभी मॉड्यूल कोड को एक ही चंक में बंडल करने का कॉन्सेप्ट, भले ही व्यावहारिक हो, लेकिन इसकी कुछ सीमाएं हैं. हर एंट्री पॉइंट में शेयर नहीं किए गए मॉड्यूल को उन रास्तों के लिए डाउनलोड किया जा सकता है जो इसका इस्तेमाल नहीं करते हैं. इससे ज़रूरत से ज़्यादा कोड डाउनलोड हो जाता है. उदाहरण के लिए, जब page1, common चंक को लोड करता है, तो वह moduleC के कोड को भी लोड करता है. भले ही, page1, moduleC का इस्तेमाल न करता हो. इस वजह से, webpack v4 ने प्लगिन को हटा दिया है और इसकी जगह एक नया प्लगिन इस्तेमाल किया है: SplitChunksPlugin.

बेहतर चंकिंग

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

हालांकि, इस प्लगिन का इस्तेमाल करने वाले कई वेब फ़्रेमवर्क, अब भी चंक स्प्लिटिंग के लिए "सिंगल-कॉमन" अप्रोच का इस्तेमाल करते हैं. उदाहरण के लिए, Next.js एक commons बंडल जनरेट करेगा. इसमें ऐसा कोई भी मॉड्यूल शामिल होगा जिसका इस्तेमाल 50% से ज़्यादा पेजों में किया जाता है. साथ ही, इसमें फ़्रेमवर्क की सभी डिपेंडेंसी (react, react-dom वगैरह) शामिल होंगी.

const splitChunksConfigs = {
  
  prod: {
    chunks: 'all',
    cacheGroups: {
      default: false,
      vendors: false,
      commons: {
        name: 'commons',
        chunks: 'all',
        minChunks: totalPages > 2 ? totalPages * 0.5 : 2,
      },
      react: {
        name: 'commons',
        chunks: 'all',
        test: /[\\/]node_modules[\\/](react|react-dom|scheduler|use-subscription)[\\/]/,
      },
    },
  },

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

  • अनुपात कम करने पर, ज़्यादा ग़ैर-ज़रूरी कोड डाउनलोड हो जाता है.
  • अनुपात बढ़ाने पर, एक से ज़्यादा रास्तों पर ज़्यादा कोड डुप्लीकेट हो जाता है.

इस समस्या को हल करने के लिए, Next.js ने SplitChunksPlugin के लिए अलग कॉन्फ़िगरेशन अपनाया है. इससे किसी भी रूट के लिए, गैर-ज़रूरी कोड कम हो जाता है.

  • तीसरे पक्ष के किसी भी बड़े मॉड्यूल (160 केबी से ज़्यादा) को उसके अलग-अलग हिस्सों में बांट दिया जाता है
  • फ़्रेमवर्क की डिपेंडेंसी (react, react-dom वगैरह) के लिए, एक अलग frameworks चंक बनाया जाता है
  • ज़रूरत के मुताबिक, शेयर किए गए ज़्यादा से ज़्यादा 25 चंक बनाए जाते हैं
  • जनरेट किए जाने वाले चंक का कम से कम साइज़ 20 केबी कर दिया गया है

इस रणनीति से ये फ़ायदे मिलते हैं:

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

Next.js ने webpack-config.ts में जो कॉन्फ़िगरेशन अपनाया है उसे देखा जा सकता है.

ज़्यादा एचटीटीपी अनुरोध

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

ब्राउज़र, एक ही ऑरिजिन के लिए टीसीपी कनेक्शन की सीमित संख्या (Chrome के लिए 6) खोल सकते हैं. इसलिए, बंडलर से आउटपुट किए गए चंक की संख्या को कम करने से यह पक्का किया जा सकता है कि अनुरोधों की कुल संख्या इस थ्रेशोल्ड से कम रहे. हालांकि, यह सिर्फ़ एचटीटीपी/1.1 के लिए सही है. एचटीटीपी/2 में मल्टीप्लेक्सिंग की सुविधा होती है. इसकी मदद से, एक ही कनेक्शन का इस्तेमाल करके, एक ही ऑरिजिन से कई अनुरोध एक साथ स्ट्रीम किए जा सकते हैं. दूसरे शब्दों में कहें, तो हमें आम तौर पर अपने बंडलर से निकाले गए चंक की संख्या को सीमित करने के बारे में चिंता करने की ज़रूरत नहीं होती.

सभी मुख्य ब्राउज़र, HTTP/2 के साथ काम करते हैं. Chrome और Next.js की टीमों को यह जानना था कि Next.js के सिंगल "कॉमन" बंडल को कई शेयर किए गए हिस्सों में बांटने से, अनुरोधों की संख्या बढ़ाने पर लोडिंग परफ़ॉर्मेंस पर कोई असर पड़ेगा या नहीं. उन्होंने maxInitialRequests प्रॉपर्टी का इस्तेमाल करके, एक साथ किए जाने वाले अनुरोधों की संख्या में बदलाव किया. साथ ही, एक साइट की परफ़ॉर्मेंस को मेज़र किया.

ज़्यादा अनुरोधों के साथ पेज लोड होने की परफ़ॉर्मेंस

एक ही वेब पेज पर कई बार किए गए टेस्ट के तीन रन के औसत के हिसाब से, load, start-render, और फ़र्स्ट कॉन्टेंटफ़ुल पेंट के समय में कोई खास बदलाव नहीं हुआ. ऐसा तब हुआ, जब शुरुआती अनुरोधों की ज़्यादा से ज़्यादा संख्या को 5 से 15 तक बदला गया. दिलचस्प बात यह है कि हमें परफ़ॉर्मेंस में थोड़ी गिरावट तब दिखी, जब हमने सैकड़ों अनुरोधों को कई हिस्सों में बांट दिया.

सैकड़ों अनुरोधों के साथ पेज लोड होने की परफ़ॉर्मेंस

इससे पता चला कि भरोसेमंद थ्रेशोल्ड (20 से 25 अनुरोध) से कम अनुरोध करने पर, पेज लोड होने की परफ़ॉर्मेंस और कैश मेमोरी की क्षमता के बीच सही संतुलन बना रहता है. कुछ बेसलाइन टेस्टिंग के बाद, 25 को maxInitialRequest की संख्या के तौर पर चुना गया.

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

ज़्यादा चंकिंग की मदद से JavaScript पेलोड को कम करना

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

webpack, जनरेट किए जाने वाले किसी भी चंक के लिए, डिफ़ॉल्ट तौर पर कम से कम 30 केबी का साइज़ इस्तेमाल करता है. हालांकि, 25 की maxInitialRequests वैल्यू को कम से कम 20 केबी के साइज़ के साथ जोड़ने पर, बेहतर कैश मेमोरी मिली.

छोटे-छोटे हिस्सों में बांटकर साइज़ कम करना

Next.js जैसे कई फ़्रेमवर्क, हर रूट ट्रांज़िशन के लिए नए स्क्रिप्ट टैग इंजेक्ट करने के लिए, क्लाइंट-साइड राउटिंग (JavaScript से मैनेज की जाती है) पर निर्भर करते हैं. हालांकि, ये डाइनैमिक चंक को बिल्ड टाइम पर पहले से कैसे तय करते हैं?

Next.js, सर्वर-साइड बिल्ड मेनिफ़ेस्ट फ़ाइल का इस्तेमाल करता है. इससे यह तय किया जाता है कि आउटपुट किए गए कौनसे चंक, अलग-अलग एंट्री पॉइंट इस्तेमाल करते हैं. क्लाइंट को भी यह जानकारी देने के लिए, क्लाइंट-साइड की छोटी बिल्ड मेनिफ़ेस्ट फ़ाइल बनाई गई थी. इससे हर एंट्री पॉइंट के लिए सभी डिपेंडेंसी को मैप किया जा सकता है.

// Returns a promise for the dependencies for a particular route
getDependencies (route) {
  return this.promisedBuildManifest.then(
    man => (man[route] && man[route].map(url => `/_next/${url}`)) || []
  )
}
Next.js ऐप्लिकेशन में, शेयर किए गए कई हिस्सों का आउटपुट.

ग्रैनुलर चंकिंग की इस नई रणनीति को सबसे पहले Next.js में फ़्लैग के पीछे रोल आउट किया गया था. यहां इसकी टेस्टिंग, शुरुआती तौर पर इसे अपनाने वाले कई लोगों ने की थी. कई लोगों को अपनी पूरी साइट के लिए इस्तेमाल की गई JavaScript में काफ़ी कमी देखने को मिली:

वेबसाइट कुल JS बदलाव % का अंतर
https://www.barnebys.com/ -238 केबी -23%
https://sumup.com/ -220 केबी -30%
https://www.hashicorp.com/ -11 एमबी -71%
JavaScript के साइज़ में कमी - सभी रास्तों पर (कंप्रेस किया गया)

फ़ाइनल वर्शन को डिफ़ॉल्ट रूप से वर्शन 9.2 में शिप किया गया था.

Gatsby

Gatsby, सामान्य मॉड्यूल तय करने के लिए, इस्तेमाल पर आधारित अनुमानित तरीके का इस्तेमाल करता था:

config.optimization = {
  
  splitChunks: {
    name: false,
    chunks: `all`,
    cacheGroups: {
      default: false,
      vendors: false,
      commons: {
        name: `commons`,
        chunks: `all`,
        // if a chunk is used more than half the components count,
        // we can assume it's pretty global
        minChunks: componentsCount > 2 ? componentsCount * 0.5 : 2,
      },
      react: {
        name: `commons`,
        chunks: `all`,
        test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
      },

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

वेबसाइट कुल JS बदलाव % का अंतर
https://www.gatsbyjs.org/ -680 केबी -22%
https://www.thirdandgrove.com/ -390 केबी -25%
https://ghost.org/ -1.1 एमबी -35%
https://reactjs.org/ -80 केबी -8%
JavaScript के साइज़ में कमी - सभी रास्तों पर (कंप्रेस किया गया)

पीआर देखें. इससे आपको पता चलेगा कि उन्होंने इस लॉजिक को अपने वेबपैक कॉन्फ़िगरेशन में कैसे लागू किया है. यह v2.20.7 में डिफ़ॉल्ट रूप से शिप किया जाता है.

नतीजा

ग्रैनुलर चंक शिप करने का कॉन्सेप्ट, Next.js, Gatsby या webpack के लिए खास नहीं है. अगर आपका ऐप्लिकेशन, "कॉमन" बंडल के बड़े अप्रोच को फ़ॉलो करता है, तो हर डेवलपर को अपने ऐप्लिकेशन की चंकिंग रणनीति को बेहतर बनाने के बारे में सोचना चाहिए. भले ही, इस्तेमाल किया गया फ़्रेमवर्क या मॉड्यूल बंडलर कोई भी हो.

  • अगर आपको किसी सामान्य React ऐप्लिकेशन पर, चंकिंग के लिए किए गए ऑप्टिमाइज़ेशन को लागू करना है, तो इस सैंपल React ऐप्लिकेशन को देखें. इसमें, ग्रेन्यूलर चंकिंग की रणनीति के आसान वर्शन का इस्तेमाल किया गया है. इससे आपको अपनी साइट पर इसी तरह का लॉजिक लागू करने में मदद मिल सकती है.
  • रोलअप के लिए, डिफ़ॉल्ट रूप से चंक को बारीकी से बनाया जाता है. अगर आपको मैन्युअल तरीके से व्यवहार कॉन्फ़िगर करना है, तो manualChunks पर जाएं.