Menu

package.json: skrypty, zależności i wersje

Co jest w package.json: pola, które mają znaczenie, jak działają skrypty i jak zakresy semver decydują, które wersje instaluje npm.

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

Plik manifestu projektu Node

Każdy projekt Node.js ma w katalogu głównym plik package.json. To zwykły plik JSON, który opisuje projekt: jego nazwę, wersję, zależności i udostępniane polecenia. To właśnie jego czyta npm przy każdej operacji. Usuń go, a npm nie będzie miał pojęcia, czym jest twój projekt.

Najszybszy sposób na jego utworzenie to npm init:

npm init -y

Flaga -y pomija pytania i przyjmuje wartości domyślne. Wynik wygląda mniej więcej tak:

{
  "name": "my-app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

To goły szkielet. Większość tych pól sama w sobie niewiele robi: stają się przydatne, gdy dodajesz zależności i skrypty.

Dependencies vs devDependencies

Prawie całą ciężką pracę wykonują dwa pola: dependencies i devDependencies. Oba mapują nazwy pakietów na zakresy wersji.

Podział ma znaczenie z jednego powodu: dependencies to pakiety, których twój kod potrzebuje do działania. devDependencies to pakiety potrzebne tylko podczas tworzenia kodu: test runnery, lintery, narzędzia do budowania, sprawdzanie typów. Gdy ktoś instaluje twój pakiet jako swoją zależność, npm pobiera twoje dependencies i pomija devDependencies.

npm aktualizuje te pola automatycznie. npm install express dodaje wpis do dependencies. npm install --save-dev vitest dodaje wpis do devDependencies. Rzadko edytujesz je ręcznie.

Zakresy wersji: ^, ~ i wersja dokładna

Ciągi wersji takie jak ^4.19.0 to nie dokładne wersje, tylko zakresy. npm stosuje semver, który dzieli wersje na MAJOR.MINOR.PATCH:

  • Zmiana MAJOR łamie zgodność wsteczną.
  • Zmiana MINOR dodaje funkcje, ale niczego nie psuje.
  • Zmiana PATCH naprawia błędy.

Dwa operatory, które zobaczysz wszędzie:

"express": "^4.19.0"   // >= 4.19.0 and < 5.0.0  (any 4.x.x at or above 4.19.0)
"express": "~4.19.0"   // >= 4.19.0 and < 4.20.0 (any 4.19.x at or above 4.19.0)
"express": "4.19.0"    // exactly 4.19.0

^ to domyślny operator, którego npm używa przy instalacji pakietu. Zakłada, że zmiany minor i patch zachowają zgodność. ~ jest ostrożniejszy: tylko aktualizacje patch. Sama wersja przypina ją dokładnie.

Haczyk: "to, co właśnie zainstalowałem" i "to, na co pozwala zakres" to nie to samo. Jeśli dziś zainstalujesz express@4.19.0, a ktoś z zespołu zainstaluje twój projekt za miesiąc, ^4.19.0 może rozwiązać się do 4.19.5. Tu wkracza package-lock.json: zapisuje dokładne wersje, które zostały rozwiązane, więc wszyscy dostają to samo drzewo. Commituj go.

Skrypty: polecenia twojego projektu

Pole scripts to miejsce na skróty do często używanych poleceń. Wszystko, co tam wpiszesz, uruchomisz przez npm run <nazwa>:

Kilka rzeczy, które warto wiedzieć o skryptach:

  • npm start, npm test i kilka innych nazw działa bez słowa run. Wszystko inne wymaga npm run <nazwa>.
  • Skrypty wykonują się w powłoce, która ma node_modules/.bin w PATH, więc możesz bezpośrednio wywoływać programy z zainstalowanych pakietów. "test": "vitest" działa, mimo że vitest nie jest zainstalowany globalnie.
  • Skrypty można łączyć: "build": "npm run lint && npm run compile". Używaj && w znaczeniu "uruchom po kolei, zatrzymaj przy błędzie".
  • Skrypty pre<nazwa> i post<nazwa> uruchamiają się automatycznie. Jeśli masz prebuild, wykona się przed build bez żadnej dodatkowej konfiguracji.

Skrypty to zestaw poleceń projektu. Dobry package.json sprawia, że nowa osoba w projekcie może sklonować repo, uruchomić npm install, a potem npm run dev / npm test bez czytania wiki.

Punkty wejścia: main, exports, type

Te pola mówią Node (i bundlerom), jak ładować twój pakiet.

  • type decyduje, jak parsowane są pliki .js. "module" oznacza ESM (import / export). Pomiń go albo ustaw "commonjs", żeby używać CommonJS (require). Całą historię znajdziesz w dokumencie o CommonJS vs ESM.
  • main to starszy punkt wejścia: to, do czego rozwiązuje się require("my-lib"). Starsze narzędzia wciąż go respektują.
  • exports to nowoczesny, bardziej rygorystyczny zamiennik. Określa dokładnie, które pliki mogą importować użytkownicy pakietu i pod jaką podścieżką. Jeśli pliku tu nie ma, import się nie uda, i to zaleta, a nie błąd. To ty kontrolujesz publiczne API.

Jeśli budujesz tylko aplikację (a nie publikujesz pakietu), z tych pól prawdopodobnie interesuje cię tylko type.

Realistyczny package.json

Po złożeniu wszystkiego razem package.json małej aplikacji Node wygląda w praktyce mniej więcej tak:

Zwróć uwagę na engines.node. To wskazówka: npm ostrzega (albo zgłasza błąd przy engine-strict), jeśli wersja Node użytkownika nie pasuje. Dobry nawyk przy wszystkim, co publikujesz.

Pola, które warto znać

Kilka kolejnych pól, na które trafisz:

  • private: true: chroni przed przypadkowym opublikowaniem pakietu w npm. Ustaw je w każdym projekcie, który nie jest przeznaczony do publikacji.
  • license: identyfikator SPDX, taki jak "MIT" albo "ISC". Ma znaczenie dla wszystkiego, co publiczne.
  • repository, bugs, homepage: wyświetlane na stronie pakietu w rejestrze npm.
  • bin: jeśli twój pakiet zawiera CLI, tutaj mapujesz nazwy poleceń na pliki skryptów. Po instalacji te polecenia da się uruchamiać.
  • workspaces: dla monorepo; mówi npm, żeby traktował podkatalogi jako połączone pakiety.

Nie potrzebujesz ich wszystkich. Potrzebujesz tych, które pasują do tego, co robisz.

Typowe pułapki

Kilka rzeczy, na których ludzie się potykają:

  • Commitowanie node_modules. Nie rób tego. Dodaj katalog do .gitignore. package.json razem z package-lock.json wystarczą, żeby każdy odbudował go przez npm install.
  • Brak commitowania package-lock.json. Commituj go. Bez lockfile'a "u mnie działa" staje się realnym problemem, bo zakresy semver mogą z czasem rozwiązywać się do różnych wersji.
  • Zależności runtime w devDependencies. Aplikacja może działać lokalnie, bo zależności deweloperskie są zainstalowane, a potem psuje się na produkcji, gdzie są pomijane. Jeśli kod, który wdrażasz, z czegoś korzysta, to należy to do dependencies.
  • Ręczna edycja wersji bez ponownej instalacji. Zmień wersję w package.json i uruchom npm install, bo inaczej node_modules i lockfile się rozjadą.

Dalej: środowisko uruchomieniowe Node

package.json mówi Node, czym jest twój projekt. Środowisko uruchomieniowe Node decyduje, jak go wykonać: rozwiązywanie modułów, moduły wbudowane, zmienne globalne i pętla zdarzeń pod spodem. O tym jest następna strona.

Najczęściej zadawane pytania

Do czego służy package.json?

To plik manifestu projektu Node.js. Zapisuje nazwę i wersję projektu, pakiety, od których zależy, skrypty, które możesz uruchomić przez npm run, oraz metadane, takie jak punkt wejścia i typ modułów. npm install czyta go, żeby zdecydować, co pobrać.

Jaka jest różnica między dependencies a devDependencies?

dependencies to pakiety, których twój kod potrzebuje w czasie działania, na przykład express czy react. devDependencies są potrzebne tylko podczas pracy nad kodem lub budowania: test runnery, bundlery, lintery. Gdy ktoś instaluje twój pakiet jako zależność swojego projektu, npm pomija twoje devDependencies.

Co oznaczają ^ i ~ w wersjach w package.json?

To operatory zakresów semver. ^1.2.3 dopuszcza dowolną wersję 1.x.x równą lub wyższą niż 1.2.3 (ten sam major). ~1.2.3 jest bardziej rygorystyczne: dopuszcza 1.2.x równe lub wyższe niż 1.2.3 (ten sam minor). Sama wersja 1.2.3 przypina dokładną wersję. package-lock.json zapisuje dokładnie rozwiązane wersje, żeby instalacje były powtarzalne.

Jak utworzyć plik package.json?

Uruchom npm init w pustym katalogu i odpowiedz na pytania albo uruchom npm init -y, żeby przyjąć wartości domyślne i od razu dostać plik. Możesz też napisać go ręcznie: to po prostu JSON. Jedyne naprawdę wymagane pola to name i version.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