Пакеты и версии — Примеры

Пример 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, который надо удалить.