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

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

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 केबी कर दिया गया है

डेटा को अलग-अलग हिस्सों में बांटने की इस रणनीति से ये फ़ायदे मिलते हैं:

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

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

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

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

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

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

अनुरोधों की संख्या बढ़ने पर, पेज लोड की परफ़ॉर्मेंस

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

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

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

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

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

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

webpack, जनरेट किए जाने वाले चंक के लिए, डिफ़ॉल्ट रूप से 30 केबी के कम से कम साइज़ का इस्तेमाल करता है. हालांकि, maxInitialRequests की वैल्यू को 25 के साथ जोड़ने और कम से कम 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 पर जाएं.