Отладка конфигурации через metafile

Метапредставление сборки (metafile) в esbuild представляет собой структурированный JSON-отчёт о процессе бандлинга, который фиксирует взаимосвязи между входными файлами, промежуточными модулями и финальными выходными артефактами. Его основная ценность заключается в возможности детально анализировать, как конфигурация сборщика влияет на результат, без необходимости вручную разбирать итоговый бандл.

Включение генерации метафайла выполняется явно, поскольку его создание увеличивает объём работы сборщика. В Node.js API это задаётся через параметр metafile: true, а в CLI — через флаг --metafile.

import * as esbuild from 'esbuild';

await esbuild.build({
  entryPoints: ['src/index.js'],
  bundle: true,
  outfile: 'dist/app.js',
  metafile: true
});

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

esbuild src/index.js --bundle --outfile=dist/app.js --metafile=meta.json

В CLI-режиме esbuild не только генерирует метафайл, но и сохраняет его в файл. В Node.js API он возвращается как часть результата сборки:

const result = await esbuild.build({
  entryPoints: ['src/index.js'],
  bundle: true,
  metafile: true,
  write: false
});

console.log(result.metafile);

Mетафайл представляет собой JSON-объект с несколькими ключевыми секциями, каждая из которых отражает отдельный аспект сборки.

inputs

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

{
  "inputs": {
    "src/index.js": {
      "bytes": 120,
      "imports": [
        { "path": "./utils.js" },
        { "path": "react" }
      ]
    }
  }
}

Ключевые поля:

  • bytes — размер исходного файла
  • imports — список импортируемых зависимостей
  • format — информация о типе модуля (ESM/CJS)

outputs

Раздел outputs описывает все итоговые бандлы, созданные сборщиком.

{
  "outputs": {
    "dist/app.js": {
      "bytes": 245000,
      "inputs": {
        "src/index.js": {},
        "src/utils.js": {}
      },
      "imports": [
        "react"
      ],
      "exports": ["default"]
    }
  }
}

Здесь можно увидеть:

  • какие входные модули попали в конкретный бандл
  • какие внешние зависимости остались внешними
  • какие экспорты были сохранены

imports и зависимости

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

Использование metafile для диагностики конфигурации

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

Потерянные зависимости

Если модуль не попал в бандл, его отсутствие можно отследить через inputs и outputs. При корректной конфигурации каждый используемый модуль должен быть отражён в дереве зависимостей.

"outputs": {
  "dist/app.js": {
    "inputs": {
      "src/index.js": {},
      "src/missing.js": {}
    }
  }
}

Если ожидаемый файл отсутствует в inputs, это указывает на:

  • неправильный путь импорта
  • исключение через external
  • условия tree shaking

Неожиданное увеличение размера бандла

Поле bytes на уровне inputs и outputs позволяет быстро выявить тяжёлые модули. Сравнение метафайлов между сборками показывает, какие зависимости увеличили вклад в итоговый размер.

Пример анализа:

  • react-dom.production.min.js — 120 KB
  • lodash — 500 KB
  • chart.js — 250 KB

Такое разбиение помогает локализовать источник роста без инструментов пост-анализа.

Проверка tree shaking

Metafile показывает, какие экспорты были фактически включены в бандл. Если ожидается, что часть API должна быть исключена, но она присутствует в outputs.imports, значит tree shaking не сработал.

Причины:

  • использование CommonJS модулей
  • side effects в package.json
  • динамические импорты

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

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

import fs from 'fs';

const metafile = JSON.parse(fs.readFileSync('meta.json', 'utf8'));

for (const [file, data] of Object.entries(metafile.inputs)) {
  console.log(file, '->', data.imports?.map(i => i.path));
}

Это позволяет выявить:

  • циклические зависимости
  • дублирование импортов
  • избыточные промежуточные слои абстракции

Работа с внешними зависимостями

В outputs.imports фиксируются зависимости, которые не были встроены в бандл (например, помеченные как external). Это критично для серверных сборок и библиотек.

"imports": [
  "react",
  "react-dom"
]

Такая информация используется для:

  • проверки корректности external-конфигурации
  • выявления случайно не встроенных модулей
  • контроля peerDependencies

Сравнение метафайлов между сборками

Один из наиболее практичных сценариев — сравнение двух metafile для оценки влияния изменений конфигурации.

Подход:

  • сохранить metafile до изменения
  • выполнить изменение конфигурации
  • сохранить новый metafile
  • сравнить outputs.bytes и структуру inputs

Даже простая дифф-операция позволяет выявить:

  • добавленные зависимости
  • удалённые модули
  • перераспределение кода по чанкам

Интеграция с визуализацией

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

Пример подготовки данных:

const graph = {};

for (const [file, data] of Object.entries(metafile.inputs)) {
  graph[file] = data.imports?.map(i => i.path) || [];
}

Дальнейшая обработка может включать:

  • построение DAG
  • выделение критического пути
  • анализ центральности модулей

Диагностика плагинов esbuild

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

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

  • проверка alias-резолвинга
  • контроль виртуальных модулей
  • диагностика трансформаций JSX/TS

Если после подключения плагина появляются неожиданные узлы в inputs, это сигнал о вмешательстве в резолвинг.

Использование в CI и мониторинге

Metafile может быть включён в pipeline сборки для автоматического контроля регрессий.

Типичные метрики:

  • общий размер бандла (outputs.bytes)
  • количество модулей (Object.keys(inputs).length)
  • количество внешних зависимостей

Эти показатели позволяют отслеживать:

  • рост зависимости проекта
  • деградацию tree shaking
  • появление дублирующих библиотек

Типичные ошибки интерпретации

Частая ошибка — воспринимать metafile как прямое отображение runtime-структуры приложения. На практике он отражает только статическую модель сборки.

Ограничения:

  • динамические импорты могут быть частично упрощены
  • runtime-условия не учитываются
  • side effects влияют на включение модулей

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

Работа с большими проектами

В монорепозиториях metafile становится особенно полезным. Он позволяет:

  • выявлять кросс-пакетные зависимости
  • контролировать влияние shared-библиотек
  • анализировать влияние изменений в отдельных пакетах на общий бандл

При большом количестве модулей JSON может становиться объёмным, поэтому часто применяется постобработка:

  • агрегация по пакетам
  • фильтрация по namespace
  • построение срезов зависимостей

Автоматизация анализа

Metafile легко интегрируется в скрипты анализа. Пример вычисления самых «тяжёлых» модулей:

const entries = Object.entries(metafile.inputs)
  .map(([name, data]) => ({ name, bytes: data.bytes }))
  .sort((a, b) => b.bytes - a.bytes);

console.log(entries.slice(0, 10));

Такой подход позволяет быстро находить узкие места без специализированных инструментов.

Связь с архитектурными решениями

Metafile часто используется для проверки архитектурных ограничений:

  • запрет на импорт между слоями
  • контроль зависимостей доменной логики
  • предотвращение циклов между модулями

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