Yarn: Nowy menedżer paczek dla JavaScript.
W społeczności JavaScript programiści dzielą się setkami tysięcy fragmentów kodu, aby uniknąć powtarzania podstawowych komponentów, bibliotek lub frameworków własnego autorstwa. Każdy kawałek kodu może z kolei zależeć od innych kawałków kodu, a te zależności są zarządzane przez menedżerów paczek. Najpopularniejszym menedżerem paczek JavaScript jest klient npm, który umożliwia dostęp do ponad 300 000 paczek w rejestrze npm. Ponad 5 milionów inżynierów korzysta z rejestru npm, który odnotowuje nawet 5 miliardów pobrań miesięcznie.
Yarn – Nowy projekt.
Yarn jest znakomitą inicjatywą, która ma na celu rozwiązanie problemów związanych z jednolitością, bezpieczeństwem i wydajnością zarządzania zależnościami w projektach o dużej skali.
Poprzez korzystanie z Yarn, inżynierowie będą mieli możliwość szybkiego i niezawodnego instalowania pakietów oraz konsekwentnego zarządzania zależnościami na różnych maszynach. Szczególnie istotne jest, że Yarn umożliwia pracę w środowiskach offline, co jest nieocenione w przypadku pracy w izolowanych sieciach lub podczas podróży.
Dzięki wykorzystaniu Yarn, inżynierowie będą mogli korzystać z rejestrów npm, które są niezwykle bogate w biblioteki i rozwiązania dostępne dla społeczności programistycznej. Jednak dzięki lepszej wydajności i bezpieczeństwu Yarn, proces korzystania z tych zależności stanie się bardziej niezawodny, a inżynierowie będą mogli skoncentrować się na tworzeniu nowych produktów i funkcji.
Współpraca z takimi partnerami jak Exponent, Google i Tilde jest znaczącym osiągnięciem, ponieważ dodaje wiarygodności i wsparcia dla projektu Yarn. Jako otwartoźródłowy projekt, Yarn ma również potencjał do dalszego rozwoju i współpracy z programistami z całego świata.
Wszystko to czyni Yarn obiecującym narzędziem dla zespołów programistycznych, które dążą do usprawnienia procesu zarządzania zależnościami i poprawy efektywności pracy. Jesteśmy podekscytowani, że Facebook podzielił się tym nowym narzędziem z szeroką społecznością, i cieszymy się na przyszłe możliwości, jakie przyniesie rozwój Yarn.
Ewolucja JavaScript.
Przed erą menedżerów pakietów, powszechną praktyką dla programistów JavaScript było poleganie na niewielkiej liczbie zależności przechowywanych bezpośrednio w ich projektach lub dostarczanych przez sieć dostarczania treści. Pierwszy znaczący menedżer pakietów dla języka JavaScript, npm, został opracowany niedługo po wprowadzeniu Node.js i szybko zdobył miano jednego z najpopularniejszych menedżerów pakietów na świecie. Powstało mnóstwo nowych projektów open source, a programiści dzielili się większą ilością kodu niż kiedykolwiek wcześniej.
Wiele naszych projektów w Facebooku, takich jak React, opiera się na kodzie przechowywanym w rejestrze npm. Jednak w miarę rozwoju naszej wewnętrznej infrastruktury, napotykamy trudności związane z spójnością instalacji zależności na różnych maszynach i u różnych użytkowników, z czasem potrzebnym na pobranie tych zależności oraz z obawami dotyczącymi bezpieczeństwa związanych z tym, w jaki sposób klient npm automatycznie wykonuje kod z niektórych zależności. Próbowaliśmy tworzyć rozwiązania wokół tych problemów, ale często rodziły one nowe zagadnienia.
Package.json
Na wstępie, zgodnie z zalecanymi standardami, jedynie przekazywaliśmy package.json i prosiliśmy programistów o ręczne wykonanie polecenia npm install. Ta metoda sprawdzała się wystarczająco dobrze dla programistów, ale nie spełniała wymagań naszych środowisk ciągłej integracji, które muszą być izolowane i odcięte od internetu ze względów bezpieczeństwa i niezawodności.
Kolejnym rozwiązaniem, które wdrożyliśmy, było dodanie całego katalogu node_modules do repozytorium. Choć ta metoda działała, utrudniała pewne proste operacje. Na przykład, zaktualizowanie mniejszej wersji Babel generowało zatrzęsienie linii w komitycie o długości 800 000, co było trudne do wdrożenia i wywoływało naruszenia reguł linta dotyczących nieprawidłowych sekwencji bajtów utf8, końcówek linii w systemie Windows, obrazów bez kompresji PNG i innych. Scalanie zmian w katalogu node_modules często zajmowało programistom cały dzień. Nasz zespół kontroli wersji zauważył również, że dodany do repozytorium folder node_modules generuje olbrzymią ilość metadanych. Obecnie w pliku package.json w React Native znajduje się tylko 68 zależności, ale po wykonaniu polecenia npm install katalog node_modules zawiera aż 121 358 plików.
Podjęliśmy ostateczną próbę skalowania klienta npm, aby obsłużyć liczbę inżynierów w Facebooku i ilość kodu, której musimy zainstalować. Zdecydowaliśmy się skompresować cały katalog node_modules i przesłać go do wewnętrznego CDN, tak aby zarówno inżynierowie, jak i nasze systemy ciągłej integracji mogły jednolicie pobierać i rozpakowywać pliki. Dzięki temu udało nam się usunąć setki tysięcy plików z systemu kontroli wersji, jednak spowodowało to konieczność, aby inżynierzy mieli dostęp do Internetu nie tylko w celu pobrania nowego kodu, ale również do jego kompilacji.
Musimy również rozwiązać problemy z funkcją shrinkwrap w npm, którą wykorzystujemy do zamrożenia wersji zależności. Pliki shrinkwrap nie są generowane domyślnie i mogą się nie zgadzać, jeśli inżynierzy zapomną je wygenerować. Dlatego napisaliśmy narzędzie do weryfikacji, czy zawartość pliku shrinkwrap odpowiada temu, co znajduje się w katalogu node_modules. Są to ogromne bloki JSON z niesortowanymi kluczami, dlatego zmiany w nich generują ogromne i trudne do przejrzenia komity. Aby temu zaradzić, musieliśmy dodać dodatkowy skrypt do posortowania wszystkich wpisów.
W końcu, aktualizacja pojedynczej zależności za pomocą npm powoduje również aktualizację wielu niezwiązanych z nią zależności na podstawie reguł semantycznego wersjonowania. Powoduje to, że każda zmiana staje się znacznie większa niż przewidywano, a konieczność wykonania czynności takich jak dodawanie do repozytorium node_modules lub przesyłanie go na CDN sprawia, że proces ten jest mniej niż optymalny dla inżynierów.
Budowanie bazy nowych klientów.
Zamiast kontynuować budowanie infrastruktury wokół klienta npm, postanowiliśmy podejść do problemu w sposób bardziej holistyczny. A co jeśli zamiast tego spróbowalibyśmy stworzyć nowy klient, który rozwiązałby główne problemy, z którymi się borykaliśmy? Sebastian McKenzie z naszego biura w Londynie rozpoczął pracę nad tym pomysłem, i szybko zaczęliśmy być podekscytowani jego potencjałem.
W miarę jak pracowaliśmy nad tym, zaczęliśmy rozmawiać z inżynierami z różnych firm i odkryliśmy, że również oni borykają się z podobnymi problemami i próbowali wielu tych samych rozwiązań, często skupiając się na rozwiązaniu jednego problemu naraz. Stało się jasne, że poprzez współpracę nad kompletnym zestawem problemów, z którymi zmaga się społeczność, możemy opracować rozwiązanie, które będzie działało dla wszystkich. We współpracy z inżynierami z Exponent, Google i Tilde, stworzyliśmy klienta Yarn, przetestowaliśmy i zweryfikowaliśmy jego wydajność na każdym ważnym frameworku JS i dla dodatkowych przypadków użycia poza Facebookiem. Dziś z radością dzielimy się nim z społecznością.
Prezentuje Yarn.
Yarn to nowy menedżer pakietów, który zastępuje istniejący workflow dla klienta npm lub innych menedżerów pakietów, jednocześnie pozostając kompatybilnym z rejestrem npm. Posiada taki sam zestaw funkcji jak istniejące workflow, ale działa szybciej, bardziej bezpiecznie i bardziej niezawodnie.
Głównym zadaniem każdego menedżera pakietów jest instalacja pewnego pakietu – fragmentu kodu, który pełni określoną funkcję – z globalnego rejestru do lokalnego środowiska programisty. Każdy pakiet może lub nie może zależeć od innych pakietów. Typowy projekt może zawierać dziesiątki, setki lub nawet tysiące pakietów w swoim drzewie zależności.
Te zależności są wersjonowane i instalowane na podstawie semantycznego wersjonowania (semver). Semver definiuje schemat wersjonowania, który odzwierciedla rodzaj zmian w każdej nowej wersji, czy zmiana powoduje złamanie interfejsu API, dodaje nową funkcję czy naprawia błąd. Jednak semver opiera się na założeniu, że programiści pakietów nie popełniają błędów – zmiany, które powodują złamanie kompatybilności lub wprowadzają nowe błędy, mogą znaleźć się w zainstalowanych zależnościach, jeśli zależności nie są zablokowane.
Budowa Yarn.
W ekosystemie Node, zależności są umieszczane w katalogu node_modules w ramach twojego projektu. Jednak struktura tego systemu plików może różnić się od rzeczywistego drzewa zależności, ponieważ duplikowane zależności są scalane. Klient npm instaluje zależności do katalogu node_modules w sposób niedeterministyczny. Oznacza to, że w zależności od kolejności instalacji, struktura katalogu node_modules może być inna dla różnych osób. Te różnice mogą powodować błędy typu „u mnie działa”, które wymagają długiego czasu na odnalezienie przyczyny.
Yarn rozwiązuje te problemy związane z wersjonowaniem i niedeterminizmem poprzez użycie plików blokad oraz algorytmu instalacji, który jest deterministyczny i niezawodny. Pliki blokad blokują zainstalowane zależności na konkretną wersję i zapewniają, że każda instalacja prowadzi do dokładnie tej samej struktury plików w katalogu node_modules na wszystkich maszynach. Zapisany plik blokady używa zwięzłego formatu z uporządkowanymi kluczami, aby zmiany były minimalne, a proces ich przeglądu był prosty.
Proces instalacji jest podzielony na trzy etapy:
- Rozwiązanie: Yarn rozpoczyna proces rozwiązywania zależności poprzez wysłanie żądań do rejestru i rekurencyjne wyszukiwanie każdej zależności.
- Pobieranie: Następnie Yarn sprawdza globalny katalog cache, aby sprawdzić, czy wymagany pakiet został już pobrany. Jeśli nie, Yarn pobiera paczkę tar dla danego pakietu i umieszcza ją w globalnym cache, aby móc działać w trybie offline i uniknąć wielokrotnego pobierania zależności. Zależności mogą również być przechowywane w kontrolie wersji jako paczki tar dla pełnych instalacji offline.
- Linkowanie: Na koniec Yarn łączy wszystko poprzez skopiowanie wszystkich niezbędnych plików z globalnego cache do lokalnego katalogu node_modules.
Poprzez rozbiór tych kroków na czynniki składowe i osiągnięcie deterministycznych rezultatów, Yarn jest w stanie równolegle wykonywać operacje, co maksymalizuje wykorzystanie zasobów i przyspiesza proces instalacji. W niektórych projektach na Facebooku, Yarn zmniejszył czas instalacji o rząd wielkości, z kilku minut do zaledwie kilku sekund. Yarn wykorzystuje również sekcję krytyczną (mutex), aby zapewnić, że wiele równocześnie uruchamianych instancji interfejsu wiersza poleceń nie koliduje ze sobą i nie zanieczyszcza środowiska.
Podczas całego tego procesu, Yarn narzuca ściśle określone gwarancje dotyczące instalacji pakietów. Masz kontrolę nad tym, które skrypty cyklu życia są wykonywane dla poszczególnych pakietów. W pliku blokady przechowywane są również sumy kontrolne pakietów, aby zapewnić, że otrzymasz dokładnie ten sam pakiet za każdym razem.
Cechy Yarn.
Poza znacznym przyspieszeniem i większą niezawodnością procesu instalacji, Yarn posiada dodatkowe funkcje, które dalej ułatwiają pracę z zarządzaniem zależnościami.
- Kompatybilność zarówno z przepływami pracy npm, jak i bower, oraz obsługa mieszania rejestrów.
- Możliwość ograniczenia licencji zainstalowanych modułów oraz sposób wyprowadzenia informacji o licencjach.
- Ujawnienie stabilnego publicznego API JS z abstrakcją rejestrowania działań, które można wykorzystać za pomocą narzędzi do kompilacji.
- Czytelny, minimalistyczny, i estetyczny wydruk w interfejsie wiersza poleceń.
W Facebooku już stosujemy Yarn w środowisku produkcyjnym i sprawdza się doskonale. Jest on wykorzystywany do zarządzania zależnościami i pakietami w wielu naszych projektach JavaScript. Dzięki każdej migracji umożliwiamy inżynierom korzystanie z trybu offline i przyspieszamy ich pracę.