Node.js i npm

Ogólnie o Node.js

Node.js to środowisko, które pozwala nam odpalać Javascript poza przeglądarką - bezpośrednio z terminala. Dla nas - frontendowców - jest to w zasadzie nierozłączna rzecz w codziennej pracy i prędzej czy później każdy z nas będzie miał z nim styczność.

Środowisko to charakteryzują trzy główne rzeczy.

Pierwsza z nich to asynchroniczność. Node tak samo jak Javascript w przeglądarce jest jednowątkowy. Aby móc wykonywać wiele operacji równocześnie konieczne jest używanie asynchronicznoci. Rozmawiamy o tych sprawach w tym rozdziale.

Drugą charakterystyczną cechą jest to, że w odróżnieniu od klasycznego dołączania skryptów do pliku html, w Node każdy plik javascript stanowi oddzielny moduł (oddzilene zamknięte pudełko), z którego musimy exsportować/importować interesujące nas rzeczy. Mówimy sobie o tym tutaj.

Trzecią cechą jest możliwość prostego doinstalowywania dodatkowych pakietów (funkcjonalności).

Wraz z Node.js dostajemy też Node Package Manager (w skrócie NPM), czyli mały program, który służy do zarządzania dodatkowymi funkcjonalnościami.

Takie managery to w sumie nic nowego. Dla Linuxa mamy APT (apt-get), dla MacOs Homebrew, dla PHP Composera itp. Już niedługo nawet Windows dostanie coś swojego.

Brakuje nam więc jakiejś funkcjonalności? Jednym poleceniem możemy to sobie doinstalować. Jeżeli dodatkowo taka funkcjonalność do swojego działania wymaga innych pakietów, manager automatycznie je doinstaluje.

W dzisiejszych czasach środowisko Node.js tak szybko się rozwija, że w zasadzie znajdziemy tutaj pakiety do każdej funkcjonalności, która cię interesuje. Od sliderów, poprzez karuzele, biblioteki do pobierania danych itp. Wygoda tego rozwiązania sprawiła, że większość narzędzi, których na co dzień używają frontendowcy to właśnie takie rozbudowane pakiety, które doinstalowujemy sobie w razie potrzeby.

Instalacja Node i uruchamianie skryptów

Zaczynamy jednak od zainstalowania Node.js.

Wchodzimy na stronę https://nodejs.org/en/ i dokonujemy instalacji.

Różni się ona w zależności od systemu. Na Windowsie nie powinno być raczej żadnego problemu - ot ściągamy instalację, next, next i tyle. Ewentualnie po instalacji czasami trzeba wylogować się i jeszcze raz zalogować by zaktualizowało zmienne Path, dzięki czemu możemy później używać Node w terminalu. Raz jeden widziałem tutaj przypadek, gdzie antywirus blokował proces instalacji.

Systemy Linux/Osx są bardziej restrykcyjne jeżeli chodzi o uprawnienia, i w wielu sytuacjach tradycyjna instalacja Node może później przynieść problemy z brakiem dostępu przy próbie instalacji globalnych pakietów.

Aby uniknąć tego problemu, możemy zainstalować Node na kilka sposobów.

