Граф модулей представляет собой внутреннюю структуру зависимостей,
которую Rollup строит во время анализа проекта. Каждый файл
рассматривается как отдельный узел графа, а операторы
import и export образуют связи между этими
узлами.
Например, структура проекта:
src/
├── main.js
├── utils.js
├── api.js
└── config.js
Содержимое файлов:
// main.js
import { fetchData } from './api.js';
import { format } from './utils.js';
// api.js
import config from './config.js';
// utils.js
import config from './config.js';
После анализа Rollup сформирует граф:
main.js
├── api.js
│ └── config.js
└── utils.js
└── config.js
Такой граф используется для:
Доступ к графу модулей особенно полезен при создании плагинов.
Наиболее распространённые задачи:
Rollup предоставляет API, позволяющий получать сведения о каждом модуле ещё до завершения сборки.
Основным инструментом инспекции графа является объект
ModuleInfo.
Получить его можно через методы контекста плагина:
this.getModuleInfo(id);
или
this.getModuleIds();
Каждый модуль содержит подробную информацию о своём состоянии внутри графа.
Пример:
export default function inspectPlugin() {
return {
name: 'inspect-plugin',
buildEnd() {
const info = this.getModuleInfo('src/main.js');
console.log(info);
}
};
}
Метод getModuleIds() возвращает итератор всех
зарегистрированных модулей.
Пример обхода:
export default function inspectPlugin() {
return {
name: 'inspect-plugin',
buildEnd() {
for (const id of this.getModuleIds()) {
console.log(id);
}
}
};
}
Результат:
src/main.js
src/api.js
src/utils.js
src/config.js
Такой обход часто становится отправной точкой для собственного анализа структуры проекта.
После получения идентификатора можно запросить подробное описание.
const info = this.getModuleInfo(id);
Пример:
for (const id of this.getModuleIds()) {
const info = this.getModuleInfo(id);
console.log(info.id);
}
Свойство:
info.id
содержит абсолютный путь к файлу.
Например:
C:\project\src\main.js
или
/home/project/src/main.js
в зависимости от операционной системы.
Поле importedIds содержит прямые зависимости текущего
модуля.
Файл:
import './a.js';
import './b.js';
Инспекция:
const info = this.getModuleInfo(id);
console.log(info.importedIds);
Результат:
[
'/src/a.js',
'/src/b.js'
]
Это позволяет строить карту связей между файлами.
Для обратного анализа используется свойство:
info.importers
Предположим:
// main.js
import './utils.js';
// admin.js
import './utils.js';
Информация о модуле:
const info = this.getModuleInfo('/src/utils.js');
console.log(info.importers);
Результат:
[
'/src/main.js',
'/src/admin.js'
]
Такой анализ помогает определить популярность отдельных модулей внутри проекта.
Rollup отдельно хранит сведения о динамических импортируемых файлах.
Пример:
const page = await import('./page.js');
Инспекция:
console.log(info.dynamicallyImportedIds);
Результат:
[
'/src/page.js'
]
Для обратной связи используется:
info.dynamicImporters
Пример:
[
'/src/main.js'
]
Это особенно важно при анализе code splitting.
Свойство:
info.isEntry
показывает, является ли файл входной точкой сборки.
Конфигурация:
export default {
input: {
main: 'src/main.js',
admin: 'src/admin.js'
}
};
Проверка:
if (info.isEntry) {
console.log('Entry:', info.id);
}
Результат:
Entry: src/main.js
Entry: src/admin.js
Если зависимость помечена как внешняя:
export default {
external: ['lodash']
};
Rollup не включает её в бандл.
Информация:
console.log(info.isExternal);
Позволяет отличать локальные зависимости от внешних пакетов.
Rollup предоставляет список экспортируемых сущностей.
Модуль:
export const api = {};
export const config = {};
export default {};
Проверка:
console.log(info.exports);
Результат:
[
'api',
'config',
'default'
]
Такой механизм используется для статического анализа API проекта.
Одной из сильных сторон Rollup является отслеживание использования экспортируемых значений.
Информация доступна через:
info.hasModuleSideEffects
и внутренние механизмы tree shaking.
Плагин может использовать эти данные для выявления файлов, которые подключаются исключительно из-за побочных эффектов.
Пример:
if (info.hasModuleSideEffects === false) {
console.log('Pure module');
}
Простейший вариант визуального представления:
export default function graphPlugin() {
return {
name: 'graph-plugin',
buildEnd() {
for (const id of this.getModuleIds()) {
const info = this.getModuleInfo(id);
console.log(id);
for (const dep of info.importedIds) {
console.log(' ->', dep);
}
}
}
};
}
Вывод:
main.js
-> api.js
-> utils.js
api.js
-> config.js
utils.js
-> config.js
Подобный формат легко преобразуется в JSON или DOT-файл для последующей визуализации.
DOT используется системой Graphviz.
Пример генерации:
export default function dotGraph() {
return {
name: 'dot-graph',
buildEnd() {
let output = 'digraph G {\n';
for (const id of this.getModuleIds()) {
const info = this.getModuleInfo(id);
for (const dep of info.importedIds) {
output += `"${id}" -> "${dep}";\n`;
}
}
output += '}';
console.log(output);
}
};
}
Результат:
digraph G {
"main.js" -> "api.js";
"main.js" -> "utils.js";
"api.js" -> "config.js";
"utils.js" -> "config.js";
}
После передачи такого файла в Graphviz можно получить полноценную диаграмму зависимостей проекта.
Цикл возникает, когда модуль прямо или косвенно импортирует сам себя.
Пример:
a.js
↓
b.js
↓
c.js
↓
a.js
Используя getModuleInfo(), можно реализовать обход графа
в глубину.
Упрощённый алгоритм:
function visit(node, visited, stack) {
if (stack.has(node)) {
console.log('Cycle:', node);
return;
}
if (visited.has(node)) {
return;
}
visited.add(node);
stack.add(node);
const info = this.getModuleInfo(node);
for (const dep of info.importedIds) {
visit.call(this, dep, visited, stack);
}
stack.delete(node);
}
Такой подход позволяет автоматически обнаруживать архитектурные проблемы.
Инспекция графа позволяет определить файлы, которые не достижимы из входных точек.
Общий алгоритм:
Непосещённые узлы являются кандидатами на удаление.
Подобный анализ особенно полезен в крупных монорепозиториях.
После завершения построения графа Rollup предоставляет дополнительные сведения через объект bundle.
Хук:
generateBundle(options, bundle) {
}
Позволяет связать модульный граф с итоговыми чанками.
Пример:
generateBundle(options, bundle) {
for (const file of Object.values(bundle)) {
if (file.type === 'chunk') {
console.log(file.modules);
}
}
}
Результат:
{
'/src/main.js': {...},
'/src/api.js': {...},
'/src/utils.js': {...}
}
Так можно выяснить, какие модули попали в конкретный выходной файл.
Инспекция графа позволяет реализовывать правила уровня архитектуры.
Пример запрета импорта из слоя UI в слой Core:
if (
id.includes('/core/') &&
dep.includes('/ui/')
) {
this.error(
'Core layer cannot depend on UI layer'
);
}
Подобные проверки выполняются непосредственно во время сборки и предотвращают накопление архитектурного долга.
При работе с тысячами модулей анализ графа может стать дорогостоящей операцией.
Распространённые рекомендации:
getModuleInfo();Set) для хранения посещённых
узлов;Поскольку Rollup уже хранит граф в памяти, получение сведений через
getModuleInfo() обычно происходит быстро. Наиболее
затратной частью становится пользовательская обработка большого
количества узлов и связей, особенно при генерации отчётов, визуализаций
и сложных архитектурных проверок.