Для новичков в веб-разработке может быть сложно понять, когда и как использовать менеджеры пакетов npm, которые служат для установки зависимостей и утилит приложения. Наряду с изучением установки пакетов можно наткнуться на инструкции по использованию yarn.

В этой статье рассмотрим, что такое Node и npm, как использовать npm и yarn для установки зависимостей в вашем проекте, а также укажем на некоторые «подводные камни», о которых следует помнить при их использовании.

Что такое Node и npm

Для начала пройдемся по двум основным понятиям, связанных с менеджерами пакетов.

Node

Node - это среда выполнения JavaScript, которая позволяет запускать код скрипта на вашем компьютере без необходимости его запуска в браузере. Это означает, что вы можете написать JavaScript, который взаимодействует с вашим компьютером так, как не может браузер. Например, вы можете разместить веб-сервер, управлять файлами на жестком диске, взаимодействовать с API-интерфейсами операционной системы и т. д.

Node также позволяет взаимодействовать с языками программирования более низкого уровня, такими как C. Это позволяет делать такие вещи, как отправка собственных уведомлений на рабочем столе, отображение чего-то определенного на панели задач или любые другие действия, которые требуют доступа более низкого уровня к вашему локальному интерфейсу.

npm

Любой полезный язык программирования нуждается в экосистеме, на которую можно положиться. Одним из основных элементов экосистемы является набор библиотек, которые вы можете использовать для создания собственных библиотек и приложений.

Библиотека - это фрагмент кода, написанный другими людьми, который вы можете легко импортировать в свой собственный код и использовать самостоятельно.

npm (node package manager - менеджер пакетов Node) представляет собой комбинацию двух вещей:

  • Реестр. Это серверы и базы данных, на которых размещены пакеты с их уникальными именами.
  • Утилита CLI. Это программа, которая запускается на вашем компьютере для установки и управления пакетами на вашем локальном диске.

Когда, скажем, Facebook хочет опубликовать новую версию React, кто-то из команды React (с учетными данными для публикации) настраивает и создает новую рабочую версию исходного кода React, открывает утилиту CLI, чтобы запустить команду npm publish, которая отправляет код новой версии в реестр. Оттуда, когда вы уже устанавливаете React с помощью команды npm локально, будут загружены соответствующие обновленные файлы из реестра.

Хотя реестр жизненно важен для использования утилиты CLI, при упоминании npm в этой статье будет иметься ввиду именно инструмент CLI.

Настройка Node

Прежде чем показать, как устанавливать Node, поясним некоторые детали, касающиеся процесса выпуска версий Node.

Существует два варианта установки:

  • LTS
  • Current

LTS означает «длительная поддержка» и считается наиболее «стабильным» релизом, рекомендуемым для использования в продакшене. Это связано с тем, что релизы LTS подлежат исправлениям критических ошибок и улучшениям даже после выхода новой версии. Релизы LTS часто поддерживаются годами.

Current - это «текущие» версии, в которых обычно реализованы новые функции и которых может не быть в версии LTS. Они часто используется для экспериментов и тестирования всего нового перед очередным выпуском LTS.

NodeJS переключается между стабильными версиями LTS и не-LTS. Например, Node 12 и 14 были LTS-релизами, а Node 13 и 15 - нет. Узнать больше об их цикле выпуска можно на их странице.

Установка Node

Вы можете найти файлы Current и LTS, пригодные к установке, на веб-сайте NodeJS. Просто скачайте нужный и установите его. Если вы не уверены, какую версию Node использовать, придерживайтесь версии LTS.

Инсталляторы Node поставляются с их собственной версией npm, поэтому можете не беспокоиться, что вам придется устанавливать ее отдельно.

Использование Node

Теперь, когда все настроено, давайте рассмотрим, как, собственно, использовать Node.

Во-первых, начните с открытия терминала. В macOS вы можете найти свой терминал, открыв средство поиска и набрав "Terminal". В Windows есть свой, однако лучше установить отдельный, заточенный под разработку (напр. cmder).

Открыв терминал, выполните следующую команду:

node

После вы должны увидеть курсор на новой строке:

