Podstawy pracy z npm

Praca z menadżerem pakietów npm oraz zrozumienie, jak działa cały ekosystem zarządzania pakietami w projekcie, sprawiały mi, jako początkującemu programiście, dość duże problemy. Mam nadzieję, że po przeczytaniu tego tekstu, nikt nie pójdzie moją drogą.

Node I npm to dwa nierozłączne narzędzia – tak można by było rzec. Jest to oczywiście nieprawda, bo przecież nikt nie broni nam posiadać zainstalowanego node, ale co z tego, jeżeli nie korzystamy z żadnego menadżera pakietów lub, jak kto woli, systemu zarządzania pakietami. Każde poważne środowisko ma swojego. I tak dla node jest to npm, dla Pythona pip, a przykładowo dla Javy - Maven. Poza npm jest masa innych menadżerów przeznaczonych dla webdevelopmentu. Najpopularniejszą alternatywą jest yarn. Dlaczego więc nie ma jednego, właściwego dla wszystkich platform? Ponieważ rynek jest wolny, a opragramowanie node oparte na licencji open-source (otwarty kod), z którym każdy, jeśli tylko chce, może integrować swoje rozwiązania. W praktyce menadżerowie pakietów różnią się swoją bazą dostępnych blbliotek i modułów (co jest dosyć istotne) oraz składnią w CLI (wierszu poleceń). Każdy menadżer ma swoje CLI i na każdym wykonywanie tych samych czynności nieco się różni. Pozostałe różnice polegają na mniej istotnych funkcjonalnościach. Spośród wielu innych menadżerów, opiszę tutaj właśnie npm ze względu na: największe rozpowszechnienie tego narzędzia wśród programistów, fakt, że sam go używam oraz z uwagi na to, iż jest to menadżer wspierany natywnie przez node.

Co robią systemy zarządzania pakietami?

Do ich najważniejszych zadań należą:

  • Instalacja i zarządzanie modułami, bibliotekami i zależnościami w projekcie (najczęściej) poprzez CLI,
  • Współpraca z systemami archiwizującymi lub systemami kontroli wersji
  • Zapewnianie integracji i zgodności wersji pobieranych modułów w celu poprawności działania projektu,
  • Grupowanie zależności i oddzielanie ich od właściwego kodu programisty,
  • Dostarczanie bazy danych bibliotek i modułów, jako zbiór w jednym miejscu, co zapewnia łatwy dostęp do najnowszych wersji zależności.

Zastosowanie menadżerów pakietów jest więc całkiem szerokie, tym bardziej, że nie wymieniłem tutaj wszystkich zalet używania tych narzędzi. Przez powyższe czynniki trudno nam sobie wyobrazić dzisiejszą pracę webdevelopera. Podejrzewam, że trudno sobie wyobrazić jakiegokolwiek zawodowego programistę, pracującego bez systemu zarządzania pakietami.

Inicjowanie nowego projektu na podstawie pracy z npm

Oprócz oczywiście zainstalowanego node (którego można pobrać tutaj), potrzebujemy również npm. Można go doinstalować wraz z node (domyślnie ta opcja jest zaznaczona), ale jeżeli nie jesteśmy pewni, co do jego posiadania, możemy to sprawdzić poprzez wpisanie w jakimkolwiek CLI polecenia:

npm -v

Dzięki temu dostajemy informację o wersji naszego npm. Przy okazji, polecam aktualizować npm i node jak najczęściej.

 

Aby zainicjować nowy projekt, wykorzystujący npm wpisujemy:

npm init

Po czym program prosi nas o wpisanie podstawowych metadanych, dotyczących projektu. Podajemy m. in. Nazwę, opis i autora projektu. Na wszystko możemy odpowiedzieć klikając enter. Npm zapisze wtedy wartości domyślne (np. nazwę przypisze z nazwy nadrzędnego katalogu). Po zainicjowaniu projektu pojawi się plik package.json. Jest to niezwykle ważny plik, którego nigdy nie należy usuwać.

package.json

Struktura nowo utworzonego pliku wygląda tak:

