Как находить и удалять неиспользуемые npm‑зависимости с depcheck

Короткий маршрут к чистому package.json — аккуратный прогон depcheck, проверка сборки и автоматизация рутин. В качестве ориентира подойдёт материал Как выявить и удалить неиспользуемые зависимости в npm с помощью depcheck: практический пример, где шаги разложены по полочкам. Ниже — связная картина процесса, его подводные камни и способы уверенно пройти до конца.

В типичном репозитории зависимости копятся, как пыль на полках: незаметно, но ощутимо. Новые библиотеки улучшают скорость старта, однако со временем превращаются в груз, удлиняющий установку, усложняющий билды и увеличивающий площадь атаки уязвимостей. Чистка перестаёт быть «наведением порядка» и становится профилактикой долгов — почти гигиеной проекта.

Depcheck предлагает простой жест: посмотреть на код и сопоставить его с тем, что заявлено в package.json. На поверхности — список подозрительных пакетов. Но настоящий эффект даёт не механическое удаление, а грамотная проверка: учёт скриптов, динамических импортов, конфигов и особенностей экосистемы. Там, где кажется очевидным, чаще всего и прячутся ловушки.

Зачем очищать зависимости и что даёт depcheck

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

Реальные проекты стареют не из-за архитектуры, а из-за незаметных мелочей: забытых билд-инструментов, мёртвых плагинов и библиотек, которые перестали использоваться после миграций. Каждая лишняя запись в dependencies — это секунды на CI, мегабайты в кэше и потенциальные конфликты при обновлениях. Depcheck не панацея, но отличный прожектор: он показывает, где код не зовёт заявленный пакет и где, наоборот, код зовёт то, чего нет в package.json. После первого прогона список кажется пугающим, однако за ним обычно скрывается понятная рутина — привести в порядок категории (dependencies/devDependencies), проверить использование в npm‑скриптах, конфигурациях и динамических сценариях. Регулярный просмотр вычищает технический сор до того, как он станет долгом, а команда перестаёт бояться апдейтов.

Как работает depcheck и где он ошибается

Инструмент анализирует исходники, ищет импорты и require, соотносит их с package.json и формирует отчёт. Ложные срабатывания возникают там, где код подгружает модули динамически или через конфиги и бинарники.

Depcheck парсит дерево файлов и, опираясь на набор детекторов, пытается понять, действительно ли проект обращается к каждой заявленной библиотеке. Картина ясна, когда импорты статичны и видны парсеру. Но экосистема Node.js жива: плагины доставляются через конфигурационные файлы, исполняемые бинарники вызываются из npm‑скриптов, а модули тянутся по алиасам из TypeScript. В таких сценариях анализатор не всегда складывает пазл. Потому и важно правильно настроить детекторы (typescript, jsx, postcss, eslint и другие) и явно игнорировать то, что по определению не должно считаться «живым импортом» — например, плагины, упомянутые в JSON‑конфигах, или CLIs, используемые только в скриптах.

Что именно depcheck считает «неиспользуемым»

Неиспользуемым считается пакет из package.json, который не вычисляется как импортируемый в исходниках по правилам детекторов. При этом скрипты, конфиги и бинарники требуют дополнительных подсказок.

Под капотом — набор извлекателей импортов и конфиг‑парсеров. Когда импорты идут стандартно — import lodash from ‘lodash’ — всё прозрачно. Но как только появляется вызов через глобально установленный бинарник в скрипте «build», или плагин указывается строкой в webpack.config.js, без специализированного детектора инструмент остаётся «слепым». Сценарии с require по вычисляемой строке — ещё один ахиллесов пятка. Поэтому зрелые команды не ждут магии: они расширяют конфиг depcheck, добавляют собственные детекторы или вносят исключения для плагинов и CLIs, которые присутствуют ради процессов, а не ради импортов в коде приложения.

Почему возникают ложные срабатывания

Главные причины — динамические импорты, алиасы путей, конфиги сторонних инструментов и use‑case, где пакет используется лишь как CLI. Все эти случаи корректируются настройкой depcheck.