Poniżej przepis dla odpowiednich systemów.

  • Instalacja dla Ubuntu

    Dla linuxowych systemów zalecana jest instalacja za pomocą Node Version Manager (1).

    Domyślnie na Ubuntu dostępny jest wget. Przechodzimy do sekcji instalacji na stronie nvm i wklejamy do terminala polecenie rozpoczynające się od wget. Zacznie się instalacja nvm.

    Po instalacji zamykamy terminal i jeszcze raz go uruchamiamy.

    Po ponownym otwarciu terminala sprawdzamy czy nvm został zainstalowany wpisując polecenie nvm i naciskając enter. Jeżeli wszystko zadziałało jak należy, instalujemy Node poleceniem:

    
            nvm install node
            

    Sprawdźmy czy wszystko poszło dobrze wpisując polecenia node -v i npm -v. Jeżeli w terminalu pokażą się wersje, znaczy, że proces instalacji mamy zakończony.

  • Instalacja Node.js dla Osx

    Dla Osx możemy skorzystać z 2 metod (pewnie i więcej - te dwie sam testowałem i działały jak należy).

    Pierwsza z nich polega na użyciu homebrew. Jest to pakiet manager, za pomocą którego możemy instalować różne programy.

    Wchodzimy na stronę https://brew.sh/index_pl i zgodnie z instrukcją instalujemy to narzędzie.

    Po instalacji sprawdź, czy system może już korzystać z tego pakietu poleceniem brew doctor

    Po pomyślnym zainstalowaniu homebrew, zainstaluj Node poleceniem: brew install node.

    Sprawdźmy czy wszystko poszło dobrze wpisując polecenia node -v i npm -v. Jeżeli w terminalu pokażą się wersje, znaczy, że proces instalacji mamy zakończony.

    Druga metoda to - podobnie dla linuxa - skorzystanie z nvm. Ciut trudniejsza, ale w przyszłości pozwala w razie czego przełączać wersje Node a i metoda z homebrew czasami może sprawiać problemy.

    Aby móc zainstalować nvm, powinieneś mieć zainstalowane narzędzia programistyczne XCode. Jeżeli ich jeszcze nie instalowałeś, możesz to zrobić ręcznie korzystając z opisu na stronie https://osxdaily.com/2014/02/12/install-command-line-tools-mac-os-x/. Wystarczy w terminalu wpisać polecenie xcode-select --install, po którym pojawi nam się okienko instalacji:

    XCode popup

    Kolejnym krokiem będzie odpalenie skryptu instalacyjnego. Przejdź na stronę https://github.com/nvm-sh/nvm#install--update-script i wklej do terminala polecenie rozpoczynające się do curl.

    Instalacja się zakończy. W wielu przypadkach pod jej koniec pojawi się informacja, że skrypt nie mógł dodać odpowiedniego wpisu rozpoczynającego się od export.

    Zanim to naprawimy, sprawdźmy jaki masz terminal poleceniem echo $SHELL. Najczęściej będzie to bash lub zsh (nazwa powinna być też widoczna na belce terminala).
    Podczas startu systemu ale też startu terminali ładowane są odpowiednie pliki konfiguracyjne. I tak podczas zalogowania do systemu wczytywany jest plik ~/.bash_profile. Podczas odpalania terminalu wczytywany jest plik .bashrc, natomiast w nowych wersjach macOS, gdzie domyślnie używany jest zsh podczas startu zsh odpalany jest plik ~/.zshrc (albo pojawia się informacja, żeby zaktualizować terminal do zsh).

    Jeżeli pierwszy raz na oczy widzisz nazwy tych plików, prawdopodobnie nie masz ich w swoim systemie, i dlatego właśnie skrypt nie mógł dodać odpowiedniego wpisu (nie ma na to uprawnień).

    Stwórz/edytuj więc odpowiedni plik dla twojego środowiska za pomocą polecenia sudo nano ~/.nazwa_pliku. Dla starszych wersji będzie to ~/.bash_profile a dla zsh ~/.zshrc, a następnie dodaj do niego odpowiedni kod. Po wklejeniu danego kawałka kodu, zakończ edycję pliku naciskając Ctrl+X i wpisując literę Y.

    Po tych czynnościach otwórz ponownie terminal i wreszcie zainstaluj node za pomocą polecenia:

    
            nvm install node
            

    W skrajnych przypadkach trzeba dopisać jeszcze polecenie:

    
            nvm use node
            

Od tej pory będziemy mogli odpalać pliki ze skryptami z poziomu terminala:


node nazwa_pliku.js
//nie musimy podawać rozszerzenia .js
node nazwa_pliku

NPM i instalowanie pakietów

Wspomniany powyżej NPM dostajemy wraz z Node.js

Menadżerów dla Node.js mamy na rynku kilka. NPM jest najpopularniejszy, ale równie często używane są yarn, pnpm czy chociażby ich starszy brat bower (a znajdzie się i kilka innych). Osobiście początkowo nie zawracał bym sobie tym głowy i pozostał przy domyślnym. W poniższym tekście skoncentruję się właśnie na nim.

Funkcjonalności, które możemy instalować dla Node.js są zgrupowane we wspólnym rejestrze pakietów dostępnych na stronie https://www.npmjs.com/. Aby znaleźć dany pakiet albo skorzystamy z wyszukiwarki na górze tej strony, albo zwyczajnie wyszukamy ją w necie wpisując npm nazwa_funkcjonalności np. npm slider. Problemem tutaj najczęściej będzie to, że pasujących wyników trafimy setki, a jak to z różnymi rozwiązaniami bywa - nie każde będzie dopracowane i nie każde będzie spełniać nasze oczekiwania. Pozostaje testowanie i nabieranie doświadczenia (czyli powolna zamiana w seniora).

Zdaniem autora jest to duża zaleta, ale też niestety i wada naszego środowiska. Wiele pakietów jakie znajdziesz okaże się działać średnio. Bardzo dużo takich rozwiązań powstaje, ale też wiele z nich dość szybko jest porzucana. Dodatkowo część z nich wygląda na tworzone po macoszemu, gdzie często gęsto nie zawierają nawet ładnych opisów. Przypomina to trochę historię jQuery pluginów do Wordpressa itp...

