چرا باید package-lock.json را دوست داشته باشیم؟!

اکثر ما توی محیط‌های کاری مختلف، به افرادی برخوردیم که بیشتر از ما توی یه سری چیزا کنجکاو میشن و میخوان علت وجود هرچیزی رو بدونن. ممکنه پیش خودمون سوال کرده باشیم که واقعا ریز شدن توی این موارد کمکی هم میکنه یا نه؟?

وقتی که وسط یه پروژه کامپوننت‌ها سنگین شد و کم‌کم به مشکلاتی به ظاهر غیرمنطقی برخوردم، متوجه شدم که جواب سوال بالا، آره هست!

یکی از این موارد، این دوتا فایلیه که هممون دیدیمش(package.json و package-lock.json). فایلایی که فقط ممکنه در حد گذاشتن یا نگذاشتن توی gitignore و یه سری لیست dependency ازشون بدونیم. توی این پست کاربرد و تفاوت‌های این دوتا فایل رو قراره یاد بگیریم.


package-lock.json

قبل از شروع بحثمون، یه سوالی رو مطرح میکنم.

چرا package-lock.json حتما و حتما و حتما باید توی source control و commitهامون وجود داشته باشه؟ تا حالا راجع بهش فکر کردین؟ جواب این سوالو آخر این مقاله متوجه خواهید شد :))

تا قبل از ورژن 5 npm، فایلی با عنوان package-lock.json بعد از زدن دستور npm install تولید نمیشد و تا اون موقع از package.json برای نصب ماژول‌ها توی سیستم شخص دیگه استفاده میشد. اما از بعد این ورژن، فایل package-lock.json اطلاعات خیلی بیشتری رو درمورد هر پکیج و dependencyهای اون پکیج بهمون میداد. و این یعنی به صفر رسوندن اختلاف برای نصب ماژول‌ها توی سیستم شخص دیگه :)

پس برعکس چیزی که هممون فکر میکنیم، npm برای نصب پکیج‌ها، از package.json یک لیست dependency پیدا میکنه و برای ورژن پکیج‌های اون لیست، سراغ package-lock.json میره( بد نیست توی پرانتز اشاره کنم که دستور npm ci، برعکس npm i مستقیما به package-lock.json نگاه میکنه و از package.json فقط برای بررسی هماهنگی بین ورژن‌های دو فایل استفاده میکنه. و اگه ورژن‌های یک پکیج در این دو فایل یکی نبودند، موقع نصب ارور میده )

ممکنه الان توی ذهنتون بیاد که package.json برای نصب یک ماژول کافیه چون دقیقا ورژنی که میخوایم رو داره و وقتی یه کاربری npm install میزنه و توی لیست dependencyها ورژن دقیق یک ماژول رو داشته باشیم، دقیقا مثل اینه که زده باشیم npm install sth@v.v.v. پس چرا package-lock.json نیاز داریم؟

جواب: dependencies of dependency :)

با مثال توضیح میدم. فرض کنید میخوایم پکیج react-helmet رو توی ورژن 5.2.1 نصب کنیم. دستور زیر رو میزنیم.

npm i react-helmet@5.2.1

بلافاصله بعد از نصب، فایل package-lock.json هم آپدیت میشه. از طرفی هم توی package.json این خط به dependencies اضافه خواهد شد:

&quotdependencies&quot: {
    &quotreact-helmet&quot: &quot^5.2.1&quot
}

مشکل دقیقا از اینجا شروع میشه! توضیحات package.json برای یک پکیج کافی نیست! خود react-helmet قطعا dependencyهایی داره که هرکدومشون ورژن خاصی دارن. وقتی که ورژن react-helmet تغییری نکرده و همون 5.2.1 باقی مونده ولی کاربرِ دیگه پروژه رو میگیره و npm i میزنه، ممکنه ورژن چندتا از dependencyهایی که react-helmet استفاده کرده تغییر کرده باشه! چیزی که به هیچ وجه توی package.json وجود نداره. و باگ از جایی شروع میشه که اون تفاوت ورژن dependency، باعث بشه یک سری فانکشن‌ها به شکل دیگه عمل کنند! و اینجاست که متوجه حرف اول مقاله میشیم! فکر میکنیم غیر منطقیه! ولی مشکل از ما بوده که package-lock.json رو فایل اضافی تلقی میکردیم و اون رو توی gitignore گذاشتیم :))

با هم اجزای موجود توی فایل package-lock.json برای مثالی که زدیم رو بررسی میکنیم:

محتویات package-lock برای react-helemt@5.2.1
محتویات package-lock برای react-helemt@5.2.1

ورژن پکیج‌هایی که react-helmet داره از اونا استفاده میکنه رو به خوبی توی عکس بالا میبینید. پس npm برای نصب ماژول‌ها از این فایل استفاده میکنه چون هرچیزی که مورد نیازشه دقیقا توی این فایل ذکر شده. اگر دوست داشتین بیشتر درمورد integrity و تفاوت sha512 , sha1, ... بدونین، این document رو پیشنهاد میکنم https://w3c.github.io/webappsec-subresource-integrity/

Semantic Versioning

توی این قسمت میخوام درمورد ورژن‌بندی npm هم موردی رو ذکر کنم. اگر دقت کرده باشید ورژن‌های همه پکیج‌های npm یا ورژن پروژه خودتون موقع زدن دستور npm init، از سه عدد تشکیل شده که به صورت a.b.c هست. به این روشی که npm استفاده میکنه، semantic versioning یا به اختصار semver میگن. که توی این روش ورژن بندی هرکدوم از حروف a, b, c، نشان‌دهنده یه نوع تغییر هستند که به صورت زیر هست:

a.b.c -> a: major version, b: minor version, c: patch versions

آخرین حرف یعنی c، نشان‌دهنده یک bugfix هست که تغییر بزرگی رو توی پکیج ایجاد نکرده.

دومین حرف یعنی b، نشان‌دهنده یک سری تغییر در فانکشن‌ها و عملکردهای پکیج هست که باز هم این تغییر باعث تغییر بزرگ توی پکیج و عوض شدن کلیِ چیزها نمیشه.

و اما اولین حرف یعنی a، نشان‌دهنده تغییر بزرگیه که ممکنه روی پروژه‌ای که ما ازش استفاده میکنیم ناهماهنگی‌هایی رو به وجود بیاره. پس اگر a توی پکیجاتون فرق کرد، احتمالا مجبورید که برای یک سری جاها document اون پکیج رو دوباره مطالعه کنید?

حالا فرض کنید یکی از dependencyهای پکیج react-helmet، از 15.5.4 تبدیل بشه به 16.5.4 و فایل package-lock توی source control وجود نداشته باشه. همه اعضای تیم بعد از باگ:



همه این‌ها گفته شد تا به یک نتیجه برسیم:

وجود package-lock.json در source control اجباریست و تنها دلیل آن هم dependencyهای پکیجیست که نصب کردیم.

و سخن آخر این که سعی کنیم شبیه اون آدمای کنجکاو باشیم! وگرنه مشکلاتی ممکنه برامون پیش بیاد که تا مرز توقف پروژه برای یه تایم طولانی پیش بره.

موفق و سربلند باشید❤ Reza Hasani