Анализ metafile вручную

Metafile в Esbuild представляет собой JSON-документ, содержащий полное описание процесса сборки: входные файлы, граф зависимостей, выходные артефакты, размеры модулей и связи между ними. Его основное назначение — анализ сборки вне самого bundler’а, без повторного запуска компиляции.

Ключевые поля верхнего уровня:

  • inputs — все входные модули
  • outputs — сгенерированные файлы
  • metafileVersion — версия формата
  • warnings / errors — диагностическая информация (в некоторых конфигурациях)

На практике основная ценность сосредоточена в связке inputs → outputs → imports.


Логическая модель данных metafile

Metafile можно рассматривать как ориентированный граф:

  • вершины — модули (файлы)
  • ребра — импорт зависимостей
  • веса — вклад в размер бандла

Каждый input описывается структурой:

  • путь файла
  • набор импортов
  • метаданные о размере и типе

Пример логики узла:

{
  "src/app.js": {
    "bytes": 1340,
    "imports": [
      { "path": "./utils/math.js", "kind": "import-statement" },
      { "path": "react", "external": true }
    ]
  }
}

Поле inputs: анализ исходного графа

inputs содержит полное дерево зависимостей до трансформации.

Каждый ключ — это абсолютный или относительный путь к модулю, а значение — описание его участия в сборке:

Основные параметры:

  • bytes — размер исходного файла
  • imports — список зависимостей
  • format — тип модуля (esm, cjs)
  • entryPoint — признак точки входа

Важная особенность

Esbuild фиксирует не только прямые импорты, но и транзитивные зависимости через цепочки модулей. Это позволяет вручную восстанавливать полный dependency graph без дополнительных инструментов.


Поле outputs: результат сборки

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

Каждый output содержит:

  • file path
  • inputs mapping
  • bytes
  • imports
  • exports (частично)
  • css / js тип

Пример структуры:

{
  "dist/app.js": {
    "bytes": 245000,
    "inputs": {
      "src/app.js": { "bytesInOutput": 1200 },
      "src/utils/math.js": { "bytesInOutput": 800 }
    },
    "imports": ["react", "react-dom"]
  }
}

Ручной анализ зависимостей

Метafile позволяет строить граф без дополнительных парсеров.

Выделение dependency graph

Алгоритм:

  1. Пройти по outputs
  2. Извлечь inputs каждого output
  3. Построить ориентированные связи
  4. Нормализовать пути

Псевдокод:

const graph = new Map();

for (const [outFile, outData] of Object.entries(metafile.outputs)) {
  for (const input of Object.keys(outData.inputs)) {
    if (!graph.has(input)) graph.set(input, []);
    graph.get(input).push(outFile);
  }
}

Анализ влияния модулей на размер бандла

Одна из ключевых задач — определение «тяжёлых» модулей.

Esbuild предоставляет поле bytesInOutput, которое показывает вклад модуля в конкретный output.

Суммарный вклад

Для расчёта общего влияния:

function calculateImpact(metafile) {
  const impact = {};

  for (const output of Object.values(metafile.outputs)) {
    for (const [input, data] of Object.entries(output.inputs)) {
      impact[input] = (impact[input] || 0) + data.bytesInOutput;
    }
  }

  return impact;
}

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

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

Поиск дублирующихся зависимостей

Metafile часто показывает повторное включение одних и тех же модулей в разных чанках.

Признаки:

  • один input встречается в нескольких outputs
  • суммарный bytesInOutput выше ожидаемого
  • пересечение dependency chains

Аналитика:

const occurrences = {};

for (const [out, data] of Object.entries(metafile.outputs)) {
  for (const input of Object.keys(data.inputs)) {
    occurrences[input] = (occurrences[input] || 0) + 1;
  }
}

Высокое значение указывает на отсутствие эффективного code splitting.


Анализ внешних зависимостей

External imports (например, react, lodash) не включаются в bundle, но фиксируются в metafile.

Они находятся в:

"imports": [
  { "external": true, "path": "react" }
]

Практическая интерпретация:

  • выявление сторонних библиотек
  • контроль версии через lockfile
  • анализ поверхностной зависимости проекта

Разбор цепочек импортов

Metafile позволяет реконструировать цепочку:

entry → moduleA → moduleB → moduleC

Алгоритм обхода:

function buildChains(inputs) {
  const chains = [];

  function dfs(file, path = []) {
    const node = inputs[file];
    if (!node) return;

    const newPath = [...path, file];

    for (const imp of node.imports || []) {
      if (!imp.external) {
        dfs(imp.path, newPath);
      }
    }

    chains.push(newPath);
  }

  for (const file of Object.keys(inputs)) {
    if (inputs[file].entryPoint) {
      dfs(file);
    }
  }

  return chains;
}

Анализ tree-shaking эффективности

Metafile позволяет оценить, насколько эффективно удаляется неиспользуемый код.

Признаки плохого tree-shaking:

  • крупные модули с малым реальным использованием
  • высокий bytesInOutput при низком количестве импортируемых символов
  • сохранение целых библиотек вместо отдельных функций

Метод оценки:

ratio = bytesInOutput / bytesInInput

Чем ближе к 1 — тем меньше эффект оптимизации.


Построение визуализации зависимостей

Metafile часто используется как источник для графов (D3, Cytoscape).

Формирование узлов:

  • node = файл
  • edge = import
const nodes = Object.keys(metafile.inputs).map(id => ({
  id
}));

const edges = [];

for (const [file, data] of Object.entries(metafile.inputs)) {
  for (const imp of data.imports || []) {
    edges.push({
      source: file,
      target: imp.path
    });
  }
}

Анализ чанков и code splitting

В outputs можно обнаружить:

  • основной бандл
  • lazy chunks
  • vendor chunks

Метод анализа:

  1. сгруппировать outputs по size
  2. выделить повторяющиеся inputs
  3. определить shared modules

Shared modules — ключ к оптимизации splitting.


Сравнение сборок через diff metafile

Metafile можно использовать для сравнения двух билдов.

Алгоритм:

  • сравнить outputs
  • сравнить bytes
  • сравнить набор inputs

Пример логики:

function diff(metaA, metaB) {
  const result = {};

  for (const file of Object.keys(metaA.outputs)) {
    const a = metaA.outputs[file];
    const b = metaB.outputs[file];

    result[file] = {
      sizeDiff: (b?.bytes || 0) - (a?.bytes || 0)
    };
  }

  return result;
}

Выявление аномалий сборки

Metafile позволяет обнаружить:

  • резкий рост одного модуля
  • неожиданные внешние зависимости
  • отсутствие tree-shaking
  • дублирование библиотек

Типичные сигналы:

  • один файл > 500KB
  • один dependency встречается во всех outputs
  • резкое увеличение bytesInOutput между сборками

Использование метаданных для оптимизации

Практический анализ metafile приводит к следующим действиям:

  • разбиение больших модулей
  • внедрение dynamic import
  • устранение циклических зависимостей
  • оптимизация vendor chunk
  • контроль сторонних библиотек

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