Node.js/modules
مؤشر الاستقرار: 2 - مستقر
يعامل كل ملف في نظام الوحدات في بيئة Node.js كوحدة منفصلة. على سبيل المثال، فليكن ملف اسمه foo.js:
في السطر الأول، يُحمِّل foo.js الوحدة circle.js الموجودة في نفس المجلد مع foo.js.
وإليك محتويات circle.js:
صدَّرت الوحدة circle.js الدالتين area() و circumference(). وتُضاف الدوال والكائنات إلى جذر الوحدة بتعيين خصائص إضافية لكائن الصادرات exports الخاص.
ستكون المتغيرات المحلية في الوحدة خاصة، نظراً لأن الوحدة مغلفة في دالة بواسطة Node.js (انظر مُغلِّف الوحدات module wrapper). في هذا المثال، يكون المتغير PI خاصًا بالوحدة circle.js.
يمكن تعيين الخاصية module.exports بقيمة جديدة (مثل دالة أو الكائن).
فيما يلي، يستخدم bar.js الوحدة square، التي تُصدِّر الصنف Square:
الوحدة square مُعرفَّة في square.js:
ويتحقق نظام الوحدات في الوحدة require('module').
الوصول إلى الوحدة الرئيسية
عند تشغيل ملف مباشرة من Node.js، تُضبط الخاصية require.main إلى وحدتها module. وهذا يعني أنه من الممكن تحديد ما إذا كان قد جرى تشغيل ملفٍ ما مباشرة عن طريق اختبار require.main === module.
بالنسبة لملف foo.js، ستكون قيمتها true إذا نُفِذت عبر node foo.js لكن تكون false إذا نُفِذت عبر require('./foo').
ولمّا كانت الوحدة module توفر الملف filename (عادة ما يكافئ __filename) يمكن الحصول على نقطة الدخول من التطبيق الحالي عن طريق فحصrequire.main.filename.
Addenda: نصائح عن مدير الحِزم
صُممت دلالات الدالة require() في Node.js لتكون عامة بما يكفي لدعم عدد من هياكل المجلدات المعقولة. تأمل تطبيقات إدارة حِزم البرامج مثل 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_modules/<name>/<version> بدلا من وضعها مباشرة في /usr/lib/node. لا تهتم Node.js بالبحث عن الاعتماديات المفقودة في /usr/node_modules أو /node_modules.
لإتاحة الوحدات لـ Node.js REPL، قد يكون من المفيد أيضا إضافة المجلد /usr/lib/node_modules إلى متغير البيئة $NODE_PATH. ولما كان البحث في الوحدات باستخدام المجلدات node_modules نسبيًا، واستنادا إلى المسار الحقيقي لملفات استدعاء require()، يمكن أن تكون الحزم نفسها في أي مكان.
All Together...
تُستخدم الدالة require.resolve() للحصول على اسم الملف الصحيح الذي سيُحمَّل عند استدعاء require().
بتجميع كل ما سبق، فيما يلي خوارزمية رفيعة المستوى في شكل شيفرة وهمية (pseudocode) عما يجريه require.resolve():
التخزين المؤقت (Caching)
تُخزَّن الوحدات مؤقتًا بعد تحميلها أول مرة. وهذا يعني (من ضمن أمور أخرى) أن كل استدعاء للتابع require('foo') سوف يعيد بالضبط الكائن نفسه إذ كان يحل نفس الملف.
قد لا تسبب عدة استدعاءات للتابع require('foo') تنفيذ شيفرة الوحدة عدة مرات. وهي ميزة هامة. مع ذلك، يمكن إعادة الكائنات "المُنفَّذة جزئيًا"، مما يسمح بتحميل الاعتماديات الانتقالية حتى عندما تتسبب في دورات.
لتنفيذ التعليمات البرمجية لوحدة عدة مرات يجب تصدير دالة واستدعاء هذه الدالة.
محاذير التخزين المؤقت للوحدات
تُخزين الوحدات مؤقتاً على أساس حل اسم الملف. لما كان حل الوحدات إلى اسم ملف مختلف على أساس موقع الوحدة المُستدعِية (تحميل من مجلدات node_modules)، فإنه لا يضمن أن يُعيد التابع require('foo') دائماً نفس الكائن بالضبط، إذا كان يحل لملفات مختلفة.
بالإضافة إلى ذلك، في أنظمة الملفات أو أنظمة التشغيل الحساسة لحالة الأحرف، يمكن أن تشير الأسماء المحلولة المختلفة إلى نفس الملف، ولكن ذاكرة التخزين المؤقت ستستمر في التعامل معها كوحدات مختلفة، وستُحمل الملف عدة مرات. على سبيل المثال، يعيد كلٌ من require('./foo') و require('./FOO') كائنين مختلفين، بغض النظر عن ما إذا كان ./foo و ./FOO هما نفس الملف.
الوحدات الأساسية
تتمتع بيئة Node.js بالعديد من الوحدات المترجمة إلى النظام الثنائي. وتوصف هذه الوحدات بمزيد من التفصيل في مكان آخر من هذا التوثيق.
وتُعرَّف الوحدات الأساسية داخل مصدر Node.js وهي موجودة في المجلد lib/.
وتُحمَّل الوحدات الأساسية دائماً بشكل تفضيلي إذا مُرر المعرف الخاص بها إلى require(). على سبيل المثال، يعيد require('http') دائماً وحدة HTTP المُضمَّنة، حتى إذا كان هناك ملف بهذا الاسم.
الدورات
عندما تكون هناك استدعاءات require() دائرية، قد لا تنتهي الوحدة من التنفيذ عند إعادتها.
إليك هذه الحالة:
a.js:
b.js:
main.js:
عند تحميل main.js للوحدة a.js، ثم تحمِّل a.js بدورها الوحدةَ b.js. عند هذه النقطة، تحاول b.js لتحميل a.js. من أجل منع حلقة لا نهائية، تُعاد نسخة غير مكتملة لكائن exports الخاص بالوحدة a.js إلى الوحدة b.js. ثم تنهي الوحدة b.js التحميل، وتُعيد كائن exports إلى الوحدة a.js.
عند تحميل main.js لكلتا الوحدتين، تنتهي كل منهما. سيكون الناتج من هذا البرنامج بالتالي هو:
التخطيط الدقيق مطلوب للسماح لاعتماديات الوحدات النمطية كي تعمل بشكل صحيح داخل أي تطبيق.
وحدات الملفات
إذا لم يُعثر على اسم الملف الصحيح، ستحاول Node.js تحميل اسم الملف المطلوب مع امتدادات إضافية: .js و .json، وأخيراً .node.
تُفسر ملفات .js على أنها ملفات JavaScript نصية، وثُحلَّل الملفات .json كملفات JSON نصية. وتُفسَّر ملفات .node كوحدات مُترجَمة مُلحَقة ومُحمَّلة مع dlopen.
الوحدة المُستدعاة المسبوقة بالعلامة '/' تمثل المسار المطلق للملف. على سبيل المثال، سيُحمِّل require('/home/marco/foo.js') الملف /home/marco/foo.js.
تمثل الوحدة المُستدعاة المسبوقة بالعلامة '/.' المسار النسبي للملف المُستدعي require(). فيجب أن يكون circle.js في نفس المجلد مع foo.js حتى يتمكن require('./circle') من العثور عليه.
بدون البادئة '/' أو './' أو '../' للإشارة إلى ملف، يجب أن تكون الوحدة وحدةً أساسيةً أو أن تكون مُحملة من مجلد node_modules.
إذا كان المسار غير موجود، سوف يلقي require() الخطأ Error مع رمز الخاصية code بالقيمة 'MODULE_NOT_FOUND'.
المجلدات كوحدات
من الملائم تنظيم البرامج والمكتبات في مجلدات متضمنة ذاتيًا، ومن ثَمَّ توفير نقطة دخول واحدة إلى المكتبة. هناك ثلاث طرق لتمرير مجلد إلى require() كوسيط.
الأولى عن طريق إنشاء ملف package.json في جذر المجلد، الذي يحدد وحدة رئيسية main. مثال لملف package.json قد يبدو كما يلي:
إذا كان هذا في مجلد ./some-library، ثم محاولة require('./some-library') لتحميل ./some-library/lib/some-library.js.
هذا هو مدى تمييز Node.js لملفات package.json.
إذا كان الملف المحدد بواسطة المُدخَل الرئيسي 'main' هو package.json مفقود ولا يمكن أن يُحل، سيُبلِغ Node.js أن الوحدة كاملةً مفقودة بسبب الخطأ الافتراضي:
Error: Cannot find module 'some-library'
إذا لم يكن هناك أي ملف package.json موجودًا في المجلد، ستحاول Node.js تحميل ملف index.js أو index.node من داخل هذا المجلد. على سبيل المثال، إذا لم يكن هناك أي ملف package.json في المثال أعلاه، سيحاول require('./some-library') عندئذ تحميل:
- ../some-library/index.js
- ../some-library/index.node
تحميل من مجلدات node_modules
إذا كان مُعرِّف الوحدة المُمررة إلى require() ليس وحدة نواة core، ولا يبدأ مع '/' أو '../' أو './'، ثم تبدأ Node.js من المجلد الأصل للوحدة الحالية، ويضيف /node_modules، ويحاول تحميل الوحدة من ذلك الموقع. لن تلحق Node المجلد 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:
حيث $HOME هو المجلد الرئيسي للمستخدم، و $PREFIX هو بادئة node_prefix المُكوّنة لبيئة Node.js.
وهذا في الغالب لأسباب تاريخية.
يُنصح بقوة بوضع الاعتماديات في مجلد node_modules المحلي. فستُحمَّل أسرع وتكون أكثر موثوقية.
مُغلِّف الوحدات (The module wrapper)
قبل تنفيذ التعليمات البرمجية في الوحدة، ستُغلِّفها Node.js بمغلف دوال يشبه ما يلي:
عند القيام بذلك، تحقق Node.js بضعة أشياء:
- تحفظ المتغيرات ذات المستوى الأعلى (المُعرَّفة مع var أو const أو let) في نطاق الوحدة بدلاً من نطاق الكائن العمومي.
- ويساعد على توفير بعض متغيرات البحث العامة والتي تكون في الواقع مخصصة بالوحدة، مثل:
- كائنات module و exports التي يمكن للمُنفِّذ استخدامها لتصدير القيم من الوحدة.
- متغيرات المواءمة __filename و __dirname، التي تحتوي على اسم ملف الوحدة ومسار مجلده المطلقَين.
نطاق الوحدة النمطية
__dirname
أُضيف مع الإصدار: v0.1.27.
- من النوع <string>.
اسم مجلد الوحدة الحالية. وهو مثل (path.dirname) في __filename.
مثال: تشغيل node example.js من /Users/mjr.
__filename
أُضيف مع الإصدار: v0.0.1.
- من النوع <string>.
اسم ملف الوحدة الحالية. وهو حل المسار المطلق لملف الوحدة الحالية.
ليس بالضرورة لبرنامج رئيسي أن يكون له نفس اسم الملف المستخدم في سطر الأوامر.
راجع __dirname لاسم مجلد الوحدة الحالية.
أمثلة
تشغيل node example.js من /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.
exports
أُضيف مع الإصدار: v0.1.12.
هو مرجع إلى module.exports أقصر في الكتابة. راجع قسم exports shortcut للحصول على تفاصيل حول متى يمكن استخدام exports ومتى تُستخدم module.exports.
module
أُضيف مع الإصدار: v0.1.16.
- من النوع <Object>.
هو مرجع إلى الوحدة الحالية، راجع قسم كائن module object. ولا سيما تستخدمmodule.exports لتحديد ما تصدره الوحدة وتتيحه من خلال require().
require()
أُضيف مع الإصدار: v0.1.13.
- من النوع <Function>
لطلب الوحدات.
require.cache
أُضيف مع الإصدار: v0.3.0.
- من النوع <Object>.
تُخزَّن الوحدات مؤقتًا في هذا الكائن عندما تكون مطلوبة. عند حذف قيمة مفتاحية من هذا الكائن، سيُعيد require التالي تحميل الوحدة. علما بأن هذا لا ينطبق على addons الأصلية، إذ تُنتج إعادة التحميل خطًأ.
require.extensions
أضيفت مع الإصدار: v0.3.0، وأُهمِلت مع الإصدار: v0.10.6.
مؤشر الاستقرار: 0 - مُهمَل
- من النوع <Object>.
إرشاد require حول كيفية التعامل مع بعض امتدادات الملفات.
معالجة الملفات بالامتداد .sjs مثل .js:
مهملة في الماضي، استخدمت هذه القائمة لتحميل الوحدات غير JavaScript إلى Node.js بترجمتها حسب الطلب. ومع ذلك، في الممارسة العملية، هناك سبل أفضل للقيام بذلك، مثل تحميل الوحدات عن طريق بعض برامج Node.js الأخرى، أو ترجمتها إلى JavaScript قبل وقتها.
لما كان نظام الوحدات مغلقًا، فربما لم تُلغى هذه الميزة أبدًا. ومع ذلك، فقد يكون بها أخطاء خفية وتعقيدات من الأفضل تركها بلا مساس.
علما بأن عدد عمليات نظام الملفات التي يجب على نظام الوحدات أداءها من أجل حل جملة require(...) إلى اسم ملف يقاس خطيًا مع عدد الملحقات المسجلة.
وبعبارة أخرى، إضافة الملحقات يبطئ من تحميل الوحدة وينبغي تجنبها.
require.main
أُضيفت مع الإصدار: v0.1.17.
- من النوع <Object>.
كائن الوحدة Module الذي يمثل سكربت الإدخال المُحمَّل عند بدء عملية Node.js. راجع "الوصول إلى الوحدة الرئيسية".
في سكربت entry.js:
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() الداخلية للبحث عن موقع وحدةٍ ما، ولكن بدلاً من تحميل الوحدة، يُعاد فقط اسم الملف المحلول.
require.resolve.paths(request)
أُضيف مع الإصدار: v8.9.0.
- request من النوع <string>: مسار الوحدة التي يُسترد مسارات البحث الخاصة بها.
- القيمة المُعادة: <string[]> | <null>
إعادة مصفوفة تحتوي على المسارات التي بُحِثَ عنها خلال حل request أو null إذا كانت سلسلة request تشير إلى وحدة نواة، على سبيل المثال http أو fs.
كائن module
أُضيف مع الإصدار: v0.1.16.
- من النوع <Object>.
في كل وحدة، يكون متغير module الحر مرجعًا للكائن الذي يمثل الوحدة الحالية. لمزيد من المواءمة، يمكن الوصول إلى module.exports أيضا عبر وحدة exports العامة. module ليست في الواقع عامة ولكنها بالأحرى محلية لكل وحدة.
module.children
أُضيف مع الإصدار: v0.1.16.
- من النوع <module[]>
كائنات الوحدة المطلوبة من قِبلها.
module.exports
أُضيف مع الإصدار: v0.1.16.
- من النوع <Object>.
يُنشِأ نظامُ Module كائنَ module.exports. في بعض الأحيان يكون هذا الأمر غير مقبول؛ يريد الكثير أن تكون وحدتهم مثيلة لصنف ما. للقيام بذلك، يجب تعيين الكائن المطلوب تصديره إلى module.exports. لاحظ أن تعيين الكائن المطلوب إلى exports سيربط ببساطة متغيرexports المحلي، وهو على الأرجح ليس هو المطلوب.
على سبيل المثال لنفترض عمل وحدة تسمى a.js:
ثم في ملف آخر يمكننا أن نعمل:
علما بأن يجب أن تكون الإحالة إلى module.exports على الفور. ولا يمكن القيام بذلك في أي استدعاءات. فهذا لا يعمل:
x.js:
y.js:
اختصار الصادرات
أُضيف مع الإصدار: v0.1.16.
متغير exports متاح داخل نطاق مستوى الملف الوحدة، ويُعيَّن بقيمة module.exports قبل تقييم الوحدة.
أنها تسمح باختصار، حتى أنه يمكن كتابة module.exports.f = ... بطريقة أكثر وضوحًا مثل exports.f = .... ومع ذلك، يجب الانتباه أنه مثل أي متغير، إذا عُيّنت exports بقيمة جديدة، فإنها لن تظل مرتبطة مع module.exports:
عندما يحل محل الخاصية module.exports كائن جديد بشكلٍ تام، فمن الشائع أن أيضا إعادة تعيين exports:
لتوضيح السلوك، تخيل هذا التنفيذ الافتراضي للتابع require()، والذي يماثل تماما ما يقوم به فعلا require():
module.filename
أُضيف مع الإصدار: v0.1.16.
- من النوع <string>.
اسم الملف المحلول بالكامل للوحدة.
module.id
أُضيف مع الإصدار: v0.1.16.
- من النوع <string>.
معرف الوحدة. عادة هو اسم الملف المحلول بالكامل.
module.loaded
أُضيف مع الإصدار: v0.1.16.
- من النوع <boolean>
ما إذا كان قد انتهى تحميل الوحدة أم لا، أو لا يزال في عملية التحميل.
module.parent
أُضيف مع الإصدار: v0.1.16.
- من النوع <module>
الوحدة التي تتطلبها أولاً.
module.paths
أُضيف مع الإصدار: v0.4.0.
- من النوع <string[]>
مسارات البحث عن الوحدة.
module.require(id)
أُضيف مع الإصدار: v0.5.1.
- id من النوع <string>
- القيمة المُعادة: <Object> من النوع module.exports من الوحدة المحلولة.
يوفر التابع module.require طريقة لتحميل الوحدة كما لو اُستدعِي require() من الوحدة الأصلية.
للقيام بذلك، من الضروري الحصول على مرجع إلى كائن module. لما كان require() يعيد module.exports، وتتوفر module عادة في التعليمات البرمجية فقط لوحدة معينة، فإنه يجب تصديرها صراحة من أجل استخدامها.
كائن Module
أُضيف مع الإصدار: v0.3.7.
- من النوع <Object>.
يوفر توابع خدمة عامة عند التعامل مع نسخ من الوحدة Module –غالباً ما يُرى متغير module في وحدات الملف. الوصول إليها عن طريق require('module').
module.builtinModules
أُضيف مع الإصدار: v9.3.0.
- من النوع <string[]>.
قائمة بأسماء كافة الوحدات التي توفرها Node.js. يمكن استخدامها للتحقق من إذا كان الوحدة تُصان من قِبَل طرف ثالث أو لا.
لاحظ أن الوحدة module في هذا السياق ليست نفس الكائن الذي توفره وحدة التغليف module wrapper. للوصول إليها، يجب طلب الوحدة Module: