Node.js: نظام الوحدات

آخر تحديث: 2026-08-26

تضخم مشروع تشارلي من 3 ملفات إلى 30 ملفًا. كانت جميع الوظائف والتكوينات وفئات الأدوات مكدسة في ملف ضخم واحد app.js، وكان تغيير وظيفة واحدة يتطلب نصف ساعة من البحث في 2,000 سطر من الكود. قرر تقسيم الكود إلى وحدات منفصلة، ليكتشف أن require وimport تبدوان مختلفتين، وأن module.exports وexports كانتا تتشابكان دائمًا، وأن التبعيات الدائرية كانت تتسبب في قيام البرنامج بإخراج مجموعة من indefinido. في هذا الدرس، سنرافق تشارلي وهو يكشف النقاب عن نظام الوحدات النمطية في Node.js، ويحول كوده من فوضى متشابكة إلى مجموعة منظمة جيدًا من اللبنات الأساسية.

ستتعلم:


1. وحدات CommonJS

(1) التصدير باستخدام module.exports

يستخدم Node.js مواصفات الوحدات النمطية CommonJS بشكل افتراضي. كل ملف يمثل وحدة نمطية تقوم بتصدير القيم باستخدام module.exports، بينما تقوم الملفات الأخرى بتحميلها باستخدام require().

JAVASCRIPT
// math.js
function add(a, b) {
  return a + b;
}

function subtract(a, b) {
  return a - b;
}

module.exports = { add, subtract };

(2) تحميل الوحدات النمطية باستخدام require

require() تقبل معرّف الوحدة النمطية وتُرجع القيمة module.exports لتلك الوحدة النمطية.

JAVASCRIPT
// app.js
const math = require('./math');

console.log(math.add(10, 3));      // 13
console.log(math.subtract(10, 3)); // 7

(3) الاختصار exports

exports هو مرجع إلى module.exports وهو مناسب لإضافة الخصائص واحدة تلو الأخرى.

JAVASCRIPT
// logger.js
exports.info = function (msg) {
  console.log(`[INFO] ${msg}`);
};

exports.error = function (msg) {
  console.log(`[ERROR] ${msg}`);
};

▶ مثال: تصدير دالة واحدة مقابل تصدير كائن

JAVASCRIPT
// greet.js — Export a Single Function
module.exports = function (name) {
  return `Hello, ${name}!`;
};

// config.js — Export Objects
module.exports = {
  port: 3000,
  host: 'localhost',
  debug: true,
};
▶ جرّب الكود
JAVASCRIPT
// app.js
const greet = require('./greet');
const config = require('./config');

console.log(greet('Charlie'));       // Hello, Charlie!
console.log(`Server: ${config.host}:${config.port}`); // Server: localhost:3000

(4) الفروق بين module.exports و exports

الميزة module.exports exports
الجوهر الكائن الفعلي الذي تم تصديره من الوحدة النمطية إشارة إلى module.exports
تصدير المهمة module.exports = fn exports = fn إزالة المرجع
أضف واحدًا تلو الآخر module.exports.foo = fn exports.foo = fn
تصدير قيمة واحدة ✅ موصى به ❌ غير متاح
الأمان صالحة دائمًا تنتهي صلاحيتها بعد إعادة التخصيص

المبدأ الأساسي: إذا كنت بحاجة إلى تصدير دالة واحدة أو فئة أو كائن جديد تمامًا، فيجب عليك استخدام module.exports؛ أما exports فيمكن استخدامه فقط لإضافة خصائص.



2. وحدات ES

(1) قواعد النحو الأساسية

تُعد وحدات ES (ESM) المعيار الرسمي لوحدات جافا سكريبت، وتستخدم صيغة export وimport.

JAVASCRIPT
// utils.mjs
export function square(n) {
  return n * n;
}

export const VERSION = '2.0.0';

export default function greet(name) {
  return `Hello, ${name}!`;
}
JAVASCRIPT
// app.mjs
import greet, { square, VERSION } from './utils.mjs';

console.log(greet('Charlie')); // Hello, Charlie!
console.log(square(5));         // 25
console.log(VERSION);           // 2.0.0

(2) ثلاث طرق لتفعيل ESM

الطريقة الوصف
امتداد الملف .mjs تقوم Node.js بمعالجته تلقائيًا على أنه ESM
"type": "module" في ملف package.json الملفات التي تحمل اسم .js داخل المشروع تُعامل افتراضيًّا على أنها ESM
--input-type=module معلمة سطر الأوامر المستخدمة لإدخال stdin
JSON
// package.json
{
  "type": "module"
}