W środowisku Node.js pakiety możemy instalować globalnie na cały komputer oraz lokalnie per dany projekt.

Te instalowane globalnie po zainstalowaniu dostępne są z każdego miejsca w komputerze. Do ich instalacji służy flaga -g:


npm install nazwa_paczki -g
//lub skrótowo
npm i nazwa_paczki -g

Po zainstalowaniu takiego pakietu będziemy mogli odpalić go w terminalu poprzez podanie jego nazwy. Przykładem takiego pakietu może być live-server, czyli serwer lokalny, który daje nam automatyczne odświeżanie strony gdy piszemy nasz kod. Wystarczy takie narzędzie zainstalować poleceniem npm i live-server -g, a następnie szybko odpalać w danym katalogu za pomocą polecenia live-server.

Na co dzień tylko niektóre pakiety będziemy instalować globalnie. Zazwyczaj będą to dodatkowe narzędzia, które chcielibyśmy łatwo odpalać z każdego miejsca.

Pracując już na konkretnym projekcie pakiety dla niego będziemy chcieli instalować raczej lokalnie - per dany katalog. Żeby to zrobić będąc w danym katalogu w terminalu wpisujemy polecenie:


npm i nazwa_pakietu_1 nazwa_pakietu_2

Pakiet taki zostanie zainstalowana do katalogu node_modules który zostanie utworzony w tym samym katalogu.

W katalogu node_modules będzie zazwyczaj dużo plików. Nigdy tego katalogu nie powinniśmy wrzucać do repozytorium. Osoba która ściągnie nasz projekt i tak zainstaluje sobie pakiety na podstawie omawianego poniżej pliku package.json. Dlatego też node_modules jak najszybciej powinniśmy dodać do .gitignore

Takich pakietów już nie odpalmy bezpośrednio z terminalu, a za pomocą skryptów znajdujących się w pliku package.json.

Plik package.json

Zanim zainstalujemy pierwszy lokalny pakiet, warto utworzyć w danym projekcie plik package.json. Jest to plik konfiguracyjny dla danego projektu (a będąc bardziej szczegółowym jest to pliku konfiguracyjny naszego pakietu. Nasz projekt też możemy traktować jako swoisty pakiet, który w przyszłości moglibyśmy publikować do głównego rejestru wszystkich paczek).

Aby utworzyć taki plik, powinniśmy w danym katalogu użyć polecenia:


npm init

Zostaniemy zapytani o kilka rzeczy jak nazwa pakietu, opis, autor itp.

npm init terminal

Wystarczy kilka razy nacisnąć Enter. Możemy też użyć polecenia npm init -y które stworzy taki plik bez zadawania nam pytań.

Po odpaleniu powyższych komend, powinniśmy dostać plik package.json z mniej więcej poniższą zawartością:


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

Plik taki jest o tyle ważny, że zapisywane są w nim instalowane przez nas lokalne pakiety. Jeżeli teraz coś doinstaluję w danym projekcie, odpowiednie informacje zostaną dodane do powyższego pliku:


npm i express body-parser

{
    "name": "test-project",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    "author": "",
    "license": "ISC",
    "dependencies": {
        "body-parser": "^1.19.0",
        "express": "^4.17.1"
    }
}

Po wstępnej konfiguracji projektu i zainstalowaniu potrzebnych paczek plik taki trafi później na repozytorium, a osoba która ściągnie dany projekt wszystkie potrzebne rzeczy zainstaluje pojedynczą komendą:


npm install
//lub
npm i

Domyślnie instalowane lokalne pakiety dodawane są do sekcji dependencies w pliku package.json. Oznacza to, że danych pakietów chcemy używać w czasie tworzenia (development), ale też podczas jego działania (production). To do jakiej fazy zakwalifikujemy dany pakiet oznacza dodatkowa flaga, którą używamy podczas instalacji:

-g pakiet instalowany globalnie na cały komputer. O tym już mówiliśmy.
--save-dev lub -D oznacza, że dany pakiet będzie przez nas używany w fazie developingu, czyli podczas tworzenia projektu
--save-prod lub -P dany pakiet będzie używany w czasie tworzenia strony ale też na produkcji. Pakiety takie zostaną zapisane do sekcji dependencies. Pakiety są domyślnie instalowane do tej sekcji, dlatego możemy tą flagę pominąć. W poprzednich wersjach npm flaga ta miała postać --save
--save-optional lub -O dany pakiet będzie używany tylko w przypadku użycia konkretnych funkcjonalności. Nie jest wymagana do działania projektu.
--no-save dany pakiet nie zostanie zapisana do pliku package.json