>

Дальше вы можете писать JavaScript и запускать его выполнение с помощью клавиши ENTER. Например:

> console.log('Привет мир');

Отработка JS-файлов в Node

Скрипты и команды, введенные в среде Node, относятся к так называемой форме REPL (read-eval-print-loop: чтение-вычисление-вывод-цикл) и являются поверхностными. Основное использование Node вступает в силу при запуске файлов JavaScript.

Чтобы увидеть, как это работает, создайте файл в пустой папке с именем index.js. Затем напишите в нем какой-нибудь скрипт:

// index.js

const randomNumber = Math.random() * 100;

if (randomNumber > 75) {
  console.log("Ты просто счастливчик! Получи 100 баллов!");
} else if (randomNumber > 50) {
  console.log("Тебе повезло! Получи 50 баллов!");
} else if (randomNumber > 25) {
  console.log("Получи 25 баллов!");
} else {
  console.log("Тебе не повезло. Пока без баллов.");
}

Затем в терминале перейдите в каталог, в котором находится файл index.js, и запустите команду:

> node index.js

Результатом будет вывод одного из сообщений, в зависимости от того, какое случайное число сгенерировалось. После будет выполнен выход из среды Node ввиду содержания скрипта.

Скрипты могут работать постоянно, включать серверы (REST, GraphQL), осуществлять наблюдение за файлами или просто находиться в фоне. Выход/сброс такого процесса можно выполнить комбинацией клавиш Ctrl+C.

NVM

Установка Node с нуля не должна быть проблематичной, однако процесс обновления и изменения уже имеющейся версии NodeJS может быть сложным. Поэтому рекомендуется использовать NVM для управления версиями Node.

Несмотря на то, что Node имеет довольно стабильный API, могут возникнуть случаи, когда требуется быстрое обновление или изменение уже установленной версии Node. Например, некоторые библиотеки одного проекта работают только с определенными версиями Node, в то время как в другом проекте функционал корректно отработает лишь с новыми версиями Node.

Windows, macOS и Linux имеют возможность установить программу под названием NVM, которая как раз позволяет управлять версиями Node с помощью команд CLI. Например, команда:

nvm install --lts

установит последнюю стабильную версию LTS. А команда:

nvm use --lts

переключит версию Node на LTS.

Кроме того, есть возможность установить конкретные версии Node и переключаться между ними.

Переключение версий Node

Как упоминалось выше, NVM - полезный инструмент для переключения версий Node, но есть кое-что, на что следует обратить внимание, прежде чем это делать. При переключении версий Node происходит сброс глобально установленных пакетов. Это означает, что если вы запустили:

npm i -g create-react-app

и при этом текущая установленная версия Node - 12, то когда вы переключитесь на Node 14 и попытаетесь запустить команду create-react-app, вы увидите сообщение "cannot find that package" ("не удается найти этот пакет").

Также стоит отметить, что некоторые пакеты (например, sass) имеют собственные зависимости. Это означает, что в зависимости от установленной версии Node они запускают определенные команды. Из-за этого, при переключении, например, с Node 12 на Node 14, вам может потребоваться повторно запустить npm i для ваших пакетов, прежде чем запустить свое приложение.

NVM для Windows

Стоит отметить, что NVM для Windows не поддерживает те же команды, что и для macOS и Linux. Поэтому, при ознакомлении с инструкциями для NVM, вам, возможно, придется найти альтернативные версии команд для системы Windows.

Как работать с NVM и какие команды использовать для переключений версий Node в системе Windows описано в отдельной статье.

Обновление npm

Версия npm, поставляемая с Node, обычно достаточно хороша для 99,99% случаев использования. Однако как и в любом другом программном обеспечении, в новые версии npm добавляются исправления ошибок и новые функции. Вы можете следить за официальным блогом npm, чтобы знать о новых функциях и исправлениях ошибок, представленных в версиях.

По иронии судьбы метод обновления npm заключается в использовании самого npm:

npm i -g npm@latest

Имейте в виду, что если вы переключаете версии Node с помощью NVM, вам нужно будет повторно запустить эту команду для каждой версии установленного Node, так как переключение Node также переключает установленную версию npm.

