Next.js और Gatsby में एक नई वेबपैक चंकिंग रणनीति, पेज लोड करने की परफ़ॉर्मेंस को बेहतर बनाने के लिए डुप्लीकेट कोड को कम करती है.
Chrome, JavaScript ओपन-सोर्स नेटवर्क में टूल और फ़्रेमवर्क के साथ साथ मिलकर काम कर रहा है. Next.js और Gatsby की लोडिंग परफ़ॉर्मेंस को बेहतर बनाने के लिए, हाल ही में कई नए ऑप्टिमाइज़ेशन जोड़े गए हैं. इस लेख में एक बेहतर और बारी-बारी से इकट्ठा की जाने वाली छोटी-छोटी रणनीति के बारे में बताया गया है, जिसे अब दोनों फ़्रेमवर्क में डिफ़ॉल्ट रूप से भेजा जाता है.
शुरुआती जानकारी
कई वेब फ़्रेमवर्क की तरह, Next.js और Gatsby, webpack का इस्तेमाल अपने कोर बंडलर. webpack v3 के तौर पर करते हैं. CommonsChunkPlugin
इससे किसी एक (या कुछ) "कॉमंस" हिस्से (या हिस्सों) में अलग-अलग एंट्री पॉइंट के बीच शेयर किए गए आउटपुट मॉड्यूल को संभव बनाया जा सकता है. शेयर किए गए कोड को अलग से डाउनलोड किया जा सकता है और ब्राउज़र की कैश मेमोरी में पहले ही सेव किया जा सकता है. ऐसा करने पर, पेज की लोडिंग परफ़ॉर्मेंस बेहतर हो सकती है.
यह पैटर्न, ऐसे एंट्रीपॉइंट और बंडल कॉन्फ़िगरेशन का इस्तेमाल करने वाले कई एक पेज वाले ऐप्लिकेशन फ़्रेमवर्क की मदद से लोकप्रिय हुआ जो कुछ इस तरह दिखते थे:
हालांकि, शेयर किए गए सभी मॉड्यूल कोड को एक ही हिस्से में इकट्ठा करने की प्रोसेस व्यावहारिक है, लेकिन इसकी कुछ सीमाएं होती हैं. हर एंट्री पॉइंट पर शेयर नहीं किए गए मॉड्यूल, उन रूट के लिए डाउनलोड किए जा सकते हैं जो इसका इस्तेमाल नहीं करते. इस वजह से, ज़रूरत से ज़्यादा कोड डाउनलोड हो जाते हैं. उदाहरण के लिए, जब page1
common
वाला डेटा लोड करता है, तो यह moduleC
के लिए कोड लोड करता है. भले ही, page1
, moduleC
का इस्तेमाल न करता हो.
इस वजह से, कुछ अन्य ऐप्लिकेशन के साथ-साथ, webpack v4 ने नए प्लग इन SplitChunksPlugin
की जगह, प्लगिन हटा दिया है.
चंकिंग की बेहतर सुविधा
SplitChunksPlugin
की डिफ़ॉल्ट सेटिंग, ज़्यादातर उपयोगकर्ताओं के लिए सही तरीके से काम करती हैं. एक से ज़्यादा रूट को डुप्लीकेट कोड फ़ेच करने से रोकने के लिए, कई conditions के आधार पर कई स्प्लिट हिस्से बनाए जाते हैं.
हालांकि, इस प्लग इन का इस्तेमाल करने वाले कई वेब फ़्रेमवर्क अब भी स्प्लिट करने के लिए "सिंगल-कॉमंस" अप्रोच का इस्तेमाल
करते हैं. उदाहरण के लिए, 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 जैसे फ़्रेमवर्क पर यह तरीका लागू करना बिलकुल नया कॉन्सेप्ट नहीं था. हालांकि, कई फ़्रेमवर्क अब भी कुछ वजहों से एक अनुमानित
और "Commons" बंडल रणनीति का इस्तेमाल कर रहे हैं. इसमें यह चिंता भी शामिल है कि कई ज़्यादा एचटीटीपी अनुरोध करने से साइट की परफ़ॉर्मेंस पर बुरा असर पड़ सकता है.
ब्राउज़र किसी एक ऑरिजिन (Chrome के लिए 6) पर सीमित संख्या में टीसीपी कनेक्शन खोल सकते हैं. इसलिए, किसी बंडलर के आउटपुट किए गए हिस्सों को कम करने से, यह पक्का किया जा सकता है कि अनुरोधों की कुल संख्या इस थ्रेशोल्ड के अंदर रहे. हालांकि, यह सिर्फ़ एचटीटीपी/1.1 पर लागू होता है. एचटीटीपी/2 में मल्टीप्लेक्सिंग से, किसी एक ऑरिजिन पर, एक कनेक्शन का इस्तेमाल करके एक साथ कई अनुरोध स्ट्रीम किए जा सकते हैं. दूसरे शब्दों में कहें, तो हमें आम तौर पर हमारे बंडलर से उत्सर्जित होने वाले हिस्सों को सीमित करने की ज़रूरत नहीं होती.
सभी मुख्य ब्राउज़र पर एचटीटीपी/2 काम करता है. Chrome और Next.js टीम
यह देखना चाहती थी कि Next.js के एक "Commons" बंडल को कई शेयर किए गए हिस्सों में
बांटने से अनुरोधों की संख्या बढ़ाने पर, लोडिंग परफ़ॉर्मेंस पर किसी भी तरह असर होगा या नहीं. इनकी शुरुआत किसी एक साइट की परफ़ॉर्मेंस को मेज़र करके की गई. इसमें maxInitialRequests
प्रॉपर्टी का इस्तेमाल करके, साथ-साथ मिलने वाले अनुरोधों की ज़्यादा से ज़्यादा संख्या में बदलाव किया गया.
किसी एक वेब पेज पर, औसतन तीन बार कई बार मुफ़्त में आज़माने के दौरान, load
, स्टार्ट-रेंडर, और फ़र्स्ट कॉन्टेंटफ़ुल पेंट समय करीब-करीब एक जैसा ही रहता है. हालांकि, शुरुआती अनुरोधों की संख्या 5 से 15 तक होती है. दिलचस्प बात यह है कि हमने सैकड़ों अनुरोधों को तेज़ी से बांटने के बाद ही, परफ़ॉर्मेंस में थोड़ा सुधार देखा.
इससे पता चला कि एक भरोसेमंद थ्रेशोल्ड (20~25 अनुरोध) के तहत बने रहने से, कॉन्टेंट लोड होने और
कैश मेमोरी में डेटा सेव करने की क्षमता के बीच सही संतुलन बना. कुछ बेसलाइन टेस्टिंग के बाद, 25 को maxInitialRequest
की गिनती के तौर
पर चुना गया.
साथ-साथ होने वाले अनुरोधों की ज़्यादा से ज़्यादा संख्या में बदलाव करने से एक से ज़्यादा शेयर किए गए बंडल मिले. साथ ही, हर एंट्री पॉइंट के लिए उन्हें सही तरीके से अलग-अलग करने से उसी पेज के लिए ग़ैर-ज़रूरी कोड की संख्या में कमी आई.
इस प्रयोग का मकसद सिर्फ़ अनुरोधों की संख्या में बदलाव करना था, ताकि यह देखा जा सके कि पेज लोड होने की परफ़ॉर्मेंस पर इसका कोई बुरा असर तो नहीं पड़ेगा. नतीजों से पता चलता है कि टेस्ट पेज पर maxInitialRequests
को
25
पर सेट करना सबसे बेहतर था, क्योंकि इसने पेज को धीमा किए बिना JavaScript पेलोड का साइज़ कम
कर दिया. पेज को हाइड्रेट करने के लिए जिस JavaScript की ज़रूरत थी वह अब भी वैसी ही बनी रही.
इससे पता चलता है कि कोड की कम संख्या से पेज लोड की परफ़ॉर्मेंस में सुधार क्यों नहीं हुआ.
किसी हिस्से को जनरेट करने के लिए वेबपैक, डिफ़ॉल्ट साइज़ के तौर पर 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 में, एक फ़्लैग के साथ लॉन्च किया गया था. इसमें, इसे कई शुरुआती उपभोक्ताओं पर टेस्ट किया गया था. कई लोगों ने अपनी पूरी साइट के लिए इस्तेमाल किए जाने वाले JavaScript में काफ़ी कमी देखी:
वेबसाइट | JS में कुल बदलाव | % का अंतर |
---|---|---|
https://www.barnebys.com/ | -238 केबी | -23% से ज़्यादा |
https://sumup.com/ | -220 केबी | -30% से कम |
https://www.hashicorp.com/ | -11 एमबी | -71% से कम |
वर्शन 9.2 में, फ़ाइनल वर्शन डिफ़ॉल्ट रूप से शिप किया गया था.
गैट्सबी
सामान्य मॉड्यूल तय करने के लिए, इस्तेमाल पर आधारित अनुमान का इस्तेमाल करने के तरीके को ही 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% |
PR पर एक नज़र डालें और जानें कि उसने अपने webpack कॉन्फ़िगरेशन में इस लॉजिक को कैसे लागू किया है, जिसे v2.20.7 में डिफ़ॉल्ट रूप से शिप किया जाता है.
नतीजा
शिपिंग के विस्तृत हिस्सों को भेजने का सिद्धांत Next.js, Gatsby या webpack तक ही सीमित नहीं है. अगर यह किसी बड़े "कॉमंस" बंडल वाले तरीके का पालन करती है, तो सभी लोगों को अपने ऐप्लिकेशन के छोटे-छोटे हिस्सों को इस्तेमाल करने की रणनीति को बेहतर बनाना चाहिए. भले ही, कोई भी फ़्रेमवर्क या मॉड्यूलर इस्तेमाल किया जा रहा हो.
- अगर आपको वनीला रिऐक्ट ऐप्लिकेशन पर एक ही तरह के छोटे-छोटे ऑप्टिमाइज़ेशन लागू करने हैं, तो इस सैंपल रिऐक्ट ऐप्लिकेशन पर नज़र डालें. इसमें बारीक लेवल वाली चंकिंग रणनीति के आसान वर्शन का इस्तेमाल किया जाता है. साथ ही, इससे आपको अपनी साइट पर उसी तरह का लॉजिक लागू करने में मदद मिल सकती है.
- रोलअप के लिए, हिस्से डिफ़ॉल्ट रूप से बारीक तरीके से बनाए जाते हैं. अगर आपको व्यवहार को मैन्युअल तरीके से कॉन्फ़िगर करना है, तो
manualChunks
पर जाएं.