Przykładowo po zainstalowaniu 4 pakietów zostaną one zapisane w odpowiednich sekcjach:


npm i express body-parser
npm i webpack webpack-cli -D

{
    "name": "test-project",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    "author": "",
    "license": "ISC",
    "dependencies": {
        "body-parser": "^1.19.0",
        "express": "^4.17.1"
    },
    "devDependencies": {
        "webpack": "^5.4.0",
        "webpack-cli": "^4.2.0"
    }
}

Od strony działania pakietów nie ma znaczenia do której sekcji trafi dany pakiet w pliku package.json. Można to traktować jako sprawy porządkowe. W razie gdy dany pakiet zostanie zapisany w złej sekcji tego pliku, możemy go albo przeinstalować dodając odpowiednią flagę, albo ręcznie edytować ten plik po prostu przenosząc odpowiednie nazwy z sekcji do sekcji.

Umieszczanie odpowiednich pakietów w odpowiednich sekcjach za pomocą flag może się przydać w sytuacji, gdy chcemy nasz projekt postawić na serwerze. W katalogu node_modules zazwyczaj jest bardzo, bardzo dużo plików. Zamiast je wszystkie przerzucać na serwer, możemy połączyć za pomocą terminala (np. za pomocą Putty) z serwerem i zainstalować je bezpośrednio na nim.
Aby nie instalować paczek które wykorzystywaliśmy w fazie tworzenia (tych z sekcji devDependencies) posłużymy się poleceniem:


npm i --only=production
//lub
npm i --only=prod

Dodatkowo jeżeli na serwerze zmienna środowiskowa NODE_ENV ustawiona jest na production, wtedy polecenie npm i zainstaluje tylko paczki z sekcji dependencies.

Ale tak naprawdę nawet jeżeli wszystkie paczki mielibyśmy w sekcji dependencies, nic wielkiego by się nie stało - po prostu wszystkie byśmy zainstalowali poleceniem npm i. Także jest to trochę marginalna sprawa (przy czym polecam trzymać porządek).

Aktualizacja pakietów

Gdy zainstalujemy dany pakiet, instalowany jest jego najnowsza wersja, a w pliku dodawany jest zapis o jej wersji:


"devDependencies": {
    "webpack": "^5.4.0",
    "webpack-cli": "^4.2.0"
}

Możemy też zainstalować daną wersję pakietu podając konkretną wersję po nazwie:


npm view gulp version --json //sprawdzam jakie mamy wersje
npm i gulp@3.8.8

Wersje są zapisane w składni semver. Zapis taki składa się z trzech części oddzielonych kropkami. Pierwsza liczba oznacza wersję główną (major), druga podwersję (minor), a trzecia aktualizacje, lub poprawy błędów (patch, bug fixes).

Z przodu wersji mogą pojawić się dwa znaki:

Znak ^, oznacza to, że dany pakiet będzie mógł być uaktualniony dla podwersji - np. wersja ^5.4.0 może być zaktualizowana do 5.5.0 ale nie do 6....

Znak ~ oznacza, że dany pakiet może być zaktualizowany tylko do nowszej wersji patchowej, czyli 5.4.0 do 5.4.2 ale nie do 5.5..

Aby zaktualizować dane pakiety użyjemy komend:


npm outdated //pokaże stan zainstalowanych pakietów
npm update //aktualizuje pakiety

Aby odinstalować daną pakiety, skorzystamy z polecenia:


npm uninstall nazwa_pakietu

Jeżeli dane pakiety chcielibyśmy zaktualizować do nowszej wersji głównej, albo wcześniej odinstalujemy i zainstalujemy je ponownie, albo też skorzystamy z przygotowanych do tego... pakietów. Jednym z nich jest npm-check. Po odpaleniu komendą npx npm-check -u w terminalu pokaże się lista pakietów, które wymagają aktualizacji. Po wyborze odpowiednich zostaną one zaktualizowane.

Kolejną możliwością jest npm-check-updates. Po odpaleniu zaktualizuje wersje w package.json. Po ten czynności musimy jeszcze odpalić npm install by realnie zaktualizować dane pakiety.

Przy czym niepisana zasada mówi o tym, że jak coś działa, to nie trzeba tego ruszać. Czasami aktualizacja pakietu do nowej wersji może pociągnąć z sobą wiele problemów - np. przestaną działać inne pakiety, które były zależne od danej wersji. Ogólnie rób to z głową.

Plik package-lock.json

Gdy zainstalujesz pierwsze pakiety, zauważysz, że obok katalogu node_modules został też utworzony plik package-lock.json.

