Граф зависимостей в монорепозитории: какие инструменты брать в 2026 году

Raghav Chamadiya11 мин

граф зависимостей монорепозиторий · циклические зависимости поиск · анализ зависимостей проекта · граф зависимостей go · jdeps · nx graph

Содержание

Короткий ответ такой: если граф нужен, чтобы собирать и тестировать только затронутое, берите Nx или Turborepo. Найти циклические импорты в JS/TS поможет Madge, а запретить их в CI можно с помощью dependency-cruiser. Для Go и Java граф пакетов уже есть в стандартном инструментарии. Если нужен граф до уровня файлов и функций вместе с историей изменений и владельцами, для этого есть repowise.

ИнструментУровень графаЦиклыЯзыкиИстория и владельцы
Nxпроекты + задачикосвенно, через границыв основном JS/TS, остальное плагинаминет
Turborepoпакеты + задачине его задачаJS/TSнет
Madgeфайлы и модулида, --circularJS/TSнет
dependency-cruiserфайлы и модулида, правиламиJS/TSнет
go list -deps, go mod graphпакеты и модули Goкомпилятор запрещает циклы пакетовGoнет
jdeps, mvn dependency:tree, gradle dependenciesклассы/пакеты, артефактывидно в выводе jdeps (-dotoutput), в выводе сборщиков нетJava/Kotlinнет
repowiseпакеты, файлы, символыда, с разбором каждого цикла16 языковда

Scroll the table sideways to see every column.

Ниже каждый инструмент разобран подробнее, с цифрами по реальным репозиториям.

Граф большого монорепозитория в голове не удержать

Пока в репозитории десяток пакетов, связи между ними помнит каждый, кто с ним работает. Когда пакетов сотни, а файлов десятки тысяч, на простые вопросы уже никто не ответит уверенно: что сломается, если поменять этот пакет; где цикл, из-за которого модуль не получается вынести; какой файл тянет за собой половину репозитория.

Масштаб хорошо видно на открытых проектах. В Grafana 18 146 файлов и 3 379 253 строки кода. В Keycloak 10 414 файлов и чуть больше миллиона строк, в Gitea 3 158 файлов и 512 376 строк. Ни один человек не держит такие графы в голове, и это нормально, но плохо, если их не видит и ни один инструмент.

Два уровня графа: пакеты и символы

Сначала стоит решить, на каком уровне нужен ответ.

Граф пакетов отвечает на вопросы сборки: в каком порядке собирать, какие пакеты затронуты изменением, что пересобрать в CI. На этом уровне работают Nx, Turborepo, go list, Maven и Gradle.

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

Большинство инструментов работают на одном уровне, и при выборе это надо учитывать.

Nx

Nx строит граф проектов воркспейса, а на его основе граф задач: что собирать, что брать из кэша, какие проекты затронуты (nx affected). Граф можно открыть в браузере через nx graph. Границы между проектами задаются тегами и правилами, и нарушения ловит линтер.

Где хорош: TypeScript-монорепозитории, в которых граф нужен прежде всего для CI и кэширования.

Где заканчивается: Nx знает, как связаны проекты, но не пытается объяснять код внутри них. Владельцев файлов, частоту изменений и горячие точки он не показывает.

Turborepo

Turborepo берёт граф пакетов из менеджера пакетов (npm, pnpm, yarn) и строит поверх него граф задач в виде DAG. Это самый простой способ получить кэширование и параллельный запуск build/lint/test в JS/TS-монорепозитории.

Где заканчивается: Turborepo задуман как таск-раннер, который знает про граф. Архитектуру он не исследует, циклы между файлами и мёртвый код не ищет.

Madge

Madge строит граф модулей JS/TS и находит циклы одной командой:

bash
npx madge --circular --extensions ts,tsx src/

Умеет выводить граф в DOT и SVG (через Graphviz), искать файлы-сироты (--orphans) и листья (--leaves). Если вы подозреваете циклический импорт, это самый быстрый первый шаг.

Где заканчивается: уровень файлов и модулей, только JS/TS, без истории и без контекста монорепозитория.

dependency-cruiser

dependency-cruiser ближе к линтеру архитектуры. Правила описываются в конфиге (например, «пакет ui не импортирует server» или «циклов быть не должно»), и при нарушении CI падает. Отчёты выводятся в DOT, JSON и CSV.

Где хорош: когда граница уже понятна и её нужно удержать.

Где заканчивается: он проверяет правила, но не объясняет, почему граница проходит именно здесь.

Go: граф уже есть в тулчейне

В Go отдельный инструмент для графа пакетов часто не нужен:

bash
go list -deps ./...        # все пакеты, от которых зависит модуль
go mod graph               # граф зависимостей между модулями

Циклические импорты пакетов в Go запрещены компилятором, поэтому классической проблемы «пакет A импортирует B, а B импортирует A» нет. Но связность от этого никуда не девается, она уходит внутрь пакетов: в больших пакетах десятки файлов зависят друг от друга. В Gitea мы нашли 7 групп циклических зависимостей на уровне файлов. Для полумиллиона строк это немного, но как раз такие циклы компилятор не ловит.

Java и Kotlin: jdeps, Maven, Gradle

Для JVM есть три стандартных способа посмотреть на зависимости:

bash
jdeps -summary -recursive target/classes   # зависимости между пакетами и JAR
mvn dependency:tree                         # дерево артефактов Maven
gradle dependencies                         # то же для Gradle

jdeps входит в JDK и показывает зависимости на уровне пакетов и классов. С -dotoutput его вывод можно отдать в Graphviz и увидеть циклы между пакетами своими глазами. dependency:tree и gradle dependencies показывают дерево артефактов, то есть внешние библиотеки и модули сборки. Связей внутри кода в этом дереве нет.