▶ مثال:(3) الصادرات المحددة والصادرات الافتراضية

JAVASCRIPT
// shapes.mjs
export const PI = 3.14159;

export function circleArea(radius) {
  return PI * radius * radius;
}

export default class Shape {
  constructor(name) {
    this.name = name;
  }
  describe() {
    return `This is a ${this.name}`;
  }
}
▶ جرّب الكود

▶ مثال: التصدير وإعادة التصدير الموحدان

JAVASCRIPT
// api.mjs — Batch Export
export { addUser, removeUser } from './users.mjs';
export { logError } from './logger.mjs';

// You can also rename it
export { add as addUser } from './math.mjs';
▶ جرّب الكود

3. مقارنة بين CommonJS و ESM

(1) الاختلافات الرئيسية

البعد CommonJS وحدات ES
الصيغة require() / module.exports import / export
طريقة التحميل متزامن، التحميل أثناء وقت التشغيل غير متزامن، التحليل الثابت في وقت التحويل البرمجي
نوع القيمة نسخ القيمة (الأنواع الأولية) ربط القيمة (مرجع نشط)
وضع هذا في المستوى الأعلى module.exports indefinido
التبعيات الدائرية إرجاع الصادرات غير المحلولة ربط مرجعي، لكنه قد يكون ضمن منطقة TDZ
حالة الاستخدام مشروع Node.js (افتراضي) مشروع جديد، مشاركة الكود في المتصفح
امتداد الملف .js / .cjs .mjs / .js (النوع: وحدة)

▶ مثال:(2) نسخ القيمة مقابل الربط

JAVASCRIPT
// counter.cjs — CommonJS
let count = 0;
function increment() {
  count++;
}
module.exports = { count, increment };
▶ جرّب الكود
JAVASCRIPT
// counter.mjs — ESM
export let count = 0;
export function increment() {
  count++;
}
JAVASCRIPT
// CJS: count is a copy, it won't change
const c = require('./counter.cjs');
c.increment();
console.log(c.count); // 0 (still the initial value)
JAVASCRIPT
// ESM: count is bound, real-time updates
import { count, increment } from './counter.mjs';
increment();
console.log(count); // 1 (updated)

▶ مثال: استيراد وحدة CJS في ESM

JAVASCRIPT
// legacy.cjs
module.exports = { legacyMethod() { return 'old school'; } };
▶ جرّب الكود
JAVASCRIPT
// app.mjs
import cjs from './legacy.cjs';
console.log(cjs.legacyMethod()); // old school

عند import وحدة CJS في ESM، تُستخدم قيمة module.exports كصادرات افتراضية.



4. آلية البحث عن الوحدة النمطية require

(1) عملية البحث

عندما تكتب require('express')، يقوم Node.js بالبحث وفقًا للترتيب التالي:

100%
flowchart TD
    A["require('express')"] --> B{Does it have a built-in module??}
    B -- Yes --> C[Return built-in module]
    B -- No --> D{Path starting with ./ or / ?}
    D -- Yes --> E[Find file by path]
    E --> E1[Try .js / .json / .node]
    E1 --> E2[Try index.js]
    D -- No --> F[Search node_modules]
    F --> F1[Current Directory/node_modules/express]
    F1 --> F2[Parent directory/node_modules/express]
    F2 --> F3[Move up one level at a time until root directory]
    F3 --> F4{Found?}
    F4 -- No --> G[Throw MODULE_NOT_FOUND]
    F4 -- Yes --> H[Load and cache module]
    E2 --> H
    C --> H

(2) قواعد تحديد المسار

المعلمة المطلوبة طريقة التحليل مثال
./math المسار بالنسبة للملف الحالي ./math/project/src/math.js
../utils بالنسبة إلى الدليل الأصلي ../utils/project/utils.js
/abs/path المسار المطلق /lib/helper.js
express الوحدات المدمجة → node_modules البحث حسب المستوى
حزمة «Scope»

▶ مثال: عرض مسار تحديد وحدة النمط

JAVASCRIPT
// show-paths.js
console.log(module.paths);
▶ جرّب الكود
TEXT 📖 للعرض فقط
[
  '/project/src/node_modules',
  '/project/node_modules',
  '/node_modules',
  'C:\\Users\\Charlie\\.node_modules',
  'C:\\Users\\Charlie\\.node_libraries',
  'C:\\Program Files\\nodejs\\lib\\node'
]


