شرح module.exports في Node.js مع أمثلة عملية على تصدير الدوال في JavaScript
مقدمة: لماذا يُعدّ تصدير الوحدات أساسياً في Node.js؟
من أهم مزايا تطوير البرمجيات الحديثة القدرة على إعادة استخدام الشيفرة والبناء على ما أنجزه الآخرون. هذه الفكرة لا توفّر الوقت فحسب، بل تساعد أيضاً على تنظيم المشاريع وتسهيل صيانتها وتوسيعها مع مرور الوقت. في بيئة Node.js، يتم تحقيق ذلك عبر نظام الوحدات Modules الذي يتيح لك تقسيم المشروع إلى ملفات مستقلة، ثم تصدير ما تحتاجه منها واستيراده عند الحاجة.
يعتمد هذا النظام في أسلوب CommonJS على عنصرين رئيسيين هما module.exports وrequire(). وفهم العلاقة بينهما ضروري جداً لتجنّب الأخطاء الشائعة وكتابة شيفرة أوضح وأسهل في القراءة.

كيف يعمل نظام الوحدات في Node.js؟
تم تصميم نظام الوحدات في Node.js لتجنّب مشكلة التلوث في النطاق العام Global Scope التي كانت شائعة في تطبيقات المتصفح. وبدلاً من وضع كل شيء في مساحة مشتركة، يتم عزل كل ملف داخل وحدة مستقلة لها نطاقها الخاص.
هذا يعني أن كل ملف JavaScript في المشروع يمكن أن يتعامل معه Node.js كوحدة منفصلة. وإذا أردت مشاركة دالة أو كائن أو قيمة نصية مع ملف آخر، فعليك تصديرها من خلال module.exports، ثم استيرادها في الجهة الأخرى باستخدام require().
ما الذي يوفّره هذا الأسلوب؟
- تنظيم أفضل للشيفرة.
- سهولة إعادة الاستخدام.
- تقليل التداخل بين الملفات.
- تحسين قابلية الاختبار والصيانة.
ما هو module.exports بالتحديد؟
الخاصية module.exports هي جزء من الكائن module الذي ينشئه Node.js لكل ملف. هذه الخاصية تمثّل القيمة النهائية التي ستصبح متاحة عند استيراد الوحدة من ملف آخر.
وعند طباعة الكائن module باستخدام console.log(module)، قد يبدو شكله كالتالي:
Module {
id: '.',
path: '/Users/stanleynguyen/Documents/Projects/blog.stanleynguyen.me',
exports: {},
parent: null,
filename: '/Users/stanleynguyen/Documents/Projects/blog.stanleynguyen.me/index.js',
loaded: false,
children: [],
paths: [
'/Users/stanleynguyen/Documents/Projects/blog.stanleynguyen.me/node_modules',
'/Users/stanleynguyen/Documents/Projects/node_modules',
'/Users/stanleynguyen/Documents/node_modules',
'/Users/stanleynguyen/node_modules',
'/Users/node_modules',
'/node_modules'
]
}
المهم هنا هو أن module.exports يمكن أن يحمل أي نوع من القيم تقريباً، مثل:
- دالة
Function - كائناً
Object - سلسلة نصية
String - رقماً
Number - مصفوفة
Array
التصدير الافتراضي باستخدام module.exports
إذا كنت تريد تصدير قيمة واحدة فقط من الملف، فغالباً ما يكون التصدير الافتراضي هو الخيار الأنسب. ويتم ذلك بإسناد القيمة مباشرة إلى module.exports.
module.exports = function anExportedFunc() {
return "yup simple as that";
};
في هذا المثال، الملف يصدّر دالة واحدة فقط. وعند استخدام require() في ملف آخر، ستحصل مباشرة على هذه الدالة.
متى يكون هذا الأسلوب مناسباً؟
- عندما يحتوي الملف على وظيفة رئيسية واحدة.
- عندما تريد واجهة استخدام بسيطة وواضحة.
- عندما يكون الهدف تصدير كائن أو دالة واحدة فقط.
التصدير المسمّى في Node.js
في بعض الحالات، لا ترغب في تصدير قيمة واحدة فقط، بل تريد إتاحة عدة عناصر من الملف نفسه، مثل دوال متعددة أو ثوابت أو إعدادات. هنا يأتي دور التصدير المسمّى.
يمكنك فعل ذلك بإضافة خصائص إلى module.exports:
module.exports.anExportedFunc = () => {};
module.exports.anExportedString = "this string is exported";
// أو جمعها داخل كائن واحد
module.exports = {
anExportedFunc,
anExportedString,
};
هذا النمط مناسب جداً عندما يحتوي الملف على مجموعة أدوات أو وظائف مترابطة.
فوائد التصدير المسمّى
- إتاحة أكثر من قيمة من الملف نفسه.
- وضوح أعلى عند الاستيراد.
- تنظيم أفضل للمكتبات الصغيرة والمساعدة
Utilities.
ما هو exports؟
يوفّر Node.js متغيراً مختصراً باسم exports، وهو يشير في البداية إلى نفس الكائن المشار إليه بواسطة module.exports. لذلك يمكنك كتابة التصدير المسمّى بشكل مختصر:
exports.anExportedFunc = () => {};
exports.anExportedString = "this string is exported";
هذا الأسلوب يعمل طالما أنك تضيف خصائص إلى الكائن نفسه. لكن يجب الانتباه إلى نقطة مهمة جداً: لا يجوز التعامل مع exports كما لو أنه بديل كامل عن module.exports في كل الحالات.
الخطأ الشائع مع exports
الكود التالي لا يعمل كما يتوقعه كثير من المطورين:
// هذا لن يعمل كما هو متوقع
exports = {
anExportedFunc,
anExportedString,
};
السبب أن هذه العملية لا تعدّل module.exports نفسه، بل تعيد توجيه المتغير exports إلى مرجع جديد منفصل. وفي النهاية، فإن require() يعتمد على القيمة الموجودة داخل module.exports فقط.
شرح الفرق بين module.exports وexports
هذا الفرق من أكثر النقاط التي تسبب التباساً لدى المبتدئين في Node.js. والخلاصة التقنية المباشرة هي:
module.exportsهو المصدر الفعلي الذي تتم إعادته عند استخدامrequire().exportsهو مجرد مرجع مختصر يشير مبدئياً إلىmodule.exports.- إضافة خصائص إلى
exportsتنجح لأنها تعدّل الكائن نفسه. - إعادة إسناد
exportsإلى قيمة جديدة تفصل العلاقة بينه وبينmodule.exports.
بمعنى آخر، إذا كتبت:
exports.name = "Qaid";
فأنت ما زلت تعدّل الكائن الأصلي الذي سيقرأه require().
لكن إذا كتبت:
exports = { name: "Qaid" };
فأنت أنشأت مرجعاً جديداً لا علاقة له بالقيمة التي سيستخدمها require().

