استيراد وتصدير الدوال: الفرق بين require و module.exports


استيراد وتصدير الدوال: الفرق بين require و module.exports

ماذا سنتعلم اليوم؟ سنتعمق في آليات استيراد وتصدير الدوال في Node.js باستخدام require و module.exports، وهما حجر الزاوية في بناء تطبيقات معيارية.

الخطوة 1: تعريف وحدة (Module) بسيطة باستخدام module.exports

لنبدأ بإنشاء ملف mathModule.js يحتوي على دالة بسيطة ونصدرها.

// mathModule.js
/**
 * دالة لجمع عددين.
 * @param {number} a - العدد الأول.
 * @param {number} b - العدد الثاني.
 * @returns {number} مجموع العددين.
 */
function add(a, b) {
  return a + b;
}

module.exports = add; // تصدير الدالة add لتكون متاحة للاستيراد
ملاحظة تقنية: كل ملف في Node.js هو وحدة (module) مستقلة. الكائن module.exports هو ما يتم تصديره عند استيراد الوحدة.

الخطوة 2: استيراد الوحدة باستخدام require

الآن، سنقوم بإنشاء ملف app.js لاستيراد واستخدام الدالة التي قمنا بتصديرها.

// app.js
const addFunction = require('./mathModule'); // استيراد الدالة المصدرة من mathModule.js

const result = addFunction(5, 3); // استخدام الدالة المستوردة
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">الناتج من الدالة add: ${result}</code>); // طباعة النتيجة

الخطوة 3: تصدير عناصر متعددة ككائن

في كثير من الأحيان، قد تحتاج الوحدة إلى تصدير عدة دوال أو ثوابت. يمكننا تحقيق ذلك عن طريق تصدير كائن يحتوي على هذه العناصر.

سنعدل ملف mathModule.js:

// mathModule.js (نسخة معدلة)
/**
 * دالة لجمع عددين.
 * @param {number} a - العدد الأول.
 * @param {number} b - العدد الثاني.
 * @returns {number} مجموع العددين.
 */
function add(a, b) {
  return a + b;
}

/**
 * دالة لطرح عددين.
 * @param {number} a - العدد الأول.
 * @param {number} b - العدد الثاني.
 * @returns {number} الفرق بين العددين.
 */
function subtract(a, b) {
  return a - b;
}

/** ثابت يمثل قيمة باي (PI). */
const PI = 3.14159;

// تصدير كائن يحتوي على دوال ومتغيرات متعددة
module.exports = {
  addFunction: add, // تصدير الدالة add باسم addFunction
  subtractFunction: subtract, // تصدير الدالة subtract باسم subtractFunction
  PI_VALUE: PI // تصدير الثابت PI باسم PI_VALUE
};

ثم نعدل ملف app.js لاستيراد الكائن واستخدام خصائصه:

// app.js (نسخة معدلة)
// استيراد الكائن المصدر من mathModule.js
const mathOperations = require('./mathModule');

const sum = mathOperations.addFunction(10, 5); // استخدام الدالة addFunction
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">ناتج الجمع: ${sum}</code>);

const difference = mathOperations.subtractFunction(10, 5); // استخدام الدالة subtractFunction
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">ناتج الطرح: ${difference}</code>);

console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">قيمة PI: ${mathOperations.PI_VALUE}</code>); // استخدام الثابت PI_VALUE

الخطوة 4: فهم الفرق الدقيق بين exports و module.exports

في Node.js، exports هو اختصار (reference) لـ module.exports. في البداية، يشير كلاهما إلى نفس الكائن الفارغ. عند إضافة خصائص إلى exports (مثل exports.myFunc = ...)، فإنك تضيفها فعلياً إلى module.exports. لكن إذا قمت بإعادة تعيين module.exports بالكامل (مثل module.exports = someObject;)، فإن exports لم يعد يشير إلى الكائن المصدر الجديد، وبالتالي أي شيء تضيفه إلى exports بعد ذلك لن يتم تصديره.

لنرى ذلك في ملف moduleDemo.js:

// moduleDemo.js

exports.name = "وحدة تجريبية"; // هذا سيتم تصديره عبر module.exports بشكل مبدئي

exports.getInfo = () => { // هذه الدالة ستكون متاحة أيضاً مبدئياً
  return "معلومات من الوحدة التجريبية.";
};

// الآن، إذا قمنا بإعادة تعيين module.exports، فإن كل ما سبق (name و getInfo) لن يتم تصديره
// وسيتم تصدير الكائن الجديد فقط الذي نحدده هنا.
module.exports = {
  version: "1.0.0",
  description: "وحدة جديدة بالكامل تم تصديرها."
};

// هذا لن يتم تصديره لأن module.exports تم استبداله بالفعل بكائن جديد.
exports.additional = "هذا لن يظهر!";