{
  "name": "reactywny.pl",
  "version": "1.0.0",
  "description": "Blog",
  "main": "index.js",
  "j": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

Oprócz podstawowych informacji o projekcie, dostajemy również, klucz scripts, do którego omówienia przejdę przy okazji wpisy o webpacku. Sam package.json jest niezmiernie ważny i należy go trzymać w projekcie od początku jego istnienia. Oprócz integracji z bazą modułów npm (publikowanie własnych modułów, czyli coś co nie wchodzi w zakres omawianych tutaj podstaw), przechowujemy tutaj dependencje, czyli wpisy o używanych przez programistę modułów w projekcie. To do projektu na GitHubie (lub jakimkolwiek innym programie obsługującym system kontroli wersji) wrzucamy sam kod projektu a nie wszystkie pobrane moduły i dependencje. Więc jak to działa?

Dependencje (zależności) w projekcie

We wcześniej zainicjowanym projekcie mamy jedynie plik package.json, a w nim zbyt dużo informacji nie znajdziemy. Zainstalujmy więc pierwszy moduł do naszego projektu, a przykładem niech będzie slugify:

npm install slugify

Co jest równoznaczne z zapisem:

npm -i slugify

Slugify to moduł, dzięki któremu możemy z dowolnego łańcucha tekstowego, stworzyć tzw. sług. Przykładowo dla stringa „Podstawy pracy z npm”, dostaniemy string „podstawy-pracy-z-npm”. Jest to przydatne, jeżeli chcemy, by string stanowił np. część linku do posta. Nie jest to jednak ważne w kontekście tego wpisu. To tylko przykład modułu, który znajduje się w bazie npm. Moglibyśmy, w taki sam sposób, zainstalować każdą inną paczkę z tej bazy. I w taki sposób to robimy - osobiście używam pierwszy wariant, z pełnym słowem install, ale tak ja mówiłem, sposoby są te równoznaczne i robią dokładnie to samo.

Po zainstalowaniu pakietu naszym oczom ukazuje się nowo utworzony plik package-lock.json oraz katalog node-modules . Package-lock.json to plik, który przedstawia, można by powiedzieć, dependencje dependencji. Prościej mówiąc, to plik, mówiący o tym, jakie inne zależności potrzebne są do zainstalowanej zależności, by ta poprawnie działała. Często to nawet same projekty wykonane przez programistów wymagają używania innych paczek, które są dla nich przydatne w działaniu. Dlatego też, przykładowo, paczka create-react-app instaluje nam setki, jeśli nie tysiące różnych zależności. Ale o samej aplikacji reactowej wspomnę na końcu. Sam plik package-lock.json możemy usuwać, ponieważ podczas instalowania modułów projektu, node i npm na podstawie dependencji w package.json, w sposób automatyczny tworzą taki plik, o czym zaraz opowiem.

Natomiast w katalogu node_modules możemy zobaczyć następującą strukturę:

zdjęcie tematyczne

Wynika z niego, że każdy zainstalowany moduł znajduje się właśnie tam – jest to świetne spełnianie jednego zadania powierzonego menadżerom pakietom, czyli oddzielanie zainstalowanych zależności od struktury właściwego projektu. Od teraz pobraną paczkę możemy wykorzystywać gdziekolwiek w naszym projekcie. Bez skonfigurowanego webpacka możemy to zrobić na razie, jedynie „po staremu”, umieszczając lokalizację w znaczniku <script> pliku HTML. Jednak po konfiguracji buildera, możemy importować moduły następująco:

import from 'slugify';

// lub

require('slugify');

Samo korzystanie z modułów nie jest związane z działaniem node i npm, dlatego zostawię ten temat na inny raz.

 

Katalog node_modules możemy bezpiecznie usuwać i ponownie instalować cały projekt. Zdarza się to dosyć często, z uwagi na chęć posprzątania projektu i zainstalowanie całego ekosystemu na świeżo. Poza tym robimy to za każdym razem, kiedy instalujemy projekt z repozytorium innego programisty (lub też swój).

Instalacja całego projektu

Przejdę tutaj do największej zalety menadżerów pakietów (w tym wypadku npm), jakim jest separacja czystego właściwego kodu, który waży bardzo mało, od masy modułów, ważących często po kilkaset MB. To dzięki temu mechanizmowi na repozytorium w ekspresowym tempie przesyłamy kod i assety ważące kilobajty. A to npm służy do tego, by potrzebne zależności zainstalować na naszym komputerze. Po zainstalowaniu slugify, w package.json pojawił się następujący wpis:

{
  "dependencies": {
       "slugify": "^1.4.6"
   }
}

Dla node i npm oznacza to tyle, że projekt korzysta z takiej dependencji, o takiej wersji i podczas instalacji całego projektu musi ją pobrać i umieścić w projekcie. Dlatego tak ważny jest ten plik. Pobrany projekt z repozytorium, w którym znajduje się package.json, instalujemy za pomocą polecenia:

npm install

I czekamy… Z reguły trochę to trwa bo npm, na podstawie dostarczonego package.json musi przetrawić, pobrać i poprawnie zainicjować wszystkie dependencje. To przy wykonywaniu tego polecenia otrzymamy stosowny komunikat informujący o błędzie, jeżeli z naszymi dependencjami jest coś nie tak i wymaga to naprawy. Jeżeli wszystko poszło tak jak powinno, to dostajemy katalog node_modules oraz package-lock.json, a projekt jest gotowy do uruchumienia w wersji deweloperskiej.

Wersje dependencji

Zależności możemy aktualizować w ten sam sposób, w który instalowaliśmy je pierwszy raz. Podczas wykonywania polecenia install npm domyślnie instaluje ostatnią wersję modułu. Co jednak, jeżeli z jakichś przyczyn chcemy zainstalować starszą wersję? Nic trudnego. Należy wtedy dopisać:

npm install slugify@1.4.0
npm install slugify@latest 	//domyślna wersja

Odinstalowanie paczki z projektu wygląda następująco:

npm uninstall slugify
npm uninstall slugify@1.4.0

Drugi wariant nie jest wymagany – npm sam wie o tym, żeby usunąć pakiet, niezależnie jaką posiadamy wersję.

Instalacja globalna

Poza zwyczajną instalacją dla projektu, możemy zainstalować paczki globalnie, które są widoczne w całym systemie. Mogą to być na przykład wszelkie programy wykorzystujące CLI, przydatne dla każdej aplikacji lub pliku. Narzędzie rimraf (odpowiednik rekursywnego usuwania katalogów i plików w systemach UNIXowych), to przykład paczki, który możemy wykorzystać w całym systemie bez potrzeby instalacji lokalnie dla pojedynczego projektu. Innym przykładem jest create-react-app, dzięki któremu instalujemy gotową aplikację reactową. Create-react-app instalujemy globalnie i od tego momentu zamiast używania:

npx create-react-app my-app

użyjemy:

create-react-app my-app

Inne przykłady to netlify-cli lub gatsby-cli, czyli takie narzędzia, z których korzystamy często, niezależnie od projektu Ale zaraz, zaraz. Czym jest npx?

npx

Wraz z instalacją npm dostajemy także narzędzie npx. Służy ono do uruchomienia danego narzędzia z bazy npm (może być to cokolwiek) w naszym lokalnym CLI. Oznacza to, że nie musimy pobierać modułu, żeby z niego korzystać. I dlatego w dokumentacjach instalacji create-react-app podane jest, aby skorzystać z polecenia npx, a nie pobierać całe create-react-app i użyć go tak jak ja zrobiłem powyżej. Możliwe jest też wykonania polecenie rimraf za pomocą npx. Świetne narzędzie. Przykład zastosowania:

npx rimraf node_modules

Typy dependencji

Oprócz standardowych dependencji do zainstalowanych modułów (występują one pod kluczem dependencies w package.json), występują jeszcze: globalne, deweloperskie, opcjonalne, opakowane oraz tzw. peer dependencies. Z uwagi jednak na to, że to co do tej pory napisałem, wyczerpuje podstawy, chciałbym zaprosić cię do wpisu omawiającego - w sposób szczegółowy - wszystkie te typy. Jest to niekiedy temat spędzający sen z powiek początkującym deweloperom i trudno go dobitnie zrozumieć, dlatego uznałem, że dobrze by było, by typy dependencji otrzymały własny artykuł. Ponadto chciałbym, żeby moje wpisy przedstawiały, w sposób treściwy, zagadnienia dotyczące omawianej rzeczy. Tak, by odbiór i czytanie tekstu były jak najlepiej uporządkowane. Jeżeli więc jesteś tymi zagadnieniami zainteresowany, nie pozostaje mi nic innego, jak zaprosić Cię raz jeszcze do tego wpisu. Do rychłego zobaczenia!

© Damian Kalka 2021
Wszelkie prawa zastrzeżone