Depcheck или dependency-cruiser: что выбрать для зависимостей

Эта статья — ориентир для тех, кто выбирает инструмент анализа зависимостей в проектах на JS/TS. Подробный контекст даёт Сравнение depcheck и dependency-cruiser: какой инструмент выбрать для анализа вашего кода, а ниже — практический взгляд на реальные сценарии, нюансы настройки и архитектурные последствия такого выбора.

Современный фронтенд и бэкенд на Node.js давно превратились в сложный организм, где каждая библиотека тянет за собой ещё десяток. Стоит потянуть одну нитку — и архитектурный свитер теряет форму. Иногда врач нужен простой, чтобы снять жар, иногда — хирург с планом операции. Два популярных инструмента решают две разные задачи, и путаница начинается там, где их пытаются поставить на один пьедестал.

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

Анализ зависимостей как инженерная страховка кода

Инструменты анализа зависимостей спасают проект от расползания и ускоряют принятие решений о рефакторинге. Один смотрит на факт использования пакетов, другой контролирует сами связи между файлами и слоями. Суть — не в моде на утилиты, а в дисциплине, которую они поддерживают.

Код живёт по законам энтропии: зависимости растут, границы стираются, удобные «временные костыли» становятся постоянными. Когда такое происходит незаметно, проект начинает шуметь: сборка тянется, баги приходят из неожиданных мест, миграции пугают масштабом. Анализ зависимостей действует как страховка: напоминает о лишнем весе, указывает на опасные трещины, показывает, где изоляция дала сбой. В практической плоскости задача распадается на две: убрать неиспользуемые пакеты и проверить, не нарушают ли зависимости логику архитектуры. Первую роль честно выполняет depcheck. Вторую — системно, с графом и правилами — берёт на себя dependency-cruiser. Ошибка лишь в попытке просить от одного инструмента работу другого. Там, где нужна карта города, не выручит калькулятор, и наоборот.

Ключевое различие: что умеет depcheck и чего ждут от dependency-cruiser

Depcheck отвечает на вопрос «какие пакеты не используются?», а dependency-cruiser — «как именно модули связаны и соответствует ли это архитектуре». Первый — сканер симптомов, второй — архитектор с правилами.

Depcheck проходит по исходникам и находит зависимости, которые объявлены, но не встречаются в импортах. Результат — конкретный список кандидатов на удаление или перенос. Dependency-cruiser строит граф импортов и проверяет его на согласованность: запрещённые направления, циклы, непубличные входы, нарушение границ слоёв. Это уже не про «убрать мусор», а про «оставить систему целостной». В живых проектах их используют вместе: быстрая гигиена от depcheck и регулярный архитектурный контроль от dependency-cruiser. Перепутать эти роли — значит либо не заметить архитектурную эрозию, либо потратить часы на избыточный ритуал ради разового аудита.

Критерий depcheck dependency-cruiser
Главная задача Поиск неиспользуемых зависимостей Контроль графа импортов и архитектурных правил
Тип результата Список пакетов-кандидатов на удаление/перенос Нарушения правил, циклы, отчёты о связях
Глубина анализа Импорты и вызовы на уровне пакетов Граф модулей, слои, паттерны импортов
Сценарий использования Разовый/еженедельный аудит гигиены Постоянный контроль в CI
Порог вхождения Низкий Средний/высокий

Распределение ролей помогает снять ложные ожидания и выставить правильную рутину. Когда в проекте нет отдельных слоёв и формальной архитектуры, dependency-cruiser будет казаться «слишком строгим». Когда слои уже есть, один depcheck не защитит их от эрозии. Поэтому выбор зависит не от рекламы функций, а от того, на каком этапе зрелости сейчас кодовая база и как быстро команда готова закрепить дисциплину в CI.

  • Нужна срочная уборка package.json — берётся depcheck.
  • Нужно зафиксировать границы слоёв — подключается dependency-cruiser.
  • В монорепо с десятками пакетов — dependency-cruiser плюс лёгкие фильтры шума.
  • В SPA/SSR без формальной архитектуры — начать с depcheck и мягких правил.
  • При миграции на ESM/TS — dependency-cruiser выявляет скрытые циклы и запреты путей.
  • В legacy с кучей лишних пакетов — depcheck первым номером, затем архитектура.

