Strona główna / Artykuły / npm kontra pnpm: porównanie przechowywania, szybkości i rzeczywistych kompromisów

npm kontra pnpm: porównanie przechowywania, szybkości i rzeczywistych kompromisów

Ten artykuł porównuje, w jaki sposób npm i pnpm radzą sobie z przechowywaniem zależności, szybkością instalacji oraz procesami pracy w monorepo, aby pomóc Ci wybrać odpowiedni narzędzie do Twojego projektu.

2380 słów

Jeśli spędziłeś czas tworząc aplikacje przy użyciu React, Next.js, Node.js lub w ogóle jakiejkolwiek nowoczesnej bazy kodu JavaScript, istnieje duże prawdopodobieństwo, że wpisałeś ten polecenie więcej razy, niż możesz zliczyć:

npm install

Po prostu działa. Wszyscy go znają. Prawie każdy tutorial online odwołuje się do niego.

A potem ktoś w końcu mówi ci coś w stylu:

"Dlaczego nadal używasz npm? Po prostu użyj pnpm."

Zatem próbujesz pnpm.

I wkrótce zaczynasz się zastanawiać:

Czy pnpm rzeczywiście stanowi ulepszenie, czy to po prostu kolejna z tych debat na temat narzędzi JavaScript, które szybko znikają?

To słuszne pytanie.

Npm w żaden sposób nie jest uszkodzony. Jest znajomy, niezawodny i dostarczany automatycznie z Node.js. Przejście na inne narzędzie wymaga solidnego uzasadnienia.

Gdy przyjrzysz się bliżej temu, jak funkcjonuje każde z tych narzędzi, zdajesz sobie sprawę, że prawdziwa kwestia to nie tylko npm versus pnpm.

Chodzi o to, jak one zarządzają zależnościami, ile miejsca na dysku zajmują, jak zachowują się podczas instalacji w różnych warunkach oraz jaki typ projektu budujesz.

A najważniejsze pytanie brzmi:

Które z nich ma sens używać w twoim przypadku?

npm i pnpm rozwiązują ten sam podstawowy problem

Zacznijmy od czegoś oczywistego.

Zarówno npm, jak i pnpm to menedżery pakietów stworzone dla świata Node.js.

Oba pobierają pakiety z rejestru npm i opierają się na tych samych konwencjach pliku package.json.

Załóżmy, że twój projekt wymaga React. Możesz go zainstalować za pomocą npm:

npm install react

Albo możesz zamiast tego użyć pnpm:

pnpm add react

W obu przypadkach ostatecznie masz zainstalowany React.

Nie przeszliście na zupełnie inny ekosystem JavaScript.

To, co faktycznie się zmienia, to to, co dzieje się wewnątrz po zainstalowaniu pakietów i przechowywaniu ich na waszym komputerze.

To właśnie w tym aspekcie projekt pnpm zaczyna się wyróżniać.

Gdzie naprawdę się różnią: jak przechowywane są zależności

Wyobraźcie sobie pięć oddzielnych projektów JavaScript na waszym komputerze.

Każdy z nich polega na React.

Przy użyciu klasycznej konfiguracji npm każdy projekt ma swój własny, niezależny folder node_modules, w którym przechowywane są potrzebne mu pakiety.

Gdy takie rozwiązanie zastosuje się do wielu projektów, powstaje mnóstwo duplikowanych plików zajmujących miejsce.

pnpm obiera inną drogę.

Przechowuje pakiety w jednym, adresowalnym magazynie, a następnie tworzy odnośniki z tego magazynu do każdego projektu, który ich potrzebuje.

Mówiąc prościej, zamiast co raz powtarzać ten sam pakiet dla każdego projektu, pnpm może ponownie wykorzystać kopię, która już znajduje się w jego centralnym magazynie.

Pomyśl o tym w ten sposób:

Project A ──┐
Project B ──┤
Project C ──┼── Shared package store
Project D ──┤
Project E ──┘

Rzeczywisty mechanizm stojący za tym jest bardziej złożony, niż sugeruje to proste diagram, ale kluczowa jest tu podstawowa koncepcja.

pnpm został stworzony po to, by zminimalizować zbędne kopie.

A jeśli jednocześnie zajmujesz się kilkoma projektami, taka decyzja projektowa może znacząco zmniejszyć zużycie dysku.

Czy pnpm rzeczywiście instaluje rzeczy szybciej?

To zazwyczaj pierwsze, co chcą wiedzieć programiści.

Szczerza odpowiedź:

Często tak — ale nie zawsze.

Wiele testów wydajności pokazuje, że pnpm znacznie przewyższa npm pod względem szybkości.

Niedogodnością jest to, że szybkość instalacji zależy od wielu czynników.

