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

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

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

इस एक्सपेरिमेंट का मकसद सिर्फ़ अनुरोधों की संख्या में बदलाव करना था, ताकि यह देखा जा सके कि पेज लोड होने की परफ़ॉर्मेंस पर कोई बुरा असर पड़ता है या नहीं. नतीजों से पता चलता है कि टेस्ट पेज पर 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 में एक फ़्लैग के पीछे रोल आउट की गई थी. यहां इसे शुरुआती उपयोगकर्ताओं पर टेस्ट किया गया था. कई लोगों ने अपनी पूरी साइट के लिए इस्तेमाल किए गए JavaScript में काफ़ी कमी देखी:
| वेबसाइट | जेएस में किए गए कुल बदलाव | % का अंतर |
|---|---|---|
| https://www.barnebys.com/ | -238 केबी | -23% |
| https://sumup.com/ | -220 केबी | -30% |
| https://www.hashicorp.com/ | -11 एमबी | -71% |
आखिरी वर्शन को डिफ़ॉल्ट रूप से वर्शन 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 के साइज़ में काफ़ी कमी देखी:
| वेबसाइट | जेएस में किए गए कुल बदलाव | % का अंतर |
|---|---|---|
| https://www.gatsbyjs.org/ | -680 केबी | -22% |
| https://www.thirdandgrove.com/ | -390 केबी | -25% |
| https://ghost.org/ | -1.1 एमबी | -35% |
| https://reactjs.org/ | -80 केबी | -8% |
पीआर पर नज़र डालें और जानें कि उन्होंने इस लॉजिक को अपने वेबपैक कॉन्फ़िगरेशन में कैसे लागू किया. यह कॉन्फ़िगरेशन, v2.20.7 में डिफ़ॉल्ट रूप से शिप किया जाता है.
नतीजा
ग्रैन्युलर चंक को शिप करने का कॉन्सेप्ट, Next.js, Gatsby या webpack तक ही सीमित नहीं है. अगर किसी ऐप्लिकेशन में बड़े "कॉमन" बंडल का इस्तेमाल किया जाता है, तो उसे चंकिंग की रणनीति को बेहतर बनाने पर विचार करना चाहिए. भले ही, उसमें किसी भी फ़्रेमवर्क या मॉड्यूल बंडलर का इस्तेमाल किया गया हो.
- अगर आपको वैनिला React ऐप्लिकेशन पर लागू किए गए चंकिंग ऑप्टिमाइज़ेशन देखने हैं, तो इस सैंपल React ऐप्लिकेशन को देखें. यह ग्रैन्युलर चंकिंग रणनीति के आसान वर्शन का इस्तेमाल करता है. इससे आपको अपनी साइट पर इसी तरह के लॉजिक को लागू करने में मदद मिल सकती है.
- रोलअप के लिए, चंक डिफ़ॉल्ट रूप से बारीकी से बनाए जाते हैं. अगर आपको मैन्युअल तरीके से व्यवहार को कॉन्फ़िगर करना है, तो
manualChunksपर जाएं.