yarn

npm - не единственный способ, когда дело доходит до установки пакетов для использования в веб-разработке. Одной из самых мощных альтернатив npm является менеджер пакетов yarn.

У yarn нет собственного реестра, поэтому когда вы устанавливаете библиотеки с помощью yarn, вы используете реестр npm и инструмент CLI yarn. По сути, это просто другой метод извлечения, обслуживания и обработки пакетов в вашей локальной системе, а не их содержимое или функциональность.

Чтобы установить библиотеку с помощью yarn, необходимо запустить:

yarn add library-name

Для командной строки CLI npm, альтернатива будет в виде:

npm i library-name

Несмотря на то, что и та и другая команда установят одну и ту же библиотеку, способы установки пакетов npm и yarn достаточно различны. Поэтому для некоторых проектов, специально построенных на функциональности yarn, просто заменить yarn на npm без какой-либо переделки не получится. Сами различия между npm и yarn многочисленны и имеют нюансы. Они, в основном, связаны с версионированием пакетов, скоростью и надежностью. Однако фокус этой статьи не на них. Важно понимать, что большинство проектов могут обойтись с npm, однако если проект предписывает вам использовать yarn для настройки вашей среды разработки, обычно для этого есть веские инженерные причины.

Установка yarn

После того как вы установили Node и npm, установить yarn так же просто:

npm i -g yarn

Стоит отметить, что, как и в случае с npm, при изменении версии Node с помощью NVM, вам потребуется повторно запустить эту команду.

В случае возникновения проблем с установкой, существуют альтернативные способы, описанные под каждый вид ОС, на сайте-документации по yarn.

Использование npm/yarn

Уяснив базы в использовании Node, мы можем расширить свои возможности, научившись эффективно использовать npm/yarn.

Начнем с файла package.json.

Когда вы клонируете проект, вы можете увидеть в корне файл с именем package.json, который будет выглядеть примерно так:

{
  "name": "my-project",
  "description": "My test project",
  "version": "0.1.0",
  "scripts": {
    "start": "node index.js",
  },
  "dependencies": {
    "classnames": "^2.1.3"
  },
  "devDependencies": {
    "prettier": "^1.19.1"
  }
}

Собственно, это то, как npm может отслеживать, какие версии каких библиотек используются в вашем проекте и на основе этого устанавливать их и их зависимости. Кроме этого, тут можно задать свой список команд для выполнения нужных нам скриптов и указать метаданные проекта.

Если вы начинаете свой проект с нуля, вы можете создать новый файл package.json, используя команду:

npm init

или

yarn init

Зависимости (Dependencies)

Большинство проектов, с которыми сталкиваются разработчики, имеют по крайней мере одну зависимость. Зависимость - это библиотека, от которой зависит функциональность проекта. Например, мы используем библиотеку classnames для создания удобных для CSS имен классов с помощью JavaScript:

var classNames = require('classnames');
classNames('foo', 'bar');

Код выше требует установленный пакет библиотеки, иначе мы увидим ошибку Error: Cannot find module 'classnames'.

Если в нашем объекте зависимостей package.json эта библиотека указана, то она будет находиться в этом разделе:

"dependencies": {
  "classnames": "^2.1.3"
},

Теперь нам достаточно просто запустить npm i или yarn install для установки всех пакетов, включая наш.

Если в package.json в разделе зависимостей ее нет, то ее необходимо установить командой npm install classnames или yarn add classnames.

Семантическое версионирование

У каждой зависимости есть число с тремя точками, связанными с ней. Эти числа представляют собой версию библиотеки, которую необходимо установить при запуске npm i. И хотя вы можете использовать эти числа произвольно, большинство проектов следуют стандарту, называемому «Семантическое версионирование» (сокращенно «SemVer»).

Основы SemVer можно разбить на три части:

  1. Основная версия (MAJOR)
  2. Минорная версия (MINOR)
  3. Версия исправления (PATCH)

