Обзор npm 7
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
пройдется по всем пакетам в проекте и зарезолвит модули «правильно» с учетом настроек воркспейса. Но этот способ обречен на провал, как только в одном из пакетов проекта обнаружится зависимость от другого пакета из этого же проекта. В таком случае есть риск получить подобную ошибку:
Эта ошибка говорит о том, что пакет 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
в корне проекта и видим следующее:
yarn.lock
все там же.Появился
package-lock.json
.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 будет прислушиваться к сообществу и разовьет нативный для экосистемы инструмент в удобном для всех направлении.
Полезные ресурсы
Workspaces
Предложение и мотивация изменения алгоритма установки peerDependencies
Why keep package-lock.json?
npm-exec
npm set-script