لماذا ترك npm 7 دعم package-lock.json؟

لقد yarn.lockطرحت السؤال نفسه عدة مرات منذ أن أعلنا أن npm 7 ستدعم الملفات . بدا الأمر كما يلي: "لماذا إذن ترك الدعم package-lock.json؟ لماذا لا تستخدم فقط yarn.lock؟ " الجواب المختصر لهذا السؤال هو: "لأنه لا يلبي تمامًا احتياجات npm. إذا كنت تعتمد عليها فقط ، فإن ذلك سيضعف قدرة npm على تشكيل مخططات تثبيت الحزمة المثلى والقدرة على إضافة وظائف جديدة إلى المشروع. " يتم تقديم إجابة أكثر تفصيلا في هذه المواد.







yarn.lock



البنية الأساسية لملف yarn.lock



الملف yarn.lockهو وصف للمراسلات بين مؤهلات تبعية الحزمة والبيانات الوصفية التي تصف حل هذه التبعيات. فمثلا:



mkdirp@1.x:
  version "1.0.2"
  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.2.tgz#5ccd93437619ca7050b538573fc918327eba98fb"
  integrity sha512-N2REVrJ/X/jGPfit2d7zea2J1pf7EAR5chIUcfHffAZ7gmlam5U65sAm76+o4ntQbSRdTjYf7qZz3chuHlwXEA==


يوضح هذا المقتطف ما يلي: " mkdirp@1.xيجب أن يتم حل أي تبعية على النحو المحدد بالضبط هنا." إذا كانت هناك عدة حزم تعتمد عليها mkdirp@1.x، فسيتم حل جميع هذه التبعيات بنفس الطريقة.



في npm 7 ، إذا كان الملف موجودًا في المشروع yarn.lock، فسيستخدم npm البيانات الوصفية التي يحتوي عليها. resolvedستخبر قيم الحقول npm بالمكان الذي تحتاج إليه لتنزيل الحزم منه ، integrityوسيتم استخدام قيم الحقول للتحقق مما تم استلامه للتأكد من أنه يطابق ما كان متوقعًا استلامه. إذا تم إضافة حزم أو إزالتها من المشروع ، يتم تحديث المحتوى وفقًا لذلك yarn.lock.



Npm في هذه الحالة ، كما كان من قبل ، يقوم بإنشاء ملفpackage-lock.json... إذا كان هذا الملف موجودًا في المشروع ، فسيتم استخدامه كمصدر موثوق للمعلومات حول هيكل (شكل) شجرة التبعية.



السؤال هنا هو ، "إذا كانت yarn.lockجيدة بما يكفي لمدير حزم Yarn ، فلماذا لا يمكن لـ npm استخدام هذا الملف فقط؟"



نتائج تثبيت التبعية الحتمية



تضمن نتائج تثبيت الحزم باستخدام Yarn أن تكون هي نفسها عند استخدام نفس الملف yarn.lockونفس الإصدار من Yarn. يمكن أن يؤدي استخدام إصدارات مختلفة من Yarn إلى وضع ملفات الحزم بشكل مختلف على القرص. يضمن



الملف yarn.lockدقة التبعية الحتمية. على سبيل المثال ، إذا كان foo@1.xمسموحًا بالدخول foo@1.2.3، نظرًا لاستخدام الملف نفسه yarn.lock، فسيحدث ذلك دائمًا ، في جميع إصدارات Yarn. لكن هذا (على الأقل في حد ذاته) لا يعادل ضمان حتمية بنية شجرة التبعية!



خذ بعين الاعتبار الرسم البياني التبعية التالي:



root -> (foo@1, bar@1)
foo -> (baz@1)
bar -> (baz@2)


إليك بعض الرسوم البيانية لشجرة التبعية ، يمكن اعتبار كل منها صحيحًا.



الشجرة رقم 1:



root
+-- foo
+-- bar
|   +-- baz@2
+-- baz@1


الشجرة رقم 2:



+-- foo
|   +-- baz@1
+-- bar
+-- baz@2


