Пакеты и версии — Примеры
Пример 1 — Сборка, изменившаяся без изменений кода
Проект объявляет в манифесте диапазоны версий и никогда не коммитит lockfile; через месяцы чистая установка даёт другую программу. Скажите, какая строка это вызвала, а затем сделайте установку воспроизводимой.
{
"dependencies": {
"date-fns": "^3.6.0",
"chart-kit": "latest"
}
}
# черновой проект, lockfile не закоммичен
npm install # заново разрешает диапазоны на сегодня
npm run build # падает из-за изменения, которого никто не делал
Решение
npm install # разрешить один раз, закоммитить package-lock.json
git add package-lock.json
npm ci # удаляет модули, ставит строго по lockfile
Диапазон latest разрешает каждой установке быть другой программой, а ^3.6.0 доверяет каждому релизу 3.x, который опубликует автор. Lockfile хранит точную версию и хеш целостности каждого пакета дерева, включая транзитивные, которых никто не называл, поэтому следующая установка даст то же дерево, что вы тестировали. Закоммитьте его и в автоматизации используйте npm ci: он ставит строго по lockfile и падает, когда манифест и lockfile расходятся, — а расхождение здесь и было причиной, по которой сборка изменилась сама.
Пример 2 — Инструментарий, установленный во время выполнения
Линтер лежит в dependencies, поэтому производственная установка тянет его за собой. Отдельно в дереве оказались две копии библиотеки состояния: разберитесь с обеими и исправьте их.
{
"dependencies": {
"react": "^19.0.0",
"eslint": "^9.0.0",
"state-lib": "^4.0.0"
}
}
npm ls state-lib
# state-lib@4.0.0
# state-lib@3.2.1 <- подтянута peer-зависимостью плагина
Решение
{
"dependencies": { "react": "^19.0.0", "state-lib": "^4.0.0" },
"devDependencies": { "eslint": "^9.0.0" }
}
npm explain state-lib # назвать родителя, который подтянул 3.2.1
dependencies нужны, чтобы приложение работало, а devDependencies — чтобы собрать или протестировать его, поэтому линтер в первом разделе заставляет каждого потребителя ставить весь инструментарий ни за чем. Дубликат — более серьёзная половина: две копии библиотеки состояния означают два независимых хранилища, и компонент, который читает старое хранилище, не увидит обновление, записанное в новое. npm ls и npm explain называют родителя и объявленный им диапазон, и непонятный дубликат превращается в конкретный пакет, который надо обновить или заменить, а не в lockfile, который надо удалить.