ट्री शेकिंग की मदद से JavaScript पेलोड कम करें

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

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

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

170 केबी के JavaScript की प्रोसेसिंग टाइम और उसके बराबर के साइज़ की JPEG इमेज की तुलना करने वाला डायग्राम. JavaScript का रिसॉर्स, बाइट के लिए JPEG से कहीं ज़्यादा रिसॉर्स-इंटेसिव बाइट है.
170 केबी के JavaScript को पार्स/कंप करने में लगने वाला खर्च और इसके बराबर के साइज़ के JPEG को डिकोड करने में लगने वाला समय. (सोर्स).

JavaScript इंजन की क्षमता को बेहतर बनाने के लिए, लगातार सुधार किए जा रहे हैं. JavaScript की परफ़ॉर्मेंस को बेहतर बनाना हमेशा की तरह डेवलपर के लिए एक काम है.

इस वजह से, JavaScript की परफ़ॉर्मेंस को बेहतर बनाने की तकनीकें हैं. कोड स्प्लिटिंग, ऐसी ही एक तकनीक है जो ऐप्लिकेशन JavaScript को कई हिस्सों में बांटकर परफ़ॉर्मेंस को बेहतर बनाती है. साथ ही, इन हिस्सों को सिर्फ़ उन ऐप्लिकेशन के रूट पर उपलब्ध कराई जाती है जिन्हें इनकी ज़रूरत है.

हालांकि, यह तकनीक काम करती है, लेकिन यह JavaScript से ज़्यादा ज़्यादा काम न करने वाले ऐप्लिकेशन की सामान्य समस्या को हल नहीं करती. इसमें ऐसे कोड शामिल होते हैं जिन्हें कभी इस्तेमाल नहीं किया जाता. पेड़ इस समस्या को हल करने की कोशिश कर रहा है.

पेड़ को हिलाने की वजह क्या है?

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

"पेड़ों को हिलाना" शब्द, आपके ऐप्लिकेशन के मानसिक मॉडल और पेड़ जैसी संरचना के रूप में उसकी डिपेंडेंसी से आया है. ट्री में हर नोड एक डिपेंडेंसी दिखाता है, जो आपके ऐप्लिकेशन के लिए अलग फ़ंक्शन उपलब्ध कराता है. मॉडर्न ऐप्लिकेशन में, ये डिपेंडेंसी स्टैटिक import स्टेटमेंट से हासिल होती हैं, जैसे:

// Import all the array utilities!
import arrayUtils from "array-utils";

अगर कोई ऐप्लिकेशन युवा है—एक तरह का पौधा, तो उस पर निर्भरता कम हो सकती है. यह आपकी जोड़ी गई डिपेंडेंसी का भी सबसे ज़्यादा इस्तेमाल करता है. भले ही, वह पूरी तरह न हो. हालांकि, जैसे-जैसे आपका ऐप्लिकेशन मैच्योर होता है, वैसे-वैसे अन्य डिपेंडेंसी भी जोड़ी जा सकती है. मामलों को जोड़ने के लिए, पुरानी डिपेंडेंसी का इस्तेमाल नहीं होता, लेकिन हो सकता है कि आपके कोड बेस से यह कम न हो. आखिरी नतीजा यह होता है कि ऐप्लिकेशन बहुत सारे इस्तेमाल नहीं की गई JavaScript के साथ शिपिंग कर देता है. ES6 मॉड्यूल के खास हिस्सों में स्टैटिक import स्टेटमेंट कैसे शामिल होते हैं, इस जानकारी का इस्तेमाल करके पेड़ों को हिलने से रोका जा सकता है:

// Import only some of the utilities!
import { unique, implode, explode } from "array-utils";

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

पेड़ को हिलाने का तरीका पता लगाना

उदाहरण के लिए, एक पेज का एक पेज का ऐप्लिकेशन उपलब्ध है, जिसमें बताया गया है कि पेड़ कैसे हिलते हैं. आप चाहें, तो इसका क्लोन बनाएं और इसे आगे बढ़ाएं. हम इस गाइड में, इसके हर चरण पर साथ मिलकर काम करेंगे. इसलिए, क्लोन करना ज़रूरी नहीं है, जब तक कि खुद से सीखना आपके लिए ज़रूरी न हो.

