Skip to content

الوحدات: وحدات CommonJS

[مستقر: 2 - مستقر]

مستقر: 2 استقرار: 2 - مستقر

تُعد وحدات CommonJS هي الطريقة الأصلية لتغليف تعليمات برمجة JavaScript لـ Node.js. يدعم Node.js أيضًا معيار وحدات ECMAScript الذي تستخدمه المتصفحات وبيئات تشغيل JavaScript الأخرى.

في Node.js، يُعامل كل ملف على أنه وحدة منفصلة. على سبيل المثال، ضع في اعتبارك ملفًا باسم foo.js:

js
const circle = require('./circle.js')
console.log(`مساحة دائرة نصف قطرها 4 هي ${circle.area(4)}`)

في السطر الأول، يقوم foo.js بتحميل الوحدة circle.js الموجودة في نفس الدليل مثل foo.js.

فيما يلي محتويات circle.js:

js
const { PI } = Math

exports.area = r => PI * r ** 2

exports.circumference = r => 2 * PI * r

قامت الوحدة circle.js بتصدير الدالتين area() و circumference(). يتم إضافة الدوال والكائنات إلى الجذر الأساسي لوحدة ما من خلال تحديد خصائص إضافية على كائن exports الخاص.

ستكون المتغيرات المحلية للوحدة خاصة، لأن Node.js يلف الوحدة في دالة (انظر لفاف الوحدة). في هذا المثال، المتغير PI خاص بـ circle.js.

يمكن تعيين قيمة جديدة لخاصية module.exports (مثل دالة أو كائن).

في التعليمات البرمجية التالية، يستخدم bar.js وحدة square، التي تصدر فئة Square:

js
const Square = require('./square.js')
const mySquare = new Square(2)
console.log(`مساحة mySquare هي ${mySquare.area()}`)

تم تعريف وحدة square في square.js:

js
// إنشاء تعيين لـ exports لن يغير الوحدة، يجب استخدام module.exports
module.exports = class Square {
  constructor(width) {
    this.width = width
  }

  area() {
    return this.width ** 2
  }
}

تم تنفيذ نظام وحدة CommonJS في وحدة النواة module.

التمكين

يحتوي Node.js على نظامي وحدات: وحدات CommonJS ووحدات ECMAScript.

بشكل افتراضي، سيعامل Node.js ما يلي على أنه وحدات CommonJS:

  • الملفات ذات الامتداد .cjs؛
  • الملفات ذات الامتداد .js عندما يحتوي أقرب ملف package.json رئيسي على حقل من أعلى مستوى "type" بقيمة "commonjs".
  • الملفات ذات الامتداد .js أو بدون امتداد، عندما لا يحتوي أقرب ملف package.json رئيسي على حقل من أعلى مستوى "type" أو لا يوجد ملف package.json في أي مجلد رئيسي؛ ما لم يحتوي الملف على بناء جملة يسبب أخطاء ما لم يتم تقييمه كوحدة ES. يجب على مؤلفي الحزم تضمين حقل "type"، حتى في الحزم التي تكون جميع مصادرها من CommonJS. إن التحديد الصريح لنوع الحزمة سيجعل الأمور أسهل لأدوات البناء وأدوات التحميل لتحديد كيفية تفسير الملفات الموجودة في الحزمة.
  • الملفات ذات الامتداد الذي ليس .mjs أو .cjs أو .json أو .node أو .js (عندما يحتوي أقرب ملف package.json رئيسي على حقل من أعلى مستوى "type" بقيمة "module"، سيتم التعرف على هذه الملفات كوحدات CommonJS فقط إذا تم تضمينها عبر require()، وليس عند استخدامها كنقطة دخول سطر الأوامر للبرنامج).

راجع تحديد نظام الوحدة لمزيد من التفاصيل.

استدعاء require() يستخدم دائمًا محمل وحدة CommonJS. استدعاء import() يستخدم دائمًا محمل وحدة ECMAScript.

الوصول إلى الوحدة النمطية الرئيسية

عندما يتم تشغيل ملف مباشرةً من Node.js، يتم تعيين require.main إلى module الخاص به. وهذا يعني أنه من الممكن تحديد ما إذا كان الملف قد تم تشغيله مباشرةً من خلال اختبار require.main === module.

بالنسبة لملف foo.js، ستكون هذه القيمة true إذا تم تشغيله عبر node foo.js، ولكن false إذا تم تشغيله بواسطة require('./foo').

عندما لا تكون نقطة الدخول وحدة نمطية CommonJS، يكون require.main مساويًا لـ undefined، وتصبح الوحدة النمطية الرئيسية بعيدة المنال.

نصائح حول إدارة الحزم

تم تصميم دلالات دالة Node.js require() لتكون عامة بما يكفي لدعم هياكل الدلائل المعقولة. من المأمول أن تجد برامج إدارة الحزم مثل dpkg و rpm و npm أنه من الممكن إنشاء حزم أصلية من وحدات Node.js بدون تعديل.

فيما يلي، نقدم هيكلًا مقترحًا للدليل قد يعمل:

لنقل أننا أردنا أن يحتوي المجلد في /usr/lib/node/\<some-package\>/\<some-version\> على محتويات إصدار محدد من حزمة.

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

