সুপারচার্জড লাইভস্ট্রিম ব্লগ - কোড বিভাজন

আমাদের সাম্প্রতিক সুপারচার্জড লাইভস্ট্রিমে আমরা কোড স্প্লিটিং এবং রুট-ভিত্তিক চঙ্কিং প্রয়োগ করেছি। HTTP/2 এবং নেটিভ ES6 মডিউলগুলির সাথে, এই কৌশলগুলি স্ক্রিপ্ট সংস্থানগুলির দক্ষ লোডিং এবং ক্যাশিং সক্ষম করার জন্য অপরিহার্য হয়ে উঠবে।

এই পর্বে বিবিধ টিপস এবং কৌশল

  • asyncFunction().catch() এর সাথে error.stack : 9:55
  • <script> ট্যাগে মডিউল এবং nomodule অ্যাট্রিবিউট: 7:30
  • নোড 8: 17:20-promisify()

টিএল; ডিআর

রুট-ভিত্তিক চাঙ্কিংয়ের মাধ্যমে কোড বিভাজন কীভাবে করবেন:

  1. আপনার এন্ট্রি পয়েন্ট একটি তালিকা প্রাপ্ত.
  2. এই সমস্ত এন্ট্রি পয়েন্টের মডিউল নির্ভরতা বের করুন।
  3. সমস্ত এন্ট্রি পয়েন্টের মধ্যে ভাগ করা নির্ভরতা খুঁজুন।
  4. ভাগ করা নির্ভরতাগুলিকে বান্ডিল করুন।
  5. এন্ট্রি পয়েন্ট পুনরায় লিখুন.

কোড স্প্লিটিং বনাম রুট-ভিত্তিক চঙ্কিং

কোড স্প্লিটিং এবং রুট-ভিত্তিক চঙ্কিং ঘনিষ্ঠভাবে সম্পর্কিত এবং প্রায়শই একে অপরের সাথে ব্যবহার করা হয়। এতে কিছুটা বিভ্রান্তির সৃষ্টি হয়েছে। আসুন এটি পরিষ্কার করার চেষ্টা করি:

  • কোড স্প্লিটিং : কোড স্প্লিটিং হল আপনার কোডকে একাধিক বান্ডিলে বিভক্ত করার প্রক্রিয়া। আপনি যদি ক্লায়েন্টকে আপনার সমস্ত জাভাস্ক্রিপ্ট সহ একটি বড় বান্ডিল শিপিং না করেন তবে আপনি কোড স্প্লিটিং করছেন। আপনার কোড বিভক্ত করার একটি নির্দিষ্ট উপায় হল রুট-ভিত্তিক চঙ্কিং ব্যবহার করা।
  • রুট-ভিত্তিক চাঙ্কিং : রুট-ভিত্তিক চাঙ্কিং বান্ডিল তৈরি করে যা আপনার অ্যাপের রুটগুলির সাথে সম্পর্কিত। আপনার রুট এবং তাদের নির্ভরতা বিশ্লেষণ করে, আমরা কোন মডিউল কোন বান্ডেলে যায় তা পরিবর্তন করতে পারি।

কেন কোড বিভাজন করবেন?

আলগা মডিউল

নেটিভ ES6 মডিউল সহ, প্রতিটি জাভাস্ক্রিপ্ট মডিউল তার নিজস্ব নির্ভরতা আমদানি করতে পারে। যখন ব্রাউজারটি একটি মডিউল পায়, তখন সমস্ত import বিবৃতিগুলি কোড চালানোর জন্য প্রয়োজনীয় মডিউলগুলিকে ধরে রাখার জন্য অতিরিক্ত আনয়নকে ট্রিগার করবে৷ যাইহোক, এই সমস্ত মডিউল তাদের নিজস্ব নির্ভরতা থাকতে পারে। বিপদ হল যে ব্রাউজারটি একটি ক্যাসকেড নিয়ে আসে যা কোডটি শেষ পর্যন্ত কার্যকর করার আগে একাধিক রাউন্ড ট্রিপের জন্য স্থায়ী হয়।

বান্ডলিং

