आज के वेब ऐप्लिकेशन का साइज़ काफ़ी बड़ा हो सकता है. खास तौर पर, इनमें JavaScript का हिस्सा. साल 2018 के मध्य तक, एचटीटीपी आर्काइव के मुताबिक मोबाइल डिवाइसों पर JavaScript के ट्रांसफ़र का औसत साइज़ करीब 350 केबी था. यह सिर्फ़ ट्रांसफ़र का साइज़ है! नेटवर्क पर भेजे जाने पर JavaScript को अक्सर कंप्रेस किया जाता है. इसका मतलब है कि ब्राउज़र के डीकंप्रेस किए जाने के बाद, असल में JavaScript की संख्या थोड़ी ज़्यादा होती है. यह बात ध्यान में रखना ज़रूरी है, क्योंकि जहां तक संसाधन प्रोसेसिंग का सवाल है, कंप्रेस करने की प्रक्रिया काम की नहीं है. डिकंप्रेस किए गए 900 केबी के JavaScript को पार्स करने और कंपाइल करने में, 900 केबी का ही इस्तेमाल होता है. भले ही, कंप्रेस करने पर यह करीब 300 केबी हो जाए.
JavaScript को प्रोसेस करना महंगा होता है. इमेज को डाउनलोड करने के बाद, उसे डिकोड करने में ज़्यादा समय नहीं लगता. हालांकि, JavaScript को पार्स, कंपाइल, और फिर उसे एक्सीक्यूट करना पड़ता है. बाइट के लिए बाइट, इससे JavaScript अन्य प्रकार के संसाधनों की तुलना में ज़्यादा महंगा बन जाता है.
JavaScript इंजन की परफ़ॉर्मेंस को बेहतर बनाने के लिए, इसमें लगातार सुधार किए जा रहे हैं. हालांकि, JavaScript की परफ़ॉर्मेंस को बेहतर बनाना, हमेशा की तरह डेवलपर का काम है.
इस वजह से, JavaScript की परफ़ॉर्मेंस को बेहतर बनाने की कुछ तकनीकें बताई गई हैं. कोड को अलग-अलग करने की यह एक ऐसी तकनीक है जो ऐप्लिकेशन के JavaScript को अलग-अलग हिस्सों में बांटकर परफ़ॉर्मेंस को बेहतर बनाती है. साथ ही, उन हिस्सों को सिर्फ़ उन ऐप्लिकेशन के रूट पर दिखाती है जिनमें उनकी ज़रूरत होती है.
यह तकनीक काम करती है, लेकिन इससे JavaScript के ज़्यादा इस्तेमाल वाले ऐप्लिकेशन की एक आम समस्या हल नहीं होती. यह समस्या, ऐसे कोड को शामिल करने से जुड़ी है जिसका कभी इस्तेमाल नहीं किया जाता. ट्री शेकिंग की मदद से, इस समस्या को हल करने की कोशिश की जाती है.
ट्री शेकिंग क्या है?
ट्री शेकिंग, डेड कोड को हटाने का एक तरीका है. इस शब्द को रोलअप टूल से लोकप्रिय बनाया गया था. हालांकि, इस शब्द को बंद करने का सिद्धांत कुछ समय से मौजूद है. इस कॉन्सेप्ट को webpack में भी खरीदा गया है. इस बारे में इस लेख में, सैंपल ऐप्लिकेशन के ज़रिए बताया गया है.
"ट्री शेकिंग" शब्द, आपके ऐप्लिकेशन और उसकी डिपेंडेंसी को ट्री जैसे स्ट्रक्चर के तौर पर दिखाने वाले मॉडल से लिया गया है. ट्री में मौजूद हर नोड, एक ऐसी डिपेंडेंसी को दिखाता है जो आपके ऐप्लिकेशन के लिए अलग-अलग फ़ंक्शन उपलब्ध कराती है. आधुनिक ऐप्लिकेशन में, इन डिपेंडेंसी को स्टैटिक import
स्टेटमेंट के ज़रिए शामिल किया जाता है. जैसे:
// Import all the array utilities!
import arrayUtils from "array-utils";
जब कोई ऐप्लिकेशन नया हो, तो हो सकता है कि उसमें कुछ ही डिपेंडेंसी हों. यह आपकी जोड़ी गई सभी डिपेंडेंसी का इस्तेमाल कर रहा है. अगर सभी डिपेंडेंसी का इस्तेमाल नहीं किया जा रहा है, तो ज़्यादातर डिपेंडेंसी का इस्तेमाल किया जा रहा है. हालांकि, आपके ऐप्लिकेशन के बेहतर होने पर, ज़्यादा डिपेंडेंसी जोड़ी जा सकती हैं. समस्या को और भी मुश्किल बनाते हुए, पुरानी डिपेंडेंसी का इस्तेमाल बंद हो जाता है. हालांकि, हो सकता है कि वे आपके कोडबेस से न हटें. इसका नतीजा यह होता है कि ऐप्लिकेशन में इस्तेमाल न होने वाली JavaScript की बहुत सारी फ़ाइलें शामिल हो जाती हैं. ट्री शेकिंग की मदद से यह समस्या हल हो जाती है. इसके लिए, स्टैटिक import
स्टेटमेंट, ES6 मॉड्यूल के खास हिस्सों को कैप्चर करने के तरीके का फ़ायदा लेते हैं:
// Import only some of the utilities!
import { unique, implode, explode } from "array-utils";
इस import
उदाहरण और पिछले उदाहरण के बीच का अंतर यह है कि "array-utils"
मॉड्यूल से सब कुछ इंपोर्ट करने के बजाय, इस उदाहरण में इसके सिर्फ़ खास हिस्सों को इंपोर्ट किया जाता है. इसमें बहुत ज़्यादा कोड हो सकते हैं. डेवलपर के लिए बने बिल्ड में, इससे कोई फ़र्क़ नहीं पड़ता, क्योंकि पूरा मॉड्यूल इंपोर्ट हो जाता है. प्रोडक्शन बिल्ड में, वेबपैक को कॉन्फ़िगर किया जा सकता है, ताकि वह ES6 मॉड्यूल से एक्सपोर्ट को "हटाया" जा सके. ये ऐसे मॉड्यूल होते हैं जिन्हें साफ़ तौर पर इंपोर्ट नहीं किया गया था. इससे प्रोडक्शन बिल्ड छोटे हो जाते हैं. इस गाइड में, आपको ऐसा करने का तरीका पता चलेगा!
पेड़ हिलाने के मौके तलाशना
उदाहरण के लिए, एक पेज वाले ऐप्लिकेशन का सैंपल उपलब्ध है. इससे यह पता चलता है कि ट्री शेकिंग कैसे काम करती है. अगर आप चाहें, तो इसे क्लोन करें और निर्देशों का पालन करें. हालांकि, हम इस गाइड में हर चरण को एक साथ कवर करेंगे. इसलिए, क्लोन करने की ज़रूरत नहीं है. ऐसा सिर्फ़ तब करें, जब आपको सीधे तौर पर सीखना हो.
सैंपल ऐप्लिकेशन, गिटार इफ़ेक्ट पेडल का ऐसा डेटाबेस है जिसमें खोज की सुविधा होती है. कोई क्वेरी डालने पर, आपको इफ़ेक्ट वाले पेडल की सूची दिखेगी.
इस ऐप्लिकेशन के काम करने के तरीके को वेंडर (यानी, Preact और Emotion) और ऐप्लिकेशन के हिसाब से कोड बंडल (या "चंक", जैसा कि वेबपैक उन्हें कहते हैं):
ऊपर दिए गए इलस्ट्रेशन में दिखाए गए JavaScript बंडल, प्रोडक्शन बिल्ड हैं. इसका मतलब है कि उन्हें uglification की मदद से ऑप्टिमाइज़ किया गया है. किसी ऐप्लिकेशन के लिए बने बंडल का साइज़ 21.1 केबी होना बुरा नहीं है. हालांकि, ध्यान रखें कि कोई ट्री शेकिंग नहीं हो रही है. चलिए, ऐप्लिकेशन कोड पर नज़र डालते हैं और देखते हैं कि इसे ठीक करने के लिए क्या किया जा सकता है.
किसी भी ऐप्लिकेशन में, ट्री शेकिंग के अवसरों को ढूंढने के लिए, स्टैटिक import
स्टेटमेंट देखना होगा. मुख्य कॉम्पोनेंट फ़ाइल के सबसे ऊपर, आपको इस तरह की लाइन दिखेगी:
import * as utils from "../../utils/utils";
ES6 मॉड्यूल को कई तरीकों से इंपोर्ट किया जा सकता है. हालांकि, आपको इस तरह के मॉड्यूल पर ध्यान देना चाहिए. इस लाइन में कहा गया है कि "utils
मॉड्यूल से import
सब कुछ ले लो और उसे utils
नाम के नेमस्पेस में डाल दो." यहां सबसे अहम सवाल यह है कि "उस मॉड्यूल में कितना सामान है?"
utils
मॉड्यूल के सोर्स कोड को देखने पर, आपको पता चलेगा कि इसमें करीब 1,300 लाइनें हैं.
क्या आपको इन सभी चीज़ों की ज़रूरत है? आइए, मुख्य कॉम्पोनेंट फ़ाइल को खोजकर इसकी दोबारा जांच करें. यह फ़ाइल, utils
मॉड्यूल को इंपोर्ट करती है. इससे यह पता चलता है कि उस नेमस्पेस के कितने इंस्टेंस आए.
हमें पता चला है कि utils
नेमस्पेस हमारे ऐप्लिकेशन में सिर्फ़ तीन जगहों पर दिखता है—लेकिन किन फ़ंक्शन के लिए? अगर मुख्य कॉम्पोनेंट की फ़ाइल पर दोबारा नज़र डाला जाए, तो ऐसा लगता है कि सिर्फ़ एक फ़ंक्शन है, जो कि utils.simpleSort
है. इसका इस्तेमाल ड्रॉपडाउन मेन्यू बदलने पर, खोज के नतीजों की सूची को कई शर्तों के मुताबिक क्रम से लगाने के लिए किया जाता है:
if (this.state.sortBy === "model") {
// `simpleSort` gets used here...
json = utils.simpleSort(json, "model", this.state.sortOrder);
} else if (this.state.sortBy === "type") {
// ..and here...
json = utils.simpleSort(json, "type", this.state.sortOrder);
} else {
// ..and here.
json = utils.simpleSort(json, "manufacturer", this.state.sortOrder);
}
1,300 लाइनों वाली फ़ाइल में कई एक्सपोर्ट हैं, लेकिन उनमें से सिर्फ़ एक का इस्तेमाल किया गया है. इस वजह से, इस्तेमाल न किए गए बहुत सारे JavaScript को शिप किया जाता है.
इस ऐप्लिकेशन के उदाहरण भले ही थोड़े फ़र्ज़ी हों, लेकिन इससे इस बात पर कोई फ़र्क़ नहीं पड़ता कि एआई से जनरेट हुआ यह सिंथेटिक टूल, किसी प्रोडक्शन वेब ऐप्लिकेशन में ऑप्टिमाइज़ेशन के असल अवसरों जैसा दिखता है. अब जब आपने पेड़ के झटकों का पता लगा लिया है, तो असल में इसे कैसे इस्तेमाल किया जा सकता है?
Babel को ES6 मॉड्यूल को CommonJS मॉड्यूल में ट्रांसपाइल करने से रोकना
Babel एक ज़रूरी टूल है. हालांकि, इससे ट्री शेकिंग के असर को समझना थोड़ा मुश्किल हो सकता है. अगर @babel/preset-env
का इस्तेमाल किया जा रहा है, तो Babel हो सकता है कि ES6 मॉड्यूल को ज़्यादा काम के CommonJS मॉड्यूल में बदल दे. ये ऐसे मॉड्यूल होते हैं जिन्हें import
के बजाय require
में इस्तेमाल किया जाता है.
CommonJS मॉड्यूल के लिए, ट्री शेकिंग करना ज़्यादा मुश्किल होता है. इसलिए, अगर आपने बंडल का इस्तेमाल करने का फ़ैसला किया, तो webpack को यह नहीं पता होगा कि बंडल से क्या हटाया जाए. इसका समाधान यह है कि @babel/preset-env
को कॉन्फ़िगर करके, ES6 मॉड्यूल को छोड़ दिया जाए. Babel को babel.config.js
या package.json
में कहीं भी कॉन्फ़िगर करने पर, आपको कुछ और भी जोड़ना होगा:
// babel.config.js
export default {
presets: [
[
"@babel/preset-env", {
modules: false
}
]
]
}
अपने @babel/preset-env
कॉन्फ़िगरेशन में modules: false
तय करने से, Babel अपनी ज़रूरत के मुताबिक काम करता है. इससे webpack को आपके डिपेंडेंसी ट्री का विश्लेषण करने और इस्तेमाल न किए गए डिपेंडेंसी को हटाने में मदद मिलती है.
साइड इफ़ेक्ट को ध्यान में रखना
अपने ऐप्लिकेशन से डिपेंडेंसी हटाते समय, इस बात का भी ध्यान रखें कि आपके प्रोजेक्ट के मॉड्यूल के साइड इफ़ेक्ट हैं या नहीं. साइड इफ़ेक्ट का एक उदाहरण यह है कि जब कोई फ़ंक्शन अपने दायरे से बाहर के किसी एलिमेंट में बदलाव करता है. यह बदलाव, फ़ंक्शन के लागू होने का साइड इफ़ेक्ट होता है:
let fruits = ["apple", "orange", "pear"];
console.log(fruits); // (3) ["apple", "orange", "pear"]
const addFruit = function(fruit) {
fruits.push(fruit);
};
addFruit("kiwi");
console.log(fruits); // (4) ["apple", "orange", "pear", "kiwi"]
इस उदाहरण में, addFruit
अपने दायरे से बाहर के fruits
कलेक्शन में बदलाव करने पर, साइड इफ़ेक्ट पैदा करता है.
ES6 मॉड्यूल पर खराब असर भी लागू होता है और पेड़ के हिलने पर इसका असर पड़ता है. ऐसे मॉड्यूल जो अनुमान लगाए जा सकने वाले इनपुट लेते हैं और अपने दायरे से बाहर किसी भी चीज़ में बदलाव किए बिना, अनुमान लगाए जा सकने वाले आउटपुट देते हैं वे कई डिपेंडेंसी होते हैं. इनका इस्तेमाल नहीं किए जाने पर, इन्हें सुरक्षित तरीके से हटाया जा सकता है. ये कोड के ऐसे मॉड्यूलर हिस्से होते हैं जो अपने-आप काम करते हैं. इसलिए, "मॉड्यूल".
webpack के मामले में, किसी प्रोजेक्ट की package.json
फ़ाइल में "sideEffects": false
की जानकारी देकर, यह बताया जा सकता है कि किसी पैकेज और उसकी डिपेंडेंसी पर कोई साइड इफ़ेक्ट नहीं पड़ता:
{
"name": "webpack-tree-shaking-example",
"version": "1.0.0",
"sideEffects": false
}
इसके अलावा, webpack को यह भी बताया जा सकता है कि किन फ़ाइलों पर साइड इफ़ेक्ट नहीं पड़ता:
{
"name": "webpack-tree-shaking-example",
"version": "1.0.0",
"sideEffects": [
"./src/utils/utils.js"
]
}
दूसरे उदाहरण में, जिस फ़ाइल के बारे में नहीं बताया गया है उसे साइड इफ़ेक्ट से मुक्त माना जाएगा. अगर आपको इसे अपनी package.json
फ़ाइल में नहीं जोड़ना है, तो अपने webpack कॉन्फ़िगरेशन में module.rules
की मदद से भी इस फ़्लैग को शामिल किया जा सकता है.
सिर्फ़ ज़रूरी डेटा इंपोर्ट करना
Babel को ES6 मॉड्यूल को छोड़ने का निर्देश देने के बाद, utils
मॉड्यूल से सिर्फ़ ज़रूरी फ़ंक्शन लाने के लिए, हमारे import
सिंटैक्स में थोड़ा बदलाव करना ज़रूरी है. इस गाइड के उदाहरण में, सिर्फ़ simpleSort
फ़ंक्शन की ज़रूरत है:
import { simpleSort } from "../../utils/utils";
पूरे utils
मॉड्यूल के बजाय, सिर्फ़ simpleSort
को इंपोर्ट किया जा रहा है. इसलिए, utils.simpleSort
के हर इंस्टेंस को simpleSort
में बदलना होगा:
if (this.state.sortBy === "model") {
json = simpleSort(json, "model", this.state.sortOrder);
} else if (this.state.sortBy === "type") {
json = simpleSort(json, "type", this.state.sortOrder);
} else {
json = simpleSort(json, "manufacturer", this.state.sortOrder);
}
इस उदाहरण में, ट्री शेकिंग के काम करने के लिए, बस इतना ही ज़रूरी है. डिपेंडेंसी ट्री को शैक करने से पहले, वेबपैक का आउटपुट यह है:
Asset Size Chunks Chunk Names
js/vendors.16262743.js 37.1 KiB 0 [emitted] vendors
js/main.797ebb8b.js 20.8 KiB 1 [emitted] main
ट्री शेकिंग की प्रोसेस पूरी होने के बाद, यह आउटपुट दिखता है:
Asset Size Chunks Chunk Names
js/vendors.45ce9b64.js 36.9 KiB 0 [emitted] vendors
js/main.559652be.js 8.46 KiB 1 [emitted] main
दोनों बंडल में कमी आई, लेकिन main
बंडल को सबसे ज़्यादा फ़ायदा हुआ. utils
मॉड्यूल के इस्तेमाल न किए गए हिस्सों को हटाने पर, main
बंडल का साइज़ करीब 60% तक कम हो जाता है. इससे स्क्रिप्ट को डाउनलोड होने में लगने वाला समय कम हो जाता है. साथ ही, प्रोसेसिंग में लगने वाला समय भी कम हो जाता है.
कुछ पेड़ों को हिलाएं!
ट्री शेकिंग से आपको मिलने वाला फ़ायदा, आपके ऐप्लिकेशन और उसकी डिपेंडेंसी और आर्किटेक्चर पर निर्भर करता है. इसे आज़माएं! अगर आपको पता है कि आपने इस ऑप्टिमाइज़ेशन को करने के लिए, मॉड्यूल बंडलर को सेट अप नहीं किया है, तो इसे आज़माने और यह देखने में कोई नुकसान नहीं है कि इससे आपके ऐप्लिकेशन को क्या फ़ायदा होगा.
ऐसा हो सकता है कि पेड़ के झटकों से आपको परफ़ॉर्मेंस में बहुत ज़्यादा फ़ायदा मिले या आपको इसका बिलकुल भी फ़ायदा न हो. हालांकि, प्रोडक्शन बिल्ड में इस ऑप्टिमाइज़ेशन का फ़ायदा लेने के लिए अपने बिल्ड सिस्टम को कॉन्फ़िगर करके और सिर्फ़ अपने ऐप्लिकेशन की ज़रूरत के हिसाब से डेटा इंपोर्ट करके, अपने ऐप्लिकेशन बंडल को जितना हो सके उतना छोटा रखा जा सकता है.
क्रिस्टफ़र बैक्स्टर, जेसन मिलर, एडी उस्मानी, जेफ़ पॉसनिक, सैम सैकोन, और फ़िलिप वॉल्टन को अपने अहम सुझावों के लिए धन्यवाद. इन सुझावों से इस लेख की क्वालिटी को बेहतर बनाया गया.