5. آلية التخزين المؤقت للوحدات النمطية

(1) كيف يعمل التخزين المؤقت

require عند تحميل الوحدة النمطية لأول مرة، يتم تنفيذ كودها وتخزين النتيجة في ذاكرة التخزين المؤقت. وبعد ذلك، require تعرض الوحدة النمطية نفسها النتيجة المخزنة في ذاكرة التخزين المؤقت مباشرةً دون إعادة تنفيذ الكود.

JAVASCRIPT
// counter.js
console.log('counter.js executed!');
let count = 0;
module.exports = {
  increment() { return ++count; },
  getCount() { return count; },
};
JAVASCRIPT
// app.js
const c1 = require('./counter'); // counter.js executed!
const c2 = require('./counter'); // (no output, using cache)

console.log(c1 === c2);           // true
console.log(c1.increment());      // 1
console.log(c2.getCount());       // 1 (shared state)

(2) require.cache

يتم تخزين جميع الوحدات النمطية التي تم تحميلها مؤقتًا في الكائن require.cache، حيث يُستخدم المسار المطلق للوحدة النمطية كمفتاح.

JAVASCRIPT
// inspect-cache.js
const path = require('path');

const math = require('./math');

const cacheKey = path.resolve(__dirname, 'math.js');
console.log(require.cache[cacheKey] !== undefined); // true
console.log(require.cache[cacheKey].exports === math); // true

▶ مثال: مسح ذاكرة التخزين المؤقت لتنفيذ إعادة التحميل السريع

JAVASCRIPT
// hot-reload.js
function loadConfig() {
  const path = require('path');
  const cacheKey = path.resolve(__dirname, 'config.js');
  delete require.cache[cacheKey];
  return require('./config');
}

const cfg1 = loadConfig();
// ... config.js Modified ...
const cfg2 = loadConfig(); // Re-execute, loading the latest content
▶ جرّب الكود

إذا قمت بحذف الإدخال الموجود في require.cache ثم قمت بتشغيل require مرة أخرى، فسيقوم Node.js بإعادة تنفيذ تلك الوحدة النمطية. وهذا مفيد لإعادة التحميل السريع في بيئة التطوير، ولكن يجب توخي الحذر عند استخدامه في بيئة الإنتاج.



6. نظرة عامة على الوحدات المدمجة

(1) مرجع سريع للوحدات المدمجة الشائعة

يأتي Node.js مزودًا بعدد كبير من الوحدات المدمجة الجاهزة للاستخدام دون الحاجة إلى التثبيت.

الوحدة الغرض الطرق/الخصائص الشائعة
fs عمليات نظام الملفات readFile، writeFile، readdir، stat
path معالجة المسار join، resolve، parse، extname، basename
http خادم/عميل HTTP createServer، get، request
https خادم/عميل HTTPS createServer، get، request
url تحليل عناوين URL وتكوينها URL، fileURLToPath، pathToFileURL
os معلومات نظام التشغيل cpus، freemem، hostname، platform
events مُصدر الأحداث EventEmitter، on، emit، off
stream معالجة التدفق Readable، Writable، Transform، pipe
crypto التشفير والتجزئة createHash، createHmac، randomBytes
util المرافق promisify، callbackify، format، inspect
child_process إدارة العمليات الفرعية exec، spawn، fork
buffer معالجة البيانات الثنائية Buffer.alloc، Buffer.from، concat

▶ مثال:(2) لا تتطلب الوحدات المدمجة أي تثبيت

JAVASCRIPT
const fs = require('fs');
const path = require('path');
const os = require('os');

console.log(os.platform());                          // win32 / darwin / linux
console.log(path.join('/project', 'src', 'app.js')); // /project/src/app.js
▶ جرّب الكود

▶ مثال: البدء السريع باستخدام path وos

JAVASCRIPT
const path = require('path');
const os = require('os');

const filePath = '/project/src/utils/helper.js';
console.log(path.extname(filePath));   // .js
console.log(path.dirname(filePath));   // /project/src/utils
console.log(path.basename(filePath));  // helper.js

console.log(`CPU cores: ${os.cpus().length}`);
console.log(`Free memory: ${(os.freemem() / 1024 / 1024).toFixed(0)} MB`);
▶ جرّب الكود

7. التبعيات الدائرية

