Управление порядком выполнения скриптов

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

В контексте Esbuild это особенно важно, поскольку сборщик оптимизирует структуру кода, стремясь минимизировать размер бандла и устранить лишние зависимости, одновременно соблюдая семантику ES-модулей.

Формирование графа модулей

При запуске сборки Esbuild выполняет несколько этапов:

  • анализ входных точек (entryPoints);
  • построение графа зависимостей;
  • разрешение путей (resolve);
  • загрузка модулей через плагины (onResolve, onLoad);
  • финальная линковка модулей в единый или разделённый набор чанков.

Каждый импорт рассматривается как потенциальная точка перехода. При этом порядок объявления импортов внутри файла не влияет на порядок исполнения модулей — важна только структура зависимостей.

Глубина обхода и порядок инициализации

В ES-модулях используется модель, при которой зависимости инициализируются до выполнения импортирующего модуля. Это приводит к поведению, близкому к post-order обходу:

  1. Сначала выполняются модули без зависимостей.
  2. Затем модули, зависящие от них.
  3. В самом конце — entry point.

Esbuild следует этой модели, формируя корректную последовательность инициализации кода в итоговом бандле.

Особое значение имеет механизм live bindings: экспортированные значения не копируются, а связываются через ссылки, поэтому порядок инициализации должен гарантировать доступность этих ссылок до их использования.

Влияние формата вывода на порядок выполнения

Формат бандла напрямую влияет на то, как организуется выполнение кода.

IIFE (Immediately Invoked Function Expression)

При использовании format: "iife" весь граф модулей сворачивается в одну функцию. Порядок выполнения становится строго детерминированным:

  • сначала выполняются вспомогательные функции рантайма;
  • затем инициализируются модули в порядке зависимостей;
  • затем выполняется код entry point.

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

CommonJS

При format: "cjs" каждый модуль оборачивается в функцию, а загрузка осуществляется через require. Порядок выполнения становится ленивым:

  • модуль выполняется только при первом require;
  • кеширование предотвращает повторную инициализацию;
  • порядок зависит от того, в какой момент выполняется require.

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

ESM

При format: "esm" сохраняется статический характер графа. Esbuild стремится минимизировать перестановки, оставляя структуру максимально близкой к исходной спецификации ES Modules. Порядок исполнения определяется спецификацией ECMAScript, а не самим сборщиком.

Множественные entry points

При указании нескольких входных точек:

esbuild app.js admin.js dashboard.js --bundle --outdir=dist

формируется несколько независимых графов зависимостей. При этом:

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

Ключевой момент заключается в том, что физический порядок файлов в outdir не является отражением порядка их выполнения в браузере или Node.js.

Разделение кода (code splitting) и влияние на порядок

При включении:

splitting: true
format: "esm"

Esbuild начинает формировать динамические чанки, загружаемые через import(). В этом случае порядок выполнения становится частично асинхронным:

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

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

Метаданные сборки и отсутствие гарантированного порядка

При использовании metafile: true Esbuild генерирует структуру вида:

{
  "outputs": {
    "chunk-A.js": {...},
    "chunk-B.js": {...}
  }
}

Важно, что outputs — это объект, а не массив, поэтому:

  • порядок ключей не должен использоваться как порядок исполнения;
  • любые попытки интерпретации порядка через перечисление свойств некорректны.

Реальный порядок выполнения всегда определяется графом зависимостей, а не структурой метафайла.

Плагины и влияние на порядок загрузки

Плагины Esbuild способны влиять на порядок выполнения косвенно через:

  • onResolve — изменение маршрутизации импортов;
  • onLoad — подмену содержимого модулей;
  • внедрение виртуальных модулей.

Порядок регистрации плагинов важен:

  • плагины выполняются в порядке их объявления;
  • первый подходящий onResolve или onLoad перехватывает обработку;
  • конкурирующие плагины могут изменить структуру графа до его финализации.

Таким образом, порядок выполнения может быть изменён ещё на этапе построения зависимостей.

Опции:

  • banner
  • footer

влияют на порядок исполнения за счёт добавления кода до и после каждого чанка.

Особенности:

  • banner выполняется до всех модульных инициализаций внутри чанка;
  • footer выполняется после завершения исполнения модуля;
  • при наличии нескольких entry points каждый чанк получает собственные вставки.

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

External зависимости и изменение порядка выполнения

При использовании:

external: ["react"]

модуль исключается из бандла. Это влияет на порядок выполнения следующим образом:

  • зависимость загружается внешней средой (браузером или Node.js);
  • инициализация внешнего модуля происходит до выполнения кода, который его использует;
  • контроль порядка передаётся runtime-окружению.

Таким образом, часть графа становится внешней по отношению к Esbuild.

Динамический импорт и асинхронный порядок

Конструкция:

import("./module.js")

создаёт точку разделения порядка выполнения:

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

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

Hoisting и статическая структура

Несмотря на возможные трансформации, Esbuild сохраняет ключевое свойство ES-модулей — статический анализ импортов. Это означает:

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

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

Циклические зависимости и их влияние

При наличии цикла:

A → B → C → A

Esbuild выполняет частичную инициализацию:

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

Это поведение соответствует спецификации ES Modules и не является особенностью сборщика.

Минимизация и перестройка порядка кода

Оптимизации Esbuild включают:

  • tree shaking;
  • inlining констант;
  • переупорядочивание независимых выражений.

Однако:

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

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

Вывод структуры выполнения

Порядок выполнения скриптов в сборке Esbuild всегда является результатом сочетания нескольких факторов:

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

Физический порядок файлов, строк в исходном коде или порядок объявления entry points не является определяющим фактором выполнения.