Наглядный пример — TypeScript с путями в tsconfig.json. Импорты типа @core/utils указывают на локальные модули, и парсер видит их как внешние. Или наоборот: PostCSS‑плагины подключаются в postcss.config.js, а в коде приложения не упоминаются. Аналогично ведут себя babel‑плагины, eslint‑плагины, stylelint‑конфиги и линтеры, запускаемые только из package.json scripts. В итоге анализатор помечает их как «мёртвые», пока не увидит, как они используются на практике. Исправляется это включением соответствующих детекторов, белых списков и учётом шаблонов имен плагинов, чтобы связь «конфиг — пакет» стала явной.

Ситуация Что видит depcheck Как исправить
CLI‑пакет, вызываемый в npm‑скрипте Неиспользуемая зависимость Добавить в игнор или включить детектор scripts/bin
Плагин в конфиге (babel, postcss, eslint) Неиспользуемая зависимость Включить профиль детекторов для соответствующего инструмента
TypeScript с алиасами путей Ложные «missing» и «unused» Настроить tsconfigPathAliases/resolve, доп. детекторы
Динамический import/require Неопределённое использование Явно задать исключения или рефакторить к статическим импортам
Peer/optional зависимости Смешение ролей, «лишние» пакеты Проверить корректность peerDependencies/optionalDependencies

Пошаговый сценарий: безопасная чистка в реальном проекте

Надёжный сценарий состоит из инвентаризации, настройки depcheck, удаления и валидации сборок. Каждый шаг опирается на проверяемые сигналы и допускает быстрый откат.

Практика показывает: чистка удалась, когда скорость сборки не упала, тесты зелёные, а локфайл не взрывается каскадом обновлений. Сценарий опирается на простые действия. Сначала фиксируется текущее состояние — lockfile коммитится, версии зависимостей и узкие места записываются в краткий чек‑лист. Затем depcheck запускается с нужными детекторами и игнорами, формируя два списка: кандидаты на удаление и спорные элементы. После этого лишнее удаляется целенаправленно, а проект прогоняется через линтеры, типизацию, юнит‑ и e2e‑тесты, проверку продовой сборки. Любая тревожная метрика — сигнал к откату одного изменения, а не ко всему пул‑реквесту.

Подготовка: инвентаризация, локфайл, точки контроля

Перед чисткой фиксируется базовая линия: lockfile, версии Node и менеджера пакетов, ключевые метрики CI. Это упрощает сравнение и ускоряет откаты.

Типовой набор включает сохранение package-lock.json/yarn.lock/pnpm-lock.yaml, проверку версии Node, менеджера (npm/yarn/pnpm) и доступности кэша на CI. Полезно сверить длительность стадий install, build и test в последнем успешном прогонах. Ещё один маркер — число уязвимостей в отчётах SCA. Когда базовая линия записана, каждое следующее изменение считывается как дифф, а не как интуитивная оценка.

Запуск depcheck с детекторами и игнорами

Depcheck следует запускать с учётом стека: TypeScript, JSX, конфиги сборщиков и линтеров, шаблоны CLI. Корректно настроенные детекторы уменьшают шум почти вдвое.

Полезно собрать команду запуска в npm‑скрипт, чтобы одинаково воспроизводить результат локально и на CI:

{
  "scripts": {
    "depcheck": "depcheck --specials=ts,jsx,babel,eslint,postcss --ignores=\"@types/*,eslint-*,stylelint-*,postcss-*,babel-plugin-*\""
  }
}

Игноры лучше документировать в README рядом с обоснованием: «плагины подключаются из конфигов, не видны парсеру». Для монорепозиториев удобно указывать рабочие каталоги или запускать depcheck пакетно. Если проект использует алиасы путей, добавляется конфиг резолвера или временно включается плагин, распознающий tsconfig paths. Там, где остаются спорные случаи, принимается консервативное решение — вначале протоколировать, а удалять только при полной уверенности.

Удаление, тестирование и откат

Удалять безопасно партиями по 1–3 пакета с немедленной проверкой билдов. При малейшей регрессии откатывается только последняя партия.

Подход с малыми инкрементами дисциплинирует и ускоряет обратную связь. После удаления повторяется установка, типизация, линтеры, юниты и e2e. Для приложений с бандлером дополнительно проверяется продовая сборка и запуск локально в режиме production, чтобы поймать ленивые импорты. Если менеджер — pnpm, полезно очищать кэш сторы между партиями. Когда серия изменений пройдена, коммит сопровождается краткими заметками: какие пакеты удалены и почему, какие игноры добавлены, где наблюдались ложные срабатывания. Это экономит часы следующему человеку, оказавшемуся у руля.

  • Зафиксировать lockfile и версии инструментов.
  • Запустить depcheck с профильными детекторами и игнорами.
  • Сформировать список кандидатов и спорных случаев.
  • Удалять малыми партиями, прогоняя все проверки.
  • Вести заметки об исключениях и причинах решений.

