يعتبر ملف package-lock.json
من الملفات المهمة التي لا يخلو منها أي مشروع Node.js مهما كان بسيطا. فما من شك أنك صادفته أو لاحظته موجودا ضمن ملفات مشروعك وربما تساءلتَ للوهلة الأولى عن مدى أهميته، وفيما إذا كان عليك إضافته إلى Git أو لاَ.
سألت نفسي هذه الأسئلة أكثر من مرة في بدايات تعاملي مع Node.js منذ سنوات وبحثت في أكثر من مكان لأفهم الصورة كاملة، وها أنا اليوم أشارك معك ما تعلمته أو ما يجب عليك معرفته حول هذا الملف حتى لا يشكل لك بعد اليوم صداعا في رأسك كلما لمحته جاثما هناك بين ملفات مشروعك.
القصة تبدأ من الإصدارات الدلالية أو Semantic versioning
كلنا تعاملنا مع حزم واعتماديات برمجية ونحن نشتغل في مشاريعنا، بغض النظر عن التقنيات المستخدمة. قد تكون PHP ،Node.js أو أي لغة برمجة أخرى.
ومنا المعلوم أن كل حزمة برمجية يتم تطويرها وصيانتها باستمرار، إما لإضافة مزايا (Features) جديدة إليها أو لتصحيح الأخطاء (Bugs) التي ظهرت بعد نشرها.
في كل مرة يتم نشر نسخة جديدة من تلك الحزمة مع الإضافات الجديدة، وكل نسخة تحمل رقم إصدار يعرف بِ Version.
تم الإتفاق بين أوساط المبرمجين على اتباع نمط معين لترقيم تلك الإصدارات أو النسخ بشكل دلالي يجعل الأطراف الأخرى (مبرمجين وكذا مدراء الحزم مثل npm أو composer بالنسبة ل PHP وغيرهما) قادرة على التنبؤ بنوعية التعديلات التي أدخلت على تلك الحزمة من إصدار لآخر.
اصطُلِح على تسمية هذا النمط في ترقيم الإصدارات بِ Semantic Versioning أو الإصدارات الدلالية، وقرأت في بعض المقالات أنهم استخدموا “الترقيم الدلالي” كترجمة إلى العربية. ويمكن قبول كلتا الترجمتين على حسب رأيي.
كل إصدار أو نسخة دلالية تكون على الشكل التالي:
كل إصدار أو نسخة دلالية تكون على شكل x.y.z
، بحيث أن:
- الرقم
x
يعرف بِ Major ويشير إلى كل إصدار كبير أو جذري غير متوافق رجعيا مع الواجهة البرمجية للإصدار السابق (Breaking changes). - الرقم
y
يعرف بِ Minor ويعني أننا أضفنا مميزات جديدة ولكنها متوافقة مع الإصدارات السابقة (Backward compatible). - الرقم
z
يعرف بِ Patch ويدل على أن ثمة ثغرة أو خطأ برمجي تم إصلاحه في الإصدار الجديد ولكنه لا يكسر التوافقية مثل سابقه.
كل مشرف (Maintainer) على حزمة برمجية موصى بالتزام اصطلاحات Semantic Versioning بشكل دقيق عند ترقيم إصداراته حتى لا يربك المطورين من مستعلمي تلك الحزمة وكذلك مدراء الإعتماديات (Dependencies managers).
إلى هنا أعتقد أنه هذا كل ما يجب عليك معرفته بخصوص الإصدارات الدلالية لكي نعود إلى موضوعنا الأساسي package-lock.json
وعلاقته بكل ما قلناه.
- يمكنك زيارة semver.org للمزيد من المعلومات حول هذا الموضوع.
مالذي يحدث عندما نقوم بتثبيت حزمة بواسطة npm ؟
عندما نضيف حزمة جديدة إلى مشروعنا عن طريق npm install
فإنها تُضاف بشكل أوتوماتيكي إلى قائمة اعتماديات المشروع (Dependencies) في الملف package.json
كالتالي:
{
"name": "test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"lodash": "^4.17.21"
}
}
لا حظ أن النسخة التي تم تثبيتها هي 4.17.21
(لا حظ نمط semantic versioning)، ولكن تمت إضافة العلامة ^
قبلها 🤔
ما الذي تعنيه هذه العلامة بالنسبة لمدير الإعتماديات npm ؟
نسيت أن أقول لك بأنه عندما ثبتنا lodash
عن طريق npm install lodash
تم توليد ملف اسمه package-lock.json
بشكل أوتوماتيكي. وبما أن مشروعنا بسيط للغاية فهذا الملف سيكون بدوره بسيطا وشبيها بما يلي:
{
"name": "test",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "test",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"lodash": "^4.17.21"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
}
},
"dependencies": {
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
}
}
}
الآن لندع هذا الملف كما هو، ولنعد إلى العلامة ^
😄
هذه العلامة تخبر npm أن بإمكانه تحميل آخر إصدار Patch أو Minor من تلك الإعتمادية (lodash
في حالتنا) عندما يتم تنفيذ الأمر npm install
في المشروع.
هنا تكمن حساسية الموضوع ويبرز دور الملف package-lock.json
ولماذا من الضروري إضافته إلى Git.
في وقت كتابتي لهذه التدوينة، آخر إصدار من مكتبة lodash
هو 4.17.21
ولكن لنفترض أنه بعد مدة من الزمن تم نشر نسخ جديدة وأصبح آخر إصدار هو 4.19.7
على سبيل المثال، وكان على أحد أعضاء الفريق العمل على نفس المشروع. ولنفرض بأنني تجاهلت package-lock.json
ولم أضفه لمدير النسخ Git، وبالتالي عندما قام زميلي بسحب المشروع كان لديه فقط ملف package.json
إضافة إلى ملفات المشروع الأخرى بطبيعة الحال.
لكي يقوم بتشغيل المشروع يجب عليه أولا تثبيت الإعتماديات عن طريق تنفيذ الأمر السطري npm install
.
خمن معي ماذا ستكون النتيجة ؟ 🤔
النتيجة هي أن npm سيقوم بتحميل وتثبيت النسخة 4.19.7
الجديدة رغم أنه في package.json
موجودة النسخة 4.17.21
، ولكن مسبوقة بالعلامة ^
. عندما يراها npm يقول: حسناً، بما أنه لدي العلامة ^
فمن حقي أن أقوم بتحميل آخر إصدار Minor وآخر إصدار Patch من هذه الإعتمادية طالما ليس هناك ملف package-lock.json
يفرض علي نسخة بحد ذاتها.
لذلك سيصبح لدينا سيناريو يكون فيه لدى مختلف أعضاء الفريق إصدارات مختلفة، ورغم أن الإختلافات لن تكون أساسية وجوهرية مادامت متعلقة فقط بال Minor و Patch، إلا أن ذلك من الممكن أن يسبب مشاكل غير متوقعة خاصة في التوافق والتناغم مع مكتبات واعتماديات أخرى للمشروع. وهذا النوع من المشاكل يصعب تتبعه وتشخيصه.
هناك طرق أخرى لإخبار npm بكيفية إيجاد وتحديد الإصدارات المقبولة، ونذكر بعضا منها في هذا الجدول:
النمط | معناه |
---|---|
~version |
تقريبا نفس الإصدار، إذ تُقبل فقط الإصدارات الجديدة من نوع Patch. |
^version |
يقبل الإصدارات المتوافقة، وهي كما شرحنا سابقا كل إصدارات Patch و Minor الأحدث إذا تم احترام نمط SemVer 😃 |
version |
لا يتم قبول وتثبيت إلا هذا الإصدار بالذات. |
>version |
كل الإصدارت الأكبر (الأحدث) بالمقارنة مع الإصدار الذي يأتي بعد الرمز < . |
>=version |
الإصدارات الأكبر من أو تساوي. |
<version |
كل الإصدارت الأصغر (الأقدم) بالمقارنة مع الإصدار الذي يأتي بعد الرمز > . |
<=version |
الإصدارات الأقل من أو تساوي. |
كما رأينا آنفاً، يقوم مدير الإعتماديات npm باستخدام النمط ^version
افتراضيا عند تثبيت أي حزمة.
الحل هو package-lock.json
✅
إذن الحل هو في الملف package-lock.json
الذي يجبر كل متدخل في المشروع على تثبيت نفس إصدارات الإعتماديات. ولذلك من الضروري تتبعه بواسطة Git وإضافته للمستودع (Repo) حتى يكون بمقدور كل شخص سحبه مع ملفات المشروع. بعد ذلك إذا قمنا بتنفيذ npm install
فإنه سيتم تحميل نفس الإصدار الموجود في package.json
. الأخير يجب أن يكون هو مصدر الحقيقة، وكل تحديث ل package-lock.json
يجب أن يمر عبره.
حذاري أن تقوم بالتعديل على package-lock.json
مباشرة 📛
إذا أردنا مثلا تحديث lodash
إلى إصدار أحدث أو أقدم، فيجب المرور عبر npm install
بهذه الطريقة:
npm install lodash@latest
أو
npm install lodash@<version>
بعد ذلك سيتم توليد package-lock.json
من جديد، ومن ثم عليك إضافته إلى Git وعمل git push
لإرساله إلى المستودع ليصبح التحديث في متناول زملائك.
جميع مديري الحزم والإعتماديات الذي تعاملت معهم مثل composer
،yarn
و npm
يستيعنون بملف Lock لمساعدتهم على تحديد النسخ والإصدارات التي عليهم تثبيتها.
في الختام
أتمنى أن موضوع أهمية package-lock.json
أصبح أكثر وضوحا بالنسبة لك الآن، وكيف أن مسألة تتبعه من طرف Git مسألة مهمة في كل مشروع لتفادي أي نوع من المفاجآت غير السارة، مع ضمان توحيد الإصدارات المثبتة لكل حزمة بين أعضاء الفريق الواحد.