Inner Graph Analysis в Webpack 5

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

Внутренний граф Webpack 5 строится вокруг сущностей Module, Dependency, Connection. Каждый модуль содержит список зависимостей, а каждая зависимость при резолве превращается в соединение между двумя модулями.

Ключевая идея заключается в том, что граф не является статической структурой. Он изменяется в процессе компиляции через последовательность фаз:

  • создание модулей (module creation)
  • связывание зависимостей (module linking)
  • оптимизация (optimization phase)
  • генерация чанков (chunk graph generation)

На этапе связывания формируется ModuleGraph, который заменил более старую модель зависимостей из Webpack 4. Он хранит не только связи модулей, но и контекст использования экспортов, причины подключения модулей и метаданные о конкретных импортируемых символах.

ModuleGraph как центр анализа

ModuleGraph в Webpack 5 является центральной структурой анализа внутреннего графа. Он объединяет:

  • зависимости между модулями
  • связи export/import на уровне символов
  • информацию о side effects
  • причины включения модулей в сборку
  • контекст использования динамических импортов

Каждый модуль получает представление о том, какие именно его экспорты используются. Это позволяет проводить тонкий tree-shaking на уровне отдельных символов, а не только целых файлов.

Структура опирается на обратные ссылки: не только модуль знает зависимости, но и dependency хранит ссылку на исходный и целевой модуль через connection.

Анализ достижимости кода

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

  • entry modules помечаются как корневые узлы
  • далее рекурсивно обходятся зависимости
  • каждый модуль получает статус достижимости
  • недостижимые узлы исключаются из финального графа

Однако достижимость в Webpack 5 не бинарна. Существует несколько уровней включения:

  • полный модуль включен
  • модуль частично включен (часть экспортов используется)
  • модуль включен из-за side effects
  • модуль включен динамически через runtime chunk loading

Эта градация критична для последующих оптимизаций.

Символьный граф экспортов

Inner Graph Analysis в Webpack 5 тесно связан с анализом экспортов на уровне AST. Каждый модуль после парсинга формирует структуру ExportInfo, которая затем попадает в ModuleGraph.

Каждый экспорт имеет статус:

  • используется явно
  • используется транзитивно
  • не используется
  • имеет неопределенное использование (reexport chains)

Webpack строит цепочки re-export’ов, связывая символы через несколько модулей. Это позволяет отслеживать ситуацию вида:

export { a } from './moduleA';

где фактическое использование a определяется только в конечной точке графа.

Side Effects и влияние на граф

Анализ sideEffects напрямую влияет на форму внутреннего графа. Если модуль помечен как:

"sideEffects": false

Webpack получает возможность вырезать узлы графа даже при отсутствии явного анализа экспортов.

В противном случае модуль становится “токсичным” для tree-shaking: он включается целиком при малейшем импорте.

На уровне графа это выражается так:

  • узел помечается как side-effectful
  • его подграф включается полностью
  • оптимизация экспорта блокируется

Это существенно влияет на плотность графа и количество включенных модулей.

Оптимизация связности модулей

Webpack 5 выполняет несколько уровней анализа связности:

1. Static linkage analysis

Определяются статические связи import/export. Это основной слой графа, формирующий структуру зависимостей.

2. Dynamic import boundaries

import() создает границы чанков. В графе появляются разрывы, которые превращаются в отдельные chunk nodes.

3. Weak dependencies

require.context и условные импорты создают слабые ребра, которые могут быть проигнорированы при оптимизации.

ChunkGraph и его связь с ModuleGraph

ChunkGraph является надстройкой над ModuleGraph. Если ModuleGraph описывает связи модулей, то ChunkGraph отвечает за упаковку этих модулей в финальные чанки.

Связь выражается следующим образом:

  • модуль → принадлежит одному или нескольким чанкам
  • чанк → содержит множество модулей
  • зависимости модулей определяют зависимости чанков

Inner Graph Analysis включает анализ того, как перемещение одного модуля влияет на весь граф чанков.

Модульные соединения (Module Connections)

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

  • какой импорт вызвал подключение
  • используется ли весь модуль или только часть
  • является ли связь ESM или CJS
  • может ли соединение быть оптимизировано

Эта информация позволяет выполнять локальные оптимизации без пересборки всего графа.

Dead Code Elimination на уровне графа

Удаление мертвого кода происходит не на уровне строк, а на уровне узлов графа и их экспортов.

Процесс включает:

  • пометку используемых экспортов
  • распространение информации вверх по графу
  • исключение неиспользуемых веток
  • пересчет связей между модулями

Особенность Webpack 5 заключается в инкрементальном пересчете графа: изменения одного модуля не требуют полного пересоздания всей структуры.

Circular dependencies в графе

Циклические зависимости не разрушают граф, но изменяют порядок анализа.

Webpack превращает цикл в strongly connected component:

  • узлы цикла объединяются в подграф
  • анализ экспортов выполняется итеративно
  • разрешение символов происходит после стабилизации состояния графа

Это позволяет корректно обрабатывать структуры вида взаимных импортов без потери информации о доступных экспортируемых значениях.

Оптимизация плотности графа

Inner Graph Analysis также включает оптимизацию структуры:

  • module concatenation (scope hoisting)
  • устранение промежуточных модулей re-export
  • слияние цепочек dependency nodes
  • упрощение транзитивных зависимостей

В результате граф становится менее глубоким и более широким, что улучшает производительность выполнения кода в runtime.

Инкрементальная природа анализа

Webpack 5 использует кэширование на уровне графа. При изменении одного модуля:

  • пересчитываются только затронутые узлы
  • соседние подграфы остаются неизменными
  • ChunkGraph обновляется локально
  • ModuleGraph сохраняет большую часть структуры

Это критично для больших проектов, где полный пересчет графа может быть дорогим.

Влияние runtime на граф

Граф не является чисто статическим. Runtime механизмы влияют на его структуру:

  • splitChunks добавляет виртуальные узлы
  • runtime chunk создает дополнительные связи
  • async boundaries формируют отдельные подграфы загрузки

Таким образом, итоговый граф представляет собой комбинацию compile-time и runtime анализа, где часть структуры становится известной только во время выполнения приложения.