Почему этого мало, хорошо видно на Keycloak. Это зрелый проект с аккуратной модульной структурой, и всё равно внутри набирается 135 групп циклических зависимостей на уровне файлов. Среди них, например, «Services and Server Spi Private», «Storage Ldap», «Util Oauth». Для Java с её SPI, плагинами и рефлексией это ожидаемо: часть связей появляется только на уровне файлов и не видна в дереве артефактов.

Циклические зависимости: как найти и где резать

Чаще всего за циклом стоит одна из четырёх причин:

  • утилитный модуль, в который со временем стали складывать всё подряд;
  • граница пакета проведена не там;
  • два модуля отвечают за одно и то же понятие;
  • короткий импорт «на время», который пережил три релиза.

Найти цикл помогают Madge, dependency-cruiser и вывод jdeps. Дальше нужно понять, какой файл в нём самый запутанный и где разрыв обойдётся дешевле всего.

Пример есть в deepseek-harness. На странице цикла «Mcp Mcp Client» три файла импортируют друг друга по кругу:

text
packages/mcp/mcp-client/src/connection.ts → transport.ts → index.ts → connection.ts

Страница ранжирует файлы по числу рёбер внутри цикла. Наверху connection.ts: два импорта внутрь цикла и один входящий. Его разумно трогать первым: вынести интерфейс или перенести импорт. Всего в deepseek-harness таких групп 47, и у каждой есть такая страница.

Для сравнения по масштабу:

РепозиторийФайловСтрок кодаГрупп циклов
grafana/grafana18 1463 379 25366
keycloak/keycloak10 4141 071 725135
deepseek-ai/deepseek-harness12 578898 68547
go-gitea/gitea3 158512 3767

Scroll the table sideways to see every column.

Качество кода эта таблица не показывает. Количество циклов зависит от языка, возраста проекта и того, как устроена сборка. Gitea на Go и Keycloak на Java сравнивать по этой цифре напрямую нельзя: компилятор Go запрещает циклы пакетов, а Java нет.

Как я бы искал и разбирал цикл

  1. Построить граф пакетов стандартным инструментом (go list, jdeps, Nx).
  2. Найти самый маленький цикл: его проще понять и дешевле разорвать.
  3. Посмотреть, кто владеет файлами в цикле и как часто они меняются.
  4. Если файл в цикле ещё и горячая точка (его часто правят и часто чинят), начать с него.
  5. Рвать там, где меньше всего рёбер наружу, и закрепить результат правилом в CI.

Где здесь repowise

Пункты 3 и 4 обычно делаются руками: git log, git blame, вопросы в чате команды. repowise мы делали именно для этого: он строит граф по AST для 16 языков, от пакетов до символов, и кладёт рядом историю git (владельцев, частоту правок, горячие точки), а для каждого цикла генерирует страницу разбора. Открытый код, AGPL-3.0, запускается локально:

bash
pip install repowise && repowise init

Он не заменяет Nx или Turborepo в CI. Хорошая связка выглядит так: Nx или Turborepo гоняют задачи, dependency-cruiser охраняет границы, а repowise показывает, что происходит внутри и где риск.

Как выбрать

  • Если нужно собирать и тестировать только затронутое, подойдут Nx или Turborepo.
  • Если подозреваете циклический импорт в JS/TS, запустите Madge.
  • Если хотите запретить нарушения архитектуры в CI, настройте dependency-cruiser.
  • Если у вас Go или Java и нужен граф пакетов, хватит стандартного тулчейна.
  • Если нужно понять, где риск и с какого файла начинать, используйте repowise.

Как мы считали

Количество файлов и строк взято из последнего индекса каждого репозитория в repowise: Grafana и Keycloak от 11 августа 2026, Gitea от 17 июля, deepseek-harness от 28 сентября. «Группой циклов» мы называем компоненту сильной связности в графе импортов между файлами. Для каждой такой компоненты repowise генерирует отдельную страницу, их мы и посчитали. Цифры проверены 6 октября 2026 года. Если репозиторий с тех пор переиндексирован, на странице могут быть другие значения.

Посмотреть свой репозиторий: вставьте ссылку на GitHub на repowise.dev или откройте repowise.dev/repo/<владелец>/<репозиторий>.

Частые вопросы

Чем построить граф зависимостей для монорепозитория?

Выбор зависит от задачи: для сборки и CI берите Nx или Turborepo, а для исследования связей между файлами в JS/TS подойдут Madge или dependency-cruiser. В Go и Java есть `go list -deps` и `jdeps`. Граф до уровня функций вместе с историей git строит repowise.

Как найти циклические зависимости в проекте?

В JS/TS запустите `npx madge --circular src/`. В Java постройте граф пакетов через `jdeps -dotoutput` по скомпилированным классам и посмотрите его в Graphviz. В Go циклы пакетов запрещает компилятор, но циклы между файлами внутри пакетов остаются. repowise находит циклы на уровне файлов в любом из 16 языков и для каждого показывает, какой файл в нём самый связанный.

Чем граф пакетов отличается от графа символов?

Граф пакетов показывает, какие пакеты или проекты зависят друг от друга, и нужен для порядка сборки. Граф символов показывает связи между файлами, классами и функциями и нужен для рефакторинга: по нему видно, где на самом деле проходит связь.

Можно ли построить граф зависимостей для Go без сторонних инструментов?

Да: `go list -deps ./...` для пакетов и `go mod graph` для модулей. Чтобы получить картинку, результат можно передать в Graphviz.

Можно ли построить граф зависимостей для приватного репозитория?

Да. Локальный repowise работает на вашей машине и никуда не отправляет код. Облачная версия индексирует приватные репозитории на платном тарифе, есть бесплатный пробный период на 10 дней.

Проиндексируйте свой репозиторий бесплатно