सैंपल ऐप, गिटार इफ़ेक्ट पैडल का खोजने लायक डेटाबेस है. कोई क्वेरी डालने पर, आपको इफ़ेक्ट देने वाले पैडल की सूची दिखेगी.

गिटार इफ़ेक्ट पैडल का डेटाबेस खोजने के लिए, एक पेज वाले ऐप्लिकेशन के सैंपल का स्क्रीनशॉट.
सैंपल ऐप्लिकेशन का स्क्रीनशॉट.

इस ऐप्लिकेशन को चलाने वाले व्यवहार को वेंडर में अलग किया जाता है (जैसे, प्रीैक्ट और इमोशन) और ऐप्लिकेशन के लिए खास कोड बंडल (या "चंक", क्योंकि वेबपैक उन्हें कहते हैं):

Chrome के DevTools के नेटवर्क पैनल में दो ऐप्लिकेशन कोड बंडल (या अलग-अलग ग्रुप) का स्क्रीनशॉट.
ऐप्लिकेशन के दो JavaScript बंडल. ये असंपीड़ित साइज़ होते हैं.

ऊपर दिखाई गई इमेज में दिखाए गए JavaScript बंडल, प्रोडक्शन बिल्ड हैं. इसका मतलब है कि उन्हें अपग्रेड करने की प्रक्रिया के ज़रिए ऑप्टिमाइज़ किया गया है. किसी खास ऐप्लिकेशन बंडल का 21.1 केबी का साइज़ खराब नहीं है, लेकिन इस बात पर ध्यान दिया जाना चाहिए कि अब भी कोई पेड़ हिल रहा नहीं है. ऐप्लिकेशन कोड पर नज़र डालते हैं और देखते हैं कि उसे ठीक करने के लिए क्या किया जा सकता है.

किसी भी ऐप्लिकेशन में, पेड़ों को हिलने-डुलने के अवसरों का पता लगाने के लिए, स्टैटिक import स्टेटमेंट का इस्तेमाल करना शामिल होगा. मुख्य कॉम्पोनेंट वाली फ़ाइल के सबसे ऊपर, आपको इस तरह की लाइन दिखेगी:

import * as utils from "../../utils/utils";

ES6 मॉड्यूल को कई तरीकों से इंपोर्ट किया जा सकता है. हालांकि, आपको इस तरह के मॉड्यूल पर ध्यान देना चाहिए. इस लाइन में लिखा है, "utils मॉड्यूल से import सब कुछ और इसे utils नाम के नेमस्पेस में रखें." यहां पूछा जाने वाला बड़ा सवाल है, "इस मॉड्यूल में बस कितनी सामान हैं?"

अगर आप utils मॉड्यूल सोर्स कोड देखें, तो आपको उसमें कोड की करीब 1,300 लाइनें दिखेंगी.

क्या आपको इन सभी चीज़ों की ज़रूरत है? आइए, मुख्य कॉम्पोनेंट फ़ाइल की खोज करके, utils मॉड्यूल को इंपोर्ट करने वाली फ़ाइल की दोबारा जांच करते हैं. इससे यह देखा जा सकता है कि उस नेमस्पेस के कितने इंस्टेंस सामने आते हैं.

'utils.' के लिए टेक्स्ट एडिटर में खोज का स्क्रीनशॉट, जिसमें सिर्फ़ तीन नतीजे दिख रहे हैं.
हमने जिस 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 को शिप किया जाता है.

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

बेबल को ES6 मॉड्यूल के ट्रांसपेलिंग से CommonJS मॉड्यूल में शामिल होने से रोकना