ثم نختبره في ملف appTester.js (ملاحظة: هذا الملف للتوضيح فقط ولن يكون جزءاً من الكود النهائي المجمع):

// appTester.js
const demoModule = require('./moduleDemo');

console.log(demoModule);
// console.log(demoModule.name); // هذا سيسبب خطأ: undefined، لأن module.exports تم استبداله
// console.log(demoModule.getInfo()); // هذا سيسبب خطأ: undefined
// console.log(demoModule.additional); // هذا سيسبب خطأ: undefined
ملاحظة تقنية: قاعدة عامة: استخدم module.exports عندما تريد تصدير كائن واحد (دالة، كائن، مصفوفة، قيمة بدائية) كواجهة للوحدة. استخدم exports فقط لإضافة خصائص إلى الكائن المصدر الحالي عندما لا تقوم بإعادة تعيين module.exports. لتجنب الالتباس، يوصى بالالتزام بـ module.exports.

الكود النهائي الكامل

إليك الكود الكامل الذي يوضح تصدير كائن متعدد العناصر واستيراده، مع دمج الأمثلة الأكثر شيوعاً وعملية.

ملف: mathModule.js

// mathModule.js
/**
 * دالة لجمع عددين.
 * @param {number} a - العدد الأول.
 * @param {number} b - العدد الثاني.
 * @returns {number} مجموع العددين.
 */
function add(a, b) {
  return a + b;
}

/**
 * دالة لطرح عددين.
 * @param {number} a - العدد الأول.
 * @param {number} b - العدد الثاني.
 * @returns {number} الفرق بين العددين.
 */
function subtract(a, b) {
  return a - b;
}

/** ثابت يمثل قيمة باي (PI). */
const PI = 3.14159;

/**
 * تصدير كائن يحتوي على دوال ومتغيرات متعددة.
 * هذا يجعلها متاحة للاستيراد كوحدة واحدة.
 */
module.exports = {
  addFunction: add, // تصدير الدالة add باسم addFunction
  subtractFunction: subtract, // تصدير الدالة subtract باسم subtractFunction
  PI_VALUE: PI // تصدير الثابت PI باسم PI_VALUE
};

ملف: moduleDemo.js (لتوضيح الفرق بين exports و module.exports)

// moduleDemo.js

exports.name = "وحدة تجريبية"; // هذا سيتم تصديره عبر module.exports بشكل مبدئي

exports.getInfo = () => { // هذه الدالة ستكون متاحة أيضاً مبدئياً
  return "معلومات من الوحدة التجريبية.";
};

// الآن، إذا قمنا بإعادة تعيين module.exports، فإن كل ما سبق (name و getInfo) لن يتم تصديره
// وسيتم تصدير الكائن الجديد فقط الذي نحدده هنا.
module.exports = {
  version: "1.0.0",
  description: "وحدة جديدة بالكامل تم تصديرها."
};

// هذا لن يتم تصديره لأن module.exports تم استبداله بالفعل بكائن جديد.
exports.additional = "هذا لن يظهر!";

ملف: app.js

// app.js
/**
 * استيراد الكائن المصدر من mathModule.js.
 * الكائن المستورد (mathOperations) سيحتوي على الخصائص التي تم تصديرها.
 */
const mathOperations = require('./mathModule');

console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">ناتج الجمع: ${mathOperations.addFunction(10, 5)}</code>); // يجب أن يكون 15
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">ناتج الطرح: ${mathOperations.subtractFunction(10, 5)}</code>); // يجب أن يكون 5
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">قيمة PI: ${mathOperations.PI_VALUE}</code>); // يجب أن تكون 3.14159

// مثال على استخدام وحدة moduleDemo.js لفهم الفرق بين exports و module.exports
const demoModule = require('./moduleDemo');
console.log("\n--- مثال على الفرق بين exports و module.exports ---");
console.log("الكائن المصدر من moduleDemo.js:", demoModule);
// محاولة الوصول إلى خصائص لم يتم تصديرها فعلياً بسبب إعادة تعيين module.exports
// console.log("demoModule.name:", demoModule.name); // سيكون undefined
// console.log("demoModule.getInfo():", demoModule.getInfo()); // سيسبب TypeError: demoModule.getInfo is not a function
// console.log("demoModule.additional:", demoModule.additional); // سيكون undefined
console.log("-------------------------------------------------");

النتيجة المتوقعة

عند تشغيل ملف app.js باستخدام Node.js (node app.js)، ستكون النتيجة المتوقعة على النحو التالي:

ناتج الجمع: 15
ناتج الطرح: 5
قيمة PI: 3.14159

--- مثال على الفرق بين exports و module.exports ---
الكائن المصدر من moduleDemo.js: { version: '1.0.0', description: 'وحدة جديدة بالكامل تم تصديرها.' }
-------------------------------------------------