Как depcheck находит мёртвые пакеты и где промахивается

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

В реальной жизни многое упирается в то, как устроен сборочный пайплайн. Если импорты прописаны строками в конфигурации webpack или Vite, если плагины подключаются через массивы, если требуемые модули загружаются динамически, инструмент может дать ложный сигнал. TypeScript тоже добавляет сложности: пути с алиасами, которые резолвятся на уровне компилятора, для наивного анализа выглядят незнакомыми. И всё же ценность depcheck в том, что он мгновенно показывает «мусор», который копится месяцами: старые линтеры, забытые утилиты, библиотеки, заменённые давно, но так и не удалённые. Грамотная настройка исключений частично снимает шум — достаточно указать паттерны для динамических импортов и связать его с реестром скриптов, чтобы учесть CLI-зависимости. Это простой молоток, который хорошо забивает гвозди; не стоит просить от него геодезическую съёмку.

Когда инструмент находит лишнюю зависимость, инженерная вежливость требует не поспешного удаления, а короткой проверки связей. Особенно опасны пакеты, участвующие в генерации кода, локализации, серверной сборке. Часто они не участвуют напрямую в рантайме, но встраиваются в жизненный цикл сборки. Поэтому в зрелых процессах результаты depcheck либо помечаются как «кандидаты», либо автоматически проходят через мини-скрипт в CI, который проверяет импорты расширенным способом и предлагает пулреквест не с удалением, а с комментарием и чек-листом.

Как dependency-cruiser выстраивает архитектурный контроль

Dependency-cruiser строит граф импортов и проверяет его на соответствие правилам: направления зависимостей, запрет циклов, белые и чёрные списки путей. Это инструмент архитектурной дисциплины, а не просто счётчик импортов.

Он видит проект как карту со слоями: домен, приложение, представление; пакеты монорепозитория; адаптеры и инфраструктура. Правила задаются декларативно — что откуда можно импортировать, а что должно оставаться за закрытой дверью. На графе быстро всплывают неожиданные связи: «виджеты тянут доменные утилиты в обход публичного API», «микросервис знает о внутренностях соседнего пакета». Там, где код обзавёлся обходными тропами, dependency-cruiser без лишней драматургии покажет их в отчёте и сорвёт лишние маски. Сложность лишь в тонкой настройке, чтобы запреты не стали невыносимыми в ежедневной разработке. Здесь выручает принцип инкремента: сначала фиксируются критические проблемы — циклы, импорт из тестов в прод, нарушение границ пакетов, — затем добавляются устойчивые контуры: запрет относительных импортов вверх из src/features в src/entities, замок на прямые ходы из UI к инфраструктуре.

Гибкость инструмента открывает дорогу автоматическим миграциям. Если в проекте действует публичное API пакетов, правила буквально «вынуждают» разработчика использовать правильные входные точки, а не резать прямо к внутренностям. Это экономит нервы при рефакторинге: переезд файлов за спиной у потребителей, смена структуры папок, укрупнение модулей не перерастают в пожар, потому что потребители не знали о внутреннем устройстве и не зависели от него. Инструмент становится не полицейским, а сторожевым псом: не лает без повода, но вовремя подсказывает, где граница.

Скорость, масштаб и прозрачность: сравнение на практике

Depcheck работает быстро и почти без настройки, dependency-cruiser требует правил, но даёт обзор архитектуры и защиту от эрозии. При росте кода второй масштабируется лучше, потому что формализует границы.

В малых и средних проектах depcheck показывает мгновенную пользу: экономит время, снижает вес зависимостей, упрощает апгрейды. В крупных кодовых базах преимущество переходит к dependency-cruiser: граф позволяет увидеть «зачем» и «почему», а не просто «сколько». Производительность упирается не столько в время выполнения, сколько в способность команды переваривать результаты. Если отчёты порождают шум, инженер перестаёт их слышать. Поэтому их следует обуздывать: отчёт в формате HTML для обзора, JSON — для CI, артефакты — в сторедж пайплайна, чтобы смотреть динамику. Там, где есть культивированная история нарушений, разговор с менеджментом и архитекторами перестаёт быть лирикой: графики видов нарушений по неделям превращаются в аргументы.