yarn.lockلا يمكن أن يخبرنا الملف عن شجرة التبعية التي يجب استخدامها. إذا rootتم تنفيذ أمر في الحزمة require(«baz»)(وهذا غير صحيح ، حيث أن هذه التبعية لا تنعكس في شجرة التبعية) ، فإن الملف yarn.lockلا يضمن التنفيذ الصحيح لهذه العملية. هذا شكل من أشكال الحتمية التي يمكن أن يعطيها الملف package-lock.json، ولكن ليس yarn.lock.



في الممارسة ، بالطبع ، منذ الغزل ، في الملفyarn.lock، هناك جميع المعلومات اللازمة لتحديد الإصدار المناسب من التبعية ، والاختيار حتمية طالما أن الجميع يستخدمون نفس الإصدار من الغزل. هذا يعني أن اختيار الإصدار يتم دائمًا بنفس الطريقة. لا يتغير الرمز حتى يقوم شخص ما بتغييره. وتجدر الإشارة إلى أن الغزل يتمتع بالذكاء الكافي بحيث أنه ، عند إنشاء شجرة التبعية ، لا يعتمد على التناقضات فيما يتعلق بوقت تحميل بيان الحزمة. خلاف ذلك ، لا يمكن ضمان حتمية النتائج.



نظرًا لأن هذا يتم تحديده من خلال تفاصيل خوارزميات الغزل وليس من خلال هياكل البيانات الموجودة على القرص (عدم تحديد الخوارزمية التي سيتم استخدامها) ، فإن ضمان الحتمية هذا أضعف بشكل أساسي من الضمان الذيpackage-lock.jsonيحتوي على وصف كامل لبنية شجرة التبعية المخزنة على القرص.



بمعنى آخر ، تتأثر كيفية بناء Yarn شجرة التبعية بملف yarn.lockYarn نفسه وتطبيقه. وفي npm ، يؤثر الملف فقط على كيفية ظهور شجرة التبعية package-lock.json. ونتيجة لذلك ، فإن هيكل المشروع الموصوف في package-lock.json، يصبح من الصعب كسره عن طريق الخطأ ، باستخدام إصدارات مختلفة من npm. وإذا تم إجراء تغييرات على الملف (ربما عن طريق الخطأ ، أو عن قصد) ، فإن هذه التغييرات ستكون مرئية بوضوح في الملف عند إضافة نسخته المعدلة إلى مستودع المشروع ، الذي يستخدم نظام التحكم في الإصدار.



تبعيات متداخلة وإلغاء تكرار التبعية



علاوة على ذلك ، هناك فئة كاملة من المواقف التي تتضمن العمل مع التبعيات المتداخلة والتبعية المكررة ، عندما yarn.lockلا يكون الملف قادرًا على عكس نتيجة دقة التبعية بدقة ، والتي سيتم استخدامها عمليًا بواسطة npm. علاوة على ذلك ، هذا صحيح حتى بالنسبة لتلك الحالات التي تستخدم فيها npm yarn.lockالبيانات الوصفية كمصدر. بينما تستخدمه npm yarn.lockكمصدر موثوق للمعلومات ، لا تعتبر npm أن هذا الملف هو المصدر الموثوق للمعلومات حول قيود إصدار التبعية.



في بعض الحالات ، ينشئ Yarn شجرة تبعية بمستوى عالٍ جدًا من ازدواجية الحزم ، ولا نحتاج إليها. ونتيجة لذلك ، اتضح أن اتباع خوارزمية Yarn بالضبط في مثل هذه الحالات بعيدًا عن المثالية.



خذ بعين الاعتبار الرسم البياني التبعية التالي:



root -> (x@1.x, y@1.x, z@1.x)
x@1.1.0 -> ()
x@1.2.0 -> ()
y@1.0.0 -> (x@1.1, z@2.x)
z@1.0.0 -> ()
z@2.0.0 -> (x@1.x)


