Czym właściwie jest npm
npm to trzy rzeczy pod jedną nazwą. To rejestr, czyli ogromna publiczna baza pakietów JavaScript pod adresem npmjs.com. To narzędzie wiersza poleceń instalowane razem z Node.js, które służy do instalowania tych pakietów i zarządzania nimi. I to specyfikacja (format package.json), która opisuje, czego potrzebuje projekt.
Gdy uruchamiasz npm install express, CLI łączy się z rejestrem, pobiera express i wszystko, od czego zależy, wrzuca pliki do folderu o nazwie node_modules i zapisuje pakiet oraz jego wersję w twoim package.json. To cały cykl.
Jeśli masz zainstalowany Node.js, masz już npm. Sprawdź:
node --version
npm --version
Jeśli obie komendy wypisują wersję, możesz zaczynać.
Nowy projekt: npm init
Każdy projekt npm potrzebuje pliku package.json. To manifest: zawiera nazwę projektu, wersję, skrypty i zależności. Najszybciej utworzysz go przez npm init -y, które akceptuje wszystkie ustawienia domyślne:
mkdir my-app
cd my-app
npm init -y
Powstaje coś takiego:
{
"name": "my-app",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
Bez -y npm przeprowadzi cię interaktywnie przez każde pole. Tak czy inaczej kończysz z plikiem package.json, do którego podpina się cała reszta. Jego pola omawiamy szczegółowo na następnej stronie.
Instalowanie pakietu
Gdy masz już package.json, zainstaluj pakiet przez npm install (w skrócie npm i):
npm install lodash
Dzieją się trzy rzeczy:
- npm pobiera
lodashi jego zależności donode_modules/. - Dodaje
"lodash": "^4.17.21"(lub najnowszą wersję) do sekcjidependencieswpackage.json. - Zapisuje
package-lock.jsonz dokładnymi wersjami wszystkich pakietów w drzewie.
Teraz możesz go użyć:
Wywołanie require (lub import w projekcie ESM) znajduje pakiet, zaglądając do node_modules. Nie piszesz ścieżki, zajmuje się tym mechanizm rozwiązywania modułów w Node.
dependencies kontra devDependencies
Nie każdy pakiet jest potrzebny, gdy aplikacja działa na produkcji. Frameworki testowe, lintery i bundlery mają znaczenie tylko podczas pracy nad kodem. Instaluj je z --save-dev (lub -D):
npm install --save-dev jest
npm install -D eslint prettier
Trafiają wtedy do devDependencies zamiast do dependencies:
{
"dependencies": {
"lodash": "^4.17.21"
},
"devDependencies": {
"jest": "^29.7.0",
"eslint": "^8.57.0",
"prettier": "^3.2.5"
}
}
Na serwerze produkcyjnym npm install --omit=dev całkowicie pomija sekcję deweloperską, dzięki czemu instalacja jest mniejsza i szybsza. Poprawny podział ma większe znaczenie, niż się wydaje: webpack wrzucony przez pomyłkę do dependencies powiększa każde wdrożenie produkcyjne.
Instalowanie wszystkiego naraz
Gdy klonujesz repozytorium, które ma już package.json, nie wymieniasz każdego pakietu. Po prostu uruchom:
npm install
Bez argumentów npm czyta package.json (i respektuje dokładne wersje z package-lock.json), a potem instaluje całe drzewo do node_modules. To pierwsza rzecz, którą uruchamiasz po świeżym sklonowaniu projektu.
Dlatego też node_modules powinien trafić do .gitignore. Da się go odtworzyć z lockfile, jest ogromny i zmienia się za każdym razem, gdy ktoś uruchomi npm install. Commituj package.json i package-lock.json, a node_modules niech każdy wygeneruje sobie sam.
Aktualizowanie pakietów
npm outdated pokazuje, co jest nieaktualne:
npm outdated
Zobaczysz tabelę z kolumnami Current, Wanted i Latest. Wanted to najnowsza wersja dozwolona przez zakres w package.json (dla ^4.17.21 wszystko poniżej 5.0.0). Latest to najnowsza opublikowana wersja, która może być wydaniem głównym, na które jeszcze się nie zdecydowałeś.
Aktualizacja w dozwolonym zakresie:
npm update
Żeby przeskoczyć do faktycznie najnowszej wersji, łącznie ze zmianą wersji głównej, zainstaluj pakiet ponownie z @latest:
npm install lodash@latest
Zmiana wersji głównej może zepsuć twój kod: właśnie to sygnalizuje numer wersji. Zanim ją przekroczysz, przeczytaj changelog.
Odinstalowywanie
Usuwanie pakietu działa symetrycznie:
npm uninstall lodash
To usuwa go z node_modules i kasuje wpis z package.json. Dodaj -D, jeśli był zależnością deweloperską (npm i tak to rozpozna, ale jawność pozwala uniknąć niespodzianek w skryptach).
Instalacja globalna kontra lokalna
Niemal każda instalacja powinna być lokalna, czyli przypięta do jednego projektu w jego node_modules. Wyjątkiem są narzędzia wiersza poleceń, które chcesz mieć dostępne wszędzie:
npm install -g typescript
npm install -g http-server
Instalacja globalna umieszcza narzędzie w lokalizacji ogólnosystemowej, a jego wpis bin na twoim PATH, więc możesz uruchomić tsc czy http-server z dowolnego katalogu. Instalacje globalne nie są jednak śledzone per projekt i mogą się rozjeżdżać między komputerami.
Lepszym kompromisem dla jednorazowych komend jest npx, dostarczany razem z npm:
npx create-react-app my-app
npx prettier --write .
npx uruchamia pakiet bez globalnej instalacji: pobiera go na żądanie, uruchamia i gotowe. Dla narzędzi używanych raz to czystsze rozwiązanie niż stała instalacja globalna.
Minimalna ściągawka
Komendy, których naprawdę będziesz używać na co dzień:
npm init -y # create package.json
npm install # install everything in package.json
npm install <pkg> # add a runtime dependency
npm install -D <pkg> # add a dev dependency
npm install -g <pkg> # install a CLI tool globally
npm uninstall <pkg> # remove a dependency
npm outdated # see what's out of date
npm update # update within allowed ranges
npm install <pkg>@latest # jump to the newest version
npm run <script> # run a script from package.json
npx <pkg> # run a package without installing it
To większość npm. Resztę, czyli publikowanie, workspaces i pakiety z zakresem (scoped packages), poznasz, gdy będzie potrzebna.
Co naprawdę jest w node_modules
Ostatni model myślowy. node_modules to w miarę płaski folder zawierający każdy pakiet, od którego zależy twój projekt, a także wszystko, od czego zależą te pakiety, i tak dalej przechodnio. Instalujesz jeden pakiet, a możesz ściągnąć ich sto: to normalne. npm usuwa duplikaty, gdzie się da, więc dwa pakiety zależne od tej samej wersji lodash współdzielą jedną kopię.
Plik lockfile (package-lock.json) zapisuje dokładną rozwiązaną wersję każdego z tych pakietów. To właśnie zapewnia powtarzalność buildów: dwie osoby uruchamiające npm install z tego samego lockfile dostają identyczne co do bajtu drzewa, nawet w odstępie miesięcy.
Traktuj node_modules jak wygenerowany wynik. Nigdy nie edytuj w nim plików, bo twoje zmiany znikną przy następnej instalacji.
Dalej: package.json
package.json to plik, który npm nieustannie czyta i przepisuje w tle. Znajomość jego pól, takich jak scripts, main, type, zakresy wersji czy engines, zamienia npm z czarnej skrzynki w coś, nad czym masz kontrolę. O tym dalej.
Najczęściej zadawane pytania
Czym jest npm?
npm to domyślny menedżer pakietów dla Node.js. Jest instalowany razem z Node, udostępnia ogromny publiczny rejestr pakietów JavaScript i zapewnia narzędzie wiersza poleceń do ich instalowania, aktualizowania i publikowania. Gdy uruchamiasz npm install lodash, npm pobiera lodash z rejestru do node_modules i zapisuje go w package.json.
Czym różnią się dependencies od devDependencies?
dependencies to pakiety potrzebne aplikacji do działania na produkcji, na przykład express czy react. devDependencies są potrzebne tylko podczas pracy nad kodem lub budowania: test runnery, bundlery, lintery. Te drugie instalujesz przez npm install --save-dev <pkg> (lub -D). Na produkcji npm install --omit=dev pomija devDependencies.
Czy commitować node_modules do gita?
Nie. node_modules może łatwo zajmować setki megabajtów i da się go w całości odtworzyć z package.json + package-lock.json. Dodaj go do .gitignore, a zamiast niego commituj plik lockfile. Każdy, kto sklonuje twoje repozytorium, uruchomi npm install i dostanie dokładnie to samo drzewo zależności.
Co oznacza instalacja globalna i lokalna w npm?
Instalacja lokalna (npm install <pkg>) umieszcza pakiet w node_modules twojego projektu i zapisuje go w package.json. Instalacja globalna (npm install -g <pkg>) instaluje go w całym systemie, zwykle dla narzędzi wiersza poleceń, które chcesz mieć wszędzie. Zależności projektu instaluj lokalnie, żeby wersje były przypięte do każdego projektu osobno.