در این مقاله قصد داریم روش تشخیص تقدم و تأخر و ترتیب نسخه های مختلف npm را بررسی کنیم و با برخی مفاهیم این حوزه بیشتر آشنا شویم. همچنین می آموزیم که علامت های مختلف در تعیین نسخه پکیج ها چه معنایی دارند و دستور npm install
در پروژه ، چه نسخه ای از پکیج ها را نصب خواهد کرد.
npm پیشنهاد میکند که در نامگذاری نسخههای مختلف پکیجها، برنامهنویسان از استاندارد semantic versioning (SemVer) استفاده کنند. به عبارت دیگر، برنامهنویسها بهتر است هر پکیجی که روی npm منتشر میکنند، در تعیین مشخصه version در فایل package.json
آن پکیج، از استاندارد SemVer استفاده کنند. طبق گفته وبسایت npmjs.com، رعایت این استاندارد دنیای جاوااسکریپت را سالمتر، امنتر و قابل اعتمادتر میکند! (اگر بخواهیم سادهتر بگوییم، استفاده از SemVer اصولیتر است و باعث سادگی کار میشود)
اگر با مفاهیم پایه ای نسخه بندی معنایی (SemVer) آشنا نیستید، پیشنهاد می کنیم این مقاله از سکان را مطالعه کنید:
آموزش کامل Semantic Versioning
با خواندن مقاله مربوط به semantic versioning، به اهمیت رعایت این استاندارد بیشتر پی میبریم و بهتر متوجه میشویم که اگر این استاندارد نبود، چه دردسری در مدیریت نیازمندیها میداشتیم.
ما وقتی با js کد مینویسیم، ناگزیریم که با این نسخهبندیها زیاد درگیر باشیم. یک توسعهدهنده دائماً با ارتقاء پکیجهای پروژهاش سر و کار دارد، مشکل هماهنگی بین آنها را حل میکند، از قابلیتهای جدید آنها در کدش استفاده میکند و ... . به همین دلیل، خوب است که همه توسعهدهندهها با یک روال مرسوم آشنایی داشته باشند:
• وقتی در پروژهمان نسخهی یک پکیج را ارتقا میدهیم، باید سعی کنیم حتماً به داکیومنت یا وبسایت رسمی آن پکیج سر بزنیم و مستندات مربوط به ارتقاءش را مطالعه کنیم. برای این کار، دنبال کلیدواژههایی مثل change log، migration، breaking change و امثال اینها میگردیم.
• بخش change log در داکیومنتها، معمولاً لیست نسخهها را همراه با لیست تغییرات اِعمال شده در آن نسخهها برایمان میآورد.
• بخش migration هم معمولاً به ما میگوید که ضمن تغییر پکیج از یک نسخه قدیمی به نسخهی جدید، لازم است چه تغییراتی را در کدمان اِعمال کنیم.
• و بخش breaking change هم میگوید کدام یک از تغییرات اِعمال شده در پکیج، برنامهنویس را ملزم به تغییر کد قبلیِ مربوط به آن قسمت میکند.
علامتهای مشخص کننده بازه (Range specifier ها)
اگر در وبسایت سکان، مقاله مربوط به نسخه بندی معنایی در npm را مطالعه کرده باشید، میدانید که هنگام انتشار یک پکیج، در تعیین شماره نسخه باید به چه نکاتی توجه کنیم و گفتیم که تفاوت نسخههای اصلی و فرعی و ... در چیست. حال میخواهیم به این بپردازیم که استفاده از یک پکیج (دانلود و نصب آن) چه ملاحظاتی دارد.
بدلیل حجیم بودن فولدر node_modules، توسعهدهنده ها موقع انتشار کد پکیج، فولدر node_modules را منتشر نمیکنند. یا وقتی میخواهند پروژه را روی مخزن گیت کامیت کنند، فولدر node_modules کامیت نمیشود. حال اگر کسی(مثلاً سایر توسعهدهنده ها، یا کاربران آن پروژه) بخواهد پروژه را در جایی دیگر راه اندازی و بازسازی کند، چهطور متوجه میشود که پروژه چه پکیجهایی را بعنوان نیازمندی داشته، و هر کدام چه نسخهای بودهاند؟ از روی اطلاعاتی که در فایل package.json
وجود دارد.
در قسمت dependencies (و سایر انواع نیازمندیها) نام پکیجهای مورد نیاز پروژه، به همراه شماره نسخه شان آمدهاست. وقتی در مسیری که package.json
در آن قرار دارد (که طبیعتاً میشود فولدر اصلی پروژه)، دستور npm install
را اجرا میکنیم، npm پکیجهای نیازمندی را دانلود و نصب میکند، و از روی شماره نسخهاش میفهمد که باید چه نسخهای از آنها را نصب نماید.
از طرفی میدانیم که بعد از مدتی، نسخهی هرکدام از این پکیجهای نیازمندی ارتقاء پیدا میکند و شماره جدیدی میشوند. اما شاید اپلیکیشن با همهی آن نسخههای جدید سازگاری نداشته باشد. حال میخواهیم بگوییم که میتوان با علامتهایی، تعیین کرد که npm تا چه حد اجازه دارد که از نسخهی ارتقاء یافتهی پکیجهای نیازمندی استفاده کند. برای همین گاهی اوقات قبل از شماره نسخه، علامتهایی مثل «^» یا «~» را مشاهده میکنیم. به این علامتها range specifier میگویند، که هر کدام مفهومی خاص دارند:
• علامت ^ (caret یا hat): به npm میگوید که از آخرین نسخههای minor و patch آن پکیج استفاده کند. یعنی نسخهای را که ما مشخص کردیم استفاده کند، و اگر بعد از آن نسخهای که ما مشخص کردیم، نسخه minor یا نسخه patch جدیدی آمده، از نسخهی جدید استفاده کند. (میتوانیم این علامت را موقع خواندن بدین ترتیب بخوانیم: «سازگار با نسخه اصلیِ ...»)
• علامت ~ (tilde): به npm میگوید که آخرین نسخهی patch موجود را استفاده کند. یعنی نسخهای را که ما مشخص کردهایم استفاده کند، ولی اگر بعد از نسخهای که ما مشخص کردهایم، نسخه patch جدیدی ارائه شده بود، از نسخهی جدید استفاده کند. به عبارت دیگر اگر نسخهی minor جدیدی برای پکیج منتشر شده باشد، npm آن را نصب نخواهد کرد. و صرفاً آخرین نسخهی patch منتشر شده بعد از نسخه minor ای که برایش مشخص کردهایم را نصب میکند. (میتوانیم این علامت را موقع خواندن اینگونه بخوانیم: «تقریباً نزدیک به نسخه ی ...»)
• اگر عدد نسخه بدون علامت باشد، npm دقیقاً همان نسخه را استفاده میکند.
به یک مثال توجه کنید. فرض کنید برای نصب پکیج express، در فایل package.json
و در قسمت dependencies اینگونه می نوشتیم:
در این حالت هر بار که دستور npm install
را میزنیم، npm پکیج express هایی با فرمت
4.x.x
را دانلود و نصب میکند. که x.x جدیدترین نسخههای minor و patch هستند.
حال اگر اینطور میگفتیم:
با هر بار زدن دستور npm install
، پکیج express هایی با فرمت
4.16.x
نصب میشد. که x جدیدترین نسخه patch است. یعنی حتماً نسخه اصلی 4 و نسخه فرعی 16 بود، ولی هر چه نسخه patch جدیدتری وجود میداشت، آن را دانلود و نصب میکرد.
برای این که راحتتر به خاطر بسپاریم، میتوانیم بگوییم عملگر ~ در خیلی حوزهها برای نمایش دادن «برابری تقریبی» استفاده میشود. میتوان گفت که این علامت در اینجا نیز معنای مشابهی دارد، یعنی ما داریم تعیین میکنیم که نسخه express تقریباً همانی باشد که گفتیم (4.16)، ولی اگر patch جدیدتری از آن موجود است، آن را استفاده کنیم.
البته range specifier های دیگری هم وجود دارند که اکثراً از ظاهرشان میتوان معنای آنها را تشخیص داد، مثلاً:
• >version باید نسخههایی را نصب کند که جدیدتر از شماره نسخهی مشخص شده باشند.
• >=version باید نسخههایی را نصب کند که مساوی یا جدیدتر از شماره نسخهی مشخص شده باشند.
• <version باید نسخههایی را نصب کند که قدیمیتر از شماره نسخهی مشخص شده باشند.
• <=version باید نسخههایی را نصب کند که مساوی یا قدیمیتر از شماره نسخهی مشخص شده باشند.
• 1.2.x باید نسخههایی مثل 1.2.0 یا 1.2.1 و ... را نصب کند. (اما نه 1.3.0)
• sometarballurl این آدرس url یک فایل tarball است که پکیج باید از آنجا دانلود و نصب شود.
• * با هر نسخهای تطابق دارد.
• “” مشابه * است.
• version1 – version2 باید جدیدترین نسخهای را نصب کند که در بازه version1 تا version2 باشد.
• range1 || range2 || … باید جدیدترین نسخهای را نصب کند که در بازه range1 باشد، اگر آنجا نشد در بازه range2، اگر نشد ... . در واقع با استفاده از || میتوانیم قوانین بازه بندی را با هم ترکیب کنیم.
• ...git باید مخزن گیت مشخص شده را بعنوان نیازمندی نصب کند.
• user/repo باید محتویات مخزن گیت url مشخص شده را بعنوان نیازمندی نصب کند.
• tagName باید آن نسخهای را نصب کند که تگِ tagName دارد(با تگ tagName مشخص شده. نکته آنکه میتوان به نسخههای مختلف پروژه تگ دلخواه زد. برای اطلاع بیشتر به آدرس زیر مراجعه کنید: docs.npmjs.com/cli/dist-tag).
• path/path/path باید آن نیازمندی را از روی آدرس مشخص شده در لوکال نصب کند.
• latest باید آخرین نسخه را دانلود و نصب کند. البته شدیداً توصیه میشود که از latest استفاده نشود، چرا که تغییر در نسخه اصلی، معمولاً مشکل ناسازگاری بوجود میآورد و وقتی ما از latest استفاده میکنیم، هیچ کنترلی روی این موضوع نداریم.
خوب است بدانیم حالت پیشفرض npm برای نصب پکیجها، استفاده از ^ است. یعنی وقتی برای نصب یک پکیج، تنها با قرار دادن نام آن در کنار دستور npm install
اقدام میکنیم(بدون علامت مشخصکننده بازه یا برچسب)، آن پکیج با علامت ^ نصب خواهد شد.
البته میشود این مقدار پیشفرض را با دستوراتی عوض کرد، مثلاً:
npm config set save-prefix=’~’
مقدار پیشفرض مشخص کننده بازه را تیلده میگذارد.
npm config set save-exact true
مشخص کننده بازه پیشفرض را برمیدارد.
برای مطالعه دقیقتر میتوانید به این سؤال از استک مراجعه کنید:
بهتر است موقع تعیین محدودهی نسخه(یعنی قرار دادن همین range specifier ها)، سعی کنیم خیلی بسته و دقیق شماره نسخه را مشخص نکنیم، مثلاً بدون علامت رهایش نکنیم. چرا که بدین ترتیب فقط و فقط نسخهای از پکیج را استفاده میکند که ما مشخص کردیم، و دیگر هیچ وقت patch ها و bugfix های جدید را نصب نخواهد کرد. بعلاوه در این حالت اگر یک پکیج، همزمان نیازمندی چند پکیج پروژه باشد، آن وقت کار npm برای تعیین نسخه سخت خواهد شد. از طرفی هم بهتر است که خیلی گسترهی نسخه را باز نگذاریم. مثلاً اجازه ندهیم نسخه اصلی یک پکیج تغییر کند، چون در غیر این صورت مشکل backward compatibility پیش میآید.
برای تمرین و درک بهتر، میتوانید به آدرس زیر مراجعه کنید. در اینجا میتوان با تعیین نام پکیج و وارد کردن یک range دلخواه، مشاهده کرد که بازهی ورودی چه شماره نسخههایی را شامل میشود:
حال که با اصول و استانداردهای نسخه بندی در npm آشنا شدیم، خوب است بدانیم که فایل package-lock.json در پروژه های مبتنی بر npm، در زمینه مدیریت نسخه ها به ما کمک می کند.
در مقاله بعد، چرایی وجود این فایل در پروژهها را بررسی خواهیم کرد...