Ashley Williams este unul dintre liderii comunității Node.js. Ea a scris pe Twitter despre un nou manager de pachete.

Nu am înțeles cu adevărat la ce se referea ea, așa că am decis să aprofundez și să citesc despre modul în care funcționează managerii de pachete.

Acest lucru a fost exact atunci când cel mai nou copil din blocul managerului de pachete JavaScript – Fire – tocmai ajunsesem și generau multe zgomote.

Așa că am folosit această ocazie și pentru a înțelege cum și de ce Yarn face lucrurile diferit de npm.

M-am distrat atât de mult cercetând acest lucru. Mi-aș dori să fi făcut asta cu mult timp în urmă. Așa că am scris această introducere simplă la npm și Yarn pentru a împărtăși ceea ce am învățat.

Să începem cu câteva definiții:

Ce este un pachet?

Un pachet este un software reutilizabil care poate fi descărcat dintr-un registru global în mediul local al dezvoltatorului. Fiecare pachet poate depinde sau nu de alte pachete.

Ce este un manager de pachete?

Pur și simplu – un manager de pachete este un software care vă permite să gestionați dependențe (cod extern scris de dvs. sau de altcineva) că proiectul dvs. trebuie să funcționeze corect.

Majoritatea managerilor de pachete jonglează cu următoarele piese ale proiectului dumneavoastră:

Codul proiectului

Acesta este codul proiectului dvs. pentru care trebuie să gestionați diferite dependențe. De obicei, tot acest cod este verificat într-un sistem de control al versiunilor precum Git.

Fișier manifest

Acesta este un fișier care ține evidența tuturor dependențelor dvs. (pachetele care trebuie gestionate). De asemenea, conține alte metadate despre proiectul dvs. În lumea JavaScript, acest fișier este al tău package.json

Cod de dependență

Acest cod constituie dependențele dvs. Nu ar trebui să fie mutat pe durata de viață a aplicației și ar trebui să fie accesibil prin codul de proiect în memorie atunci când este necesar.

Blocare fișier

Acest fișier este scris automat chiar de managerul de pachete. Conține toate informațiile necesare pentru a reproduce arborele sursă de dependență completă. Conține informații despre fiecare dintre dependențele proiectului dvs., împreună cu versiunile lor respective.

Merită subliniat în acest moment că Yarn folosește un fișier de blocare, în timp ce npm nu. Vom vorbi despre consecințele acestei distincții într-un pic.

Acum, că v-am prezentat părțile unui manager de pachete, să discutăm ele însele despre dependențe.

Dependențe plate versus imbricate

Pentru a înțelege diferența dintre schemele de dependență Flat versus Nested, să încercăm să vizualizăm un grafic de dependență al dependențelor din proiectul dvs.

Este important să rețineți că dependențele de care depinde proiectul dvs. ar putea avea dependențe proprii. Și aceste dependențe pot avea la rândul lor unele dependențe în comun.

Pentru a clarifica acest lucru, să presupunem că aplicația noastră depinde de dependențele A, B și C, iar C depinde de A.

Dependențe plate

O introducere a modului in care functioneaza managerii de pachete
Graficul dependenței în cazul dependențelor plate

După cum se arată în imagine, atât aplicația, cât și C au dependența de A. Pentru rezoluția dependenței într-o schemă de dependență plană, există doar un singur strat de dependențe pe care managerul dvs. de pachete trebuie să îl parcurgă.

Poveste lungă scurtă – puteți avea o singură versiune a unui anumit pachet în arborele sursă, deoarece există un spațiu de nume comun pentru toate dependențele dvs.

Să presupunem că pachetul A este actualizat la versiunea 2.0. Dacă aplicația dvs. este compatibilă cu versiunea 2.0, dar pachetul C nu este, atunci avem nevoie de două versiuni ale pachetului A pentru ca aplicația noastră să funcționeze corect. Acest lucru este cunoscut Iadul dependenței.

Dependențe imbricate

1612050187 128 O introducere a modului in care functioneaza managerii de pachete
Grafic de dependență în cazul dependențelor imbricate

O soluție simplă pentru a rezolva problema Dependency Hell este de a avea două versiuni diferite ale pachetului A – versiunea 1.0 și versiunea 2.0.

Aici intră în joc dependențele imbricate. În cazul dependențelor imbricate, fiecare dependență își poate izola propriile dependențe de alte dependențe, într-un spațiu de nume diferit.

Managerul de pachete trebuie să traverseze mai multe niveluri pentru rezolvarea dependenței.

Putem avea mai multe copii ale unei singure dependențe într-o astfel de schemă.