rootيعتمد المشروع على إصدارات 1.xالحزمة x، yو z. الحزمة yتعتمد على x@1.1وعلى z@2.x. لا تحتوي حزمة zالإصدار 1 على تبعيات ، ولكن تعتمد نفس حزمة الإصدار 2 على x@1.x.



بناءً على هذه المعلومات ، تقوم npm بإنشاء شجرة التبعية التالية:



root (x@1.x, y@1.x, z@1.x) <--   x@1.x
+-- x 1.2.0                <-- x@1.x   1.2.0
+-- y (x@1.1, z@2.x)
|   +-- x 1.1.0            <-- x@1.x   1.1.0
|   +-- z 2.0.0 (x@1.x)    <--   x@1.x
+-- z 1.0.0


الحزمة z@2.0.0تعتمد على x@1.x، ويمكن قول الشيء نفسه عن root. يعيّن الملف yarn.lockإلى x@1.xج 1.2.0. ومع ذلك ، بدلاً من ذلك سيتم حل تبعية الحزمة zحيث تم تحديدها أيضًا . ونتيجة لذلك ، على الرغم من وصف الاعتمادية في المكان الذي ذكر فيه أنه يجب حلها إلى إصدار الحزمة ، هناك نتيجة دقة ثانية لإصدار الحزمة . إذا قمت بتشغيل npm بعلامة ، فسيذهب النظام خطوة أخرى إلى الأمام وتثبيت مثيل واحد فقط من التبعية ، مما سيؤدي إلى تكوين شجرة التبعية التالية:x@1.xx@1.1.0



x@1.xyarn.lock1.2.0x@1.x1.1.0



--prefer-dedupex



root (x@1.x, y@1.x, z@1.x)
+-- x 1.1.0       <-- x@1.x       1.1.0
+-- y (x@1.1, z@2.x)
|   +-- z 2.0.0 (x@1.x)
+-- z 1.0.0


هذا يقلل من ازدواجية التبعيات ، وتلتزم شجرة التبعية الناتجة بالملف package-lock.json.



نظرًا لأن الملف yarn.lockيلتقط الترتيب الذي يتم فيه حل التبعيات فقط ، وليس شجرة الحزم الناتجة ، فسوف ينشئ Yarn شجرة التبعية التالية:



root (x@1.x, y@1.x, z@1.x) <--   x@1.x
+-- x 1.2.0                <-- x@1.x   1.2.0
+-- y (x@1.1, z@2.x)
|   +-- x 1.1.0            <-- x@1.x   1.1.0
|   +-- z 2.0.0 (x@1.x)    <-- x@1.1.0   , ...
|       +-- x 1.2.0        <-- Yarn     ,    yarn.lock
+-- z 1.0.0


تظهر حزمة x، عند استخدام الغزل في شجرة التبعية ثلاث مرات. عند استخدام npm بدون إعدادات إضافية - مرتين. وعند استخدام العلم --prefer-dedupe- مرة واحدة فقط (على الرغم من أن شجرة التبعية ليست هي الأحدث وليست أفضل نسخة من الحزمة).



يمكن اعتبار جميع أشجار التبعية الثلاثة الصحيحة صحيحة بمعنى أن كل حزمة ستتلقى إصدارات التبعيات التي تلبي المتطلبات المذكورة. لكننا لا نريد إنشاء أشجار حزمة بعدد كبير جدًا من التكرارات. فكر في ما سيحدث إذا x- إنها حزمة كبيرة لها العديد من التبعيات الخاصة بها!



ونتيجة لذلك ، هناك طريقة واحدة فقط يمكنها من خلال npm تحسين شجرة الحزمة مع الحفاظ على إنشاء أشجار تبعية حتمية وقابلة للتكرار. تتكون هذه الطريقة من استخدام ملف قفل ، يختلف مبدأ تكوينه واستخدامه على مستوى أساسي yarn.lock.



تحديد نتائج تنفيذ نوايا المستخدم



