कोड-स्प्लिट JavaScript

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

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

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

कोड स्प्लिटिंग की मदद से, स्टार्टअप के दौरान JavaScript पार्स करने और एक्ज़ीक्यूशन कम करें

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

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

Lighthouse ऑडिट, हर उस JavaScript फ़ाइल के लागू होने में लगने वाले समय की रिपोर्ट करता है जिसके लिए आपके पेज का अनुरोध किया गया है. इससे आपको यह पहचानने में मदद मिल सकती है कि कोड को अलग-अलग करने के लिए कौनसी स्क्रिप्ट का इस्तेमाल किया जा सकता है. इसके बाद, Chrome DevTools में कवरेज टूल का इस्तेमाल करके, यह पता लगाया जा सकता है कि पेज लोड होने के दौरान, पेज के JavaScript के किन हिस्सों का इस्तेमाल नहीं किया जाता.

कोड को बांटना एक उपयोगी तकनीक है, जिससे किसी पेज के शुरुआती JavaScript पेलोड को कम किया जा सकता है. इससे JavaScript बंडल को दो हिस्सों में बांटा जा सकता है:

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

डाइनैमिक import() सिंटैक्स का इस्तेमाल करके, कोड को अलग-अलग किया जा सकता है. यह सिंटैक्स—स्टार्टअप के दौरान दिए गए JavaScript संसाधन का अनुरोध करने वाले <script> एलिमेंट के उलट, पेज की लाइफ़साइकल के दौरान बाद में, JavaScript संसाधन के लिए अनुरोध करता है.

document.querySelectorAll('#myForm input').addEventListener('blur', async () => {
  // Get the form validation named export from the module through destructuring:
  const { validateForm } = await import('/validate-form.mjs');

  // Validate the form:
  validateForm();
}, { once: true });

पिछले JavaScript स्निपेट में, validate-form.mjs मॉड्यूल सिर्फ़ तब डाउनलोड, पार्स, और एक्ज़ीक्यूट किया जाता है, जब कोई उपयोगकर्ता किसी फ़ॉर्म के <input> फ़ील्ड को धुंधला करता है. ऐसी स्थिति में, फ़ॉर्म की पुष्टि करने वाला लॉजिक, पेज पर सिर्फ़ तब शामिल होता है, जब उस JavaScript रिसॉर्स का इस्तेमाल किया जाना हो.

जब भी आपके सोर्स कोड में डाइनैमिक import() कॉल होती है, तब वेबपैक, Parcel, Rollup, और esbuild जैसे JavaScript बंडलर को कॉन्फ़िगर किया जा सकता है, ताकि JavaScript बंडल को छोटे-छोटे हिस्सों में बांटा जा सके. इनमें से ज़्यादातर टूल अपने-आप यह काम कर लेते हैं. हालांकि, खास तौर पर इसे अपडेट करने के लिए आपको ऑप्टिमाइज़ेशन के लिए ऑप्ट-इन करना पड़ता है.

कोड को अलग-अलग हिस्सों में बांटने से जुड़ी ज़रूरी जानकारी

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

अगर हो सके, तो बंडलर का इस्तेमाल करें

डेवलपमेंट प्रोसेस के दौरान, डेवलपर के लिए JavaScript मॉड्यूल का इस्तेमाल करना आम बात है. यह डेवलपर को बेहतर अनुभव देने वाला बेहतरीन अनुभव है. इससे कोड को पढ़ने और उसके रखरखाव में सुधार होता है. हालांकि, परफ़ॉर्मेंस से जुड़ी कुछ ऐसी विशेषताएं होती हैं जिनकी वजह से JavaScript मॉड्यूल को प्रोडक्शन में भेजा जा सकता है.

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

बंडलर नेटवर्क पर बड़ी संख्या में अनबंडल किए गए मॉड्यूल को भेजने की समस्या से भी बचते हैं. JavaScript मॉड्यूल का इस्तेमाल करने वाले आर्किटेक्चर में, बड़े और मुश्किल मॉड्यूल ट्री होते हैं. जब मॉड्यूल ट्री अनबंडल किए जाते हैं, तो हर मॉड्यूल एक अलग एचटीटीपी अनुरोध दिखाता है. अगर मॉड्यूल बंडल नहीं किए जाते हैं, तो आपके वेब ऐप्लिकेशन में इंटरैक्टिविटी में देरी हो सकती है. बड़े मॉड्यूल ट्री को जल्द से जल्द लोड करने के लिए, <link rel="modulepreload"> रिसॉर्स हिंट का इस्तेमाल किया जा सकता है. हालांकि, लोड होने की परफ़ॉर्मेंस के स्टैंडर्ड के मुकाबले, JavaScript बंडल को प्राथमिकता दी जाती है.

अनजाने में स्ट्रीमिंग कंपाइलेशन बंद न करें

