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 testi kilka innych nazw działa bez słowarun. Wszystko inne wymaganpm run <nazwa>.- Skrypty wykonują się w powłoce, która ma
node_modules/.binwPATH, więc możesz bezpośrednio wywoływać programy z zainstalowanych pakietów."test": "vitest"działa, mimo żevitestnie 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>ipost<nazwa>uruchamiają się automatycznie. Jeśli maszprebuild, wykona się przedbuildbez ż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.
typedecyduje, 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.mainto starszy punkt wejścia: to, do czego rozwiązuje sięrequire("my-lib"). Starsze narzędzia wciąż go respektują.exportsto 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.jsonrazem zpackage-lock.jsonwystarczą, żeby każdy odbudował go przeznpm 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 dodependencies. - Ręczna edycja wersji bez ponownej instalacji. Zmień wersję w
package.jsoni uruchomnpm install, bo inaczejnode_modulesi 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.