رزولوشن ماژول در Node.js چطور کار میکنه؟
خلاصهٔ کاملتر
هر بار که توی Node.js یه require('something') مینویسی، یه الگوریتم پشتپرده اون رشته رو به آدرس مطلق یه فایل روی دیسک تبدیل میکنه. این فرآیند بیشتر از چیزیه که به نظر میرسه — چندین شاخه داره، چند بار به فایلسیستم دسترسی میزنه، و متادیتای package.json نقش مهمی توش داره.
سه نوع specifier
اولین کاری که Node میکنه اینه که رشته رو دستهبندی میکنه. اگه رشته اسم یه ماژول توکار (built-in) مثل 'fs' یا 'path' باشه، بلافاصله برمیگرده و اصلاً سراغ فایلسیستم نمیره. اگه با ./، ../ یا / شروع بشه، یه مسیر نسبی یا مطلقه. هر چیز دیگهای — مثل 'lodash' یا '@babel/core' — یه bare specifier حساب میشه که پیچیدهترین مسیر رو طی میکنه.
پروب کردن پسوند فایل
برای مسیرهای نسبی، Node دقیقاً همون فایل رو نگاه نمیکنه. اگه require('./utils') بنویسی، ابتدا دنبال utils میگرده، بعد utils.js، بعد utils.json، و بعد utils.node. اولین موردی که پیدا بشه برندهست. فایلهای .json با JSON.parse بارگذاری میشن و توی cache ذخیره میشن — یعنی تمام require های بعدی به همون آبجکت اشاره میکنن و اگه اون رو عوض کنی، تأثیرش همهجا دیده میشه.
الگوریتم صعود node_modules
برای bare specifierها، Node از دایرکتوری فایل فعلی شروع میکنه و به سمت ریشه بالا میره، توی هر سطح دنبال پوشهی node_modules میگرده. مثلاً اگه از /home/app/src/lib/foo.js بنویسی require('lodash')، Node به ترتیب این مسیرها رو چک میکنه:
/home/app/src/lib/node_modules
/home/app/src/node_modules
/home/app/node_modules
/home/node_modules
/node_modules
این طراحی باعث میشه پکیجهای تودرتو بتونن نسخههای مجزا از یه dependency داشته باشن بدون اینکه با هم تداخل پیدا کنن.
فیلد "exports" در package.json
فیلد "exports" که از Node 12.7 اضافه شده، کنترل دقیقتری روی API عمومی پکیج میده. وقتی این فیلد وجود داشته باشه، اولویت بالاتری از "main" داره و هر فایلی که توش لیست نشده، غیرقابل دسترس میشه. یه کاربرد مهمش اینه که میشه بسته به اینکه کد با require() لود میشه یا با import، فایل متفاوتی برگردوند (conditional exports):
{
"exports": {
".": {
"import": "./lib/index.mjs",
"require": "./lib/index.cjs",
"default": "./lib/index.js"
}
}
}ترتیب شرطها مهمه — Node از بالا به پایین میخونه و اولین تطابق رو برمیداره.
فیلد "imports" برای alias های داخلی
فیلد "imports" معادل داخلی "exports" هست و فقط توی خود پکیج قابل استفادهست. با پیشوند # مشخص میشه و میشه باهاش مسیرهای عمیق و شکننده مثل require('../../../../utils') رو به یه alias ساده مثل require('#utils') تبدیل کرد. این alias ها هم conditional هستن — مثلاً میشه در حالت development یه mock تزریق کرد و در حالت production پیادهسازی واقعی رو لود کرد.
نکات کلیدی:
- Node سه نوع specifier داره: built-in، مسیر نسبی/مطلق، و bare specifier
- برای فایلهای بدون پسوند، Node به ترتیب .js، .json و .node رو امتحان میکنه
- فایلهای JSON یه بار parse میشن و cache میشن؛ همه به یه رفرنس مشترک دسترسی دارن
- bare specifierها با صعود از دایرکتوری فعلی به سمت ریشه توی node_modules پیدا میشن
- فیلد "exports" از Node 12.7 جایگزین مدرن "main" هست و API عمومی پکیج رو محدود میکنه
- فیلد "imports" برای alias های داخلی پکیج استفاده میشه و از خارج قابل دسترس نیست