Монорепозиторий, воркспейсы и границы пакетов

В монорепозиториях чистка требует учёта границ пакетов и общих зависимостей. Depcheck запускается по каждому пакету, а общие зависимости проверяются отдельно.

Структуры на базе workspaces (npm/yarn/pnpm) добавляют новый слой: есть корневые зависимости, есть локальные пакеты, есть взаимные peer‑связи. Ошибка в определении владельца зависимости превращается в плавающие баги. Поэтому depcheck запускается на каждом пакете с его собственным package.json, а для корня используется отдельный профиль игноров, закрывающий инфраструктурные инструменты — линтеры, билд‑систему, релизные кли. Дополнительно проверяются импорт‑границы: локальные пакеты должны публиковать API, а не кражи внутренних файлов друг друга через относительные пути. Такой режим держит монорепозиторий в форме, а сборщик получает меньше поводов накачивать кэши мусором.

Менеджер и воркспейсы Особенности чистки Замечания
npm workspaces Явные границы пакетов, общий lockfile Запуск depcheck на каждом пакете; отдельный профиль для корня
Yarn workspaces Hoisting зависимостей к корню Проверить, кому «принадлежит» зависимость; следить за nohoist
pnpm workspaces Изолированные сторы, симлинки Ложные «unused» реже; полезна очистка кэшей между партиями

Особенности npm/yarn/pnpm workspaces

Главное — источник истины о владельце зависимости. Если пакет А импортирует библиотеку X, то X должна быть объявлена в package.json пакета А, а не у соседа и не в корне.

Hoisting может маскировать ошибку: код работает, потому что библиотека нашлась «выше», но логика принадлежности нарушена. Depcheck лишь отражает факт использования, поэтому дополнительно проверяется consistency зависимостей: import — в том же пакете, где объявлена версия. Практика — включать в CI проверку, запрещающую «чужие» импорты и требующую явного peerDependency, если пакет только «поддерживает» библиотеку, но не зависит от неё напрямую. Так дисциплина монорепозитория превращается в автоматизированные правила, а не в устные договорённости.

Автоматизация: CI, pre-commit и защита от регрессий

Стабильность даёт автоматика: depcheck в CI, контроль категорий зависимостей, тесты и отчёты. Локально помогают pre-commit хуки и единый npm‑скрипт.

Чистка, превращённая в процесс, перестаёт зависеть от энтузиазма. Depcheck можно запускать по расписанию или при изменениях package.json, складывая результаты в артефакты или комментарии к pull request. В связке работают линтеры импортов (eslint-plugin-import/no-extraneous-dependencies), типизация и smoke‑сборка prod‑режима. Если задача — удерживать бюджет зависимостей, в репозиторий добавляется простой счётчик: число записей в dependencies и devDependencies, пороги и уведомления. Тогда каждый новый пакет проходит через вопрос «зачем», а не через привычку «на всякий случай».

Этап Инструменты Цель проверки
Pre-commit depcheck — с быстрым профилем; линтер импортов Поймать явные огрехи до пуша
CI — PR Полный depcheck, tsc, unit/e2e, prod‑build Подтвердить безопасность удаления
Ночной прогон depcheck + отчёт, SCA, метрики Долговременная гигиена зависимостей

Скрипты npm, GitHub Actions и pre-commit хуки

Один скрипт — один источник правды. В package.json удобно собрать команды для локального прогона и для CI, чтобы поведение не расходилось.

Минимальный набор включает «depcheck», «depcheck:strict» и «depcheck:report». Первый — быстрый профиль для разработчиков; второй — строгий для CI с нулевой терпимостью к ошибкам; третий — формирует отчёт в артефакты. В GitHub Actions job удобно разбить по матрице менеджеров пакетов или Node‑версий, если поддержка кросс‑сред вызывает вопросы. В pre-commit запускается только быстрый профиль, чтобы не замедлять цикл обратной связи. Такой расклад уравновешивает скорость и строгость.

  • Собрать быстрый и строгий профили depcheck.
  • Добавить проверку категорий зависимостей через eslint‑plugin‑import.
  • В CI фиксировать артефакт отчёта и тайминги стадий.
  • Хуки запускать на «дешёвых» задачах; полноту оставлять CI.

