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

گردآوری و تالیف : عرفان کاکایی
تاریخ انتشار : 14 مرداد 1397
دسته بندی ها : جاوا اسکریپت

اتصال دهنده ماژول (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;
}
],})

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

منبع

مقالات پیشنهادی

خالق لاراول چگونه کار می‌کند؟

اخیرا افرادی را دیدم که وضعیت / روند کاری خود را به اشتراک می‌گذارند. پس من هم تصمیم گرفتم که همین کار را انجام دهم.

دکتر استارتاپ (مهدی علیپور) چگونه کار می کند

این مطلب به درخواست وبسایت راکت تهیه شده ، از من خواسته شده تا در مورد روتین های روزمره ، سبک کاری ، فضای کارم و ابزارهایی که استفاده میکنم براتون توض...

چگونه با استفاده از Webpack 4 و Babel یک پروژه React را از ابتدا بسازیم؟

اخیرا در حال یادگیری React بودم و همیشه از create-react-app برای ساخت پروژه‌های خودم، با کمترین زحمت و پیکربندی استفاده می‌کردم؛ و حدث میزنم که اکثر ش...

چگونه با استفاده از Node.js و Now، یک ربات تلگرام بسازیم؟

گسترش بدون سرور، موضوع جذابی که توجه تعداد زیادی از تازه‌کارها و همچنین کهنه‌کاران در اکوسیستم فناوری را جلب می‌کند، بالاخره رسیده است. در این مقاله،...