Сценарий depcheck — поведение dependency-cruiser — поведение
Малый проект (до 100 файлов) Запуск секундный, точные списки «мусора» Быстрый граф, простые правила «без циклов»
Средний проект (100–1000 файлов) Стабильно быстрый, редкий ложный шум Нужна стратификация слоёв, первые визуализации
Крупная база (1000+ файлов, монорепо) Полезен для гигиены, но мало даёт архитектурно Раскрывает связи, поддерживает правила пакетов
Миграции (ESM/TS/переезд слоёв) Почти не помогает Находит циклы, направляет по публичным API
Встраивание в CI Просто: идущие отчёты как warning Правила, пороги, артефакты, историзация

CI/CD, монорепо и DX: как подружить инструменты с процессом

Их лучше внедрять ступенями: depcheck — как быстрая гигиена в еженедельном джобе, dependency-cruiser — как обязательный чек в PR с мягкими порогами и ясным репортом. Тогда разработчики не воюют с ботом, а видят пользу.

В CI важно не столько время работы, сколько поведение на ошибках. Слепой «fail the build» превращает проверку в раздражитель. Работает другой подход: пониженные пороги, автогенерация артефактов, понятная подсветка в diff. В монорепозиториях выигрывает пакетная сегментация: каждый пакет держит свои правила, а корневой конфиг включает общие запреты. Это позволяет пилить фичи без блокировки соседних доменов. С технической стороны полезно собирать отчёты в машинном и человекочитаемом видах: JSON уходит в анализ трендов, HTML — в артефакты пайплайна. Простая схема «предупреждения в первом спринте — ошибки спустя два» помогает команде привыкнуть. За это время накапливаются фиксы, а шум снижается благодаря точным исключениям и продуманным алиасам путей.

  • Запуск depcheck как nightly job, публикация списка кандидатов на удаление.
  • Dependency-cruiser — на каждый PR, но с порогами, не ломающими сборку в стартовый период.
  • Артефакты отчётов — в CI-сторедж с линком в PR.
  • Сегментация правил по пакетам в монорепо, общий набор запретов — сверху.
  • История нарушений — метрика качества в инженерном дашборде.
  • Автокоммент бота в PR с конкретной рекомендацией и ссылкой на правило.
Элемент процесса Решение Эффект
Первое включение правил Порог «warning», недельный период адаптации Снижение сопротивления, рост принятия
Тяжёлые отчёты HTML артефакты и линк в PR Быстрая навигация по проблемам
Шум от «ложных» нарушений Исключения и точные паттерны, алиасы TS Чистый сигнал, меньше игнора
Долги копятся Спринтовые квоты на погашение Предсказуемое снижение нарушений
Монорепо Локальные правила + корневой baseline Самостоятельность пакетов, порядок в ядре

Порог вхождения, правила и борьба с шумом: практические приёмы

Грамотные правила dependency-cruiser строятся от архитектурных границ, а не от текущих случайных зависимостей. Шум гасят точными исключениями, алиасами и поэтапным ужесточением.

Начальный соблазн — легализовать всё, что уже есть. Но тогда инструмент лишь фиксирует хаос и не меняет поведение. Эффективнее иначе: очертить желаемые уровни — entities, features, widgets, pages; infrastructure, domain, application — и разрешать движение только «вниз» или только через публичные входы. Чувствительность повышается аккуратно: сначала — запрет циклов и импортов из тестов, затем — запрет относительных импортов между слоями, потом — закрытие внутренних директорий «/internal». На шум влияют и резолверы: tsconfig-paths должны быть отражены в конфиге, чтобы отчёты понимали алиасы. Для depcheck работу облегчают ignore-паттерны на плагины сборщиков и CLIs, а также фиксация сценариев, где динамический импорт — часть нормальной жизни. Итогом становится чистая лента нарушений, где каждая запись — настоящее отклонение, а не артефакт инструмента.