كيف يعمل require() عند الاستيراد؟
بعد تصدير القيم من ملف ما، يمكن للملفات الأخرى استيرادها باستخدام require(). لنفترض أن لدينا ملفاً باسم ship.js يحتوي على التصدير التالي:
module.exports = {
containerA,
containerB,
};
في ملف آخر مثل receiving-port.js يمكننا الاستيراد بطريقتين:
// استيراد الكائن بالكامل
const ship = require("./ship.js");
console.log(ship.containerA);
console.log(ship.containerB);
// أو استيراد الخصائص مباشرة عبر التفكيك
const { containerA, containerB } = require("./ship.js");
console.log(containerA);
console.log(containerB);
متى تستخدم كل طريقة؟
- استخدم الاستيراد الكامل إذا كنت تريد التعامل مع الوحدة كوحدة واحدة.
- استخدم التفكيك
Destructuringإذا كنت تحتاج إلى عناصر محددة فقط.
أفضل الممارسات عند تصدير الوحدات في CommonJS
من الناحية العملية، من الأفضل اعتماد أسلوب واضح وثابت داخل المشروع لتقليل الالتباس وتحسين قابلية القراءة. واحدة من أفضل الاستراتيجيات هي جمع الصادرات في أسفل الملف بشكل صريح باستخدام module.exports.
// default export
module.exports = function defaultExportedFunction() {};
// named export
module.exports = {
something,
anotherThing,
};
لماذا يُعد هذا الأسلوب أفضل؟
- يقلّل الخلط بين
exportsوmodule.exports. - يسهّل على القارئ معرفة ما الذي يخرجه الملف بسرعة.
- يجعل بنية الملف أكثر وضوحاً وثباتاً.
- يحدّ من الأخطاء المرتبطة بإعادة إسناد
exports.
إذا كنت تعمل ضمن فريق أو على مشروع طويل الأمد، فإن وضوح الشيفرة أهم من الاختصار البسيط في الكتابة.
الانتقال إلى ECMAScript Modules في Node.js
مع تطور Node.js، أصبح هناك معيار أحدث وأكثر اتساقاً لتعامل الوحدات، وهو ECMAScript Modules أو ما يعرف اختصاراً بـ ESM. هذا الأسلوب يقدّم صياغة أوضح وأكثر قرباً من المعايير الحديثة في JavaScript.
التصدير الافتراضي في ESM
export default function exportedFunction() {}
التصدير المسمّى في ESM
// named exports on separate LOC
export const constantString = "CONSTANT_STRING";
export const constantNumber = 5;
// consolidated named exports
export default {
constantString,
constantNumber,
};
ثم يمكن استيراد هذه القيم بسهولة كالتالي:
// default exported value
import exportedFunction from "exporting-module.js";
// import named exported values through object destructuring
import { constantString, constantNumber } from "exporting-module.js";
لماذا يفضّل كثير من المطورين ESM؟
- صياغته أوضح وأسهل في الفهم.
- يتوافق مع المعايير الحديثة في النظام البيئي لـ
JavaScript. - يلغي كثيراً من الالتباس الموجود بين
module.exportsوexports. - مناسب للمشاريع الجديدة التي تعمل على إصدارات حديثة من
Node.js.
مقارنة سريعة بين CommonJS وESM
| النقطة | CommonJS |
ESM |
|---|---|---|
| التصدير | module.exports |
export وexport default |
| الاستيراد | require() |
import |
| الوضوح | جيد لكن قد يسبب التباساً | أوضح وأكثر حداثة |
| التوافق مع المشاريع القديمة | ممتاز | يتطلب بيئة أو إعداداً مناسباً |
| الحالات الموصى بها | المشاريع الحالية أو القديمة | المشاريع الجديدة والحديثة |
نصائح عملية لاختيار أسلوب التصدير المناسب
- إذا كان مشروعك قديماً أو يعتمد على حزم مبنية على
CommonJS، فاستمر علىmodule.exportsمع الالتزام بالوضوح. - إذا كنت تبدأ مشروعاً جديداً على إصدار حديث من
Node.js، ففكّر جدياً في استخدامESM. - لا تستخدم
exportsلإعادة إسناد كائن جديد بالكامل. - اجعل الصادرات واضحة ومجمّعة في مكان يسهل مراجعته.
- اختر نمطاً موحداً في المشروع بالكامل لتسهيل القراءة والمراجعة.
أخطاء شائعة يجب تجنّبها
- الخلط بين
module.exportsوexports. - إعادة تعيين
exportsبدلاً من تعديل خصائصه. - تصدير عناصر كثيرة من ملف واحد دون حاجة، مما يربك بنية المشروع.
- عدم توحيد أسلوب التصدير بين ملفات المشروع.
- استخدام
CommonJSوESMبشكل عشوائي من دون فهم إعدادات البيئة.
الخلاصة التقنية
إذا أردنا تبسيط الفكرة إلى أقصى حد، فاعلم أن require() يقرأ من module.exports فقط، بينما exports ليس إلا اختصاراً مؤقتاً له. لذلك، فإن أكثر أسلوب عملي وأماناً في مشاريع Node.js التقليدية هو الاعتماد المباشر على module.exports، خصوصاً عند تصدير كائن كامل أو واجهة عامة للملف. أما إذا كانت بيئة المشروع حديثة وتدعم ECMAScript Modules، فإن الانتقال إلى import وexport يمنحك صياغة أنظف وتجربة تطوير أكثر وضوحاً واتساقاً.