Fire: Un nou manager de pachete pentru JavaScript – Facebook Engineering

În comunitatea JavaScript, inginerii împărtășesc sute de mii de bucăți de cod pentru a evita rescrierea unor componente de bază, biblioteci sau framework-uri proprii. Fiecare bucată de cod poate, la rândul său, să depindă de alte bucăți de cod, iar aceste dependențe sunt gestionate de managerii de pachete. Cel mai popular manager de pachete JavaScript este clientul npm, care oferă acces la mai mult de 300.000 de pachete din registrul npm. Peste 5 milioane de ingineri folosesc registrul npm, care înregistrează până la 5 miliarde de descărcări în fiecare lună.

Am folosit cu succes clientul npm la Facebook timp de ani de zile, dar, pe măsură ce dimensiunea bazei noastre de cod și numărul de ingineri a crescut, ne-am confruntat cu probleme de coerență, securitate și performanță. După ce am încercat să rezolvăm fiecare problemă pe măsură ce apărea, ne-am propus să construim o nouă soluție care să ne ajute să ne gestionăm dependențele într-un mod mai fiabil. Produsul acestei munci se numește Yarn – un client npm alternativ, rapid, fiabil și sigur.

Suntem încântați să anunțăm lansarea open source a Yarn, o colaborare cu Exponent, Google și Tilde. Cu Yarn, inginerii au în continuare acces la registrul npm, dar pot instala pachete mai rapid și pot gestiona dependențele în mod consecvent pe mai multe mașini sau în medii offline sigure. Yarn le permite inginerilor să se miște mai repede și cu încredere atunci când folosesc cod partajat, astfel încât să se poată concentra pe ceea ce contează – construirea de noi produse și caracteristici.

Evoluția gestionării pachetelor JavaScript la Facebook

În zilele dinaintea managerilor de pachete, era ceva obișnuit ca inginerii JavaScript să se bazeze pe un număr mic de dependențe stocate direct în proiectele lor sau servite de un CDN. Primul manager de pachete JavaScript important, npm, a fost construit la scurt timp după ce Node.js a fost introdus și a devenit rapid unul dintre cei mai populari manageri de pachete din lume. Mii de noi proiecte open source au fost create, iar inginerii au partajat mai mult cod ca niciodată.

Multe dintre proiectele noastre de la Facebook, cum ar fi React, depind de codul din registrul npm. Cu toate acestea, pe măsură ce ne-am extins la nivel intern, ne-am confruntat cu probleme legate de consecvența la instalarea dependențelor pe diferite mașini și utilizatori, cu timpul necesar pentru a atrage dependențele și am avut unele probleme de securitate legate de modul în care clientul npm executa automat codul din unele dintre aceste dependențe. Am încercat să construim soluții în jurul acestor probleme, dar de multe ori ele însele au ridicat noi probleme.

Tentative de scalare a clientului npm

Inițial, urmând cele mai bune practici prescrise, am verificat doar package.json și am cerut inginerilor să execute manual npm install. Acest lucru a funcționat destul de bine pentru ingineri, dar s-a stricat în mediile noastre de integrare continuă, care trebuie să fie sandboxate și izolate de internet din motive de securitate și fiabilitate.

Următoarea soluție pe care am implementat-o a fost să verificăm toate node_modules în depozit. Deși acest lucru a funcționat, a făcut ca unele operațiuni simple să fie destul de dificile. De exemplu, actualizarea unei versiuni minore de babel a generat un commit de 800.000 de linii care a fost dificil de aterizat și a declanșat reguli de lint pentru secvențe de octeți utf8 nevalabile, terminații de linie Windows, imagini care nu erau comprimate în format png și altele. Fuzionarea modificărilor la node_modules le lua adesea inginerilor o zi întreagă. Echipa noastră de control al sursei a subliniat, de asemenea, că dosarul nostru verificat node_modules era responsabil pentru o cantitate imensă de metadate. React Native package.json listează în prezent doar 68 de dependențe, dar după rularea npm install directorul node_modules conține 121.358 de fișiere.