كما ذكرنا من قبل ، في npm 7 ، يمكن للمستخدم استخدام العلامة لتطبيق --prefer-dedupeخوارزمية إنشاء شجرة التبعية ، حيث يتم إعطاء الأولوية لإلغاء تكرار البيانات التبعية ، وليس الرغبة في تثبيت أحدث إصدارات الحزمة دائمًا. --prefer-dedupeعادةً ما يكون العلم مثاليًا في المواقف التي تحتاج إلى تقليل الحزم المكررة.



إذا تم استخدام هذه العلامة ، فستظهر الشجرة الناتجة للمثال أعلاه كما يلي:



root (x@1.x, y@1.x, z@1.x) <--   x@1.x 
+-- x 1.1.0                <-- x@1.x   1.1.0   
+-- y (x@1.1, z@2.x)
|   +-- z 2.0.0 (x@1.x)    <--   x@1.x
+-- z 1.0.0


في هذه الحالة ، يرى npm أنه على الرغم من أنها x@1.2.0أحدث إصدار من الحزمة التي تلبي المتطلبات x@1.x، فمن الممكن أن تختار بدلاً من ذلك x@1.1.0. سيؤدي تحديد هذا الإصدار إلى تقليل ازدواجية الحزم في شجرة التبعية.



إذا لم نصلح هيكل شجرة التبعية في ملف القفل ، فسيتعين على كل مبرمج يعمل في مشروع في فريق ما إعداد بيئة عمله بنفس الطريقة التي قام بها أعضاء الفريق الآخرون بإعدادها. هذا فقط سيسمح له بالحصول على نفس النتيجة مثل البقية. إذا كان من الممكن تعديل "تنفيذ" آلية بناء شجرة التبعية بهذه الطريقة ، فإنها تمنح مستخدمي npm خيارات جادة لتحسين التبعيات لاحتياجاتهم الخاصة. ولكن ، إذا كانت نتائج إنشاء الشجرة تعتمد على تنفيذ النظام ، فهذا يجعل من المستحيل إنشاء أشجار تبعية حتمية. هذا هو بالضبط ما يؤدي إليه استخدام الملف yarn.lock.



في ما يلي بعض الأمثلة الإضافية حول كيف يمكن لإعدادات npm المتقدمة أن تؤدي إلى إنشاء أشجار تبعية مختلفة:



  • --legacy-peer-deps، علامة تتسبب في تجاهل npm تمامًا peerDependencies.
  • --legacy-bundling، علم يخبر npm أنه يجب ألا يحاول حتى جعل شجرة التبعية "مسطحة" أكثر.
  • --global-style، العلامة التي يتم من خلالها تثبيت كل التبعيات متعدية التبعيات المتداخلة في مجلدات التبعية ذات المستوى الأعلى.


يتم تسجيل نتائج حل التبعيات وتصحيحها وحساب أن نفس الخوارزمية سيتم استخدامها عند إنشاء شجرة التبعية لا تعمل في الظروف التي نمنح فيها المستخدمين الفرصة لتكوين آلية لبناء شجرة التبعيات.



يسمح لنا إصلاح هيكل شجرة التبعية النهائية بتزويد المستخدمين بقدرات متشابهة وفي نفس الوقت لا يعطل عملية بناء أشجار التبعية الحتمية والقابلة للتكرار.



الأداء واكتمال البيانات



الملف package-lock.jsonمفيد ليس فقط عندما تحتاج إلى التأكد من الحتمية وإعادة إنتاج أشجار التبعية. بالإضافة إلى ذلك ، نعتمد على هذا الملف لتتبع البيانات الوصفية للحزمة وتخزينها ، مما يوفر الوقت بشكل كبير والذي ، بخلاف ذلك ، package.jsonقد يستغرق العمل مع سجل npm. نظرًا لأن إمكانيات الملف yarn.lockمحدودة للغاية ، فإنه لا يحتوي على بيانات وصفية نحتاج إلى تنزيلها باستمرار.



في npm 7 ، package-lock.jsonيحتوي الملف على كل ما يحتاجه npm لبناء شجرة تبعية المشروع بالكامل. في npm 6 ، لا يتم تخزين هذه البيانات بشكل ملائم ، لذلك عندما نأتي عبر ملف قفل قديم ، يتعين علينا تحميل النظام بعمل إضافي ، ولكن يتم ذلك ، لمشروع واحد ، مرة واحدة فقط.



