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