Wyobraź sobie, że zacząłeś pracę nad projektem. Instalujesz pakiet np. express poleceniem npm i express.

W twoim pliku package.json pojawił się odpowiedni zapis:


{
    ...
    "dependencies": {
        "express": "^4.17.1"
    }
}

Twój współpracownik ściąga za jakiś czas dany projekt, po czym odruchowo instaluje wszystkie wymagane pakiety poleceniem npm i. W pliku package.json nic się oczywiście nie zmieni, ponieważ nic nowego nie doinstalowywał.

Ale cóż to - gdy wpisze w terminalu polecenie npm list --depth=0, okaże się, że zainstalowała mu się nowsza wersja 4.18.1, która w międzyczasie została wydana. Odpala projekt i coś nie działa jak należy.

I tu właśnie przychodzi z pomocą plik package-lock.json. Plik ten trzyma informacje o strukturze katalogu node_modules, o dokładnych zainstalowanych wersjach pakietów i wszystkich operacjach jakie przeprowadzane były na tym katalogu (instalowanie, odinstalowywanie pakietów itp). Dzięki niemu wszystkie osoby z zespołu instalując pakiety zainstalują je w dokładnie takich samych wersjach jak u innych.

npx

W nowych wersjach npm dostajemy także narzędzie npx. Polecenie to służy do odpalania pakietów.

Aby użyć tego polecenia wystarczy wpisać:


npx nazwa_paczki
//np
npx react-create-app my_project

Jeżeli w danym katalogu jest zainstalowany lokalny pakiet o danej nazwie, polecenie to spróbuje go odpalić. Jeżeli takiego pakietu nie ma, ściągnie go, odpali bez instalacji w node_modules, a po zakończonej pracy usunie.

Poza szybkim uruchamianiem różnych narzędzi (np. npx live-server) narzędzie to możemy wykorzystać też do testów odmiennych wersji danego pakietu.

Wystarczy sprawdzić jakie mamy dostępne wersje:


npm v create-react-app

A następnie użyć odpowiedniej wersji:


npx create-react-app@next myapp

Skrypty w package.json

Jeżeli jeszcze raz spojrzymy na nasz plik package.json, zobaczymy tam sekcję script:


{
    "name": "test-project",
    "version": "1.0.0",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    "devDependencies": {
        ...
    }
}

W sekcji tej możemy zamieszczać własne skrypty, które potem będziemy odpalać bezpośrednio z terminala. Najczęściej są one wykorzystywane do uruchamiania lokalnych pakietów, które dość często wymagają dodatkowych parametrów.

Aby odpalić skrypt o danej nazwie, w terminalu wpiszemy polecenie:


npm run nazwa_skryptu
//np.
npm run test

Nasze skrypty mogą mieć dowolne nazwy. Istnieją też predefiniowane. I tak jeżeli stworzymy skrypt o nazwie start, możemy go odpalić komendą npm run start ale także npm start. Podobne aliasy istnieją dla start, restart, stop, config, test i script.
Cechę tą wykorzystuje sporo popularnych narzędzi np. create-react-app, które po zainstalowaniu odpala się poleceniem między innymi poleceniem npm start.

Dodatkowo dla każdego skryptu o danej nazwie możemy też stworzyć dwa dodatkowe z nazwami rozpoczynającymi się od pre i post. Pierwszy z nich będzie odpalany tuż przed naszym skryptem, a drugi tuż po:


{
    "name": "test-project",
    "version": "1.0.0",
    "scripts": {
        "prestart"  : "echo '--- rozpoczynam ---'",
        "start"     : "echo '--- działam ---'",
        "poststart" : "echo '--- kończę ---'"
    }
}

I teraz w terminalu odpalenie naszego skryptu za pomocą komendy npm start odpali po kolei prestart, start a następnie poststart.

Więcej na ten temat możesz przeczytać tutaj.

W poniższym przykładowym projekcie skrypty będziemy głównie wykorzystywać do uruchamiania dodatkowych pakietów. Pamiętaj jednak, ze możesz je użyć do odpalania dowolnego kodu, który wklepujesz w terminal.


{
    "name": "test-project",
    "version": "1.0.0",
    "scripts": {
        "clean": "rm -rf dist/*",
        "makeFiles": "mkdir dist/css && mkdir dist/js"
        "start": "npm run clean && npm run makeFiles"
    },
    "devDependencies": {
        ...
    }
}

Wszelkie prawa zastrzeżone. Jeżeli chcesz używać jakiejś części tego kursu, skontaktuj się z autorem.
Aha - i ta strona korzysta z ciasteczek.

Menu