বান্ডলিং, যা আপনার সমস্ত মডিউলকে একটি একক বান্ডেলে ইনলাইন করছে তা নিশ্চিত করবে যে ব্রাউজারে 1 রাউন্ড ট্রিপের পরে প্রয়োজনীয় সমস্ত কোড রয়েছে এবং কোডটি আরও দ্রুত চালানো শুরু করতে পারে৷ এটি অবশ্য ব্যবহারকারীকে অনেক কোড ডাউনলোড করতে বাধ্য করে যার প্রয়োজন নেই, তাই ব্যান্ডউইথ এবং সময় নষ্ট হয়েছে। অতিরিক্তভাবে, আমাদের মূল মডিউলগুলির একটিতে প্রতিটি পরিবর্তনের ফলে বান্ডেলে একটি পরিবর্তন হবে, বান্ডেলের যেকোনও ক্যাশে করা সংস্করণটিকে বাতিল করে দেবে। ব্যবহারকারীদের পুরো জিনিসটি পুনরায় ডাউনলোড করতে হবে।

কোড বিভাজন

কোড বিভাজন মধ্যম স্থল. আমরা শুধুমাত্র আমাদের যা প্রয়োজন তা ডাউনলোড করে নেটওয়ার্ক দক্ষতা পেতে অতিরিক্ত রাউন্ড ট্রিপ বিনিয়োগ করতে ইচ্ছুক, এবং প্রতি বান্ডেলে মডিউলের সংখ্যাকে অনেক ছোট করে আরও ভালো ক্যাশিং দক্ষতা। যদি বান্ডলিং সঠিকভাবে সম্পন্ন করা হয়, তাহলে মোট রাউন্ড ট্রিপের সংখ্যা আলগা মডিউলের তুলনায় অনেক কম হবে। অবশেষে, প্রয়োজনে অতিরিক্ত রাউন্ড ত্রয়ী সময় বাঁচাতে link[rel=preload] এর মতো প্রিলোডিং মেকানিজম ব্যবহার করতে পারি।

ধাপ 1: আপনার এন্ট্রি পয়েন্টের একটি তালিকা পান

এটি অনেক পদ্ধতির মধ্যে একটি মাত্র, কিন্তু পর্বে আমরা আমাদের ওয়েবসাইটে প্রবেশের পয়েন্ট পেতে ওয়েবসাইটের sitemap.xml পার্স করেছি। সাধারণত, সমস্ত এন্ট্রি পয়েন্ট তালিকাভুক্ত একটি ডেডিকেটেড JSON ফাইল ব্যবহার করা হয়।

জাভাস্ক্রিপ্ট প্রক্রিয়া করার জন্য ব্যাবেল ব্যবহার করা

ব্যাবেল সাধারণত "ট্রান্সপিলিং" এর জন্য ব্যবহৃত হয়: ব্লিডিং-এজ জাভাস্ক্রিপ্ট কোড ব্যবহার করা এবং এটিকে জাভাস্ক্রিপ্টের একটি পুরানো সংস্করণে পরিণত করা যাতে আরও ব্রাউজার কোডটি কার্যকর করতে সক্ষম হয়। এখানে প্রথম ধাপ হল নতুন জাভাস্ক্রিপ্টকে একটি পার্সার দিয়ে পার্স করা (ব্যাবেল ব্যাবিলন ব্যবহার করে) যা কোডটিকে তথাকথিত "অ্যাবস্ট্রাক্ট সিনট্যাক্স ট্রি" (AST) এ পরিণত করে। একবার AST তৈরি হয়ে গেলে, প্লাগইনগুলির একটি সিরিজ AST কে বিশ্লেষণ করে এবং ম্যাঙ্গেল করে।

আমরা একটি জাভাস্ক্রিপ্ট মডিউলের আমদানি সনাক্ত করতে (এবং পরে ম্যানিপুলেটেড) ব্যাবেলের ব্যাপক ব্যবহার করতে যাচ্ছি। আপনি রেগুলার এক্সপ্রেশন অবলম্বন করতে প্রলুব্ধ হতে পারেন, কিন্তু রেগুলার এক্সপ্রেশন একটি ভাষাকে সঠিকভাবে পার্স করার জন্য যথেষ্ট শক্তিশালী নয় এবং বজায় রাখা কঠিন। বাবেলের মত পরীক্ষিত টুলের উপর নির্ভর করা আপনাকে অনেক মাথাব্যথা বাঁচাবে।

এখানে একটি কাস্টম প্লাগইন সহ Babel চালানোর একটি সহজ উদাহরণ:

const plugin = {
  visitor: {
    ImportDeclaration(decl) {
      /* ... */
    }
  }
}
const {code} = babel.transform(inputCode, {plugins: [plugin]});