Таким образом, версия пакета может выглядеть примерно так: MAJOR.MINOR.PATCH. Например, пакет "classnames": "^2.1.3" имеет «основную версию» 2, «минорную версию» 1 и «версию исправления» 3.

Рассмотрим, что означает каждая из версий.

Версия PATCH может содержать обновления документации, исправления ошибок, исправления безопасности или что-либо еще, что не добавляет функциональности или не приводит к критическим изменениям.

Версия MINOR обычно представляет собой обновление функциональности. В этом релизе в библиотеку добавлены некоторые новые функции без каких-либо критических изменений.

Версия MAJOR - это более глобальное обновление, которое часто требует изменение вашего кода. Такие изменения библиотеки называются критическими. В больших библиотеках критические изменения не выходят в виде мини-релизов, а накапливаются для создания основного глобального релиза, дополненного документацией о них и о том, как необходимо изменить ваш код, чтобы они возымели действие.

Если мы еще раз взглянем на строку зависимости нашего пакета "classnames": "^2.1.3", то обратим внимание еще на одну вещь - странный символ, который не является числом ^. Это символ, который npm понимает как «вы можете установить любую минорную версию classnames, которая будет выше 2.1.3».

Например, наша библиотека classnames имеет следующие версии релизов:

2.1.2
2.1.3
2.1.4
2.2.0
...
2.2.6
3.0.0

Имея версию зависимости ^2.1.3, для установки будут доступны следующие версии:

- 2.1.2
+ 2.1.3
+ 2.1.4
+ 2.2.0
+ ...
+ 2.2.6
- 3.0.0

Это позволяет нам установить версию, на функциональность которой мы полагаемся, не беспокоясь о глобальных изменениях.

Однако ^ - не единственный символ, который вы можете использовать, чтобы указать менеджеру пакетов, какую версию установить. Вы также можете использовать символ ~, который означает, что вы хотите установить ближайшую PATCH-версию. Для такой конфигурации "classnames": "~2.1.3" для установки будут доступны версии:

- 2.1.2
+ 2.1.3
+ 2.1.4
- 2.2.0
- ...
- 2.2.6
- 3.0.0

Модификаторы ^ и ~ - наиболее часто встречающиеся. Однако существуют и другие, обладающие более детальным указанием требуемой версии. Подробнее о них можно почитать на странице SemVer калькулятора.

Зависимости для разработки (Dev Dependencies)

Если мы еще раз обратимся к нашему ранее рассмотренному package.json, то увидим наряду с элементом dependencies еще один - devDependencies.

"dependencies": {
  "classnames": "^2.1.3"
},
"devDependencies": {
  "prettier": "^1.19.1"
}

Раздел devDependencies содержит собственный список библиотек. Тут находятся зависимости для разработчиков.

Основное отличие этих разделов в том, что в dependencies перечислены библиотеки, которые вы используете в коде своего проекта, а в devDependencies перечислены библиотеки, которые вы используете для своей среды разработки. Например, библиотека prettier служит для того, чтобы поддерживать единый стиль кода для всех ваших файлов JavaScript. Для функционала приложения она бесполезна, поэтому и попадает в соответствующий раздел.

Различия этих разделов чрезвычайно важны для библиотек. Библиотека из dependencies будет учитывать все остальные там находящиеся, что увеличит размер установленных ей файлов. А если она не будет нужна в продакшене, то это и вовсе будет зря у нас забирать место. Библиотека из devDependencies просто будет находиться на компьютере пользователя.

Равные зависимости (Peer Depenedencies)

Как мы уже выяснили, зависимости невероятно полезны, однако если вы используете такую ​​​​инфраструктуру, как React, установка для каждой зависимости своей версии React может вызвать проблемы. Наличие разных версий React будут выполнять различные функции, в результате чего ваши node_modules (папка с установленными зависимостями) будут раздуты.