Chromium का V8 JavaScript इंजन, कई तरह के ऑप्टिमाइज़ेशन की सुविधा देता है, ताकि यह पक्का किया जा सके कि आपका प्रोडक्शन JavaScript कोड ज़्यादा से ज़्यादा बेहतर तरीके से लोड हो. इनमें से एक ऑप्टिमाइज़ेशन को स्ट्रीमिंग कंपाइलेशन कहा जाता है. जैसे, ब्राउज़र पर स्ट्रीम किए गए एचटीएमएल की इंक्रीमेंटल पार्सिंग, नेटवर्क से आते ही JavaScript के स्ट्रीम किए गए हिस्सों को कंपाइल करती है.

आपके पास यह पक्का करने के कई तरीके हैं कि Chromium में आपके वेब ऐप्लिकेशन के लिए स्ट्रीमिंग कंपाइलेशन होती है या नहीं:

  • JavaScript मॉड्यूल का इस्तेमाल करने से बचने के लिए, अपने प्रोडक्शन कोड को पूरी तरह बदलें. बंडलर, कंपाइलेशन टारगेट के आधार पर आपके JavaScript सोर्स कोड को बदल सकते हैं. साथ ही, टारगेट अक्सर दिए गए एनवायरमेंट के लिए खास होता है. V8 स्ट्रीमिंग कंपाइलेशन को ऐसे किसी भी JavaScript कोड पर लागू कर देगा जो मॉड्यूल का इस्तेमाल नहीं करता है. साथ ही, अपने बंडलर को ऐसे सिंटैक्स में बदलने के लिए कॉन्फ़िगर किया जा सकता है जिसमें JavaScript मॉड्यूल और उनकी सुविधाओं का इस्तेमाल न किया जाता हो.
  • अगर आपको JavaScript मॉड्यूल को प्रोडक्शन में भेजना है, तो .mjs एक्सटेंशन का इस्तेमाल करें. आपके प्रोडक्शन JavaScript में मॉड्यूल का इस्तेमाल किया जाता हो या न हो, इसके लिए JavaScript के लिए ऐसा कोई खास कॉन्टेंट टाइप नहीं है जिसमें मॉड्यूल बनाम JavaScript का इस्तेमाल न किया जाता हो. जहां V8 की बात हो, तो असल में स्ट्रीमिंग कंपाइलेशन से ऑप्ट आउट कर लिया जाता है. ऐसा तब होता है, जब .js एक्सटेंशन का इस्तेमाल करके, JavaScript मॉड्यूल को प्रोडक्शन में भेजा जाता है. अगर JavaScript मॉड्यूल के लिए .mjs एक्सटेंशन का इस्तेमाल किया जाता है, तो V8 यह पक्का कर सकता है कि मॉड्यूल पर आधारित JavaScript कोड के लिए, स्ट्रीमिंग कंपाइलेशन ठीक तरह से काम न करता हो.

इन बातों की वजह से, कोड को अलग-अलग करने में परेशानी न होने दें. कोड को स्प्लिट करना, उपयोगकर्ताओं के लिए शुरुआती JavaScript पेलोड को कम करने का एक असरदार तरीका है. हालांकि, बंडलर का इस्तेमाल करके और यह जानकर कि V8 के स्ट्रीमिंग कंपाइलेशन व्यवहार को कैसे सुरक्षित रखा जा सकता है, यह पक्का किया जा सकता है कि आपका प्रोडक्शन JavaScript कोड उपयोगकर्ताओं के लिए उतना ही तेज़ है जितना हो सकता है.

डाइनैमिक इंपोर्ट का डेमो

वेबपैक

webpack SplitChunksPlugin नाम के प्लगिन के साथ भेजा जाता है. इससे आपको यह कॉन्फ़िगर करने की सुविधा मिलती है कि बंडलर, JavaScript फ़ाइलों को कैसे अलग-अलग करता है. वेबपैक, डाइनैमिक import() और स्टैटिक import स्टेटमेंट, दोनों की पहचान करता है. SplitChunksPlugin के व्यवहार में बदलाव करने के लिए, उसके कॉन्फ़िगरेशन में chunks विकल्प तय करना होगा:

  • chunks: async डिफ़ॉल्ट वैल्यू है और यह डाइनैमिक import() कॉल के लिए इस्तेमाल की जाती है.
  • chunks: initial का मतलब है, स्टैटिक import कॉल.
  • chunks: all में, डाइनैमिक import() और स्टैटिक इंपोर्ट, दोनों शामिल होते हैं. इससे, async और initial इंपोर्ट के बीच कई हिस्सों को शेयर किया जा सकता है.

डिफ़ॉल्ट रूप से, जब भी वेबपैक को कोई डाइनैमिक import() स्टेटमेंट मिलता है, तो वह उस मॉड्यूल के लिए एक अलग हिस्से बनाता है:

/* main.js */

// An application-specific chunk required during the initial page load:
import myFunction from './my-function.js';

myFunction('Hello world!');

// If a specific condition is met, a separate chunk is downloaded on demand,
// rather than being bundled with the initial chunk:
if (condition) {
  // Assumes top-level await is available. More info:
  // https://v8.dev/features/top-level-await
  await import('/form-validation.js');
}