Dar, după cum ați fi putut ghici, acest lucru duce și la câteva probleme. Ce se întâmplă dacă adăugăm un alt pachet – pachetul D – și depinde și de versiunea 1.0 a pachetului A?

Deci, cu această schemă, putem ajunge la duplicare din versiunea 1.0 a pachetului A. Acest lucru poate provoca confuzie și ocupă spațiu inutil pe disc.

O soluție la problema de mai sus este să aveți două versiuni ale pachetului A, v1.0 și v2.0, dar o singură copie a v1.0 pentru a evita duplicarea inutilă. Acesta este abordare adoptată de npm v3, ceea ce reduce considerabil timpul necesar pentru a traversa arborele dependenței.

După cum explică Ashley Williams, npm v2 instalează dependențe într-un mod imbricat. De aceea, npm v3 este considerabil mai rapid în comparație.

Determinism vs Nedeterminism

Un alt concept important în managerii de pachete este cel al determinismului. În contextul ecosistemului JavaScript, determinismul înseamnă că toate computerele cu un anumit package.json fișierul va avea toți același arbore sursă de dependențe instalat pe ele în fișierul node_modules pliant.

Dar cu un manager de pachete nedeterminist, acest lucru nu este garantat. Chiar dacă aveți exact același lucru package.json pe două computere diferite, aspectul computerului dvs. node_modules pot diferi între ele.

Determinismul este de dorit. Te ajută să eviți „A funcționat pe mașina mea, dar s-a rupt când am implementat-o” probleme, care apar atunci când aveți diferite node_modules pe diferite computere.

O introducere a modului in care functioneaza managerii de pachete
Această populară memă de dezvoltator ilustrează problemele cu nedeterminismul.

npm v3, implicit are instalări nedeterministe și oferă o caracteristică shrinkwrap a face instalările deterministe. Aceasta scrie toate pachetele de pe disc într-un fișier de blocare, împreună cu versiunile lor respective.

Yarn oferă instalări deterministe, deoarece folosește un fișier de blocare pentru a bloca recursiv toate dependențele la nivel de aplicație. Deci, dacă pachetul A depinde de v1.0 din pachetul C, iar pachetul B depinde de v2.0 din pachetul A, ambele vor fi scrise separat în fișierul de blocare.

Când cunoașteți versiunile exacte ale dependențelor cu care lucrați, puteți reproduce cu ușurință compilările, apoi urmăriți și izola erorile.

„Ca să fie mai clar, dumneavoastră package.json stări “ceea ce vreau” pentru proiect, în timp ce fișierul dvs. de blocare spune „Ce am avut” în ceea ce privește dependențele. – Dan Abramov

Așa că acum putem reveni la întrebarea inițială care m-a pornit în acest joc de învățare în primul rând: De ce se consideră o bună practică să aveți fișiere de blocare pentru aplicații, dar nu și pentru biblioteci?

Principalul motiv este că implementați aplicații. Deci, trebuie să aveți dependențe deterministe care să ducă la versiuni reproductibile în diferite medii – testare, etapizare și producție.

Dar același lucru nu este valabil și pentru biblioteci. Bibliotecile nu sunt implementate. Sunt folosite pentru a construi alte biblioteci sau în aplicații. Bibliotecile trebuie să fie flexibile, astfel încât să poată maximiza compatibilitatea.

Dacă am avea un fișier de blocare pentru fiecare dependență (bibliotecă) pe care am folosit-o într-o aplicație și aplicația a fost forțată să respecte aceste fișiere de blocare, ar fi imposibil să ajungem oriunde aproape de o structură de dependență plană despre care am vorbit mai devreme, cu versiuni semantice flexibilitate, care este cel mai bun scenariu pentru rezolvarea dependenței.

Iată de ce: dacă aplicația dvs. trebuie să onoreze recursiv fișierele de blocare ale tuturor dependențelor dvs., ar exista conflicte de versiune peste tot – chiar și în proiecte relativ mici. Acest lucru ar cauza o cantitate mare de duplicări inevitabile din cauza versiuni semantice.

Acest lucru nu înseamnă că bibliotecile nu pot avea fișiere de blocare. Cu siguranță pot. Însă principala soluție este că managerii de pachete precum Yarn și npm – care consumă aceste biblioteci – nu vor respecta aceste fișiere de blocare.

Mulțumesc pentru lectură! Dacă credeți că această postare a fost utilă, vă rugăm să atingeți „︎❤” pentru a ajuta la promovarea acestei piese către alții.

O introducere a modului in care functioneaza managerii de pachete