Концепция peerDependencies состоит в том, чтобы позволить клиентским проектам иметь одну установленную версию зависимости, которая совместно используется другими зависимостями. Например, мы используем на проекте React. Это значит, что он уже у нас будет находится среди зависимостей в файле package.json. Дальше, к примеру нам необходима библиотека, созданная с помощью JSX, которая в качестве зависимости тоже требует установку React. Т.е. уже React у нас установился два раза. Потом появляется еще один суперплагин, который тоже требует React. Согласитесь, даже звучит это немного странно. Как раз во избежание этого, нам достаточно поместить в peerDependencies один раз зависимость react, к которой будут обращаться все другие зависимости при необходимости:

"peerDependencies": {
  "react": "^17.0.2"
}

Стоит отметить, что в npm 6 приходилось устанавливать это все самостоятельно. Однако в npm 7 было внесено изменение, заключающееся в том, что удаленные узлы устанавливаются автоматически. Если вы видите сообщение об ошибке пакета, говорящее о том, что ваши равные зависимости не совпадают, правильным решением будет зайти на страницу пакета и создать pull request, чтобы авторы добавили корректные версии зависимостей.

Игнорирование node_modules

После того как вы установили свои пакеты (не важно, с помощью npm или yarn), вы можете обратить внимание, что у вас появилась папка node_modules. В эту папку как раз и устанавливаются все ваши зависимости и их зависимости.

Важным фактором является то, чтобы вы позаботились об игнорировании этой папки при загрузке проекта на репозиторий или сервер. Добавление в репозиторий node_modules чревато тем, что вы увеличите размер кодовой базы вашего репозитория, замедлите клонирование вашего проекта, усложните обработку кода, сломаете системы CI/CD и т.д.

Обычно игнорирование осуществляется путем добавления папки в .gitignore, либо просто старайтесь не упаковывать ее в передаваемый архив.

lock-файлы

Когда зависимости установлены и вы увидели новую папку node_modules, среди прочих можно заметить еще один новый файл - package-lock.json. Этот файл как раз и называется lock-файлом. Он автоматически генерируется с помощью npm и не должен изменяться вручную.

Если вы использовали yarn, вместо package-lock.json будет файл yarn.lock. Цель его та же, и обращаться с ним следует так же.

В то время как package.json описывает, какие версии зависимостей вы бы предпочли установить, package-lock.json указывает, какая версия каждой зависимости была выбрана и установлена. Это позволяет использовать такую команду, как npm ci для установки зависимостей, непосредственно опираясь на информацию в lock-файле, что приведет к установке тех же версий пакетов, что были установлены ранее.

Это может быть невероятно полезно для отладки проблем с разрешением пакетов, а также для проверки того, что ваш CI/CD отрабатывает желаемым образом.

Если папку node_modules включать в репозиторий не нужно, lock-файл, наоборот, желательно сохранять.

Следует иметь в виду, что в разных версиях npm lock-файлы формируются по-разному. И если часть вашей команды использует npm 6, а другая часть использует npm 7, вы обнаружите, что каждая команда будет его перетирать после установки npm i. Поэтому, во избежание подобного, следует убедиться, что все члены команды использует одну и ту же основную версию npm.

Scripts

Вы наверняка заметили, что в рассмотренном выше package.json имеется элемент scripts. Тут мы можем описать команды и выполняемые с помощью них скрипты. Для нашего примера, если в консоли ввести npm run start или yarn start, выполнится скрипт node index.js, который запустит файла index.js с помощью Node. Вы можете использовать любую команду, допустимую на вашем компьютере. Например:

"scripts": {
  "start": "gatsby build"
}

Ограничений одной командой нет, поэтому скриптов можно оформить сколько угодно:

"scripts": {
  "build": "gatsby build",
  "develop": "gatsby develop",
  "lint": "eslint ./src/**/*.{ts,tsx}",
  "start": "npm run develop",
  "test": "jest"
}

Большинство проектов содержат скрипты для таких вещей, как сборка проекта для разработки/для продакшена, запуск сервера, запуск линтеров и многих других.

Вывод

Данная статья является всего лишь введением в npm, yarn и Node. С этой информацией вы будете иметь больше контекста, когда дело дойдет до реального управления вами зависимостями и использования JavaScript в веб-проектах. В качестве практики, просмотрите некоторые проекты с открытым исходным кодом на GitHub, чтобы вручную пощупать, как они работают.