Twoje połączenie internetowe również ma znaczenie.

Również sprzęt dysku odgrywa kluczową rolę.

Ilość pakietów, od których zależy twój projekt, też ma znaczenie.

To, czy te pakiety są już zlokalizowane w pamięci tymczasowej, ma wpływ na wyniki.

Rozmiar projektu również jest istotny.

Nawet wykonanie nowej instalacji w porównaniu z ponowną instalacją czegoś, co już wcześniej pobrano, może dać zupełnie różne rezultaty.

Architektura pnpm opiera się na efektywnym pobieraniu i łączeniu pakietów, a dzięki utrzymywaniu wspólnej bazy danych, pakiety, które już masz lokalnie, można po prostu ponownie wykorzystać zamiast je ponownie pobierać.

To właśnie w takich sytuacjach jego zalety najwyraźniej się ujawniają.

Instalacje „zimne” w porównaniu z instalacjami „ciepłymi” faktycznie robią różnicę

To szczegół, który często jest pomijany podczas porównywania menedżerów pakietów.

Załóżmy, że instalujesz pakiet po raz pierwszy.

Nie masz innego wyjścia, jak tylko pobrać to od zera.

To w istocie scenariusz rozruchu od zera.

A teraz wyobraź sobie, że dokładnie ten sam pakiet już znajduje się w innym projekcie na twoim komputerze.

To zupełnie inny punkt wyjścia.

Dokładnie w tym miejscu przydaje się wspólny magazyn pnpm.

Zamiast traktować każdy projekt jako całkowicie izolowany silo, pnpm może korzystać z pakietów już przechowywanych lokalnie.

Dlatego jeśli regularnie tworzysz nowe projekty, usuwasz foldery node_modules, często reinstalujesz zależności lub przechodzisz między różnymi repozytoriami, efektywność pnpm staje się z czasem znacznie bardziej widoczna.

Ale jeśli twoja praca polega na jednym niewielkim projekcie, w którym zależności instalujesz tylko raz, prawdopodobnie nie zauważysz takiej różnicy.

Dlatego unikałbym twierdzeń typu:

"pnpm zawsze jest szybszą opcją."

Dokładniejszy sformułowanie brzmiałoby:

pnpm ma tendencję do bycia znacznie bardziej wydajnym, szczególnie w procesach pracy opartych na wielokrotnych instalacjach lub złożonych drzewach zależności.

npm rozwinął się daleko poza swoją dawną reputację

Jeszcze jeden punkt wart wspomnienia.

Wiele dyskusji na temat npm kontra pnpm przedstawia npm jako jakieś przestarzałe narzędzie, od którego programiści powinni dawno już odejść.

Taka charakterystyka nie jest do końca dokładna.

npm znacznie się rozwinął.

Obecna wersja obsługuje takie funkcjonalności jak przestrzenie robocze, pliki lockfile oraz npm ci służące do uzyskiwania powtarzalnych instalacji w pipeline’ach CI.

Jako przykład:

npm ci

jest regularnie używany, gdy chce się czystej instalacji kierowanej przez plik lockfile.

Nazywanie npm powolnym, przestarzałym lub źle zaprojektowanym nie ma uzasadnienia.

Pośród ogromnej liczby projektów nadal stanowi doskonały wybór.

To, co wyróżnia pnpm, to specjalne decyzje projektowe skupione na efektywności, które stają się coraz bardziej widoczne w miarę rozwoju projektu.

Kolejna różnica, z którą borykają się programiści: izolacja zależności

To nie jest od razu oczywiste, szczególnie jeśli jesteś nowy w tym ekosystemie.

Pomyśl o takiej sytuacji: twoja aplikacja polega na pakiecie A. Pakiet A z kolei polega na pakiecie B. Sam nigdy nie instalowałeś B – nawet go nie wymieniłeś w swoim manifestie. Mimo to, ze względu na sposób organizacji plików node_modules, twój kod może nadal bez problemu używać funkcji require lub import do dostępu do B, i to będzie działać.

Przez jakiś czas nic nie wydaje się nie tak.

Następnie pakiet A aktualizuje się i zastępuje swoje zależności. Pakiet B nie znajduje się już w miejscu, którego oczekuje twój kod, i nagle aplikacja wyświetla błąd, którego w ogóle się nie spodziewałeś.

Taka sytuacja ma nazwę: zależność widmowa. Polegasz na czymś, na co w rzeczywistości nie miałeś prawa polegać.

pnpm unika tego dzięki swojej specyfice. Jego domyślna struktura jest znacznie bardziej restrykcyjna jeśli chodzi o to, co pakiet może faktycznie widzieć, co znacznie utrudnia projektowi korzystanie z czegoś, co nigdy nie zostało wyraźnie deklarowane jako zależność.

