हाल ही की Supercharged लाइव स्ट्रीम में, हमने अलग-अलग कोड का इस्तेमाल किया और अलग-अलग रूट के हिसाब से डेटा का इस्तेमाल बंद किया. एचटीटीपी/2 और नेटिव ES6 मॉड्यूल के साथ, स्क्रिप्ट संसाधनों को बेहतर तरीके से लोड और कैश मेमोरी में सेव करने में मदद करने के लिए, ये तकनीकें ज़रूरी हो जाएंगी.
इस एपिसोड में अन्य सुझाव और सलाह
asyncFunction().catch()
के साथerror.stack
: 9:55<script>
टैग पर मॉड्यूल औरnomodule
एट्रिब्यूट: 7:30- आठवें नोड में
promisify()
: 17:20
कम शब्दों में कहा जाए तो
रूट-आधारित चंकिंग के ज़रिए कोड को अलग-अलग करने का तरीका:
- अपने एंट्री पॉइंट की सूची पाएं.
- इन सभी एंट्री पॉइंट की मॉड्यूल डिपेंडेंसी निकालें.
- सभी एंट्री पॉइंट के बीच शेयर की गई डिपेंडेंसी ढूंढें.
- शेयर की गई डिपेंडेंसी को बंडल करें.
- एंट्री पॉइंट दोबारा लिखें.
कोड को अलग-अलग हिस्सों में बांटने के बजाय, रूट के हिसाब से अलग-अलग हिस्सों में बांटना
कोड को अलग-अलग हिस्सों में बांटने और रूट के हिसाब से अलग-अलग हिस्सों में बांटने की सुविधा एक-दूसरे से मिलती-जुलती है. साथ ही, इनका इस्तेमाल अक्सर एक-दूसरे के बदले किया जाता है. इससे कुछ उलझन पैदा हुई है. आइए, इस बारे में ज़्यादा जानकारी दें:
- कोड को अलग-अलग करने की सुविधा: कोड को अलग-अलग करने की सुविधा का इस्तेमाल करके, अपने कोड को कई बंडलों में बांटा जा सकता है. अगर क्लाइंट को अपने सभी JavaScript के साथ एक बड़ा बंडल नहीं भेजा जा रहा है, तो इसका मतलब है कि कोड को अलग-अलग किया जा रहा है. कोड को अलग-अलग करने का एक खास तरीका, रूट के हिसाब से चंकिंग का इस्तेमाल करना है.
- रूट-आधारित चंकिंग: रास्ते के हिसाब से चंकिंग ऐसे बंडल बनाता है जो आपके ऐप्लिकेशन के रास्तों से जुड़े होते हैं. आपके रूट और उनकी डिपेंडेंसी का विश्लेषण करके, हम बदल सकते हैं कि कौनसे मॉड्यूल किस बंडल में जाएंगे.
कोड को अलग-अलग हिस्सों में क्यों बांटते हैं?
अलग-अलग मॉड्यूल
नेटिव ES6 मॉड्यूल के साथ, हर JavaScript मॉड्यूल अपनी डिपेंडेंसी इंपोर्ट कर सकता है. जब ब्राउज़र को कोई मॉड्यूल मिलता है, तो कोड को चलाने के लिए ज़रूरी मॉड्यूल हासिल करने के लिए, सभी import
स्टेटमेंट अतिरिक्त फ़ेच को ट्रिगर करेंगे. हालांकि, ये सभी मॉड्यूल अपनी
अपनी निर्भरता पर निर्भर हो सकते हैं. खतरा यह है कि ब्राउज़र पर कोड फ़ेच करने से पहले, उसे कई बार प्रोसेस कर लिया जाता है.
बंडलिंग
बंडल में आपके सभी मॉड्यूल एक ही बंडल में इनलाइन हो जाते हैं. इससे यह पक्का हो जाता है कि एक दोतरफ़ा यात्रा के बाद ब्राउज़र के पास वे सभी कोड हैं जिनकी उसे ज़रूरत है और वह कोड को ज़्यादा तेज़ी से चलाना शुरू कर सकता है. हालांकि, इससे उपयोगकर्ता को बहुत सारा ऐसा कोड डाउनलोड करना पड़ता है जिसकी ज़रूरत नहीं होती. इससे बैंडविड्थ और समय बर्बाद होता है. इसके अलावा, हमारे किसी ओरिजनल मॉड्यूल में किए जाने वाले हर बदलाव की वजह से बंडल में बदलाव होगा. इससे बंडल का, कैश मेमोरी में सेव किया गया वर्शन अमान्य हो जाएगा. उपयोगकर्ताओं को फिर से पूरी चीज़ डाउनलोड करनी होगी.
कोड बांटना
कोड को अलग-अलग हिस्सों में बांटना, इन दोनों के बीच का विकल्प है. हम नेटवर्क की परफ़ॉर्मेंस को बेहतर बनाने के लिए, ज़्यादा राउंड ट्रिप का इस्तेमाल करने को तैयार हैं. इसके लिए, हम सिर्फ़ ज़रूरी कॉन्टेंट डाउनलोड करेंगे. साथ ही, हर बंडल में मॉड्यूल की संख्या को बहुत कम करके, कैश मेमोरी का बेहतर इस्तेमाल करेंगे. अगर बंडलिंग सही तरीके से की गई है, तो लूज़ मॉड्यूल की तुलना में, राउंड ट्रिप की कुल संख्या काफ़ी कम होगी. आखिर में, ज़रूरत पड़ने पर हम link[rel=preload]
जैसे पहले से लोड करने के तरीकों का इस्तेमाल करके, तीनों राउंड में लगने वाले समय को कम कर सकते हैं.
पहला चरण: एंट्री पॉइंट की सूची पाना
यह कई तरीकों में से एक है. हालांकि, इस एपिसोड में हमने अपनी वेबसाइट के एंट्री पॉइंट पाने के लिए, वेबसाइट के sitemap.xml
को पार्स किया है. आम तौर पर, सभी एंट्री पॉइंट की सूची वाली खास JSON फ़ाइल का इस्तेमाल किया जाता है.
JavaScript को प्रोसेस करने के लिए babel का इस्तेमाल करना
आम तौर पर, Babel का इस्तेमाल “ट्रांसपाइल करने” के लिए किया जाता है: इसमें नए वर्शन के JavaScript कोड का इस्तेमाल करके, उसे JavaScript के पुराने वर्शन में बदला जाता है, ताकि ज़्यादा ब्राउज़र उस कोड को चला सकें. यहां पहला चरण, किसी पार्स करने वाले टूल (Babel, babylon का इस्तेमाल करता है) की मदद से नए JavaScript को पार्स करना है. यह टूल, कोड को "ऐब्स्ट्रैक्ट सिंटैक्स ट्री" (AST) में बदल देता है. एएसटी जनरेट होने के बाद, कई प्लगिन, एएसटी का विश्लेषण करते हैं और उन्हें अलग करते हैं.
हम babel का ज़्यादा से ज़्यादा इस्तेमाल करेंगे, ताकि किसी JavaScript मॉड्यूल के इंपोर्ट का पता लगाया जा सके और बाद में उनमें बदलाव किया जा सके. आप शायद रेगुलर एक्सप्रेशन का इस्तेमाल करना चाहें, लेकिन रेगुलर एक्सप्रेशन इतनी असरदार नहीं हैं कि किसी भाषा को सही तरीके से पार्स किया जा सके. साथ ही, इन्हें बनाए रखना भी मुश्किल होता है. बेबल जैसे आज़माए हुए और आज़माए हुए टूल पर भरोसा करके, आप कई समस्याओं से बच सकते हैं.
कस्टम प्लग इन के साथ Babel को चलाने का एक आसान उदाहरण यहां दिया गया है:
const plugin = {
visitor: {
ImportDeclaration(decl) {
/* ... */
}
}
}
const {code} = babel.transform(inputCode, {plugins: [plugin]});
प्लग इन, visitor
ऑब्जेक्ट दे सकता है. विज़िटर में, किसी भी तरह के ऐसे नोड के लिए फ़ंक्शन होता है जिसे प्लग इन मैनेज करना चाहता है. AST को ट्रैवर्स करते समय उस तरह का कोई नोड मिलने पर, visitor
ऑब्जेक्ट में उससे जुड़े फ़ंक्शन को पैरामीटर के तौर पर उस नोड के साथ शुरू किया जाएगा. ऊपर दिए गए उदाहरण में, फ़ाइल में हर import
एलान के लिए ImportDeclaration()
तरीका इस्तेमाल किया जाएगा. नोड टाइप और एएसटी के बारे में ज़्यादा जानने के लिए, astexplorer.net पर जाएं.
दूसरा चरण: मॉड्यूल की डिपेंडेंसी निकालना
किसी मॉड्यूल का डिपेंडेंसी ट्री बनाने के लिए, हम उस मॉड्यूल को पार्स करेंगे और इंपोर्ट किए जाने वाले सभी मॉड्यूल की सूची बनाएंगे. हमें उन डिपेंडेंसी को भी पार्स करना होगा, क्योंकि हो सकता है कि उनमें भी डिपेंडेंसी हों. यह फ़ंक्शन, बार-बार दोहराए जाने वाले फ़ंक्शन का एक क्लासिक उदाहरण है!
async function buildDependencyTree(file) {
let code = await readFile(file);
code = code.toString('utf-8');
// `dep` will collect all dependencies of `file`
let dep = [];
const plugin = {
visitor: {
ImportDeclaration(decl) {
const importedFile = decl.node.source.value;
// Recursion: Push an array of the dependency’s dependencies onto the list
dep.push((async function() {
return await buildDependencyTree(`./app/${importedFile}`);
})());
// Push the dependency itself onto the list
dep.push(importedFile);
}
}
}
// Run the plugin
babel.transform(code, {plugins: [plugin]});
// Wait for all promises to resolve and then flatten the array
return flatten(await Promise.all(dep));
}
तीसरा चरण: सभी एंट्री पॉइंट के बीच एक जैसी डिपेंडेंसी ढूंढें
हमारे पास डिपेंडेंसी ट्री का एक सेट है – इसे डिपेंडेंसी फ़ॉरेस्ट भी कहा जा सकता है. इसलिए, हर ट्री में दिखने वाले नोड की मदद से, शेयर की गई डिपेंडेंसी ढूंढी जा सकती हैं. हम अपने फ़ॉरेस्ट को फ़्लैट करेंगे और डुप्लीकेट एलिमेंट हटा देंगे. साथ ही, हम सिर्फ़ उन एलिमेंट को फ़िल्टर करेंगे जो सभी ट्री में दिखते हैं.
function findCommonDeps(depTrees) {
const depSet = new Set();
// Flatten
depTrees.forEach(depTree => {
depTree.forEach(dep => depSet.add(dep));
});
// Filter
return Array.from(depSet)
.filter(dep => depTrees.every(depTree => depTree.includes(dep)));
}
चौथा चरण: शेयर की गई डिपेंडेंसी को बंडल करना
शेयर की गई डिपेंडेंसी के सेट को बंडल करने के लिए, हम सभी मॉड्यूल फ़ाइलों को एक साथ जोड़ सकते हैं. इस तरीके का इस्तेमाल करने पर, दो समस्याएं आती हैं: पहली समस्या यह है कि बंडल में अब भी import
स्टेटमेंट मौजूद होंगे. इनकी वजह से, ब्राउज़र संसाधनों को फ़ेच करने की कोशिश करेगा. दूसरी समस्या यह है कि डिपेंडेंसी की डिपेंडेंसी को बंडल नहीं किया गया है. हमने पहले भी ऐसा किया है. इसलिए, हम babel का एक और प्लग इन लिखने जा रहे हैं.
यह कोड हमारे पहले प्लग इन से काफ़ी हद तक मिलता-जुलता है, लेकिन सिर्फ़ इंपोर्ट निकालने के बजाय हम उन्हें हटा देंगे और इंपोर्ट की गई फ़ाइल का बंडल किया गया वर्शन भी शामिल कर देंगे:
async function bundle(oldCode) {
// `newCode` will be filled with code fragments that eventually form the bundle.
let newCode = [];
const plugin = {
visitor: {
ImportDeclaration(decl) {
const importedFile = decl.node.source.value;
newCode.push((async function() {
// Bundle the imported file and add it to the output.
return await bundle(await readFile(`./app/${importedFile}`));
})());
// Remove the import declaration from the AST.
decl.remove();
}
}
};
// Save the stringified, transformed AST. This code is the same as `oldCode`
// but without any import statements.
const {code} = babel.transform(oldCode, {plugins: [plugin]});
newCode.push(code);
// `newCode` contains all the bundled dependencies as well as the
// import-less version of the code itself. Concatenate to generate the code
// for the bundle.
return flatten(await Promise.all(newCode)).join('\n');
}
पांचवां चरण: एंट्री पॉइंट फिर से लिखें
आखिरी चरण के लिए, हम एक और Babel प्लग इन लिखेंगे. इसका काम, शेयर किए गए बंडल में मौजूद सभी मॉड्यूल के इंपोर्ट हटाना है.
async function rewrite(section, sharedBundle) {
let oldCode = await readFile(`./app/static/${section}.js`);
oldCode = oldCode.toString('utf-8');
const plugin = {
visitor: {
ImportDeclaration(decl) {
const importedFile = decl.node.source.value;
// If this import statement imports a file that is in the shared bundle, remove it.
if(sharedBundle.includes(importedFile))
decl.remove();
}
}
};
let {code} = babel.transform(oldCode, {plugins: [plugin]});
// Prepend an import statement for the shared bundle.
code = `import '/static/_shared.js';\n${code}`;
await writeFile(`./app/static/_${section}.js`, code);
}
End
यह वाकई एक शानदार अनुभव था, क्या नहीं? कृपया याद रखें कि इस एपिसोड का हमारा मकसद, कोड को अलग-अलग हिस्सों में बांटने के बारे में जानकारी देना और इसे आसान बनाना था. यह तरीका काम करता है – लेकिन यह हमारी डेमो साइट के लिए खास तौर पर है और सामान्य मामले में, यह तरीका काम नहीं करेगा. हमारा सुझाव है कि प्रोडक्शन के लिए, WebPack, RollUp वगैरह जैसे टूल का इस्तेमाल करें.
आपको हमारा कोड GitHub रिपॉज़िटरी में मिल जाएगा.
अगली बार मिलते हैं!