(1) ما المقصود بالتبعية الدائرية؟

تتطلب الوحدة النمطية «أ» الوحدة النمطية «ب»، وتطلب الوحدة النمطية «ب» الوحدة النمطية «أ»، مما يؤدي إلى حدوث مرجع دائري. ولا تدخل Node.js في حلقة لا نهائية؛ بل تقوم بدلاً من ذلك بإرجاع الصادرات من الجزء الذي تم تنفيذه بالفعل.

▶ مثال:(2) كيف يتعامل Node.js مع ذلك

JAVASCRIPT
// a.js
exports.loaded = false;
const b = require('./b');
exports.loaded = true;
console.log('a.js - b.loaded =', b.loaded);
▶ جرّب الكود
JAVASCRIPT
// b.js
exports.loaded = false;
const a = require('./a');  // Received a Unfinished exports { loaded: false }
exports.loaded = true;
console.log('b.js - a.loaded =', a.loaded);
BASH
node a.js
TEXT 📖 للعرض فقط
b.js - a.loaded = false
a.js - b.loaded = true

عندما يقوم b.js بتنفيذ require('./a')، لا يكون a.js قد انتهى من التنفيذ بعد، لذا تقوم Node.js بإرجاع الجزء من a.js الذي تم تعيين قيمة له في تلك اللحظة ({ loaded: false }).

(3) استراتيجيات لتجنب التبعيات الدائرية

الاستراتيجية الوصف
استخراج المنطق المشترك نقل الأجزاء المشتركة إلى وحدة ثالثة
متطلب مؤجل نقل require إلى داخل الدالة بحيث يتم تحميله فقط عند استدعائه
فصل الأحداث استخدم EventEmitter بدلاً من الاستدعاءات المباشرة
حقن التبعيات تمرير التبعيات عبر المعلمات بدلاً من تضمينها بشكل ثابت في الكود

▶ مثال: تأجيل require لحل التبعيات الدائرية

JAVASCRIPT
// user.js
exports.getName = function () {
  return 'Charlie';
};

exports.getProfile = function () {
  const format = require('./format'); // Delay until the time of the call require
  return format.upper(exports.getName());
};
▶ جرّب الكود
JAVASCRIPT
// format.js
exports.upper = function (str) {
  return str.toUpperCase();
};

exports.getUserDisplay = function () {
  const user = require('./user'); // Deferred require
  return `User: ${user.getName()}`;
};

يضمن تأجيل require أن تقوم الوحدة النمطية بتحميل تبعياتها فقط عند استدعاء إحدى الطرق للمرة الأولى، حيث تكون الوحدتان النمطيتان قد تم تهيئتهما بالكامل بحلول ذلك الوقت، مما يمنع استرداد exports غير المكتملة.



8. مثال شامل: مشروع معياري

فيما يلي، سنقوم بإنشاء مشروع معياري يتضمن وحدة مساعدة، ووحدة تسجيل، ونقطة دخول رئيسية:

JAVASCRIPT
// math.js — Tools Module
const PI = 3.14159;

function circleArea(radius) {
  return PI * radius * radius;
}

function rectangleArea(width, height) {
  return width * height;
}

function round(value, decimals = 2) {
  const factor = Math.pow(10, decimals);
  return Math.round(value * factor) / factor;
}

module.exports = { circleArea, rectangleArea, round };
JAVASCRIPT
// logger.js — Log Module
const LEVELS = { INFO: 'INFO', WARN: 'WARN', ERROR: 'ERROR' };

function formatMessage(level, msg) {
  const timestamp = new Date().toISOString();
  return `[${timestamp}] [${level}] ${msg}`;
}

function info(msg) {
  console.log(formatMessage(LEVELS.INFO, msg));
}

function warn(msg) {
  console.warn(formatMessage(LEVELS.WARN, msg));
}

function error(msg) {
  console.error(formatMessage(LEVELS.ERROR, msg));
}

module.exports = { info, warn, error, LEVELS };
JAVASCRIPT
// app.js — Main Entrance
const { circleArea, rectangleArea, round } = require('./math');
const { info, error } = require('./logger');

const radius = 5;
const area = round(circleArea(radius));

info(`Circle area (r=${radius}): ${area}`);

const roomArea = rectangleArea(4.5, 6.2);
info(`Room area: ${round(roomArea)} sqm`);

