Cross-chunk dependencies и их влияние на размер

При включённом разделении кода (splitting: true при сборке в формате ESM) esbuild строит ориентированный граф модулей, в котором каждый файл становится вершиной, а импорт — ребром. На основе этого графа формируются чанки — наборы модулей, объединённые в отдельные выходные файлы.

Чанк в esbuild — это не произвольная группировка, а результат анализа достижимости модулей и точек входа. Если несколько entry-points используют одни и те же модули, такие модули могут быть вынесены в отдельные shared-chunks.

Cross-chunk dependencies: определение и природа возникновения

Cross-chunk dependencies возникают тогда, когда один чанк импортирует модуль, который уже был частично или полностью включён в другой чанк, либо когда зависимости между чанками образуют перекрёстные ссылки.

Такая ситуация появляется в нескольких типичных случаях:

  • использование динамических импортов, создающих отдельные чанки;
  • пересечение зависимостей между несколькими entry-points;
  • re-export модулей через промежуточные баррели;
  • наличие общих утилит, используемых в разных частях графа;
  • цепочки импорта, где модуль A импортирует B, а B оказывается в другом чанке, чем A.

Cross-chunk dependency в esbuild — это не просто импорт, а граница между двумя результатами бандлинга, где один итоговый файл зависит от другого.

Механика формирования cross-chunk связей

При анализе модулей esbuild выполняет несколько этапов:

  1. построение полного графа импортов;
  2. определение reachable-модулей для каждого entry-point;
  3. выделение shared-модулей;
  4. разбиение на чанки с учётом code splitting;
  5. генерация связей между чанками через import/export.

Cross-chunk связь формируется, когда модуль, находящийся в одном чанке, требуется другому чанку, но не может быть локально включён без нарушения принципа единственного экземпляра модуля в ESM-графе.

Влияние на размер итогового бандла

Cross-chunk dependencies напрямую влияют на размер сборки через несколько механизмов.

Увеличение количества служебного кода

Каждый чанк в ESM-сборке содержит:

  • runtime-обвязку для загрузки зависимостей;
  • таблицу импортов;
  • ссылки на внешние чанки.

При росте числа cross-chunk связей увеличивается количество таких связей и, соответственно, объём служебного кода. В больших приложениях это становится заметным фактором.

Разделение общих модулей и эффект shared chunk

esbuild старается выносить повторяющиеся модули в shared chunks. Однако cross-chunk зависимости могут усложнять этот процесс:

  • модуль может оказаться в более чем одной цепочке зависимости;
  • часть логики может быть вынесена в shared chunk, а часть — остаться в конкретном чанке;
  • увеличивается количество небольших промежуточных чанков.

Это приводит к росту общего числа файлов, даже если суммарный объём кода остаётся близким.

Потенциальная фрагментация кода

При интенсивных cross-chunk зависимостях возникает фрагментация:

  • один логический модуль разбивается между несколькими чанками;
  • увеличивается количество межчанковых импортов;
  • возрастает количество сетевых запросов в runtime.

Даже при HTTP/2 или HTTP/3 большое количество чанков увеличивает overhead загрузки.

Влияние на tree-shaking и удаление мёртвого кода

Tree-shaking в esbuild выполняется на уровне модулей, но cross-chunk зависимости могут ограничивать его эффективность.

Потеря контекста при пересечении чанков

Если экспорт используется через цепочку cross-chunk импортов, анализ побочных эффектов становится более консервативным. В результате:

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

Re-export цепочки

Barrel-файлы (index.ts, index.js) часто усиливают эффект cross-chunk зависимостей:

// a.ts
export const A = 1;

// index.ts
export * from "./a";

// entry.ts
import { A } from "./index";

В таком сценарии модуль a.ts может оказаться в отдельном чанке, а index.ts станет промежуточным узлом, увеличивающим количество межчанковых ссылок. Это добавляет дополнительные слои индирекции и снижает предсказуемость tree-shaking.

Дублирование зависимостей и границы чанков

При корректной работе esbuild избегает физического дублирования модулей между чанками. Однако cross-chunk dependencies влияют на то, как именно формируются границы:

  • общий модуль может быть вынесен в shared chunk;
  • при сложной структуре импортов он может оказаться разделён по разным путям достижимости;
  • разные entry-points могут формировать разные представления о «общем» коде.

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

Динамические импорты как источник cross-chunk связей

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

button.oncl ick = async () => {
  const module = await import("./feature.js");
};

Если feature.js зависит от модулей, используемых и в основном бандле, и в других ленивых чанках, возникает сеть cross-chunk зависимостей.

Это приводит к:

  • росту количества shared chunks;
  • увеличению количества runtime-резолвинга;
  • усложнению графа загрузки.

Циклические cross-chunk зависимости

Циклы в графе модулей усложняют разбиение на чанки. В ESM циклы допустимы, но при код-splitting они приводят к дополнительным связям между чанками.

Типичный сценарий:

  • chunk A импортирует chunk B;
  • chunk B импортирует chunk A косвенно через общий модуль.

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

Косвенные зависимости через промежуточные модули

Особенно заметный вклад в cross-chunk dependencies дают промежуточные слои:

  • утилитные библиотеки;
  • абстракции над API;
  • слои адаптеров.

Такие модули становятся «хабами», через которые проходят зависимости из разных частей приложения. При разбиении на чанки они оказываются в shared слоях, увеличивая плотность cross-chunk связей.

Метаданные сборки и анализ графа

esbuild может генерировать метафайл (metafile), который отражает структуру чанков и зависимостей между ними. В нём cross-chunk зависимости видны как связи между outputs.

Анализ таких данных позволяет выявлять:

  • чрезмерное количество shared chunks;
  • глубокие цепочки межчанковых импортов;
  • модули, выступающие центрами зависимости;
  • участки графа с высокой связностью.

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

Архитектурные паттерны, влияющие на cross-chunk рост

Некоторые структуры кода усиливают cross-chunk зависимости:

  • глубокие barrel-архитектуры;
  • повторное использование доменных слоёв в разных entry-points;
  • отсутствие изоляции feature-модулей;
  • смешивание UI и утилитарных слоёв в одном графе;
  • чрезмерная централизация общих модулей.

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

Поведение esbuild при оптимизации графа чанков

При формировании output esbuild стремится:

  • минимизировать дублирование модулей;
  • сохранять корректность ESM-ссылок;
  • ограничивать количество shared chunks разумными границами;
  • избегать чрезмерной фрагментации.

Однако cross-chunk dependencies задают структурные ограничения, при которых оптимизация размера конкурирует с необходимостью сохранить корректный порядок загрузки и независимость чанков.