Menu

npm od podstaw: install, init, update i zarządzanie zależnościami

Jak naprawdę działa npm: instalowanie pakietów, init, zależności deweloperskie, aktualizacje oraz model myślowy stojący za node_modules i plikiem lockfile.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

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:

  1. npm pobiera lodash i jego zależności do node_modules/.
  2. Dodaje "lodash": "^4.17.21" (lub najnowszą wersję) do sekcji dependencies w package.json.
  3. Zapisuje package-lock.json z 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.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