بسبب قيام Node.js بالبحث عن realpath لأي وحدات تقوم بتحميلها (أي أنها تحل الارتباطات الرمزية) ثم البحث عن تبعياتها في مجلدات node_modules، يمكن حل هذا الموقف باستخدام البنية التالية:

  • /usr/lib/node/foo/1.2.3/: محتويات حزمة foo، الإصدار 1.2.3.
  • /usr/lib/node/bar/4.3.2/: محتويات حزمة bar التي تعتمد عليها foo.
  • /usr/lib/node/foo/1.2.3/node_modules/bar: رابط رمزي إلى /usr/lib/node/bar/4.3.2/.
  • /usr/lib/node/bar/4.3.2/node_modules/*: روابط رمزية إلى الحزم التي تعتمد عليها bar.

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

عندما يقوم الرمز الموجود في حزمة foo بتنفيذ require('bar')، سيحصل على الإصدار الذي تم إنشاء رابط رمزي له في /usr/lib/node/foo/1.2.3/node_modules/bar. ثم، عندما يقوم الرمز الموجود في حزمة bar بالاتصال بـ require('quux')، سيحصل على الإصدار الذي تم إنشاء رابط رمزي له في /usr/lib/node/bar/4.3.2/node_modules/quux.

علاوة على ذلك، لجعل عملية البحث عن الوحدات النمطية أكثر مثالية، بدلاً من وضع الحزم مباشرةً في /usr/lib/node، يمكننا وضعها في /usr/lib/node_modules/\<name\>/\<version\>. عندئذٍ لن يكلف Node.js نفسه عناء البحث عن التبعيات المفقودة في /usr/node_modules أو /node_modules.

من أجل جعل الوحدات النمطية متاحة لـ Node.js REPL، قد يكون من المفيد أيضًا إضافة مجلد /usr/lib/node_modules إلى متغير البيئة $NODE_PATH. نظرًا لأن عمليات البحث عن الوحدات النمطية باستخدام مجلدات node_modules كلها نسبية، وبناءً على المسار الحقيقي للملفات التي تقوم بالاتصالات بـ require()، يمكن أن تكون الحزم نفسها في أي مكان.

تحميل وحدات ECMAScript باستخدام require()

[السجل]

الإصدارالتغييرات
v23.5.0لم تعد هذه الميزة تُصدر تحذيرًا تجريبيًا افتراضيًا، على الرغم من أنه لا يزال بإمكان التحذير أن يُصدره --trace-require-module.
v23.0.0لم تعد هذه الميزة خلف علم سطر الأوامر --experimental-require-module.
v23.0.0دعم تصدير التوافق 'module.exports' في require(esm).
v22.0.0، v20.17.0تمت الإضافة في: v22.0.0، v20.17.0

[مستقر: 1 - تجريبي]

مستقر: 1 استقرار: 1.2 - إصدار مرشح

الامتداد .mjs محجوز لـ وحدات ECMAScript. راجع قسم تحديد نظام الوحدة لمزيد من المعلومات حول الملفات التي يتم تحليلها كوحدات ECMAScript.

يدعم require() فقط تحميل وحدات ECMAScript التي تلبي المتطلبات التالية:

  • الوحدة متزامنة بالكامل (لا تحتوي على await على مستوى أعلى)؛ و
  • يتم استيفاء أحد هذه الشروط:

إذا كانت وحدة ES التي يتم تحميلها تلبي المتطلبات، فيمكن لـ require() تحميلها وإرجاع كائن مساحة اسم الوحدة. في هذه الحالة، يكون مشابهًا لـ import() الديناميكي ولكنه يتم تشغيله بشكل متزامن ويعيد كائن مساحة الاسم مباشرةً.

مع وحدات ES التالية:

js
// distance.mjs
export function distance(a, b) {
  return (b.x - a.x) ** 2 + (b.y - a.y) ** 2
}
js
// point.mjs
export default class Point {
  constructor(x, y) {
    this.x = x
    this.y = y
  }
}

يمكن لوحدة CommonJS تحميلها باستخدام require():

js
const distance = require('./distance.mjs')
console.log(distance)
// [Module: null prototype] {
//   distance: [Function: distance]
// }

const point = require('./point.mjs')
console.log(point)
// [Module: null prototype] {
//   default: [class Point],
//   __esModule: true,
// }

للتوافق مع الأدوات الموجودة التي تحول وحدات ES إلى CommonJS، والتي يمكنها بعد ذلك تحميل وحدات ES الحقيقية من خلال require()، ستحتوي مساحة الاسم المُرجعة على خاصية __esModule: true إذا كان لديها تصدير افتراضي بحيث يمكن للرمز المُستهلك الذي تم إنشاؤه بواسطة الأدوات التعرف على الصادرات الافتراضية في وحدات ES الحقيقية. إذا كانت مساحة الاسم تُعرّف بالفعل __esModule، فلن تتم إضافة ذلك. هذه الخاصية تجريبية ويمكن أن تتغير في المستقبل. يجب استخدامها فقط من قبل الأدوات التي تحول وحدات ES إلى وحدات CommonJS، باتباع اتفاقيات النظام الإيكولوجي الحالية. يجب تجنب الاعتماد عليها من قبل الرمز المُؤلف مباشرةً في CommonJS.

عندما تحتوي وحدة ES على كل من الصادرات المسمّاة والتصدير الافتراضي، تكون النتيجة التي تُرجعها require() هي كائن مساحة اسم الوحدة، والذي يضع التصدير الافتراضي في الخاصية .default، على غرار النتائج التي تُرجعها import(). لتخصيص ما يجب أن تُعيده require(esm) مباشرةً، يمكن لوحدة ES تصدير القيمة المطلوبة باستخدام اسم السلسلة "module.exports".

js
// point.mjs
export default class Point {
  constructor(x, y) {
    this.x = x
    this.y = y
  }
}

// `distance` ضائعة على مستهلكي CommonJS لهذه الوحدة، ما لم تُضاف إلى `Point` كخاصية ثابتة.
export function distance(a, b) {
  return (b.x - a.x) ** 2 + (b.y - a.y) ** 2
}
export { Point as 'module.exports' }
js
const Point = require('./point.mjs')
console.log(Point) // [class Point]

// الصادرات المسمّاة ضائعة عند استخدام 'module.exports'
const { distance } = require('./point.mjs')
console.log(distance) // undefined

لاحظ في المثال أعلاه، عند استخدام اسم التصدير module.exports، ستضيع الصادرات المسمّاة على مستهلكي CommonJS. للسماح لمستهلكي CommonJS بالاستمرار في الوصول إلى الصادرات المسمّاة، يمكن للوحدة التأكد من أن التصدير الافتراضي هو كائن مع الصادرات المسمّاة المرفقة به كخصائص. على سبيل المثال، مع المثال أعلاه، يمكن إرفاق distance بالتصدير الافتراضي، فئة Point، كطريقة ثابتة.

js
export function distance(a, b) {
  return (b.x - a.x) ** 2 + (b.y - a.y) ** 2
}

export default class Point {
  constructor(x, y) {
    this.x = x
    this.y = y
  }
  static distance = distance
}

export { Point as 'module.exports' }
js
const Point = require('./point.mjs')
console.log(Point) // [class Point]

const { distance } = require('./point.mjs')
console.log(distance) // [Function: distance]

إذا كانت الوحدة التي يتم require() لها تحتوي على await على مستوى أعلى، أو الرسم البياني للوحدة الذي تقوم import به يحتوي على await على مستوى أعلى، فسيتم طرح ERR_REQUIRE_ASYNC_MODULE. في هذه الحالة، يجب على المستخدمين تحميل الوحدة غير المتزامنة باستخدام import().

إذا تم تمكين --experimental-print-required-tla، فبدلاً من طرح ERR_REQUIRE_ASYNC_MODULE قبل التقييم، سيقوم Node.js بتقييم الوحدة، ويحاول تحديد عمليات الانتظار على مستوى الأعلى، ويطبع موقعها لمساعدة المستخدمين على إصلاحها.

يُعد دعم تحميل وحدات ES باستخدام require() تجريبيًا حاليًا ويمكن تعطيله باستخدام --no-experimental-require-module. لطباعة مكان استخدام هذه الميزة، استخدم --trace-require-module.

يمكن اكتشاف هذه الميزة عن طريق التحقق مما إذا كان process.features.require_module هو true.

الكل معًا

للحصول على اسم الملف الدقيق الذي سيتم تحميله عند استدعاء require()، استخدم دالة require.resolve().

جمعًا لكل ما سبق، إليك الخوارزمية عالية المستوى بالرمز الكاذب لما تفعله require() :

text
require(X) من الوحدة النمطية في المسار Y
1. إذا كان X وحدة أساسية،
   أ. أعد الوحدة الأساسية
   ب. توقف
2. إذا بدأ X بـ '/'
   أ. ضع Y ليكون جذر نظام الملفات
3. إذا بدأ X بـ './' أو '/' أو '../'
   أ. LOAD_AS_FILE(Y + X)
   ب. LOAD_AS_DIRECTORY(Y + X)
   ج. ألقِ "لم يتم العثور عليه"
4. إذا بدأ X بـ '#'
   أ. LOAD_PACKAGE_IMPORTS(X, dirname(Y))
5. LOAD_PACKAGE_SELF(X, dirname(Y))
6. LOAD_NODE_MODULES(X, dirname(Y))
7. ألقِ "لم يتم العثور عليه"

MAYBE_DETECT_AND_LOAD(X)
1. إذا تم تحليل X كوحدة نمطية CommonJS، قم بتحميل X كوحدة نمطية CommonJS. توقف.
2. خلاف ذلك، إذا كان بإمكان تحليل شفرة المصدر لـ X كوحدة نمطية ECMAScript باستخدام
  <a href="esm#resolver-algorithm-specification">DETECT_MODULE_SYNTAX المُعرّف في
  محلّي الوحدات النمطية ESM</a>،
  أ. حمّل X كوحدة نمطية ECMAScript. توقف.
3. ألقِ SyntaxError من محاولة تحليل X كـ CommonJS في 1. توقف.

LOAD_AS_FILE(X)
1. إذا كان X ملفًا، حمّل X بتنسيق امتداد الملف الخاص به. توقف
2. إذا كان X.js ملفًا،
    أ. ابحث عن نطاق الحزمة الأقرب SCOPE إلى X.
    ب. إذا لم يتم العثور على نطاق
      1. MAYBE_DETECT_AND_LOAD(X.js)
    ج. إذا كان SCOPE/package.json يحتوي على حقل "type"،
      1. إذا كان حقل "type" هو "module"، حمّل X.js كوحدة نمطية ECMAScript. توقف.
      2. إذا كان حقل "type" هو "commonjs"، حمّل X.js كوحدة نمطية CommonJS. توقف.
    د. MAYBE_DETECT_AND_LOAD(X.js)
3. إذا كان X.json ملفًا، حمّل X.json إلى كائن JavaScript. توقف
4. إذا كان X.node ملفًا، حمّل X.node كإضافة ثنائية. توقف

LOAD_INDEX(X)
1. إذا كان X/index.js ملفًا
    أ. ابحث عن نطاق الحزمة الأقرب SCOPE إلى X.
    ب. إذا لم يتم العثور على نطاق، حمّل X/index.js كوحدة نمطية CommonJS. توقف.
    ج. إذا كان SCOPE/package.json يحتوي على حقل "type"،
      1. إذا كان حقل "type" هو "module"، حمّل X/index.js كوحدة نمطية ECMAScript. توقف.
      2. خلاف ذلك، حمّل X/index.js كوحدة نمطية CommonJS. توقف.
2. إذا كان X/index.json ملفًا، قم بتحليل X/index.json إلى كائن JavaScript. توقف
3. إذا كان X/index.node ملفًا، حمّل X/index.node كإضافة ثنائية. توقف

LOAD_AS_DIRECTORY(X)
1. إذا كان X/package.json ملفًا،
   أ. قم بتحليل X/package.json، وابحث عن حقل "main".
   ب. إذا كانت قيمة "main" خاطئة، انتقل إلى 2.
   ج. دع M = X + (حقل json الرئيسي)
   د. LOAD_AS_FILE(M)
   هـ. LOAD_INDEX(M)
   و. LOAD_INDEX(X) مُعطّل
   ز. ألقِ "لم يتم العثور عليه"
2. LOAD_INDEX(X)

LOAD_NODE_MODULES(X, START)
1. دع DIRS = NODE_MODULES_PATHS(START)
2. لكل DIR في DIRS:
   أ. LOAD_PACKAGE_EXPORTS(X, DIR)
   ب. LOAD_AS_FILE(DIR/X)
   ج. LOAD_AS_DIRECTORY(DIR/X)

NODE_MODULES_PATHS(START)
1. دع PARTS = تقسيم المسار(START)
2. دع I = عدد PARTS - 1
3. دع DIRS = []
4. بينما I >= 0،
   أ. إذا كان PARTS[I] = "node_modules"، انتقل إلى د.
   ب. DIR = ربط المسار(PARTS[0 .. I] + "node_modules")
   ج. DIRS = DIR + DIRS
   د. دع I = I - 1
5. أعد DIRS + GLOBAL_FOLDERS

LOAD_PACKAGE_IMPORTS(X, DIR)
1. ابحث عن نطاق الحزمة الأقرب SCOPE إلى DIR.
2. إذا لم يتم العثور على نطاق، فارجع.
3. إذا كان SCOPE/package.json "imports" فارغًا أو غير مُعرّف، فارجع.
4. إذا تم تمكين `--experimental-require-module`
  أ. دع CONDITIONS = ["node", "require", "module-sync"]
  ب. خلاف ذلك، دع CONDITIONS = ["node", "require"]
5. دع MATCH = PACKAGE_IMPORTS_RESOLVE(X, pathToFileURL(SCOPE),
  CONDITIONS) <a href="esm#resolver-algorithm-specification">المُعرّف في محلّي الوحدات النمطية ESM</a>.
6. RESOLVE_ESM_MATCH(MATCH).

LOAD_PACKAGE_EXPORTS(X, DIR)
1. حاول تفسير X كمجموعة من NAME و SUBPATH حيث قد يكون الاسم
   يحتوي على بادئة @scope/ ويبدأ المسار الفرعي بعلامة مائلة (`/`).
2. إذا لم يتطابق X مع هذا النمط أو لم يكن DIR/NAME/package.json ملفًا،
   فارجع.
3. قم بتحليل DIR/NAME/package.json، وابحث عن حقل "exports".
4. إذا كان "exports" فارغًا أو غير مُعرّف، فارجع.
5. إذا تم تمكين `--experimental-require-module`
  أ. دع CONDITIONS = ["node", "require", "module-sync"]
  ب. خلاف ذلك، دع CONDITIONS = ["node", "require"]
6. دع MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(DIR/NAME), "." + SUBPATH،
   `package.json` "exports"، CONDITIONS) <a href="esm#resolver-algorithm-specification">المُعرّف في محلّي الوحدات النمطية ESM</a>.
7. RESOLVE_ESM_MATCH(MATCH)

LOAD_PACKAGE_SELF(X, DIR)
1. ابحث عن نطاق الحزمة الأقرب SCOPE إلى DIR.
2. إذا لم يتم العثور على نطاق، فارجع.
3. إذا كان SCOPE/package.json "exports" فارغًا أو غير مُعرّف، فارجع.
4. إذا لم يكن SCOPE/package.json "name" هو الجزء الأول من X، فارجع.
5. دع MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(SCOPE)،
   "." + X.slice("name".length)، `package.json` "exports"، ["node", "require"])
   <a href="esm#resolver-algorithm-specification">المُعرّف في محلّي الوحدات النمطية ESM</a>.
6. RESOLVE_ESM_MATCH(MATCH)

RESOLVE_ESM_MATCH(MATCH)
1. دع RESOLVED_PATH = fileURLToPath(MATCH)
2. إذا كان الملف الموجود في RESOLVED_PATH موجودًا، حمّل RESOLVED_PATH بتنسيق امتداد الملف الخاص به. توقف
3. ألقِ "لم يتم العثور عليه"

تخزين مؤقت

يتم تخزين الوحدات النمطية مؤقتًا بعد تحميلها لأول مرة. هذا يعني (من بين أمور أخرى) أن كل مكالمة إلى require('foo') ستحصل على نفس الكائن المُرجع بالضبط، إذا كان سيُحل إلى نفس الملف.

بشرط عدم تعديل require.cache، فإن المكالمات المتعددة إلى require('foo') لن تتسبب في تنفيذ رمز الوحدة النمطية عدة مرات. هذه ميزة مهمة. معها، يمكن إرجاع الكائنات "المكتملة جزئيًا"، مما يسمح بتحميل التبعيات العابرة حتى عندما تسبب دورات.

لجعل وحدة نمطية تُنفذ الكود عدة مرات، قم بتصدير دالة، واستدعاء تلك الدالة.

تحذيرات تخزين الوحدات النمطية مؤقتًا

يتم تخزين الوحدات النمطية مؤقتًا بناءً على اسم الملف المُحل لها. نظرًا لأن الوحدات النمطية قد تُحل إلى اسم ملف مختلف بناءً على موقع الوحدة النمطية المُستدعية (التحميل من مجلدات node_modules)، فإنه ليس ضمانًا أن require('foo') ستُرجع دائمًا نفس الكائن بالضبط، إذا كانت ستُحل إلى ملفات مختلفة.

بالإضافة إلى ذلك، في أنظمة الملفات أو أنظمة التشغيل غير حساسة لحالة الأحرف، يمكن لأسماء الملفات المُحلّة المختلفة أن تشير إلى نفس الملف، لكن ذاكرة التخزين المؤقت ستظل تعاملها على أنها وحدات نمطية مختلفة وستُعيد تحميل الملف عدة مرات. على سبيل المثال، require('./foo') و require('./FOO') تُرجع كائنين مختلفين، بغض النظر عما إذا كان ./foo و ./FOO هما نفس الملف.

الوحدات النمطية المُدمجة

[السجل]

الإصدارالتغييرات
v16.0.0، v14.18.0تمت إضافة دعم استيراد node: إلى require(...).

يحتوي Node.js على عدة وحدات نمطية مُجمعة في الملف الثنائي. يتم وصف هذه الوحدات النمطية بمزيد من التفاصيل في مكان آخر من هذه الوثائق.

يتم تعريف الوحدات النمطية المُدمجة داخل مصدر Node.js وتقع في مجلد lib/.

يمكن تحديد الوحدات النمطية المُدمجة باستخدام بادئة node:، وفي هذه الحالة، تتجاوز ذاكرة التخزين المؤقت require. على سبيل المثال، require('node:http') ستُرجع دائمًا وحدة HTTP المُدمجة، حتى لو كان هناك إدخال require.cache بهذا الاسم.

يتم دائمًا تحميل بعض الوحدات النمطية المُدمجة بشكل تفضيلي إذا تم تمرير مُعرّفها إلى require(). على سبيل المثال، require('http') ستُرجع دائمًا وحدة HTTP المُدمجة، حتى لو كان هناك ملف بهذا الاسم. تتوفر قائمة الوحدات النمطية المُدمجة التي يمكن تحميلها دون استخدام بادئة node: في module.builtinModules، مدرجة بدون البادئة.

الوحدات المُدمجة مع بادئة node: الإلزامية

عند التحميل بواسطة require()، يجب طلب بعض الوحدات المُدمجة باستخدام بادئة node:. يوجد هذا الشرط لمنع حدوث تعارض بين الوحدات المُدمجة المُضافة حديثًا وحزم مساحة المستخدم التي تحمل نفس الاسم بالفعل. حاليًا، الوحدات المُدمجة التي تتطلب بادئة node: هي:

تُعرض قائمة هذه الوحدات في module.builtinModules، بما في ذلك البادئة.

الدورات

عندما تكون هناك مُكالمات require() دائرية، قد لا يكون جرى تنفيذ وحدة ما بالكامل عند إعادتها.

ضع في اعتبارك هذا الموقف:

a.js:

js
console.log('a starting')
exports.done = false
const b = require('./b.js')
console.log('in a, b.done = %j', b.done)
exports.done = true
console.log('a done')

b.js:

js
console.log('b starting')
exports.done = false
const a = require('./a.js')
console.log('in b, a.done = %j', a.done)
exports.done = true
console.log('b done')

main.js:

js
console.log('main starting')
const a = require('./a.js')
const b = require('./b.js')
console.log('in main, a.done = %j, b.done = %j', a.done, b.done)

عندما تقوم main.js بتحميل a.js، تقوم a.js بدورها بتحميل b.js. في تلك المرحلة، تحاول b.js تحميل a.js. ولمنع حدوث حلقة لا نهائية، يتم إرجاع نسخة غير مكتملة من كائن exports الخاص بـ a.js إلى وحدة b.js. ثم تنتهي b.js من التحميل، ويتم توفير كائن exports الخاص بها لوحدة a.js.

بحلول الوقت الذي تقوم فيه main.js بتحميل كلا الوحدتين، يكون كلاهما قد انتهى. لذلك سيكون مخرجات هذا البرنامج:

bash
$ node main.js
main starting
a starting
b starting
in b, a.done = false
b done
in a, b.done = true
a done
in main, a.done = true, b.done = true

يُطلب التخطيط الدقيق للسماح باعتماديات الوحدات الدائرية بالعمل بشكل صحيح داخل التطبيق.

وحدات الملفات

إذا لم يتم العثور على اسم الملف بالضبط، فستحاول Node.js تحميل اسم الملف المطلوب مع ملحقات إضافية: .js، و.json، وأخيرًا .node. عند تحميل ملف يحتوي على امتداد مختلف (مثل .cjs)، يجب تمرير اسمه الكامل إلى require()، بما في ذلك امتداد الملف (مثل require('./file.cjs')).

يتم تحليل ملفات .json كملفات نصية JSON، ويتم تفسير ملفات .node كملحقات إضافية مُجمعة يتم تحميلها باستخدام process.dlopen(). يتم تحليل الملفات التي تستخدم أي امتداد آخر (أو بدون امتداد على الإطلاق) كملفات نصية JavaScript. يرجى الرجوع إلى قسم تحديد نظام الوحدة لفهم هدف التحليل الذي سيتم استخدامه.

الوحدة المطلوبة التي تبدأ بـ '/' هي مسار مطلق إلى الملف. على سبيل المثال، سيقوم require('/home/marco/foo.js') بتحميل الملف الموجود في /home/marco/foo.js.

الوحدة المطلوبة التي تبدأ بـ './' هي مسار نسبي إلى الملف الذي يستدعي require(). وهذا يعني أن الملف circle.js يجب أن يكون في نفس الدليل مثل foo.js حتى يتمكن require('./circle') من العثور عليه.

بدون بادئة '/' أو './' أو '../' للإشارة إلى ملف، يجب أن تكون الوحدة إما وحدة أساسية أو يتم تحميلها من مجلد node_modules.

إذا لم يكن المسار المعطى موجودًا، فسيقوم require() بإطلاق خطأ MODULE_NOT_FOUND.

المجلدات كـوحدات

[مستقر: 3 - إرث]

مستقر: 3 ثبات: 3 - إرث: استخدم تصدير المسارات الفرعية أو استيراد المسارات الفرعية بدلاً من ذلك.

هناك ثلاث طرق يمكن من خلالها تمرير مجلد إلى require() كوسيطة.

الأولى هي إنشاء ملف package.json في جذر المجلد، والذي يحدد وحدة main. قد يبدو ملف package.json مثالياً كالتالي:

json
{ "name": "some-library", "main": "./lib/some-library.js" }

إذا كان هذا في مجلد في ./some-library، فسوف يحاول require('./some-library') تحميل ./some-library/lib/some-library.js.

إذا لم يكن هناك ملف package.json موجودًا في الدليل، أو إذا كانت إدخال "main" مفقودًا أو لا يمكن حله، فستحاول Node.js تحميل ملف index.js أو index.node من ذلك الدليل. على سبيل المثال، إذا لم يكن هناك ملف package.json في المثال السابق، فسوف يحاول require('./some-library') تحميل:

  • ./some-library/index.js
  • ./some-library/index.node

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

bash
Error: Cannot find module 'some-library'

في جميع الحالات الثلاث أعلاه، ستؤدي مكالمة import('./some-library') إلى خطأ ERR_UNSUPPORTED_DIR_IMPORT. يمكن أن يوفر استخدام تصدير مسارات فرعية أو استيراد مسارات فرعية نفس فوائد تنظيم المحتوى مثل المجلدات كوحدات، وتعمل مع كل من require و import.

التحميل من مجلدات node_modules

إذا لم يكن مُعرّف الوحدة المُمرّر إلى require() وحدة مدمجة، ولم يبدأ بـ '/'، أو '../'، أو './'، فإن Node.js يبدأ من مُجلّد الوحدة الحالية، ويضيف /node_modules، ويحاول تحميل الوحدة من هذا الموقع. لن يُضيف Node.js node_modules إلى مسار ينتهي بالفعل بـ node_modules.

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

على سبيل المثال، إذا كان الملف الموجود في '/home/ry/projects/foo.js' قد دعا require('bar.js')، فسيبحث Node.js في المواقع التالية، بهذا الترتيب:

  • /home/ry/projects/node_modules/bar.js
  • /home/ry/node_modules/bar.js
  • /home/node_modules/bar.js
  • /node_modules/bar.js

هذا يسمح للبرامج بتوطين اعتماداتها، بحيث لا تتضارب.

من الممكن طلب ملفات محددة أو وحدات فرعية موزعة مع وحدة معينة عن طريق تضمين لاحقة مسار بعد اسم الوحدة. على سبيل المثال، require('example-module/path/to/file') سيُحلّ path/to/file نسبةً إلى مكان وجود example-module. تتبع لاحقة المسار نفس دلالات حل الوحدة.

التحميل من المجلدات العالمية

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

في Windows، يتم فصل NODE_PATH بواسطة الفواصل المنقوطة (;) بدلاً من النقاط.

تم إنشاء NODE_PATH في الأصل لدعم تحميل الوحدات من مسارات متنوعة قبل تعريف خوارزمية حل الوحدة الحالية.

لا يزال يتم دعم NODE_PATH، ولكنه أصبح أقل ضرورة الآن بعد أن استقر نظام Node.js البيئي على اتفاقية لتحديد موقع الوحدات التابعة. في بعض الأحيان، تُظهر عمليات النشر التي تعتمد على NODE_PATH سلوكًا مُفاجئًا عندما لا يدرك الأشخاص أنه يجب تعيين NODE_PATH. في بعض الأحيان تتغير اعتماديات الوحدة، مما يتسبب في تحميل إصدار مختلف (أو حتى وحدة مختلفة) أثناء البحث في NODE_PATH.

بالإضافة إلى ذلك، سيقوم Node.js بالبحث في القائمة التالية من GLOBAL_FOLDERS:

  • 1: $HOME/.node_modules
  • 2: $HOME/.node_libraries
  • 3: $PREFIX/lib/node

حيث $HOME هو مُجلّد المستخدم الرئيسي، و $PREFIX هو node_prefix المُهيّأ في Node.js.

هذه الأسباب في الغالب تاريخية.

يُنصح بشدة بوضع الاعتماديات في مُجلّد node_modules المحلي. سيتم تحميلها بشكل أسرع، وبشكل أكثر موثوقية.

غلاف الوحدة النمطية

قبل تنفيذ شفرة الوحدة النمطية، سيقوم Node.js بتغليفها باستخدام غلاف دالة يشبه ما يلي:

js
;(function (exports, require, module, __filename, __dirname) {
  // توجد شفرة الوحدة النمطية هنا بالفعل
})

من خلال القيام بذلك، يحقق Node.js بعض الأمور:

  • يحافظ على المتغيرات العليا (المعرّفة باستخدام var أو const أو let) ضمن نطاق الوحدة النمطية بدلاً من الكائن العام.
  • يساعد في توفير بعض المتغيرات التي تبدو عامة والتي هي في الواقع خاصة بالوحدة النمطية، مثل:
    • كائنات module و exports التي يمكن للمُنفذ استخدامها لتصدير القيم من الوحدة النمطية.
    • متغيرات الراحة __filename و __dirname، التي تحتوي على اسم الملف المطلق لمسار دليل الوحدة النمطية.

نطاق الوحدة النمطية

__dirname

مُضاف في: v0.1.27

اسم دليل الوحدة النمطية الحالية. هذا هو نفسه مثل path.dirname() من __filename.

مثال: تشغيل node example.js من /Users/mjr

js
console.log(__dirname)
// يُطبع: /Users/mjr
console.log(path.dirname(__filename))
// يُطبع: /Users/mjr

__filename

مُضاف في: v0.0.1

اسم ملف الوحدة النمطية الحالية. هذا هو المسار المطلق لملف الوحدة النمطية الحالي مع حل الارتباطات الرمزية.

بالنسبة للبرنامج الرئيسي، ليس هذا بالضرورة هو نفسه اسم الملف المستخدم في سطر الأوامر.

راجع __dirname للحصول على اسم دليل الوحدة النمطية الحالية.

أمثلة:

تشغيل node example.js من /Users/mjr

js
console.log(__filename)
// يُطبع: /Users/mjr/example.js
console.log(__dirname)
// يُطبع: /Users/mjr

بافتراض وجود وحدتين نمطيتين: a و b، حيث أن b هي تبعية لـ a ويوجد بنية دليل:

  • /Users/mjr/app/a.js
  • /Users/mjr/app/node_modules/b/b.js

ستُرجع مراجع __filename داخل b.js قيمة /Users/mjr/app/node_modules/b/b.js بينما ستُرجع مراجع __filename داخل a.js قيمة /Users/mjr/app/a.js.

exports

مضاف في: v0.1.12

مرجع إلى module.exports وهو أقصر في الكتابة. راجع قسم اختصار exports للحصول على تفاصيل حول متى تستخدم exports ومتى تستخدم module.exports.

module

مضاف في: v0.1.16

مرجع إلى الوحدة النمطية الحالية، راجع قسم module object. على وجه الخصوص، يتم استخدام module.exports لتحديد ما تقوم الوحدة النمطية بتصديره وما يجعله متاحًا من خلال require().

require(id)

مضاف في: v0.1.13

  • id <string> اسم الوحدة النمطية أو المسار
  • الإرجاع: <any> محتوى الوحدة النمطية المصدرة

يستخدم لاستيراد الوحدات النمطية و JSON والملفات المحلية. يمكن استيراد الوحدات النمطية من node_modules. يمكن استيراد الوحدات النمطية المحلية وملفات JSON باستخدام مسار نسبي (مثل ./، ./foo، ./bar/baz، ../foo) سيتم حله مقابل الدليل المسمى بـ __dirname (إذا تم تعريفه) أو دليل العمل الحالي. يتم حل المسارات النسبية لأسلوب POSIX بطريقة مستقلة عن نظام التشغيل، مما يعني أن الأمثلة المذكورة أعلاه ستعمل على Windows بنفس الطريقة التي تعمل بها على أنظمة Unix.

js
// استيراد وحدة نمطية محلية بمسار نسبي إلى `__dirname` أو دليل العمل الحالي. (على Windows، سيتم حل هذا إلى .\path\myLocalModule.)
const myLocalModule = require('./path/myLocalModule')

// استيراد ملف JSON:
const jsonData = require('./path/filename.json')

// استيراد وحدة نمطية من node_modules أو وحدة نمطية مدمجة في Node.js:
const crypto = require('node:crypto')

require.cache

مضاف في: v0.3.0

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

إضافة أو استبدال الإدخالات ممكن أيضًا. يتم التحقق من هذه ذاكرة التخزين المؤقت قبل الوحدات النمطية المدمجة وإذا تمت إضافة اسم يطابق وحدة نمطية مدمجة إلى ذاكرة التخزين المؤقت، فإن مكالمات node:-prefixed require فقط ستتلقى الوحدة النمطية المدمجة. استخدم بحذر!

js
const assert = require('node:assert')
const realFs = require('node:fs')

const fakeFs = {}
require.cache.fs = { exports: fakeFs }

assert.strictEqual(require('fs'), fakeFs)
assert.strictEqual(require('node:fs'), realFs)

require.extensions

مضاف في: v0.3.0

مُحذّف منذ: v0.10.6

[مستقر: 0 - مُحذّف]

مستقر: 0 ثبات: 0 - مُحذّف

توجيه require حول كيفية التعامل مع امتدادات الملفات المعينة.

معالجة الملفات التي تحمل الامتداد .sjs كملفات .js:

js
require.extensions['.sjs'] = require.extensions['.js']

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

تجنب استخدام require.extensions. قد يتسبب الاستخدام في أخطاء خفية، ويصبح حل الامتدادات أبطأ مع كل امتداد مُسجّل.

require.main

مضاف في: v0.1.17

كائن Module الذي يمثل البرنامج النصي الرئيسي المُحمّل عند تشغيل عملية Node.js، أو undefined إذا لم تكن نقطة دخول البرنامج وحدة CommonJS. راجع "الوصول إلى الوحدة الرئيسية".

في برنامج نصي entry.js:

js
console.log(require.main)
bash
node entry.js
js
Module {
  id: '.',
  path: '/absolute/path/to',
  exports: {},
  filename: '/absolute/path/to/entry.js',
  loaded: false,
  children: [],
  paths:
   [ '/absolute/path/to/node_modules',
     '/absolute/path/node_modules',
     '/absolute/node_modules',
     '/node_modules' ] }

require.resolve(request[, options])

[السجل]

الإصدارالتغييرات
v8.9.0خيار paths مدعوم الآن.
v0.3.0مضاف في: v0.3.0
  • request <string> مسار الوحدة لحلها.

  • options <Object>

    • paths <string[]> مسارات لحل موقع الوحدة منها. إذا وُجدت، فسيتم استخدام هذه المسارات بدلاً من مسارات الحل الافتراضية، باستثناء GLOBAL_FOLDERS مثل $HOME/.node_modules، والتي يتم تضمينها دائمًا. يتم استخدام كل من هذه المسارات كنقطة بداية لخوارزمية حل الوحدة، مما يعني أنه يتم فحص التسلسل الهرمي node_modules من هذا الموقع.
  • المُرجَع: <string>

استخدم آلية require() الداخلية للبحث عن موقع وحدة، ولكن بدلاً من تحميل الوحدة، قم فقط بإرجاع اسم الملف المُحل.

إذا تعذر العثور على الوحدة، فسيتم إطلاق خطأ MODULE_NOT_FOUND.

require.resolve.paths(request)

مضاف في: v8.9.0

  • request <string> مسار الوحدة التي يتم استرداد مسارات البحث عنها.
  • القيمة المُرجعه: <string[]> | <null>

يرجع مصفوفة تحتوي على المسارات التي تم البحث عنها أثناء حل request أو null إذا كانت سلسلة request تشير إلى وحدة أساسية، مثل http أو fs.

كائن module

مضاف في: v0.1.16

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

module.children

مضاف في: v0.1.16

كائنات الوحدات المطلوبة لأول مرة بواسطة هذه الوحدة.

module.exports

مضاف في: v0.1.16

يتم إنشاء كائن module.exports بواسطة نظام Module. في بعض الأحيان لا يكون هذا مقبولاً؛ يريد الكثيرون أن تكون وحدتهم مثيلًا لبعض الفئات. للقيام بذلك، قم بتعيين كائن التصدير المطلوب إلى module.exports. إن تعيين الكائن المطلوب إلى exports سيؤدي ببساطة إلى إعادة ربط المتغير المحلي exports، وهو ما لا يُرجّح أن يكون مرغوبًا فيه.

على سبيل المثال، لنفترض أننا نقوم بإنشاء وحدة تسمى a.js:

js
const EventEmitter = require('node:events')

module.exports = new EventEmitter()

// القيام ببعض العمل، وبعد مرور بعض الوقت، انبعاث
// حدث 'ready' من الوحدة نفسها.
setTimeout(() => {
  module.exports.emit('ready')
}, 1000)

ثم في ملف آخر يمكننا أن نفعل:

js
const a = require('./a')
a.on('ready', () => {
  console.log('module "a" is ready')
})

يجب أن يتم التعيين إلى module.exports على الفور. لا يمكن القيام بذلك في أي استدعاءات مرتدة. هذا لا يعمل:

x.js:

js
setTimeout(() => {
  module.exports = { a: 'hello' }
}, 0)

y.js:

js
const x = require('./x')
console.log(x.a)

اختصار exports

مُضاف في: v0.1.16

متغير exports متوفر ضمن نطاق الملف في المُستوى الأعلى للوحدة، ويتم تعيين قيمة module.exports له قبل تقييم الوحدة.

يسمح باختصار، بحيث يمكن كتابة module.exports.f = ... بشكل أكثر إيجازًا كـ exports.f = .... ومع ذلك، انتبه إلى أنه مثل أي متغير، إذا تم تعيين قيمة جديدة لـ exports، فلن يكون مرتبطًا بعد الآن بـ module.exports:

js
module.exports.hello = true // مُصدر من `require` للوحدة
exports = { hello: false } // غير مُصدر، متوفر فقط في الوحدة

عندما يتم استبدال خاصية module.exports بالكامل بكائن جديد، فمن الشائع إعادة تعيين exports:

js
module.exports = exports = function Constructor() {
  // ... إلخ
}

لتوضيح السلوك، تخيل هذا التنفيذ الافتراضي لـ require()، والذي يشبه إلى حد كبير ما يتم فعله بالفعل بواسطة require():

js
function require(/* ... */) {
  const module = { exports: {} }
  ;((module, exports) => {
    // كود الوحدة هنا. في هذا المثال، قم بتعريف دالة.
    function someFunc() {}
    exports = someFunc
    // في هذه المرحلة، لم يعد `exports` اختصارًا لـ `module.exports`، وستصدر هذه الوحدة كائنًا افتراضيًا فارغًا.
    module.exports = someFunc
    // في هذه المرحلة، ستصدر الوحدة الآن `someFunc`، بدلاً من الكائن الافتراضي.
  })(module, module.exports)
  return module.exports
}

