چگونه اتصال دهنده ماژول خود را از ابتدا خودمان بسازیم؟

13 خرداد 1398, خواندن در 2 دقیقه

اتصال دهنده ماژول (Module bundler) چیست؟

اتصال دهنده‌های ماژول به ما در اتصال کدهای خود کمک می‌کنند. البته حتی بیشتر از این‌ها را نیز می‌توانند انجام دهند؛ مثلا به مانند Webpack، می‌توانند کد را تقسیم‌بندی کنند تا کاربران فقط فایل‌های مورد نیاز را دانلود کنند.

چرا نمی‌توانیم بدانیم که اتصال دهنده‌ها در پشت پرده چگونه کار می‌کند؟

بیایید این مبحث را با ساخت یک اتصال دهنده ماژول ساده درک درک کنیم. در ابتدا، باید برخی Dependencyها را نصب کنیم:

npm init -y
npm install @babel/parser babel-core babel-traverse babel-preset-env 

ما در حال استفاده از babel parser برای parse کردن کد خود به یک شاخه سینتکس چکیده (AST = Abstract Syntax Tree) هستیم.

ساختار پوشه

در اینجا، من دو ماژول به نام‌های add.js و answer.js ساختم.

در این پوشه، فایلی به نام packup.js بسازید و این کد را در آن قرار دهید:

const fs = require("fs");

const path = require("path");

const babelParser = require("@babel/parser");

const babelTraverse = require("babel-traverse");

const { transformFromAst } = require("babel-core");

در ابتدا، باید فایل را بخوانیم و Dependecyهایش را با parse کردن کد و تبدیل آن به AST بیابیم.

function createAsset(filename) {

  let getData = fs.readFileSync(filename, "utf-8");

  let ast = babelParser.parse(getData, {

    sourceType: "module"

  });

  const dependencies = []; 

  let genrateDependencies = babelTraverse.default(ast, {

    ImportDeclaration: ({ node }) => {

      dependencies.push(node.source.value);

    }

  });

  let { code } = transformFromAst(ast, null, {

      presets: ["env"]  //applying the presets it means converting to es5 code

  });

  return {

    filename,

    dependencies,

    code

  };

}

حال نوبت به نمودار Dependency می‌رسد. ما در حال بررسی این که کدام ماژول به کدام ماژول مربوط است، هستیم.

function dependencyGraph(entry) {

  const initialAsset = createAsset(entry);

  //collecting all assets

  const assets = [initialAsset];

  for (const asset of assets) {

    const dirname = path.dirname(asset.filename);

    asset.dependencies.forEach(relativePath => {

      // گرفتن نام

      const extname = path.extname(asset.filename);

      //تولید مسیر مطلق

      const absolutePath = path.join(dirname, relativePath + extname);

      const childAsset = createAsset(absolutePath);

      childAsset.filename = relativePath + extname;

      assets.push(childAsset);

    });

  }

  return assets;

}

آخرین کار، اتصال دادن است. باید از طریق نمودار Dependency، تمام ماژول‌های خود را اتصال دهیم.

function bundle(graph) {

  let modules = '';

// برای هر ماژول، جفت‌های مقادیر کلیدی می‌سازیم که در آن‌ها کلید، همان نام فایل است و مقدار نیز، کد آن است

  graph.forEach(mod => {

    modules += `${JSON.stringify(mod.filename.replace(/.js$/gi, ""))}: [

      function ( module, exports,require) {

        ${mod.code}

      }

    ],`;

  });

  var result = `(function (modules) {

    function require(name) {

      const [fn] = modules[name];

      const module={},exports={};

      fn(module, exports,(name)=>require(name));

      return exports;

    }

    require("./getSum");

  })({${modules}})`;

  return result; //finally we are returning the IIFE with modules

}

بگذراید با نمایش کد ساخته شده توسط اتصال دهنده ماژول، این موارد را به صورت جزئی‌تر توضیح دهم.

const graph = dependencyGraph("./getSum.js");

//تولید فایل و اضافه کردن کد
fs.appendFile("bundle.js", bundle(graph), err => {
  if (err) throw err;
  console.log("bundle.js created");
});

می‌توانید کد تولید شده را با کپی و پیست کردن آن به کنسول مرورگر خود ببینید.

(function (modules) {
function require(name) {
const [fn] = modules[name];
const module = {},exports={};
fn(module, exports,(name)=>require(name));
return exports;
}
require("./getSum");
})({"./getSum": [
function ( module, exports,require) {
"use strict";

var _answer = require("./add/answer/answer");

var _answer2 = _interopRequireDefault(_answer);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

console.log(_answer2.default);
}
],"./add/answer/answer": [
function ( module, exports,require) {
"use strict";

Object.defineProperty(exports, "__esModule", {
value: true
});

var _add = require("../add");

var _add2 = _interopRequireDefault(_add);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

var sum = (0, _add2.default)(1, 9);
exports.default = sum;
}
],"../add": [
function ( module, exports,require) {
"use strict";

Object.defineProperty(exports, "__esModule", {
value: true
});

function add(a, b) {
return a + b;
}

exports.default = add;
}
],})

کاری که کد بالا انجام می‌دهد، انتقال آبجکت‌های ماژول به عنوان آرگومان است. به طور پیشفرض، باید نام ابتدای فایل خود را به تابع مورد نیاز انتقال دهیم، تا تابع مربوطه شروع به تقبل ماژول‌ها بنماید. بگذارید این مسئله را با نمایش دیباگر نشان دهم:

منبع

چه امتیازی به این مقاله می دید؟
خیلی بد
بد
متوسط
خوب
عالی

دیدگاه‌ها و پرسش‌ها

برای ارسال دیدگاه لازم است، ابتدا وارد سایت شوید.

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

آفلاین
user-avatar
عرفان کاکایی @er79ka
دنبال کردن

گفتگو‌ برنامه نویسان

بخشی برای حل مشکلات برنامه‌نویسی و مباحث پیرامون آن وارد شو