Обзор npm 7

2a7a9ce150f75229108a5c0b104bcfa5.png

13 октября состоялся релиз npm@7.0.0. Релиз носит экспериментальный характер и доступен для скачивания из публичного npm-реестра с тегом next-7. Также npm 7 поставляется вместе с node.js 15.

Как вы помните, нечетные версии node.js тоже скорее являются нестабильными предрелизами. Только четные версии получают статус LTS и рекомендуются для использования в продакшене. Но сейчас мы поговорим исключительно об npm, а если быть более точным, то разговор пойдет о нескольких очень ожидаемых фичах, вошедших в свежий релиз.

1. Workspaces

Вне сомнений, npm workspaces — самая ожидаемая фича npm@7. Workspaces позиционируется как набор инструментов для управления несколькими пакетами из одного верхнеуровнего, проще говоря — для управления монорепозиторием (proof). 

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

Для начала нужно добавить свойство workspaces в package.json корневого пакета. В нем нужно перечислить все дочерние пакеты в репозитории:

{
  "name": "root-project",
  "workspaces": [
    "workspace-a",
    "packages/*"
  ]
}

При запуске команды npm installв корневые node_modules будут добавлены симлинки на все пакеты из рабочей области. Также будут установлены зависимости, описанные в package.json каждого дочернего пакета, и сформирован package-lock.json новой версии, поддерживающей монорепозиторий.

Важно отметить, что выполнение команды npm ci проигнорирует новое свойство в package.json и установит только то, что найдет в package-lock.json.

И на этом весь инструментарий заканчивается. То есть npm пока умеет только резолвить зависимости. Например, вот так будет выглядеть команда установки модуля babel-core с помощью lerna в пакет монорепозитория awesome-package:

lerna add babel-core --scope=awesome-package

Чтобы сделать аналогичное действие с npm, придется немного поприседать и для начала установить в пакет awesome-package пакет babel-core с помощью флага --prefix:

npm i babel-core --prefix=packages/awesome-package

Эта команда добавит зависимость в package.json, создаст package-lock.json и node_modules в корне пакета. Но нужно понимать, что в таком случае npm знает только о пакете и зависимостях из packages/awesome-package/package.json и точно ничего не знает о воркспейсе. Чтобы все встало на свои места придется еще раз запустить полную установку моделей в корне проекта командой npm i

Так npm пройдется по всем пакетам в проекте и зарезолвит модули «правильно» с учетом настроек воркспейса. Но этот способ обречен на провал, как только в одном из пакетов проекта обнаружится зависимость от другого пакета из этого же проекта. В таком случае есть риск получить подобную ошибку:

f59ea43d2d28d8bd59f19c2aaf3ad92f

Эта ошибка говорит о том, что пакет lib-a отсутствует в используемом npm-реестре. Но этот пакет существует только в рамках проекта. А что, если пакет с таким именем будет найден в реестре? Тогда в ваш проект будет установлен совсем не тот пакет, который вы ожидали.

Но, несмотря на скудность инструментария, npm workspaces — отличная отправная точка для создания удобного CLI для работы с монорепозиториями.

2. Автоматически устанавливаемые peerDependencies

До npm@7 пользователю приходилось самому устанавливать peerDependencies, npm мог кинуть предупреждение об отсутствии или неправильных версиях таких зависимостей.

Как и следует из заголовка, в npm@7 при установке зависимостей peerDependencies будут резолвиться автоматически. Кроме того, npm будет вываливать ошибки о несоответствии версии уже установленной библиотеки и версии из peerDependencies и прерывать процесс установки. 

Это достаточно серьезные ломающие изменения, и не во всех кейсах разработчики с ходу смогут разрулить все конфликты версий. Поэтому разработчики предусмотрели флаг --legacy-peer-deps, который отключает поведение автоустановки.

Очень коротко о мотивации внедрения фичи: автоматическая установка решает проблемы с неправильным резолвом зависимостей и является частью алгоритма «максимально простой дедупликации». Более подробно о мотивации можно почитать вот здесь.

3. Поддержка yarn.lock

Дано: проект с package.json и yarn.lock.

Действия: запускаем npm install в корне проекта и видим следующее:

  1. yarn.lock все там же.

  2. Появился package-lock.json.

  3. package.json остался нетронутым.

Фактически это работало так же и в npm 6. Тогда в чем заключается поддержка yarn.lock?

При запуске npm не находит нативный для себя lock-файл, но находит yarn.lock. Конечно же, считывает его и на основе полученной информации устанавливает зависимости и формирует package-lock.json. Интересно, что после формирования package-lock.json-файла npm больше не обращается к yarn.lock.

Кажется, что команда npm перепутала слово «поддержка» со словом «миграция». Но мигрировать с yarn на самом деле станет более безопасно.

4. The standalone npx is deprecated

Это значит, что автономные версии npx более не будут поддерживать. Под капотом npx будет обращаться к npm exec и не будет иметь своего собственного алгоритма запуска.

npm exec, что ты за зверь такой?

npm execпозволяет вам запускать произвольную команду из пакета npm (установленного локально или извлеченного удаленно) в контексте, аналогичном запуску через npm run 

Тут есть одно большое отличие от npx ранних версий: npm execявно разделяет аргументы вызываемой утилиты и собственные с помощью --

Для примера:

npx foo@latest bar --package=@npmcli/foo

Это будет соответствовать вот такой команде:

foo bar --package=@npmcli/foo

Флаг --packageбыл передан дальше, потому что npx считает, что после позиционного аргумента идут аргументы самой программы. В случае использования npm exec:

npm exec foo@latest bar --package=@npmcli/foo

Это будет соответствовать такой команде:

foo@latest bar

Чтобы получить эквивалентную команду, нужно выполнить:

npm exec -- foo@latest bar --package=@npmcli/foo 

И еще одно важное отличие от npx ранних версий с точки зрения безопасности: если запускаемый пакет отсутствует в зависимостях проекта, то npm спросит о необходимости запуска команды. Это поведение можно предупредить с помощью флагов --yes и --no

5. Бонус-фича из npm@7.1 — npm set-script

Маленькая, но приятная фича из npm@7.1. npm set-script позволяет устанавливать команды в секции scripts в файле package.json.

Например, с помощью этой фичи вы сможете не покидая терминала установить зависимость, прописать ее использование в секции scripts и сразу запустить через npm run:

npm init -y && npm install --save-dev http-server && npm set-script start "http-server ."&& npm start

Вместо заключения

Долгожданная седьмая версия npm получилась неоднозначной.

С одной стороны — ломающие изменения установки peerDependencies, с другой — workspaces и более безопасный npx. Но на это как раз и нужны developer preview релизы.

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

Полезные ресурсы

  1. Workspaces

  2. Предложение и мотивация изменения алгоритма установки peerDependencies

  3. Why keep package-lock.json?

  4. npm-exec

  5. npm set-script

© Habrahabr.ru