وقتی با npm کار میکنیم، وجود یک فایل مرموز به نام package-lock.json
، همان ابتدا توجه ما را به خود جلب میکند. اما این فایل چیست و چه کاری انجام میدهد؟
قبل از توضیح در این باره، خوب است بدانیم نیازمندی های پروژه ممکن است مستقیم یا غیرمستقیم باشند. نیازمندی های مستقیم، یعنی همان هایی که نامشان در فایل package.json
مشخص شده و ما خودمان آنها را در پروژه نصب کرده ایم. اما این نیازمندی های مستقیم، خود پکیج هایی هستند که سایر پکیج ها را بعنوان نیازمندی خود دارند، و مجدداً این پکیج های نیازمندی نیازمندی هایی دارند و به همین ترتیب این زنجیره ادامه پیدا می کند. سایر نیازمندی ها، یعنی نیازمندی های غیرمستقیم پروژه، علیرغم این که در فایل package.json
پروژه خبری از آنها نیست، ولی در فولدر node_modules نصب می شوند.
این زنجیره نیازمندی ها ادامه پیدا می کند و در نهایت درخت نیازمندی های یک پروژه تشکیل می شود.
هر بار که npm فایل package.json
، یا ساختار درخت نیازمندیهای node_modules را تغییر میدهد، بصورت خودکار یک فایل به نام package-lock.json
ایجاد میشود. بعنوان مثال هر بار که دستور npm install
را اجرا میکنیم، فایل package-lock.json
ایجاد و یا اگر قبلاً وجود داشته بروز میشود. دستورات دیگری مثل npm rm
، npm update
نیز همینگونه هستند، یعنی هر تغییری ایجاد میکنند، بلافاصله فایل package-lock.json
نیز بصورت خودکار با آن تغییرات همگام میشود.
این فایل لیست تمام پکیجهای نیازمندی یک پروژه را(چه نیازمندی مستقیم باشند و چه غیرمستقیم) به همراه شماره دقیق نسخهاش (بدون مشخصه بازه) در خود دارد. منظور از شماره دقیق نسخه، exact version number است. یعنی سه عدد ابتدایی مشخص کننده شماره نسخه، بدون حضور range modifier. و می دانیم که اگر شماره نسخه را اینگونه بنویسیم، موقع نصب پکیج باید حتماً همان شماره نسخه پکیج نصب شود، نه آپدیتهای بعدی آن.
به این ترتیب اگر یک نفر فایل package-lock.json
را در اختیار داشته باشد، در هر لحظه میداند که دقیقاً چه نسخهای از پکیجها در برنامه به کار رفتهاند. یعنی میتواند دقیقاً همان درخت نیازمندیها را دوباره ایجاد کند. حالا فرق این که ما شماره نسخهی پکیج را بصورت دقیق در package.json
بنویسیم با این که در فایل package-lock.json
قرار داشته باشد چیست؟ تفاوت اینجاست که در فایل package.json
صرفاً نیازمندیهای مستقیم پروژه آمده و ما میتوانیم شماره نسخهشان را تعیین کنیم، اما به نیازمندیهای غیرمستقیم دسترسی نداریم. بنابراین اگر نیازمندیهای غیرمستقیم آپدیت بشوند، ما کنترلی روی آنها نداریم. به یک مثال توجه کنید:
فرض کنیم پروژهمان از پکیج express نسخه 4.17.1 استفاده میکند. خود پکیج express به یکی از نسخههای بازه ~1.17.4 از پکیج body-parser نیاز دارد. پکیج body-parser خودش نیازمند یکی از نسخههای بازه ~1.3.4از پکیج accepts هست و ... (چنانکه بالاتر گفتیم این زنجیره میتواند همینطور ادامه داشته باشد). حال فرض کنیم که میخواهیم نسخهای از پروژهمان داشته باشیم، که هر زمان هر برنامهنویسی، و در هر محیطی که خواست آن را بازسازی کند، به یک پروژه یکسان برسد(به یک درخت نیازمندی یکسان برسد). اگر صرفاً نسخه express را که نیازمندی مستقیم ماست در فایل package.json
بصورت دقیق مشخص کنیم، هیچ کنترلی روی شماره نسخه سایر پکیجها، مثلاً عوض شدن نسخه body-parser یا accepts که پکیجهای درخت نیازمندیِ خودِ express هستند نداریم و با گذشت زمان، ممکن است نسخههای جدیدتری از آنها نصب شود که این مطلوب ما نیست. برای حل این مشکل از فایل package-lock.json
استفاده میکنیم.
بطور کلی package-lock.json
در چند زمینه به ما کمک میکند:
1. مورد اول که به آن اشاره کردیم، آنست که به ما امکان تولید درخت نیازمندیهای یکسان را میدهد. به این ترتیب کار اعضای تیم توسعه پروژه، کار پیاده سازی(deployment) پروژه، و همینطور کار continuous integration (و در کل کار هر کس که نیاز دارد دستور npm install
را روی پروژهمان اجرا کند) برای بازسازی پروژه مشابه راحت میشود. بعنوان مثال دستور جدید npm ci که مخصوص CI/CD است، برای نصب پکیجها مستقیماً از package-lock.json
استفاده میکند و سراغ package.json نمیرود.
2. به ما این امکان را میدهد که بدون کامیت کردن دایرکتوری node_modules، در زمانهای مختلف وضعیت فولدر node_modules و پکیجهای داخل آن را بدانیم و بتوانیم بازیابیاش کنیم.
3. اگر چنین فایلی داشته باشیم، براحتی میتوانیم با diff گرفتن از حالتهای این فایل در زمانهای مختلف، تفاوتها و تغییرات درخت نیازمندیها را بصورت واضح ببینیم. (ابزار های source control مانند git امکان مقایسه بین حالات مختلف یک فایل در طول زمان را از طریق ابزار یا دستوری به نام diff در اختیار ما می گذارند)
4. بهینه سازی فرایند نصب. موقعی که دستور npm install
را اجرا میکنیم، npm اطلاعات(metadata) مربوط به هر پکیج را تحلیل میکند. در این بین فایل package-lock.json
کاری میکند که اطلاعات تکراری دوباره تجزیه و تحلیل نشوند و از این طریق فرایند نصب را بهینهتر و سریعتر میکند.
اگر بخواهیم کمی عمیقتر بشویم، package-lock.json
نوعی package-lock یا lock-file است. وقتی lock-file ایجاد میشود، شماره نسخهی تمامی پکیجهای حاضر در درخت نیازمندیها را قفل میکند. این کار با استفاده از اطلاعاتی نظیر شماره نسخه، آدرس آن پکیج(location) و یک integrity hash انجام میگیرد. برای مطالعهی بیشتر در مورد package-lock ها، خوب است به توضیحات سایت npmjs.com سری بزنید:
docs.npmjs.com/files/package-locks
در مثال زیر، یک پروژه به نام npm-test داریم که فایلهای package.json
و package-lock.json مربوط به آن را آوردهایم:
package.json
package-lock.json
تصویر بالا تصویرِ فایل package-lock.json
پروژهای به نام npm-test است. ملاحظه میکنیم npm-test فقط یک پکیج به اسم npm-test-12-12-4 را به عنوان نیازمندی خود داراست، خود این پکیج نیز دارد از پکیج jalaali-js استفاده میکند. همانطور که میبینیم، با این که شماره نسخه پکیج jalaali-js با علامت ^ مشخص شده(چه در فایل package.json
در پکیج npm-test-12-12-4، و چه در تصویر بالا قسمت requires از علامت ^ استفاده شده)، اما شماره نسخه ی jalaali-js بصورت دقیق(بدون هیچ مشخصکننده بازهای) در این فایل آمدهاست.
چند نکته تکمیلی درباره package-lock.json
• در حالت طبیعی اطلاعات دو فایل package.json
و package-lock.json
با یکدیگر همگام هستند. ولی اگر یکی از این دو بصورت دستی(manual) تغییر کنند، اطلاعات package.json
نسبت به package-lock.json
اولویت دارند. به این معنی که اگر پکیجی در قسمت نیازمندیهای فایل package.json
وجود داشته باشد ولی در package-lock.json
نباشد، آن پکیج نصب میشود. یا اگه شماره نسخه پکیجی بصورت دستی تغییر کند، شمارهای که در package.json
بدان اشاره شده نصب خواهد شد.
• از آنجایی که این فایل در بازسازی پکیجها نقش مهمی دارد، باید حتماً روی مخزن گیت کامیت شود.
• امکان انتشار این فایل وجود ندارد. یعنی وقتی کد پکیجمان را نوشتیم و خواستیم پکیج را publish کنیم، با اجرای دستور npm publish
، این فایل منتشر نمیشود و بنابراین در tarball تولید شده وجود نخواهد داشت و روی npm آپلود نمیشود. اگر بخواهیم سادهتر بگوییم، بعد از این که پکیجمان منتشر شد و یک کاربر میخواست آن را در پروژهاش نصب کند و از آن استفاده کند، در فولدر node_modules پروژهاش یک فولدر به نام پکیج ما ایجاد میشود. حال اگر به درون آن فولدر سری بزند، خواهد دید که فایلی به نام package-lock.json
در آن وجود ندارد.
• این فایل باید دقیقاً در بالاترین سطح از دایرکتوری روتِ پروژه قرار گرفته باشد و در غیر این صورت حضورش بی تأثیر است.
• خوب است بدانیم که npm یک lockfile دیگر به نام npm-shrinkwrap.json
نیز دارد که دقیقاً مثل package-lock.json
است، و قالب و کاربردشان هم مشابه یکدیگر است و میتوانند بجای هم بکار روند. تنها تفاوتشان در این است که package-lock.json
منتشر نمیشود، ولی npm-shrinkwrap.json
میتواند منتشر گردد. برای ایجاد این فایل کافی است در دایرکتوری روتِ پروژه دستور npm shrinkwrap را اجرا کنیم. وقتی npm-shrinkwrap.json
حضور داشته باشد، در اولویت است و دیگر npm به package-lock.json
توجهی نمیکند. برای اطلاعات بیشتر در مورد npm-shrinkwrap.json
به توضیحات سایت npm در این باره مراجعه کنید:
https://docs.npmjs.com/configuring-npm/shrinkwrap-json.html
در ضمن برای مطالعه بیشتر در مورد package-lock.json
و همچنین مشاهده قالب کلی این فایل و لیست مشخصههایی که در آن بکار رفته، به این آدرس مراجعه کنید:
https://docs.npmjs.com/files/package-lock.json
تصویر زیر خلاصهای از توضیحاتی است که درباره lock-file ها دادهایم:
و این هم یک مقاله خیلی خوب در مورد کاربرد lock-file ها:
https://snyk.io/blog/what-is-package-lock-json/
• وقتی دستور npm install package-name
را اجرا میکنیم، بصورت خودکار تغییرات فایل package.json
با package-lock.json
همگام میشوند. اگر بخواهیم این اتفاق نیفتد، میتوانیم از گزینه no-save--
استفاده کنیم. بدین ترتیب هیچکدام از این دو فایل تغییری نخواهند کرد، ولی در فولدر node_modules پکیج مورد نظر ما نصب (یا بروز) میشود.
• در پروژههایی که از yarn استفاده میکنند، فایل yarn.lock
نیز مشابه package-lock.json
کار میکند. (البته این دو تفاوتهایی دارند، مثلاً این که فایل yarn.lock
هم مثل npm-shrinkwrap.json
قابل انتشار میباشد)
فرض کنید در یک پروژه، دو یا چند پکیج نیازمندی، خود به نسخه های مختلفی از یک پکیج خاص نیازمند باشند. در این صورت کدامیک از نسخه های این پکیج اولویت دارد و در نهایت کدامیک در پروژه نصب خواهد شد؟ این سؤالیست که در مقاله بعدی مفصل به آن پرداخته ایم. پس مقاله بعدی را از دست ندهید ...