Am făcut o ultimă încercare de a scala clientul npm pentru a lucra cu numărul de ingineri de la Facebook și cu cantitatea de cod pe care trebuie să o instalăm. Am decis să comprimăm întregul director node_modules și să îl încărcăm pe un CDN intern, astfel încât atât inginerii, cât și sistemele noastre de integrare continuă să poată descărca și extrage fișierele în mod constant. Acest lucru ne-a permis să eliminăm sute de mii de fișiere din controlul sursei, dar a făcut ca inginerii să aibă nevoie de acces la internet nu doar pentru a extrage noul cod, ci și pentru a-l construi.

De asemenea, a trebuit să rezolvăm probleme cu caracteristica shrinkwrap de la npm, pe care am folosit-o pentru a bloca versiunile de dependență. Fișierele shrinkwrap nu sunt generate în mod implicit și se vor desincroniza dacă inginerii uită să le genereze, așa că am scris un instrument pentru a verifica dacă conținutul fișierului shrinkwrap se potrivește cu ceea ce este în node_modules. Totuși, aceste fișiere sunt blocuri JSON uriașe cu chei nesortate, astfel încât modificările aduse acestora ar genera comisioane masive și greu de revizuit. Pentru a atenua acest lucru, a trebuit să adăugăm un script suplimentar pentru a sorta toate intrările.

În cele din urmă, actualizarea unei singure dependențe cu npm actualizează, de asemenea, multe altele care nu au legătură între ele, pe baza regulilor semantice de versionare. Acest lucru face ca fiecare modificare să fie mult mai mare decât s-a anticipat, iar faptul că trebuie să facem lucruri precum comiterea node_modules sau încărcarea pe un CDN a făcut ca procesul să fie mai puțin ideal pentru ingineri.

Constituirea unui nou client

În loc să continuăm să construim infrastructura în jurul clientului npm, am decis să încercăm să privim problema într-un mod mai holistic. Ce-ar fi dacă, în schimb, am încerca să construim un nou client care să abordeze problemele de bază cu care ne confruntăm? Sebastian McKenzie din biroul nostru din Londra a început să lucreze la această idee și am devenit rapid entuziasmați de potențialul ei.

În timp ce lucram la acest lucru, am început să vorbim cu ingineri din întreaga industrie și am descoperit că se confruntau cu un set similar de probleme și că încercaseră multe dintre aceleași soluții, adesea concentrate pe rezolvarea unei singure probleme la un moment dat. A devenit evident că, prin colaborarea asupra întregului set de probleme cu care se confrunta comunitatea, am putea dezvolta o soluție care să funcționeze pentru toată lumea. Cu ajutorul inginerilor de la Exponent, Google și Tilde, am construit clientul Yarn și am testat și validat performanța acestuia pe fiecare cadru JS major și pentru cazuri de utilizare suplimentare în afara Facebook. Astăzi, suntem încântați să îl împărtășim cu comunitatea.

Introducerea lui Yarn

Yarn este un nou manager de pachete care înlocuiește fluxul de lucru existent pentru clientul npm sau alți manageri de pachete, rămânând în același timp compatibil cu registrul npm. Acesta are același set de caracteristici ca și fluxurile de lucru existente, în timp ce funcționează mai rapid, mai sigur și mai fiabil.

Funcția principală a oricărui manager de pachete este de a instala un anumit pachet – o bucată de cod care servește unui anumit scop – dintr-un registru global în mediul local al unui inginer. Fiecare pachet poate să depindă sau nu de alte pachete. Un proiect tipic ar putea avea zeci, sute sau chiar mii de pachete în arborele său de dependențe.

Aceste dependențe sunt versionate și instalate pe baza versiunii semantice (semver). Semver definește o schemă de versionare care reflectă tipurile de modificări în fiecare nouă versiune, indiferent dacă o modificare întrerupe o API, adaugă o nouă caracteristică sau corectează o eroare. Cu toate acestea, semver se bazează pe faptul că dezvoltatorii de pachete nu fac greșeli – modificările de întrerupere sau bug-urile noi pot ajunge în dependențele instalate dacă dependențele nu sunt blocate.

Arhitectura

În ecosistemul Node, dependențele sunt plasate într-un director node_modules din proiectul dumneavoastră. Cu toate acestea, această structură de fișiere poate fi diferită de arborele real de dependențe, deoarece dependențele duplicate sunt îmbinate împreună. Clientul npm instalează dependențele în directorul node_modules în mod nedeterminat. Acest lucru înseamnă că, pe baza ordinii în care sunt instalate dependențele, structura unui director node_modules ar putea fi diferită de la o persoană la alta. Aceste diferențe pot cauza bug-uri „funcționează pe mașina mea” care necesită mult timp pentru a fi vânate.