if (radius < 0) {
  error('Radius cannot be negative');
} else {
  info('Calculation complete');
}
BASH
node app.js
TEXT 📖 للعرض فقط
[2026-07-03T10:30:00.000Z] [INFO] Circle area (r=5): 78.54
[2026-07-03T10:30:00.001Z] [INFO] Room area: 27.9 sqm
[2026-07-03T10:30:00.001Z] [INFO] Calculation complete


❓ أسئلة شائعة

س هل يمكن استخدام CommonJS و ESM معًا؟
ج هناك بعض القيود. في ESM، يمكنك استيراد وحدات CJS (مع استخدام module.exports كصادرات افتراضية)، ولكن في CJS، لا يمكنك استخدام require لتحميل وحدات ESM؛ بل يجب عليك استخدام الدالة الديناميكية import() بدلاً من ذلك. يُنصح بأن تلتزم المشاريع بمواصفات وحدة واحدة فقط.
س هل require متزامن أم غير متزامن؟
ج متزامن. require يوقف تنفيذ الكود حتى تنتهي الوحدة النمطية من التحميل. ولهذا السبب توصي Node.js بوضع require في أعلى الملف وتجنب الاستدعاءات المتكررة لـ require للوحدات النمطية الجديدة في المسار الساخن أثناء وقت التشغيل.
س ما الفرق بين module.exports وexports؟
ج exports هي إشارة مختصرة إلى module.exports. يمكنك إضافة خصائص باستخدام exports.xxx = ...، لكن exports = xxx يقطع الإشارة، مما يتسبب في فشل التصدير. عندما تحتاج إلى تصدير دالة واحدة أو كائن جديد تمامًا، يجب عليك استخدام module.exports = xxx.
س كيف يمكنني عرض ذاكرة التخزين المؤقتة لأحد المكونات؟
ج يمكنك عرضها باستخدام الكائن require.cache. المفاتيح هي المسارات المطلقة للمكون، والقيم هي كائنات المكون. إذا حذفت مفتاحًا (delete require.cache[key])، فسيتم إعادة تنفيذ المكون في المرة التالية التي تستخدم فيها require.
س ما المقصود بالتبعية الدائرية؟ وكيف تتعامل Node.js معها؟
ج تحدث التبعية الدائرية عندما تعتمد وحدتان أو أكثر من الوحدات النمطية على بعضهما البعض. لا تتعطل Node.js في حلقة لا نهائية؛ بل تعيد exports التي لم يتم تنفيذها بالكامل بعد عند نقطة بدء الحلقة (والتي قد تكون كائنات غير مكتملة)، مما قد يؤدي إلى خصائص غير محددة. وتشمل الحلول استخراج وحدة نمطية مشتركة، وتأجيل استدعاءات require، وفصل الأحداث.
س لماذا يجب كتابة عبارات import في المستوى الأعلى في ESM؟
ج يتم تحليل ESM تحليلاً ثابتًا، لذا يتم تحديد التبعيات خلال مرحلة الترجمة، مما يسهل عملية «هز الشجرة» والتحسين. بالنسبة لسيناريوهات التحميل الديناميكي، يمكنك استخدام الدالة import().
س ماذا يحدث عند استخدام require لتحميل ملف JSON؟
ج تقوم Node.js بقراءة ملف JSON وتحليله تلقائيًا باستخدام JSON.parse()، لتُرجع كائن JavaScript مُحلَّل. ويُستخدم هذا عادةً لتحميل ملفات التكوين.

📖 ملخص


📝 تمارين

  1. قم بإنشاء الوحدة النمطية calculator.js، وقم بتصدير الدوال الأربع add وsubtract وmultiply وdivide، ثم قم باستدعائها واستخدامها في main.js
  2. حوّل السؤال السابق إلى نسخة ESM: استخدم صيغة export واللاحقة .mjs، وقم بتحميله باستخدام import.
  3. اكتب كودًا للتحقق من وجود require.cache: بعد استدعاء وحدة نمطية، اعرض المعلومات المتعلقة بتلك الوحدة النمطية من require.cache.
  4. قم عمدًا بإنشاء تبعية دائرية (ملف a.js يتطلب ملف b.js، وملف b.js يتطلب ملف a.js)، وراقب النتيجة، ثم قم بإصلاحها باستخدام أوامر الاستدعاء المؤجلة.
  5. استخدم الوحدتين path وos لطباعة نظام التشغيل الحالي، وعدد نوى وحدة المعالجة المركزية (CPU)، والمسار المطلق للدليل الحالي.
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%