فایل package-lock.json چیست و چه کاری انجام می دهد؟


وقتی با 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 قابل انتشار می‌باشد)

فرض کنید در یک پروژه، دو یا چند پکیج نیازمندی، خود به نسخه های مختلفی از یک پکیج خاص نیازمند باشند. در این صورت کدامیک از نسخه های این پکیج اولویت دارد و در نهایت کدامیک در پروژه نصب خواهد شد؟ این سؤالیست که در مقاله بعدی مفصل به آن پرداخته ایم. پس مقاله بعدی را از دست ندهید ...

لیست نظرات
کاربر میهمان
دیدگاه شما چیست؟
کاربر میهمان