Нюансы экосистемы: TypeScript, ESM/CJS, бандлеры, peerDependencies

Точная настройка нужна там, где экосистема сложнее: алиасы TS, смешанные модули ESM/CJS, плагины бандлеров и роль peer/optional зависимостей. Игнорирование этих нюансов даёт шум и риск.

TypeScript, Next.js, Vite, Webpack и Babel по-разному трактуют пути и плагины. Если парсер не понимает алиасов, он теряет связь между импортом и пакетом. Если бандлер вытягивает плагины из конфигов, depcheck считает их мёртвыми. Наконец, peerDependencies — отдельная история: они сигнализируют «работает с библиотекой, но не тащит её» и не должны записываться как обычные dependencies. Такое смешение ролей влечёт дубли и странные конфликты версий. Поэтому политика простая: статические импорты там, где это возможно; явные peer там, где интеграция; devDependencies для инструментов билда и тестов; optionalDependencies — только если функционал и правда опционален на рантайме.

TypeScript и пути: tsconfig, алиасы, генерация типов

Алиасы путей и генерация типов путают анализ. Решение — резолвер путей для depcheck и дисциплина в структуре модулей.

Если в tsconfig.json определены paths с префиксами @core/*, то импорты не должны выглядеть как внешние. Иначе depcheck сочтёт локальные модули «потерянными», а внешние — «лишними». Плагины резолва или собственная прослойка снимают проблему. Дополнительно учитывается стадия генерации типов: если пакет используется только для tsc и не попадает в рантайм, он остаётся в devDependencies. Такой разрез держит производство в чистоте, а сборочную инфраструктуру — в порядке.

ESM/CJS, бандлеры и плагины

Смешанный мир модулей добавляет притирки. Плагины бандлеров и транспайлеров редко видны в исходном коде приложения и чаще всего живут в конфигах.

В проектах с Vite, Webpack или Rollup значительная часть функционала прячется в конфигурации: загрузчики стилей, хэширование, оптимизация картинок, макросы. Depcheck без специальных подсказок относит эти пакеты к неиспользуемым. Трюк прост: расширить профиль specials (babel, postcss, webpack), а при необходимости — явно прописать исключения для пакетов, чьё использование полностью декларативно. Если конфиг хранится в формате JS, детектору легче; JSON‑конфиги требуют строковых соответствий и регулярных выражений. В обоих случаях помогает документированная белая зона.

peerDependencies, optional и bundleDependencies

Правильная роль зависимостей избавляет от фантомных проблем. Peer — для интеграций, optional — для условного рантайма, bundle — редкость для библиотек.

Если библиотека расширяет React или ESLint, она не должна тянуть их в dependencies — вместо этого указывается peerDependencies c совместимым диапазоном, а для локальной разработки — devDependencies. Optional имеет смысл, когда функционал включается на части инсталляций, и падать без него нельзя. BundleDependencies уместны, когда артефакт должен поехать вместе с набором библиотек, обычно в CLI‑утилитах. Depcheck в этой зоне не судья, а свидетелем: он лишь отражает использование. Правильная классификация исключает «призрачные» лишние пакеты.

Скриптовые бинарники, линтеры и генераторы

Многие инструменты живут только в scripts. Для них заранее определяется политика: оставить в devDependencies и добавить в игнор depcheck.

eslint, stylelint, prettier, commitlint, conventional‑changelog, release‑плагины, тестовые раннеры — почти всё это вызывается из scripts и не импортируется в коде приложения. Без игнора depcheck считает их мёртвыми. Лекарство в двух каплях: хранить такие инструменты в devDependencies и держать компактный игнор‑список с пометкой «используется в scripts». Картина перестаёт быть шумной, а отчёты — становятся полезными.

Сравнение подходов к чистке: depcheck и родные команды

Depcheck — не единственный путь. Родные команды npm и аудит‑инструменты помогают иначе и решают соседние задачи. Сочетание даёт максимальный эффект.

npm ls показывает фактическое дерево, npm prune удаляет лишнее относительно package.json, а аудит‑сервисы указывают на уязвимости. Depcheck же задаёт вопрос «что заявлено, но не используется в коде». Вместе они образуют крепкую сетку безопасности: сначала — привести package.json к правде, затем — урезать инсталлированное до этой правды, и параллельно — держать уязвимости под контролем.

Инструмент Что проверяет Когда полезен
depcheck Сопоставление кода и package.json Очистка «мёртвых» зависимостей
npm ls / yarn list / pnpm list Фактическое дерево установленных пакетов Понимание дубликатов и конфликтов версий
npm prune Удаление неописанных в package.json После правок package.json и lockfile
npm audit / SCA Уязвимости и их пути Регулярная безопасность и апдейты

Отчётность и культура: как закрепить чистоту зависимостей

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

Проще всего начать с числа зависимостей в каждой категории и их динамики по неделям. Второй слой — тайминги install/build/test на CI и их тренды. Третий — качество: сколько «ложных» кандидатов выдаёт depcheck до нужной настройки и сколько — после. Такой ракурс переводит диалог из вкусовых оценок в объективные цифры. Когда таблица наглядно показывает, как одна‑две уборки в квартал возвращают минуты в каждом пайплайне, скепсис улетучивается. Возникает культура бережного отношения к зависимостям — не минимализм ради минимализма, а чистота ради скорости и предсказуемости.

FAQ: частые вопросы о depcheck и чистке зависимостей

Depcheck показал «unused», но пакет используется только в npm‑скрипте. Удалять?

Нет. Использование в scripts — валидный кейс. Пакет следует оставить в devDependencies и добавить в игнор depcheck или включить детектор scripts/bin. Так отчёты станут чище, а сборочный процесс — не пострадает.

Почему depcheck не распознаёт плагины в конфигурациях Babel и PostCSS?

Подключение плагинов часто происходит декларативно через строки в конфиге, а не через импорты в коде. Depcheck без специальных детекторов не видит эту связь. Решение — включить профиль specials для соответствующих инструментов и, при необходимости, расширить игнор‑список.

Как действовать с TypeScript алиасами путей, чтобы depcheck не путался?

Нужно добавить резолвер путей (детектор, понимающий tsconfig paths) и проверить, чтобы внешние пакеты не маскировались под локальные и наоборот. При невозможности — добавить точечные исключения с пояснениями в документации.

Что делать, если после удаления зависимостей сборка падает только в production?

Такая ситуация указывает на ленивые импорты или динамические вызовы, которые не проявляются в dev. Следует откатить последнюю партию удалений, найти место динамической загрузки и либо сделать импорт статичным, либо добавить пакет в исключения depcheck.

Как различать dependencies и devDependencies при чистке?

Зависимости рантайма — в dependencies, инструменты разработки и сборки — в devDependencies. Если библиотека нужна только для тестов, линтинга, сборки или генерации кода, она не должна попадать в production‑слой. Такой разрез уменьшает размер продового окружения и ускоряет деплой.

Имеет ли смысл запускать depcheck в монорепозитории с hoisting?

Да, но по пакетам. Hoisting может маскировать ошибки принадлежности зависимостей. Каждый пакет проверяется отдельно, а для корня применяется отдельный профиль игноров, чтобы инфраструктурные инструменты не давали ложных «unused».

Можно ли полностью автоматизировать удаление «unused» без ручного ревью?

Безопаснее — нет. Автоматизация хороша для отчётности и сигналов, а вот удаление лучше делать малыми партиями с проверкой билдов и тестов. Так исключаются скрытые зависимости через динамику и конфиги.

Итог: чистый package.json как привычка, а не разовая уборка

Чистота зависимостей — не героический рейд, а точная рутинная практика. Depcheck делает невидимые хвосты заметными, но силу даёт настройка: правильные детекторы, дисциплина в ролях зависимостей и автоматические проверки. Когда процесс закреплён, исчезает страх перед апдейтами, ускоряются пайплайны и проступает контроль над сложностью.

How To: короткая дорожная карта действий. 1) Зафиксировать lockfile и метрики. 2) Запустить depcheck с профильными детекторами и составить список кандидатов. 3) Удалять малыми партиями по 1–3 пакета с немедленной проверкой tsc, тестов и prod‑сборки. 4) Настроить игноры для CLI и плагинов из конфигов, выровнять роли dependencies/devDependencies/peer. 5) Включить depcheck в CI и ночной прогон, добавить бюджет зависимостей и отчёт. Такая простая последовательность превращает чистку в стабильную привычку проекта.