نتيجة لذلك ، حتى فيyarn.lock وتم تسجيل المعلومات حول هيكل شجرة التبعية ، علينا استخدام ملف آخر لتخزين بيانات تعريف إضافية.



فرص مستقبلية



ما كنا نتحدث عنه هنا يمكن أن يتغير بشكل كبير إذا أخذنا في الاعتبار مختلف الأساليب الجديدة لوضع التبعيات على الأقراص. هذه هي pnpm و yarn 2 / berry و PnP Yarn.



بينما نعمل على npm 8 ، سنستكشف نهج نظام الملفات الظاهري لأشجار التبعية. تم تصميم هذه الفكرة في Tink ، وتم التحقق من صحة المفهوم في عام 2019. بالإضافة إلى ذلك ، نحن نناقش فكرة التحول إلى شيء مثل البنية المستخدمة من قبل pnpm ، على الرغم من أن هذا ، إلى حد ما ، هو تغيير أساسي أكبر من استخدام نظام ملفات افتراضي.



إذا كانت جميع التبعيات في بعض المستودعات المركزية ، ولا يتم تمثيل التبعيات المتداخلة إلا بروابط رمزية أو نظام ملفات افتراضي ، فإن نمذجة بنية شجرة التبعية لن تكون مشكلة مهمة بالنسبة لنا. لكننا ما زلنا بحاجة إلى بيانات وصفية أكثر مما يمكن أن يوفره الملف yarn.lock. ونتيجة لذلك ، من المنطقي تحديث وترشيد تنسيق الملف الحالي package-lock.jsonبدلاً من الانتقال الكامل إليه yarn.lock.



هذه ليست مقالة يمكن تسميتها "على مخاطر الغزل".



أود أن أؤكد أنه ، بناءً على ما أعرفه ، يخلق الغزل بشكل موثوق أشجار تبعية المشروع. وبالنسبة لنسخة معينة من الغزل (في وقت كتابة هذا المقال ، وهذا ينطبق على جميع الإصدارات الجديدة من الغزل) ، فإن هذه الأشجار ، كما هو الحال مع npm ، حتمية تمامًا.



الملف yarn.lockيكفي لإنشاء أشجار تبعية حتمية باستخدام نفس الإصدار من الغزل. ولكن لا يمكننا الاعتماد على الآليات التي تعتمد على تنفيذ مدير الحزم نظرًا لاستخدام آليات مماثلة في العديد من الأدوات. هذا صحيح أكثر عندما تفكر في أن تنفيذ تنسيق الملفyarn.lockلم يتم توثيقه رسميًا في أي مكان. (هذه ليست مشكلة فريدة بالنسبة إلى Yarn ، فقد كان الوضع هو نفسه في npm. يعد توثيق تنسيقات الملفات مهمة خطيرة جدًا.)



أفضل طريقة لضمان موثوقية بناء أشجار التبعية هي أن يتم تحديد نتائج حل التبعيات على المدى الطويل. في الوقت نفسه ، يجب ألا تعتمد على الاعتقاد بأن عمليات التنفيذ المستقبلية لمدير الحزم ، عند حل التبعيات ، ستتبع نفس المسار مثل عمليات التنفيذ السابقة. يحد هذا النهج من قدرتنا على بناء أشجار تبعية محسنة.



يجب أن يكون الانحراف عن البنية الثابتة في البداية لشجرة التبعية نتيجة لرغبة واضحة للمستخدم. يجب أن توثق مثل هذه الانحرافات نفسها عن طريق إجراء تغييرات على البيانات المسجلة سابقًا حول هيكل شجرة التبعية.



فقط package-lock.json، أو آلية مثل هذا الملف قادرة على إعطاء npm مثل هذه القدرات.



ما هو مدير الحزم الذي تستخدمه في مشاريع JavaScript الخاصة بك؟






All Articles