पिछले कोड स्निपेट के लिए डिफ़ॉल्ट वेबपैक कॉन्फ़िगरेशन से, दो अलग-अलग हिस्से मिलते हैं:

  • main.js डेटा सेगमेंट—जो वेबपैक, initial हिस्सों की कैटगरी में आता है—इसमें main.js और ./my-function.js मॉड्यूल शामिल हैं.
  • async हिस्सा, जिसमें सिर्फ़ form-validation.js शामिल होता है. अगर कॉन्फ़िगर किया गया हो, तो संसाधन के नाम में फ़ाइल हैश मौजूद होता है. यह हिस्सा सिर्फ़ तब डाउनलोड होता है, जब condition सही हो.

इस कॉन्फ़िगरेशन की मदद से, form-validation.js वाले हिस्से को तब तक लोड होने से रोका जा सकता है, जब तक उसकी ज़रूरत न हो. यह पेज लोड होने के शुरुआती समय के दौरान स्क्रिप्ट मूल्यांकन में लगने वाला समय कम करके, पेज के लोड होने का समय कम कर सकता है. कोई खास शर्त पूरी होने पर, form-validation.js वाले हिस्से के लिए स्क्रिप्ट डाउनलोड और उसका आकलन किया जाता है. इस स्थिति में, डाइनैमिक तरीके से इंपोर्ट किया गया मॉड्यूल डाउनलोड किया जाता है. इसका एक उदाहरण, ऐसी शर्त हो सकती है जिसमें पॉलीफ़िल को सिर्फ़ किसी खास ब्राउज़र के लिए डाउनलोड किया गया हो या—जैसा कि पिछले उदाहरण में बताया गया है—उपयोगकर्ता के इंटरैक्शन के लिए इंपोर्ट किया गया मॉड्यूल ज़रूरी है.

दूसरी ओर, SplitChunksPlugin के कॉन्फ़िगरेशन को chunks: initial के हिसाब से बदलने पर, यह पक्का होता है कि कोड सिर्फ़ शुरुआती हिस्सों में बंटा होगा. ये कुछ हिस्से होते हैं, जैसे कि स्टैटिक रूप से इंपोर्ट किए गए या वेबपैक की entry प्रॉपर्टी में शामिल. पिछले उदाहरण पर गौर करें, तो नतीजे के तौर पर मिलने वाला हिस्सा, एक ही स्क्रिप्ट फ़ाइल में form-validation.js और main.js को मिलाकर दिखेगा. इस वजह से, शुरुआत में पेज लोड होने की परफ़ॉर्मेंस खराब हो सकती है.

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

इसके अलावा, बड़ी JavaScript फ़ाइलें जनरेट करने का मतलब यह भी है कि स्क्रिप्ट को कैश मेमोरी के अमान्य होने की वजह से ज़्यादा परेशानी हो सकती है. उदाहरण के लिए, अगर फ़्रेमवर्क और पहले पक्ष के ऐप्लिकेशन कोड, दोनों के साथ बहुत बड़ी स्क्रिप्ट शिप की जाती है, तो सिर्फ़ फ़्रेमवर्क को अपडेट किए जाने पर पूरा बंडल अमान्य हो सकता है. बंडल के रिसॉर्स में और कुछ नहीं होने पर भी ऐसा होता है.

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

वेबपैक डेमो

वेबपैक SplitChunksPlugin डेमो.

देखें कि आपको कितना ज्ञान है

कोड को अलग-अलग हिस्सों में बांटने के लिए, किस तरह के import स्टेटमेंट का इस्तेमाल किया जाता है?

डाइनैमिक import().
सही जवाब!
स्टैटिक import.
फिर से कोशिश करें.

किस तरह का import स्टेटमेंट JavaScript मॉड्यूल में सबसे ऊपर होना चाहिए, और किसी दूसरी जगह पर नहीं होना चाहिए?

डाइनैमिक import().
फिर से कोशिश करें.
स्टैटिक import.
सही जवाब!

वेबपैक में SplitChunksPlugin का इस्तेमाल करते समय, async और initial हिस्सों में क्या अंतर है?

async हिस्सों को डाइनैमिक import() का इस्तेमाल करके लोड किया जाता है और initial हिस्सों को स्टैटिक import का इस्तेमाल करके लोड किया जाता है.
सही जवाब!
async हिस्सों को स्टैटिक import का इस्तेमाल करके लोड किया जाता है और initial हिस्सों को, डाइनैमिक import() का इस्तेमाल करके लोड किया जाता है.
फिर से कोशिश करें.

अगला कॉन्टेंट: इमेज और <iframe> एलिमेंट को देर से लोड करना

हालांकि, यह काफ़ी महंगा है, लेकिन JavaScript ही ऐसा रिसॉर्स टाइप नहीं है जिसे लोड होने से रोका जा सकता है. इमेज और <iframe> एलिमेंट, अपने-आप में महंगे रिसॉर्स हैं. JavaScript की तरह ही, इमेज और <iframe> एलिमेंट को लेज़ी लोडिंग से, लोड होने से रोका जा सकता है. इस बारे में इस कोर्स के अगले मॉड्यूल में बताया गया है.