Babel एक ज़रूरी टूल है. हालांकि, इसकी वजह से पेड़ों को हुए झटकों के असर को देख पाना थोड़ा मुश्किल हो सकता है. अगर @babel/preset-env का इस्तेमाल किया जा रहा है, तो Nearby ES6 मॉड्यूल को ज़्यादा से ज़्यादा काम करने वाले CommonJS मॉड्यूल में बदल सकता है. इसका मतलब है कि आप import के बजाय require मॉड्यूल का इस्तेमाल कर सकते हैं.

CommonJS मॉड्यूल के लिए ट्री हिलना ज़्यादा मुश्किल होता है. इसलिए, अगर आपने बंडल का इस्तेमाल करने का फ़ैसला किया, तो Webpack को यह पता नहीं चलेगा कि इनमें से क्या कम करना है. इस समस्या को ठीक करने के लिए, @babel/preset-env को कॉन्फ़िगर करना होगा, ताकि ES6 मॉड्यूल को साफ़ तौर पर सिर्फ़ छोड़ा जा सके. चाहे आप बेबल को कहीं भी कॉन्फ़िगर करें, फिर चाहे वह babel.config.js में हो या package.json में—इसमें कुछ अलग जोड़ना शामिल है:

// babel.config.js
export default {
  presets: [
    [
      "@babel/preset-env", {
        modules: false
      }
    ]
  ]
}

आपके @babel/preset-env कॉन्फ़िगरेशन में modules: false तय करने से, बेबल मनमुताबिक काम करने लगता है. इससे वेबपैक आपके डिपेंडेंसी ट्री का विश्लेषण करता है और इस्तेमाल नहीं की गई डिपेंडेंसी को अलग करता है.

खराब असर को ध्यान में रखना

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

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"]

इस उदाहरण में, fruits के कलेक्शन में बदलाव करने पर, addFruit साइड इफ़ेक्ट देता है, जो इसके दायरे से बाहर है.

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

जहां वेबपैक का संबंध है, वहां हिंट का इस्तेमाल यह बताने के लिए किया जा सकता है कि पैकेज और उसकी डिपेंडेंसी के किसी भी साइड इफ़ेक्ट के बिना, प्रोजेक्ट की package.json फ़ाइल में "sideEffects": false की जानकारी दी जा सकती है:

{
  "name": "webpack-tree-shaking-example",
  "version": "1.0.0",
  "sideEffects": false
}

इसके अलावा, आप वेबपैक को बता सकते हैं कि कौनसी खास फ़ाइलें साइड इफ़ेक्ट-फ़्री नहीं हैं:

{
  "name": "webpack-tree-shaking-example",
  "version": "1.0.0",
  "sideEffects": [
    "./src/utils/utils.js"
  ]
}

बाद वाले उदाहरण में, अगर फ़ाइल के बारे में कोई जानकारी नहीं दी गई है, तो यह माना जाएगा कि उस फ़ाइल में कोई खराब असर नहीं होगा. अगर आपको इसे अपनी package.json फ़ाइल में नहीं जोड़ना है, तो module.rules के ज़रिए अपने वेबपैक कॉन्फ़िगरेशन में इस फ़्लैग के बारे में भी बताया जा सकता है.

सिर्फ़ ज़रूरी जानकारी इंपोर्ट की जा रही है

बेबल को ES6 मॉड्यूल में सिर्फ़ एक बार छोड़ने का निर्देश देने के बाद, हमारे import सिंटैक्स में थोड़ा बदलाव करना ज़रूरी है. ऐसा करने पर, सिर्फ़ utils मॉड्यूल के लिए ज़रूरी फ़ंक्शन शामिल किए जा सकेंगे. इस गाइड के उदाहरण में, 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% कम हो जाता है. इससे, स्क्रिप्ट के डाउनलोड होने में लगने वाला समय और प्रोसेस होने में लगने वाला समय भी कम होता है.

कुछ पेड़ हिलाओ!

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

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

क्रिस्टोफ़र बैक्सटर, जेसन मिलर, एडी उस्मानी, जेफ़ पॉस्निक, सैम सैकोन, और फ़िलिप वॉल्टन को उनके कीमती सुझाव, राय या शिकायत के लिए धन्यवाद. इनसे इस लेख की क्वालिटी में काफ़ी सुधार हुआ.