Правило Зачем Комментарий к внедрению
Запрет циклов Устраняет хрупкость и скрытые зависимости Включается первым, отчёты — в HTML, фиксы — по очереди
Импорт только через публичный API пакета Обеспечивает стабильные контракты Требует явных entry points и реэкспортов
Закрытие /internal и /src/private Ограничивает соблазн лезть во внутренности Поначалу через warning, затем — error
Слои: entities → features → widgets → pages Предсказуемые направления зависимостей Нужна карта слоёв и пара исключений на адаптеры
Запрет импортов из тестов в прод Чистый прод-артефакт, без dev-следов Лёгкая победа, сразу в error
  • Сначала отключить шум: алиасы TS, исключения для сборщика.
  • Фиксировать только принципиальные нарушения: циклы и границы.
  • Повышать строгость раз в спринт, фиксируя «дорогу обратно».
  • Не смешивать стихийные исключения с общими правилами: всё через PR.

Пример минимального конфига dependency-cruiser с фокусом на архитектурный скелет выглядит предельно прозрачно: пару правил достаточно, чтобы граф начал «говорить» человеческим голосом.

{
  "forbidden": [
    { "name": "no-cycles", "severity": "error", "from": {}, "to": { "circular": true } },
    { "name": "no-deep-internal",
      "comment": "Импорт только через публичный API пакетов",
      "from": { "pathNot": ["^packages/.+/src/public"] },
      "to":   { "path": "^packages/.+/src/(internal|private)/" } }
  ],
  "options": {
    "doNotFollow": { "path": "node_modules" },
    "tsConfig": { "fileName": "tsconfig.json" }
  }
}

Практический маршрут выбора: от аудита до автоматического контроля

Если проект молод и архитектура ещё плывёт, начать проще с depcheck и мягких правил dependency-cruiser. В зрелой базе приоритет — за архитектурой, а депендэнси-гигиена становится рутиной в фоновом джобе.

Последовательность действий предельно прагматична. Сначала — навести порядок в зависимостях, потому что мусор отвлекает и замедляет апгрейды. Затем — закрепить границы, чтобы новые фичи не рвали ткань проекта. Параллельно — включить метрики: сколько циклов, как меняется число нарушений по слоям, где чаще всего ломают публичные API. Эти числа не ради красоты: они направляют время инженеров туда, где эффект от рефакторинга максимальный. Когда граф становится читабельным, уходит страх перед миграциями: перенос папки не означает, что приложение рассыплется, потому что импорты идут через официальный вход. В этот момент инструменты перестают казаться обузой и начинают работать как навигатор: не требуют постоянного внимания, но всегда подсказывают, если машина съезжает с полосы.

Антипаттерн внедрения Чем опасен Как исправить
Сразу «всё запрещено» Массовые фейлы, саботаж разработчиков Постепенное ужесточение, baseline нарушений
Исключения без ревью Незаметная эрозия правил Каждое исключение — через PR с обоснованием
Игнор алиасов TS Ложные срабатывания, потеря доверия Отразить резолверы в конфиге инструмента
Линков нет в PR Никто не открывает отчёты Артефакты с прямой ссылкой и подсветкой
Нет владельцев правил Правила стареют, шум растёт Назначить ответственных по пакетам/слоям

FAQ: частые вопросы об анализе зависимостей

Какой инструмент лучше для быстрого удаления неиспользуемых пакетов?

Для этой задачи эффективнее depcheck: он просканирует импортируемые модули и покажет явных «пассажиров». Dependency-cruiser не предназначен для чистки package.json, его сила — в контроле связей и правил. Впрочем, даже с depcheck стоит проверять найденные кандидаты вручную, особенно если пакет участвует в сборке или генерации.

Практика показывает, что надёжнее запускать depcheck в ночных джобах и формировать отчёт для команды. Удаление — отдельный PR с чек-листом: проверить конфиги сборщика, скрипты npm, devDependencies. Это снижает риск, что инструмент спровоцирует случайный регресс.

Можно ли использовать depcheck и dependency-cruiser вместе?