module.filename

مُضاف في: v0.1.16

اسم الملف الكامل المُحل لوحدة.

module.id

مُضاف في: v0.1.16

المُعرّف للوحدة. عادةً ما يكون هذا هو اسم الملف الكامل المُحل.

module.isPreloading

مُضاف في: v15.4.0، v14.17.0

  • النوع: <boolean> true إذا كانت الوحدة تعمل أثناء مرحلة التحميل المسبق لـ Node.js.

module.loaded

مُضاف في: v0.1.16

ما إذا كان جاري تحميل الوحدة النمطية أو تم الانتهاء من تحميلها.

module.parent

مُضاف في: v0.1.16

مُهمل منذ: v14.6.0, v12.19.0

[مستقر: 0 - مُهمل]

مستقر: 0 الثبات: 0 - مُهمل: يرجى استخدام require.main و module.children بدلاً من ذلك.

الوحدة النمطية التي طلبتها أولاً، أو null إذا كانت الوحدة النمطية الحالية هي نقطة دخول العملية الحالية، أو undefined إذا تم تحميل الوحدة النمطية بواسطة شيء ليس وحدة نمطية CommonJS (مثل: REPL أو import).

module.path

مُضاف في: v11.14.0

اسم الدليل للوحدة النمطية. هذا عادةً ما يكون نفس path.dirname() من module.id.

module.paths

مُضاف في: v0.4.0

مسارات البحث للوحدة النمطية.

module.require(id)

مُضاف في: v0.5.1

  • id <string>
  • تُرجع: <any> محتوى الوحدة النمطية المُصدر

توفر طريقة module.require() طريقة لتحميل وحدة نمطية كما لو تم استدعاء require() من الوحدة النمطية الأصلية.

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

كائن Module

تم نقل هذا القسم إلى الوحدات: وحدة module الأساسية.

دعم خريطة المصدر الإصدار 3

تم نقل هذا القسم إلى الوحدات: وحدة module الأساسية.