একটি প্লাগইন একটি visitor অবজেক্ট প্রদান করতে পারে। ভিজিটর যে কোনো নোড ধরনের জন্য একটি ফাংশন রয়েছে যা প্লাগইন পরিচালনা করতে চায়। যখন এএসটি অতিক্রম করার সময় এই ধরণের একটি নোডের সম্মুখীন হয় তখন visitor বস্তুর সংশ্লিষ্ট ফাংশনটি প্যারামিটার হিসাবে সেই নোডের সাথে আহ্বান করা হবে। উপরের উদাহরণে, ফাইলের প্রতিটি import ঘোষণার জন্য ImportDeclaration() পদ্ধতিটি বলা হবে। নোডের ধরন এবং AST সম্পর্কে আরও অনুভূতি পেতে, astexplorer.net এ একবার দেখুন।

ধাপ 2: মডিউল নির্ভরতা বের করুন

একটি মডিউলের নির্ভরতা গাছ তৈরি করতে, আমরা সেই মডিউলটিকে পার্স করব এবং এটি আমদানি করা সমস্ত মডিউলের একটি তালিকা তৈরি করব। আমাদের সেই নির্ভরতাগুলিকে বিশ্লেষণ করতে হবে, কারণ তাদেরও নির্ভরতা থাকতে পারে। পুনরাবৃত্তির জন্য একটি ক্লাসিক কেস!

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));
}

ধাপ 3: সমস্ত এন্ট্রি পয়েন্টের মধ্যে ভাগ করা নির্ভরতা খুঁজুন

যেহেতু আমাদের কাছে নির্ভরতা গাছের একটি সেট রয়েছে - আপনি যদি চান তবে একটি নির্ভরতা বন - আমরা প্রতিটি গাছে উপস্থিত নোডগুলি সন্ধান করে ভাগ করা নির্ভরতাগুলি খুঁজে পেতে পারি। আমরা আমাদের বনকে চ্যাপ্টা এবং ডিডপ্লিকেট করব এবং শুধুমাত্র সমস্ত গাছে উপস্থিত উপাদানগুলিকে রাখতে ফিল্টার করব৷

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)));
}

ধাপ 4: ভাগ করা নির্ভরতা বান্ডিল করুন

আমাদের ভাগ করা নির্ভরতাগুলির সেট বান্ডিল করতে, আমরা কেবল সমস্ত মডিউল ফাইলগুলিকে সংযুক্ত করতে পারি। সেই পদ্ধতিটি ব্যবহার করার সময় দুটি সমস্যা দেখা দেয়: প্রথম সমস্যাটি হল বান্ডেলটিতে এখনও import বিবৃতি থাকবে যা ব্রাউজারকে সংস্থানগুলি আনার চেষ্টা করবে। দ্বিতীয় সমস্যা হল যে নির্ভরতাগুলির নির্ভরতাগুলি বান্ডিল করা হয়নি। কারণ আমরা এটি আগে করেছি, আমরা আরেকটি ব্যাবেল প্লাগইন লিখতে যাচ্ছি।

কোডটি আমাদের প্রথম প্লাগইনের সাথে মোটামুটি একই রকম, কিন্তু শুধু আমদানিগুলি বের করার পরিবর্তে, আমরা সেগুলিকে সরিয়ে ফেলব এবং আমদানি করা ফাইলের একটি বান্ডিল সংস্করণ সন্নিবেশ করব:

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');
}

ধাপ 5: এন্ট্রি পয়েন্ট পুনরায় লিখুন

শেষ ধাপের জন্য আমরা আরেকটি ব্যাবেল প্লাগইন লিখব। এর কাজ হল শেয়ার করা বান্ডেলে থাকা সমস্ত মডিউলের আমদানি অপসারণ করা।

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);
}

শেষ

এটা বেশ রাইড ছিল, তাই না? অনুগ্রহ করে মনে রাখবেন যে এই পর্বের জন্য আমাদের লক্ষ্য ছিল কোড বিভাজন ব্যাখ্যা করা এবং ডিমিস্টিফাই করা । ফলাফল কাজ করে - তবে এটি আমাদের ডেমো সাইটের জন্য নির্দিষ্ট এবং জেনেরিক ক্ষেত্রে ভয়ঙ্করভাবে ব্যর্থ হবে। উত্পাদনের জন্য, আমি ওয়েবপ্যাক, রোলআপ ইত্যাদির মতো প্রতিষ্ঠিত সরঞ্জামগুলির উপর নির্ভর করার পরামর্শ দেব।

আপনি GitHub সংগ্রহস্থলে আমাদের কোড খুঁজে পেতে পারেন।

পরে আবার দেখা হবে!