Можно и нужно: depcheck поддерживает гигиену зависимостей, а dependency-cruiser защищает архитектуру. Они дополняют друг друга. Разумный сценарий — ночной запуск depcheck и проверка dependency-cruiser на каждый PR с порогами в первый период внедрения.

Комбинация особенно удачна в монорепозиториях: depcheck держит пакеты в чистоте, а dependency-cruiser не даёт им прорезать друг друга в обход публичных API. Так появляется здоровая изоляция и предсказуемость изменений.

Как запретить циклические зависимости с помощью dependency-cruiser?

Достаточно включить правило, запрещающее циклы, и сделать его критичным. Инструмент найдёт замкнутые пути и покажет цепочки. Дальше — рефакторинг: вынос общих частей в третий модуль, разрыв лишних импортов, замена относительных ходов на публичные входы.

Лучше сначала собрать HTML отчёт и отсортировать циклы по длине, решая короткие и наиболее массовые. Это даёт быстрый эффект и не ломает психику команды, сталкивающейся с десятками сообщений сразу.

Как уменьшить ложные срабатывания depcheck?

Использовать ignore-паттерны для конфигурируемых импортов и учесть CLI-зависимости, которые вызываются из npm-скриптов, но не импортируются из кода. Полезно складывать устойчивые правила игнора в отдельный файл и ревьюить их как код.

Часть шума уходит, когда сборщик и плагины оформлены в preset-пакеты. Тогда зависимость используется явно, а depcheck видит импорт корректно. В спорных случаях помогает «dry-run PR»: собрать проект без кандидата на удаление и убедиться в чистоте сборки и тестов.

Подходит ли dependency-cruiser для монорепозиториев?

Да, и там он особенно раскрывается. Локальные правила на уровне пакетов плюс общий корневой baseline дают точную настройку. Каждый пакет живёт по собственным правилам, но не нарушает магистральных ограничений слоёв.

Правильно настраивать путь резолва, указывать tsconfig и не позволять импортировать внутренности соседних пакетов. Тогда граф перестаёт быть кашей и превращается в карту города, где видны магистрали и тупики.

Как настроить проверки в CI, чтобы не раздражать разработчиков?

Порог «warning» в стартовый период, артефакты отчётов с лейблами в PR, и понятные рекомендации к каждому нарушению. Жёсткое «красное» включается после недели-двух, когда основная волна фиксов пройдёт.

Хорошо работает бот, который оставляет комментарий в PR: где нарушение, какое правило, как исправить, ссылка на отчёт. Такой формат экономит время и снижает эмоциональный накал.

Есть ли альтернатива этим инструментам?

Существуют eslint-плагины для импортов, статические анализаторы IDE, кастомные скрипты. Они полезны как дополнение, но редко заменяют связку «depcheck + dependency-cruiser»: первый решает задачу чистки, второй — держит архитектуру в узде.

Комбинация инструментов — не дань моде, а отражение двух разных задач. Альтернативы часто упрощают одну грань, но не охватывают обе одновременно настолько же прозрачно и масштабируемо.

Финальный аккорд: дисциплина зависимостей как часть инженерной культуры

Выбор между depcheck и dependency-cruiser не про вкус, а про намерение. Если кодовая база просит лёгкого дыхания — первыми станут отчёты depcheck. Если уже важно, как связаны слои и через какие входы ходят импорты, — рулит dependency-cruiser. Вместе они создают ясную рутину: чистый список зависимостей и карта отношений, которые не расползаются при каждом изменении.

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

How To: внедрить проверку зависимостей без боли

  1. Запустить depcheck в отдельном джобе, собрать список кандидатов и вынести на ревью.
  2. Включить dependency-cruiser с двумя правилами: «нет циклам» и «только публичные API».
  3. Отразить алиасы tsconfig в конфиге инструмента, закрыть шумные пути игнорами.
  4. Подключить отчёты как артефакты CI и линк в каждый PR, порог — «warning» на старте.
  5. Раз в спринт ужесточать правила и снимать baseline нарушений, фиксируя тренды.
  6. Назначить владельцев на правила слоёв/пакетов, ревьюить исключения как код.