To jest naprawdę cenna gwarancja. Zmusza cię do szczerości co do tego, czego faktycznie potrzebuje twoja aplikacja, zamiast czerpać korzyści z przypadkowego układu plików. Taka poprawność ma tendencję do bycia ważniejsza w dłuższej perspektywie niż skrócenie czasu instalacji o kilka sekund.

Sytuacja, w której pnpm naprawdę się wyróżnia: monorepo

To prawdopodobnie najsilniejszy argument za użyciem pnpm.

Załóżmy, że baza kodu firmy jest zorganizowana w ten sposób:

my-project/
│
├── apps/
│   ├── web/
│   └── admin/
│
├── packages/
│   ├── ui/
│   ├── utils/
│   └── config/
│
└── package.json

Istnieje kilka aplikacji oraz kilka wspólnych pakietów wewnętrznych, wszystkie znajdujące się razem w jednym repozytorium. Taka konfiguracja nazywana jest monorepo.

Npm rzeczywiście obsługuje przestrzenie robocze, więc stworzenie monorepo za pomocą npm jest w pełni możliwe. Jednak pnpm włożył znacznie więcej wysiłku specjalnie w narzędzia do zarządzania monorepo. Oferuje takie funkcje jak protokół przestrzeni roboczej, flagi filtrowania do wykonywania poleceń w konkretnych pakietach oraz model zależności opracowany z myślą o repozytoriach wielopakietowych — wszystko to może znacznie ułatwić pracę z dużą bazą kodu.

Jeśli zarządzasz małym projektem osobistym, to wszystko nie ma dla ciebie większego znaczenia.

Ale jeśli zarządzasz repozytorium z wieloma aplikacjami oraz dziesiątkami wewnętrznych pakietów udostępnianych między użytkownikami, te narzędzia stają się znacznie bardziej przydatne.

Przejście z npm na pnpm to niewielki krok

Częstym powodem, dla którego programiści unikają prób używania pnpm, jest przekonanie, że będą musieli nauczyć się zupełnie nowego zestawu poleceń.

W rzeczywistości tak nie jest. Większość codziennych poleceń odpowiada sobie niemal w sposób jednoznaczny.

Instalowanie zależności za pomocą npm wygląda tak:

npm install

Za pomocą pnpm jest to:

pnpm install

Dodawanie pakietu w npm:

npm install axios

przekształca się w pnpm w ten sposób:

pnpm add axios

Dodawanie zależności rozwojowej w npm:

npm install -D typescript

staje się:

pnpm add -D typescript

Usuwanie pakietu w npm:

npm uninstall axios

staje się:

pnpm remove axios

Uruchamianie skryptu w npm:

npm run dev

może zostać skrócone do:

pnpm dev

A jeśli już dobrze znasz npx, pnpm ma swój własny odpowiednik:

pnpm dlx

Zatem krzywa uczenia się w tym przypadku jest minimalna.

Sytuacje, gdy npm nadal jest najlepszym wyborem

Jeśli po raz pierwszy przedstawiasz komuś JavaScript lub Node.js, npm jest naturalnym wyborem do rozpoczęcia. Nie dlatego, że jest technicznie lepszy we wszystkim — po prostu jest domyślnie dostępny, a początkujący mają już mnóstwo rzeczy do przyswojenia, bez konieczności dodawania decyzji dotyczącej menedżera pakietów.

Jeśli tutorial każe ci uruchomić:

npm install express

powinieneś móc po prostu to wpisać i kontynuować naukę omawianego koncepcji.

Npm jest również właściwym wyborem, gdy przechodzisz do bazy kodu już zbudowanej wokół niego. Nie ma sensu nalegać na coś innego:

„Zespół zawsze używał npm, ale tutaj preferowany jest pnpm, więc przekonwertujmy całe ustawienie.”

W środowisku zespołowym lepiej jest zachować spójność z tym, co używają wszyscy inni, niż optymalizować pod kątem indywidualnych preferencji.

Sytuacje, w których pnpm staje się bardziej sensowny

pnpm staje się atrakcyjniejszy w miarę wzrostu skali projektu lub procesu pracy.

Jeśli regularnie zarządzasz kilkoma projektami JavaScript jednocześnie, wspólny magazyn adresowy pnpm może zmniejszyć zbędne zużycie dysku w tych projektach. Jeśli twój drzewo zależności jest duże i złożone, szybsze oraz lżejsze instalacje stają się ważniejsze. A jeśli zarządzasz monorepo, funkcje przestrzeni roboczej pnpm zasługują na poważną uwagę.