Yarn rezolvă aceste probleme legate de versionare și nedeterminism prin utilizarea fișierelor de blocare și a unui algoritm de instalare care este determinist și fiabil. Aceste fișiere de blocare blochează dependențele instalate la o anumită versiune și se asigură că fiecare instalare are ca rezultat exact aceeași structură de fișiere în node_modules pe toate mașinile. Fișierul de blocare scris folosește un format concis cu chei ordonate pentru a se asigura că modificările sunt minime și că revizuirea este simplă.

Procesul de instalare este împărțit în trei etape:

  1. Rezoluție: Yarn începe să rezolve dependențele făcând cereri la registru și căutând recursiv fiecare dependență.
  2. Fetching: Apoi, Yarn caută într-un director global de cache pentru a vedea dacă pachetul necesar a fost deja descărcat. Dacă nu a fost deja descărcat, Yarn extrage tarball-ul pentru pachetul respectiv și îl plasează în memoria cache globală, astfel încât să poată lucra offline și să nu fie nevoie să descarce dependențele de mai multe ori. Dependențele pot fi, de asemenea, plasate în controlul sursei sub formă de tarball-uri pentru instalări complete offline.
  3. Linking: În cele din urmă, Yarn leagă totul împreună prin copierea tuturor fișierelor necesare din memoria cache globală în directorul local node_modules.

Prin împărțirea curată a acestor pași și având rezultate deterministe, Yarn este capabil să paralelizeze operațiunile, ceea ce maximizează utilizarea resurselor și face ca procesul de instalare să fie mai rapid. Pe unele proiecte Facebook, Yarn a redus procesul de instalare cu un ordin de mărime, de la câteva minute la doar câteva secunde. Yarn utilizează, de asemenea, un mutex pentru a se asigura că mai multe instanțe CLI care rulează nu se ciocnesc și nu se poluează reciproc.

În tot acest proces, Yarn impune garanții stricte în jurul instalării pachetelor. Aveți controlul asupra scripturilor de ciclu de viață care sunt executate pentru ce pachete. Sumele de verificare ale pachetelor sunt, de asemenea, stocate în fișierul de blocare pentru a vă asigura că obțineți același pachet de fiecare dată.

Caracteristici

Pe lângă faptul că face ca instalările să fie mult mai rapide și mai fiabile, Yarn are caracteristici suplimentare pentru a simplifica și mai mult fluxul de gestionare a dependențelor.

  • Compatibilitate cu ambele fluxuri de lucru npm și bower și suportă amestecarea registrelor.
  • Capacitatea de a restricționa licențele modulelor instalate și un mijloc de ieșire a informațiilor privind licențele.
  • Expune o API JS publică stabilă cu logare abstractizată pentru consumul prin intermediul instrumentelor de construire.
  • Legizibilă, minimală, cu ieșire CLI frumoasă.

Yarn în producție

La Facebook folosim deja Yarn în producție și a funcționat foarte bine pentru noi. Acesta alimentează gestionarea dependențelor și a pachetelor pentru multe dintre proiectele noastre JavaScript. Cu fiecare migrare am permis inginerilor să construiască offline și am contribuit la accelerarea fluxului lor de lucru. You can see how install times for Yarn and npm compare on React Native under different conditions, which you can find here.

Getting started

The easiest way to get started is to run:

npm install -g yarn

yarn

The yarn CLI replaces npm in your development workflow, either with a matching command or a new, similar command:

  • npm installyarn

    With no arguments, the yarn command will read your package.json, fetch packages from the npm registry, and populate your node_modules folder. It is equivalent to running npm install.

  • npm install --save <name>yarn add <name>

    We removed the „invisible dependency” behavior of npm install <name> and split the command. Running yarn add <name> is equivalent to running npm install --save <name>.

Future

Many of us came together to build Yarn to solve common problems, and we knew that we wanted Yarn to be a true community project that everyone can use. Yarn is now available on GitHub and we’re ready for the Node community to do what it does best: Use Yarn, share ideas, write documentation, support each other, and help build a great community to care for it. We believe that Yarn is already off to a great start, and it can be even better with your help.