स्क्रिप्ट लोड करते समय, ब्राउज़र को स्क्रिप्ट लोड होने से पहले उनका आकलन करने में समय लगता है. इस वजह से, कई काम लंबे समय तक किए जा सकते हैं. जानें कि स्क्रिप्ट की जांच कैसे की जाती है. साथ ही, पेज लोड होने के दौरान लंबे टास्क न करने के लिए क्या किया जा सकता है.
जब इंटरैक्शन टू नेक्स्ट पेंट (आईएनपी) को ऑप्टिमाइज़ करने की बात आती है, तो आपको मिलने वाली ज़्यादातर सलाह यह होती है कि इंटरैक्शन को खुद ऑप्टिमाइज़ करना हो. उदाहरण के लिए, लंबे टास्क ऑप्टिमाइज़ करने की गाइड में, setTimeout
, isInputPending
वगैरह की मदद से मिलने वाले फ़ायदों जैसी तकनीकों के बारे में बताया गया है. ये तकनीक फ़ायदेमंद हैं, क्योंकि इनसे मुख्य थ्रेड को लंबे समय तक काम करने से बचाया जा सकता है. इससे, बातचीत और अन्य गतिविधियों को जल्दी पूरा करने के ज़्यादा मौके मिलते हैं, न कि लंबे टास्क के लिए इंतज़ार करने की.
हालांकि, स्क्रिप्ट लोड करने से होने वाले लंबे टास्क का क्या होगा? ये टास्क, उपयोगकर्ता के इंटरैक्शन में रुकावट डाल सकते हैं. साथ ही, लोड होने के दौरान पेज के आईएनपी पर असर डाल सकते हैं. इस गाइड में बताया गया है कि ब्राउज़र, स्क्रिप्ट की जांच से शुरू हुए टास्क को कैसे मैनेज करते हैं. साथ ही, इस बारे में भी जानकारी मिलेगी कि स्क्रिप्ट की जांच के काम को अलग-अलग करने के लिए क्या किया जा सकता है. इससे, पेज लोड होने के दौरान उपयोगकर्ता के इनपुट के हिसाब से, आपका मुख्य थ्रेड ज़्यादा रिस्पॉन्सिव बन सकता है.
स्क्रिप्ट इवैलुएशन क्या है?
अगर आपने ऐसे ऐप्लिकेशन की प्रोफ़ाइल बनाई है जो बहुत ज़्यादा JavaScript भेजता है, तो शायद आपने ऐसे लंबे टास्क देखे हों जिनमें गड़बड़ी वाले टास्क को स्क्रिप्ट का आकलन करें के तौर पर लेबल किया गया हो.
स्क्रिप्ट का आकलन करना, ब्राउज़र में JavaScript को लागू करने का एक ज़रूरी हिस्सा है. इसकी वजह यह है कि JavaScript को इस्तेमाल करने से ठीक पहले इकट्ठा किया जाता है. स्क्रिप्ट का आकलन होने पर, उसे गड़बड़ियों के लिए पहले पार्स किया जाता है. अगर पार्सर को गड़बड़ियां नहीं मिलती हैं, तो स्क्रिप्ट को बाइटकोड में इकट्ठा करके, उसे चलाया जा सकता है.
हालांकि, स्क्रिप्ट की जांच करना मुश्किल हो सकता है, क्योंकि हो सकता है कि किसी पेज के रेंडर होने के कुछ समय बाद ही, उपयोगकर्ता उससे इंटरैक्ट करने की कोशिश करे. हालांकि, किसी पेज के रेंडर होने का मतलब यह नहीं है कि पेज पूरी तरह लोड हो गया है. पेज लोड होने के दौरान होने वाले इंटरैक्शन में देरी हो सकती है, क्योंकि पेज, स्क्रिप्ट का आकलन करने में व्यस्त है. इस बात की कोई गारंटी नहीं है कि इस समय मनचाहा इंटरैक्शन हो सकता है—क्योंकि इसके लिए ज़िम्मेदार स्क्रिप्ट अब तक लोड नहीं हुई है. हालांकि, JavaScript पर निर्भर ऐसे इंटरैक्शन हो सकते हैं जो तैयार हैं या इंटरैक्टिविटी JavaScript पर बिलकुल भी निर्भर नहीं है.
स्क्रिप्ट और उनका आकलन करने वाले टास्क के बीच का संबंध
स्क्रिप्ट के आकलन के लिए ज़िम्मेदार टास्क को शुरू करने का तरीका इस बात पर निर्भर करता है कि आप जो स्क्रिप्ट लोड कर रहे हैं वह सामान्य <script>
एलिमेंट से लोड होती है या कोई स्क्रिप्ट type=module
के साथ लोड किया गया मॉड्यूल है. ब्राउज़र में चीज़ों को अलग तरीके से हैंडल करने की आदत होती है. इसलिए, मुख्य ब्राउज़र इंजन, स्क्रिप्ट की जांच को कैसे हैंडल करते हैं, यह इससे जुड़ा होगा कि उन सभी के स्क्रिप्ट की जांच के व्यवहार अलग-अलग हैं.
<script>
एलिमेंट के साथ स्क्रिप्ट लोड की जा रही हैं
आम तौर पर, स्क्रिप्ट का आकलन करने के लिए भेजे गए टास्क की संख्या, पेज पर मौजूद <script>
एलिमेंट की संख्या से सीधे तौर पर जुड़ी होती है. हर <script>
एलिमेंट, अनुरोध की गई स्क्रिप्ट का आकलन करने के लिए टास्क शुरू करता है, ताकि इसे पार्स, कंपाइल, और एक्ज़ीक्यूट किया जा सके. यह स्थिति Chromium पर आधारित ब्राउज़र, Safari, और Firefox पर होती है.
यह क्यों मायने रखता है? मान लें कि अपनी प्रोडक्शन स्क्रिप्ट को मैनेज करने के लिए किसी बंडलर का इस्तेमाल किया जा रहा है और आपने पेज को एक ही स्क्रिप्ट में चलाने के लिए ज़रूरी सभी चीज़ों को बंडल करने के लिए उसे कॉन्फ़िगर किया है. अगर आपकी वेबसाइट पर भी ऐसा होता है, तो उस स्क्रिप्ट का आकलन करने के लिए, आपको एक टास्क भेजा जाएगा. क्या यह कोई बुरी बात है? ज़रूरी नहीं है—जब तक कि वह स्क्रिप्ट बड़ी न हो.
JavaScript के बड़े हिस्सों को लोड करने से बचते हुए, स्क्रिप्ट की जांच करने से जुड़े काम को अलग-अलग किया जा सकता है. साथ ही, <script>
एलिमेंट का इस्तेमाल करके, ज़्यादा अलग-अलग और छोटी स्क्रिप्ट लोड की जा सकती हैं.
आपको हमेशा पेज लोड होने के दौरान कम से कम JavaScript लोड करने की कोशिश करनी चाहिए, लेकिन स्क्रिप्ट को अलग-अलग हिस्सों में बांटने से यह पक्का होता है कि मुख्य थ्रेड को ब्लॉक करने वाले किसी बड़े टास्क के बजाय, आपके पास छोटे-छोटे टास्क ज़्यादा संख्या में हों जो मुख्य थ्रेड को ब्लॉक नहीं करेंगे—या उससे कम होंगे, जो आपने शुरू किए थे.
स्क्रिप्ट की जांच के लिए, कई टास्क को अलग-अलग करके देखा जा सकता है. यह कुछ हद तक इंटरैक्शन के दौरान चलने वाले इवेंट कॉलबैक के दौरान यील्डिंग के मिलता-जुलता है. हालांकि, स्क्रिप्ट की जांच करने पर, यह प्रक्रिया पूरी होने वाली JavaScript को, लोड की गई कई छोटी स्क्रिप्ट में बांट देती है. इससे मुख्य थ्रेड को ब्लॉक करने की संभावना ज़्यादा होती है. ऐसा इसलिए होता है, न कि बड़ी स्क्रिप्ट की कम संख्या.
<script>
एलिमेंट और type=module
एट्रिब्यूट के साथ स्क्रिप्ट लोड की जा रही हैं
अब ES मॉड्यूल को ब्राउज़र में मूल रूप से, <script>
एलिमेंट पर मौजूद type=module
एट्रिब्यूट के साथ लोड किया जा सकता है. स्क्रिप्ट लोड करने के इस तरीके से, डेवलपर को कुछ फ़ायदे मिलते हैं. जैसे, प्रोडक्शन में इस्तेमाल के लिए कोड को नहीं बदलना. खास तौर पर जब इसे इंपोर्ट मैप के साथ इस्तेमाल किया जाता है. हालांकि, स्क्रिप्ट को इस तरीके से लोड करने से, अलग-अलग ब्राउज़र के लिए अलग-अलग टास्क शेड्यूल हो जाते हैं.
Chromium-आधारित ब्राउज़र
Chrome जैसे ब्राउज़र या इससे हासिल किए गए ब्राउज़र में, type=module
एट्रिब्यूट का इस्तेमाल करके ES मॉड्यूल लोड करने पर, इस तरह के टास्क मिलते हैं जो आम तौर पर type=module
का इस्तेमाल न करने पर होते हैं. उदाहरण के लिए, हर मॉड्यूल स्क्रिप्ट के लिए एक टास्क चलेगा, जिसमें कंपाइल मॉड्यूल के तौर पर लेबल की गई गतिविधि शामिल है.
मॉड्यूल कंपाइल होने के बाद, उनमें चलने वाला कोई भी कोड मॉड्यूल का आकलन करें लेबल वाली गतिविधि शुरू कर देगा.
Chrome और इससे जुड़े ब्राउज़र में इसका असर यह होता है कि ES मॉड्यूल का इस्तेमाल करने पर, कंपाइलेशन के चरण अलग हो जाते हैं. लंबे टास्क मैनेज करने के मामले में यह साफ़ तौर पर एक फ़ायदा है. हालांकि, मॉड्यूल की जांच के नतीजे से नतीजे मिलते हैं. इसका मतलब है कि आप कुछ खर्च से बच सकते हैं. आपको कम से कम JavaScript भेजने की कोशिश करनी चाहिए, फिर चाहे ब्राउज़र किसी भी तरह के हो, ES मॉड्यूल का इस्तेमाल करने से ये फ़ायदे मिलते हैं:
- सभी मॉड्यूल कोड स्ट्रिक्ट मोड में अपने-आप चलते हैं. इससे JavaScript इंजन ऐसे संभावित ऑप्टिमाइज़ेशन कर सकते हैं जिन्हें किसी मुश्किल स्थिति में नहीं किया जा सकता.
type=module
का इस्तेमाल करके लोड की गई स्क्रिप्ट को, डिफ़ॉल्ट रूप से स्थगित किया जाता है. इस व्यवहार में बदलाव के लिए,type=module
के साथ लोड की गई स्क्रिप्ट परasync
एट्रिब्यूट का इस्तेमाल किया जा सकता है.
Safari और Firefox
जब Safari और Firefox में मॉड्यूल लोड किए जाते हैं, तो उनमें से हर एक का एक अलग टास्क में आकलन किया जाता है. इसका मतलब है कि सैद्धांतिक तौर पर, अन्य मॉड्यूल के लिए सिर्फ़ स्टैटिक import
स्टेटमेंट वाले एक टॉप-लेवल मॉड्यूल को लोड किया जा सकता है. इसके अलावा, लोड किए गए हर मॉड्यूल की जांच करने के लिए, एक अलग नेटवर्क अनुरोध और टास्क लागू होंगे.
डाइनैमिक import()
के साथ स्क्रिप्ट लोड हो रही हैं
स्क्रिप्ट लोड करने का एक और तरीका डाइनैमिक import()
है. ES मॉड्यूल के सबसे ऊपर दिखने वाले स्टैटिक import
स्टेटमेंट के उलट, डाइनैमिक import()
कॉल, स्क्रिप्ट में कहीं भी दिख सकता है, ताकि मांग पर JavaScript का कुछ हिस्सा लोड किया जा सके. इस तकनीक को कोड बांटना कहा जाता है.
आईएनपी को बेहतर बनाने के मामले में, डाइनैमिक import()
के दो फ़ायदे हैं:
- जिन मॉड्यूल को बाद में लोड होने के लिए स्थगित कर दिया जाता है वे उस समय लोड की गई JavaScript की मात्रा को कम करके, स्टार्टअप के दौरान मुख्य थ्रेड के विवाद को कम करते हैं. इससे मुख्य थ्रेड हट जाती है, ताकि यह उपयोगकर्ता के इंटरैक्शन के लिए ज़्यादा रिस्पॉन्सिव हो.
- डाइनैमिक
import()
कॉल करने पर, हर कॉल, हर मॉड्यूल के कंपाइलेशन और इवैलुएशन को उसके टास्क से अलग करेगा. बेशक, बहुत बड़े मॉड्यूल को लोड करने वाला डाइनैमिकimport()
, स्क्रिप्ट की जांच का एक बड़ा टास्क शुरू कर देगा. इससे, डाइनैमिकimport()
कॉल के साथ इंटरैक्शन होने पर, मुख्य थ्रेड की उपयोगकर्ता के इनपुट का जवाब देने में रुकावट आ सकती है. इसलिए, यह अब भी बहुत ज़रूरी है कि आप कम से कम JavaScript लोड करें.
डाइनैमिक import()
कॉल, सभी मुख्य ब्राउज़र इंजन में एक जैसा ही काम करते हैं: स्क्रिप्ट की जांच करने वाले टास्क, डाइनैमिक तौर पर इंपोर्ट किए गए मॉड्यूल की संख्या के बराबर होंगे.
किसी वेब वर्कर में स्क्रिप्ट लोड हो रही हैं
वेब वर्कर, JavaScript के इस्तेमाल के खास उदाहरण होते हैं. वेब वर्कर, मुख्य थ्रेड पर रजिस्टर होते हैं और फिर वर्कर में मौजूद कोड अपने थ्रेड पर चलता है. यह इस हिसाब से बहुत फ़ायदेमंद है कि वेब वर्कर को रजिस्टर करने वाला कोड, मुख्य थ्रेड पर चलता है, लेकिन वेब वर्कर में मौजूद कोड काम नहीं करता. इससे मुख्य थ्रेड की कंजेशन कम हो जाती है. साथ ही, उपयोगकर्ता के इंटरैक्शन के लिए, मुख्य थ्रेड को ज़्यादा रिस्पॉन्सिव बनाने में मदद मिलती है.
मुख्य थ्रेड के काम को कम करने के अलावा, वेब वर्कर खुद ही बाहरी स्क्रिप्ट लोड कर सकते हैं, ताकि वर्कर के हिसाब से इनका इस्तेमाल किया जा सके. ऐसा उन ब्राउज़र में importScripts
या स्टैटिक import
स्टेटमेंट के ज़रिए किया जा सकता है जिनमें मॉड्यूल वर्कर काम करता है. इस वजह से, किसी वेब वर्कर से अनुरोध की गई किसी भी स्क्रिप्ट का आकलन, मुख्य थ्रेड से होता है.
ट्रेड-ऑफ़ और विचार
अपनी स्क्रिप्ट को अलग-अलग हिस्सों में बांटने के दौरान, छोटी फ़ाइलें लंबे टास्क को सीमित करने में मदद करती हैं, जबकि बहुत बड़ी फ़ाइलें कम लोड होती हैं. स्क्रिप्ट को अलग-अलग करने का तरीका तय करते समय कुछ बातों का ध्यान रखना ज़रूरी है.
कंप्रेस करने की क्षमता
स्क्रिप्ट के अलग-अलग वर्शन बनाने के लिए, कंप्रेशन का इस्तेमाल किया जाता है. जब स्क्रिप्ट छोटी होती हैं, तो कंप्रेस करने की प्रोसेस थोड़ी कम कारगर हो जाती है. बड़ी स्क्रिप्ट को कंप्रेस करने से ज़्यादा फ़ायदा होता है. एक ओर जहां कंप्रेशन दक्षता बढ़ाने से स्क्रिप्ट को लोड होने में लगने वाले समय को सबसे कम बनाए रखने में मदद मिलती है, वहीं दूसरी तरफ़ स्क्रिप्ट को छोटे-छोटे हिस्सों में बांटकर उन्हें छोटे-छोटे हिस्सों में बांटा जा सकता है, ताकि स्टार्टअप के दौरान बेहतर तरीके से इंटरैक्ट किया जा सके.
बंडलर, आपकी वेबसाइट के लिए तय की गई स्क्रिप्ट के आउटपुट साइज़ को मैनेज करने के लिए सबसे अच्छे टूल हैं:
- जहां वेबपैक का सवाल है, तो इसका
SplitChunksPlugin
प्लगिन मदद कर सकता है. एसेट के साइज़ को मैनेज करने के लिए, आप जो विकल्प सेट कर सकते हैं उनके बारे में जानने के लिए,SplitChunksPlugin
का दस्तावेज़ देखें. - Rollup और esbuild जैसे दूसरे बंडलर के लिए, अपने कोड में डाइनैमिक
import()
कॉल का इस्तेमाल करके, स्क्रिप्ट फ़ाइल का साइज़ मैनेज किया जा सकता है. वेबपैक के साथ-साथ, ये बंडलर, डाइनैमिक तौर पर इंपोर्ट की गई ऐसेट को अपनी फ़ाइल में अपने-आप अलग कर देंगे. इसलिए, शुरुआती बंडल साइज़ के बड़े सोर्स का इस्तेमाल नहीं किया जाएगा.
कैश मेमोरी अमान्य होना
कैश मेमोरी अमान्य होने की वजह से यह तय होता है कि बार-बार विज़िट करने पर कोई पेज कितनी तेज़ी से लोड होता है. जब बड़े और मोनोलिथिक स्क्रिप्ट बंडल शिप किए जाते हैं, तो ब्राउज़र को कैश मेमोरी में सेव करने की बात करें, तो आपकी परेशानी कम होती है. ऐसा इसलिए है, क्योंकि पैकेज अपडेट करने या शिपिंग की गड़बड़ियों को ठीक करने के ज़रिए, पहले पक्ष के कोड को अपडेट करने पर, पूरा बंडल अमान्य हो जाता है और उसे फिर से डाउनलोड करना पड़ता है.
अपनी स्क्रिप्ट को बांटने से, न सिर्फ़ छोटे-छोटे टास्क में स्क्रिप्ट की जांच के काम को अलग किया जाता है, बल्कि इससे इस बात की भी संभावना बढ़ जाती है कि वेबसाइट पर आने वाले लोग, नेटवर्क के बजाय ब्राउज़र कैश से ज़्यादा स्क्रिप्ट पाएं. इस वजह से, पेज तेज़ी से लोड होता है.
नेस्ट किए गए मॉड्यूल और लोड होने की परफ़ॉर्मेंस
अगर प्रोडक्शन में ES मॉड्यूल शिप किए जा रहे हैं और उन्हें type=module
एट्रिब्यूट के साथ लोड किया जा रहा है, तो आपको पता होना चाहिए कि मॉड्यूल नेस्टिंग से, स्टार्टअप के समय पर क्या असर पड़ सकता है. मॉड्यूल नेस्टिंग का मतलब है कि जब कोई ES मॉड्यूल, किसी दूसरे ES मॉड्यूल को स्टैटिक रूप से इंपोर्ट करता है, जो किसी अन्य ES मॉड्यूल को स्टैटिक रूप से इंपोर्ट करता है:
// a.js
import {b} from './b.js';
// b.js
import {c} from './c.js';
अगर आपके ES मॉड्यूल एक साथ बंडल नहीं किए गए हैं, तो पिछले कोड से नेटवर्क अनुरोध चेन बनती है: जब a.js
का अनुरोध <script>
एलिमेंट से किया जाता है, तो b.js
के लिए एक और नेटवर्क अनुरोध भेजा जाता है. इसके बाद, c.js
के लिए एक और अनुरोध भेजा जाता है. इससे बचने का एक तरीका यह है कि आप बंडलर का इस्तेमाल करें. हालांकि, यह पक्का कर लें कि आपने अपने बंडलर को इस तरह कॉन्फ़िगर किया हो कि आप स्क्रिप्ट की जांच करने के काम को, स्क्रिप्ट के अलग-अलग हिस्सों में बांटने के लिए सेट कर रहे हों.
अगर आपको किसी बंडलर का इस्तेमाल नहीं करना है, तो नेस्ट किए गए मॉड्यूल कॉल का एक और तरीका है, modulepreload
रिसॉर्स हिंट का इस्तेमाल करना. यह नेटवर्क के अनुरोध की चेन से बचने के लिए, ES मॉड्यूल को समय से पहले ही लोड कर देगा.
नतीजा
इसमें कोई संदेह नहीं है कि ब्राउज़र में स्क्रिप्ट के मूल्यांकन को ऑप्टिमाइज़ करना एक मुश्किल काम है. यह तरीका आपकी वेबसाइट से जुड़ी ज़रूरी शर्तों और पाबंदियों पर निर्भर करता है. हालांकि, स्क्रिप्ट को अलग-अलग करके, कई छोटे टास्क में स्क्रिप्ट का आकलन किया जा रहा है. इसलिए, मुख्य थ्रेड को ब्लॉक करने के बजाय, मुख्य थ्रेड को उपयोगकर्ता के इंटरैक्शन को ज़्यादा बेहतर तरीके से मैनेज करने की सुविधा दी जा रही है.
हम आपको याद दिलाना चाहते हैं कि स्क्रिप्ट की जांच करने वाले बड़े टास्क को अलग-अलग करने के लिए, ये काम किए जा सकते हैं:
type=module
एट्रिब्यूट के बिना<script>
एलिमेंट का इस्तेमाल करके स्क्रिप्ट लोड करते समय, ऐसी स्क्रिप्ट लोड करने से बचें जो बहुत बड़ी हों. ऐसा करने से, मुख्य थ्रेड को ब्लॉक करने वाले रिसॉर्स-इंटेसिव स्क्रिप्ट आकलन टास्क बंद हो जाएंगे. इस काम को खत्म करने के लिए, अपनी स्क्रिप्ट को<script>
से ज़्यादा एलिमेंट पर फैलाएं.- ब्राउज़र में ES मॉड्यूल को मूल रूप से लोड करने के लिए,
type=module
एट्रिब्यूट का इस्तेमाल करने से, हर अलग मॉड्यूल स्क्रिप्ट के आकलन के लिए अलग-अलग टास्क शुरू हो जाएंगे. - डाइनैमिक
import()
कॉल का इस्तेमाल करके, अपने शुरुआती बंडल का साइज़ कम करें. यह बंडलर में भी काम करता है, क्योंकि बंडलर डाइनैमिक तौर पर इंपोर्ट किए गए हर मॉड्यूल को "स्प्लिट पॉइंट" के तौर पर इस्तेमाल करेंगे. इसकी वजह से, डाइनैमिक तौर पर इंपोर्ट किए गए हर मॉड्यूल के लिए एक अलग स्क्रिप्ट जनरेट होगी. - कम्प्रेशन परफ़ॉर्मेंस और कैश अमान्य होने जैसी सुविधाओं पर ध्यान देना न भूलें. बड़ी स्क्रिप्ट को बेहतर तरीके से कंप्रेस किया जाएगा, लेकिन इस बात की संभावना ज़्यादा होती है कि उनमें ज़्यादा महंगे स्क्रिप्ट इवैलुएशन से जुड़े काम शामिल हों और वे कम टास्क में हों. इसकी वजह से, ब्राउज़र कैश अमान्य हो जाता है और कैश मेमोरी में सेव करने की क्षमता कम हो जाती है.
- अगर बंडल किए बिना ES मॉड्यूल का इस्तेमाल किया जा रहा है, तो
modulepreload
रिसॉर्स हिंट का इस्तेमाल करें, ताकि स्टार्टअप के दौरान उनकी लोडिंग को ऑप्टिमाइज़ किया जा सके. - हमेशा की तरह, कम से कम JavaScript भेजें.
यह पक्का करने के लिए एक संतुलन है, लेकिन डाइनैमिक import()
के ज़रिए स्क्रिप्ट को अलग-अलग करके और शुरुआती पेलोड कम करके, आप बेहतर स्टार्टअप परफ़ॉर्मेंस पा सकते हैं. साथ ही, उस अहम स्टार्टअप अवधि के दौरान उपयोगकर्ताओं के इंटरैक्शन को बेहतर तरीके से शामिल कर सकते हैं. इससे आपको आईएनपी मेट्रिक में बेहतर स्कोर पाने में मदद मिलेगी और आपको बेहतर उपयोगकर्ता अनुभव मिलेगा.
Unsplash की हीरो इमेज, जिसे मार्कस स्पिस्क ने लिखा है.