Izolacja zależności to kolejny powód, by go wybrać — jeśli ścisłe granice między tym, co jest deklarowane, a tym, co można używać, faktycznie mają znaczenie dla twojego projektu, pnpm automatycznie to zapewnia przez domyślne ustawienia.

Mówiąc prościej: im większy i bardziej skomplikowany staje się setup w JavaScript, tym bardziej atrakcyjny staje się pnpm.

Który więc wygrywa pod względem szybkości?

Jeśli chodzi o jednoznaczną odpowiedź, pnpm zazwyczaj ma przewagę pod względem efektywności instalacji, szczególnie gdy jego wspólny magazyn już przechowuje pakiety lokalnie w formie cache’u.

Mimo to nie byłoby dokładne twierdzenie coś w stylu:

"pnpm jest dokładnie dwa razy szybszy od npm."

Taki stwierdzenie zbyt upraszcza sprawę. Wyniki testów wydajności różnią się znacznie w zależności od warunków. Uruchomienie zupełnie nowej instalacji przez szybkie połączenie sieciowe nie jest porównywalne z ponowną instalacją pakietów na maszynie, która już ma większość z nich w pamięci lokalnej. Ponadto środowisko CI zachowuje się inaczej niż maszyna lokalna.

Dlatego jeśli jedynym powodem rozważania zmiany menedżerów pakietów jest wykres testów wydajności, lepszym krokiem będzie przeanalizowanie własnego procesu pracy przed podjęciem takiej decyzji.

Co bym wybrał

Dla małego projektu React npm doskonale spełnia swoją rolę bez żadnych problemów.

To samo dotyczy projektu edukacyjnego — npm jest w porządku.

Jeśli dołączasz do istniejącego kodbase zespołu, użyj tego, co zespół już ustandaryzował.

Dla dużego monorepo jednak pnpm staje się poważnym konkurentem.

A jeśli pracujesz na maszynie, na której ciągle przechodzisz między wieloma projektami w JavaScript, pnpm zazwyczaj okazuje się bardziej praktycznym wyborem.

To wszystko tłumaczy, dlaczego nie ma tu jednego uniwersalnego zwycięzcy.

Nie zmieniaj narzędzia tylko dlatego, że pnpm jest popularny

To może być najważniejszy punkt w całym tym porównaniu.

Nie musisz migrować każdego istniejącego projektu z npm na pnpm tylko dlatego, że temat ten często pojawia się w dyskusjach programistów w internecie.

Z pewnością też nie musisz zmieniać narzędzia, ponieważ ktoś nalega:

"npm nie żyje."

To nieprawda. Obie narzędzia są aktywnie utrzymywane, oba mają za sobą rozwinięte ekosystemy i oba doskonale radzą sobie z nowoczesnymi projektami w JavaScript.

Prawdziwe pytanie nie brzmi „który menedżer pakietów jest obiektywnie najlepszy”. Chodzi raczej o to „który menedżer pakietów pasuje do mojego sposobu pracy?”

Jeśli tworzysz małe aplikacje, uczysz się języka lub pracujesz w zespole, który już korzysta z npm, pozostanie przy nim to zupełnie rozsądna decyzja.

Jeśli natomiast zajmujesz się dużymi projektami, wieloma repozytoriami lub monorepo i chcesz bardziej efektywnego przechowywania zależności oraz szybszych instalacji, wtedy warto spróbować pnpm.

Podsumowanie

Przed porównaniem npm i pnpm wydawało się, że będzie jasna, prosta odpowiedź – npm to przestarzała opcja, a pnpm szybsze ulepszenie.

Rzeczywistość okazała się bardziej złożona.

Główną zaletą npm jest jego prostota oraz fakt, że prawie wszyscy go już znają. To domyślna opcja z dobrego powodu.

Głównymi zaletami pnpm są jego model przechowywania zależności, wydajność podczas instalacji oraz narzędzia dostępne dla większych projektów.

Jeśli jesteś początkującym w JavaScript, nie martw się tą decyzją — po prostu użyj npm i zacznij tworzyć.

Jeśli już dobrze znasz Node.js, a twoje projekty rozwijają się, spróbuj pnpm i zobacz, czy ułatwi on twoją codzienną pracę.

Ostatecznie to nie menedżer pakietów decyduje o jakości aplikacji.

To twój kod ma na to wpływ.

Powiązane materiały

  • Node.js Streams poza elementarnymi zasadami: pamięć, backpressure i rzeczywiste błędy — Dowiedz się, jak strumienie Node.js współpracują z Web Streams, jakie są oszczędności pamięci uzyskane dzięki rzeczywistym testom wydajności oraz jakie błędy w produkcji pojawiają się tylko pod obciążeniem.