Интерпретация stats.json

Файл stats.json — это подробный отчёт Webpack о процессе сборки проекта. Он содержит информацию о:

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

stats.json используется для:

  • анализа производительности;
  • поиска тяжёлых зависимостей;
  • диагностики проблем оптимизации;
  • анализа структуры бандла;
  • работы с инструментами визуализации;
  • аудита tree shaking;
  • анализа chunk splitting.

Генерация stats.json

Базовый способ генерации:

webpack --profile --json > stats.json

Либо:

npx webpack --json > stats.json

Для production-сборки:

npx webpack --mode production --json > stats.json

Через конфигурацию:

module.exports = {
  stats: 'verbose'
};

Или программно:

const webpack = require('webpack');
const config = require('./webpack.config');

webpack(config, (err, stats) => {
  require('fs').writeFileSync(
    'stats.json',
    JSON.stringify(stats.toJson(), null, 2)
  );
});

Структура stats.json

Типичный файл состоит из следующих разделов:

{
  "hash": "...",
  "version": "5.x.x",
  "time": 3456,
  "builtAt": 1712345678901,
  "publicPath": "auto",
  "outputPath": "/dist",
  "assets": [],
  "chunks": [],
  "modules": [],
  "entrypoints": {},
  "namedChunkGroups": {}
}

Основной интерес представляют:

  • assets
  • chunks
  • modules
  • entrypoints
  • namedChunkGroups

Раздел assets

Раздел assets описывает итоговые файлы сборки.

Пример:

{
  "assets": [
    {
      "name": "main.js",
      "size": 452331,
      "emitted": true,
      "cached": false,
      "chunkNames": ["main"]
    }
  ]
}

Основные поля assets

name

Имя итогового файла:

"name": "vendors.js"

size

Размер файла в байтах:

"size": 854331

Размер показывает:

  • итоговый объём JS;
  • эффективность минификации;
  • влияние зависимостей;
  • успешность tree shaking.

emitted

Показывает, был ли файл реально записан на диск:

"emitted": true

Если файл не изменился:

"emitted": false

cached

Файл взят из кеша:

"cached": true

chunkNames

Список chunk name:

"chunkNames": ["main"]

info

Дополнительная информация:

"info": {
  "minimized": true
}

Раздел chunks

chunks описывает логические части бандла.

Пример:

{
  "chunks": [
    {
      "id": 179,
      "names": ["vendors"],
      "files": ["vendors.js"],
      "entry": false,
      "initial": true,
      "modules": []
    }
  ]
}

Что такое chunk

Chunk — это группа модулей, объединённых Webpack.

Chunk может представлять:

  • entry point;
  • async chunk;
  • vendor bundle;
  • lazy-loaded module;
  • runtime.

Важные поля chunks

id

Уникальный идентификатор:

"id": 179

names

Имя chunk:

"names": ["main"]

files

Файлы, связанные с chunk:

"files": [
  "main.js"
]

entry

Является ли chunk entry point:

"entry": true

initial

Загружается ли chunk сразу:

"initial": true

Если:

"initial": false

значит chunk асинхронный.


rendered

Был ли chunk реально сгенерирован:

"rendered": true

size

Размер chunk:

"size": 945122

siblings

Связанные chunks:

"siblings": [23, 24]

parents

Родительские chunks:

"parents": [1]

children

Дочерние chunks:

"children": [45]

Раздел modules

Наиболее важный раздел.

Именно здесь анализируются:

  • тяжёлые зависимости;
  • tree shaking;
  • дублирование;
  • side effects;
  • причины включения модуля;
  • nested dependencies.

Пример:

{
  "modules": [
    {
      "name": "./src/index.js",
      "size": 532,
      "chunks": [179],
      "issuer": null,
      "reasons": []
    }
  ]
}

Основные поля modules

name

Путь к модулю:

"name": "./src/components/Button.js"

size

Размер модуля:

"size": 14221

Это размер до минификации.


chunks

Chunks, в которые попал модуль:

"chunks": [179]

issuer

Кто импортировал модуль:

"issuer": "./src/index.js"

Это одно из ключевых полей для анализа цепочек зависимостей.


issuerPath

Полный путь импорта:

"issuerPath": [
  {
    "name": "./src/index.js"
  },
  {
    "name": "./src/app.js"
  }
]

Позволяет понять:

  • почему модуль оказался в сборке;
  • через какую цепочку импортов он подключён.

reasons

Причины включения модуля.

Пример:

"reasons": [
  {
    "type": "harmony import specifier",
    "userRequest": "./Button"
  }
]

Анализ reasons

reasons особенно важен при поиске:

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

Пример:

{
  "type": "cjs require",
  "userRequest": "lodash"
}

Это означает:

  • модуль был подключён через CommonJS;
  • tree shaking может работать хуже.

Типы reasons

harmony import

ES Modules импорт:

"type": "harmony import"

cjs require

CommonJS require:

"type": "cjs require"

import()

Динамический импорт:

"type": "import()"

entry

Модуль является entry point:

"type": "entry"

Анализ размеров

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

Пример:

{
  "name": "./node_modules/moment/moment.js",
  "size": 329122
}

Это сигнал:

  • библиотека сильно влияет на bundle size;
  • возможно, требуется замена.

Частые виновники большого размера

moment

Очень тяжёлые locale-файлы.


lodash

Импорт всей библиотеки:

import _ fr om 'lodash';

вместо:

import debounce fr om 'lodash/debounce';

chart.js

Крупная визуализационная библиотека.


monaco-editor

Огромный редактор кода.


date-fns vs moment

date-fns обычно значительно меньше.


Анализ tree shaking

Webpack может удалять неиспользуемый код.

В stats.json это видно через:

  • usedExports
  • providedExports
  • optimizationBailout

usedExports

Какие exports реально используются:

"usedExports": ["debounce"]

Если:

"usedExports": false

значит tree shaking не сработал.


providedExports

Какие exports предоставляет модуль:

"providedExports": [
  "map",
  "filter",
  "reduce"
]

optimizationBailout

Причины отказа оптимизации:

"optimizationBailout": [
  "CommonJS bailout: module.exports is used directly"
]

Это крайне важный раздел.


Типичные optimization bailouts

CommonJS

"CommonJS bailout"

ESM оптимизируются лучше.


Side effects

"ModuleConcatenation bailout"

Dynamic exports

"Cannot determine exports"

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


Анализ splitChunks

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

optimization: {
  splitChunks: {
    chunks: 'all'
  }
}

в stats.json появляются:

  • vendor chunks;
  • shared chunks;
  • async chunks.

Как анализировать splitChunks

Проверяются:

  • размеры vendor chunks;
  • дублирование модулей;
  • повторное попадание библиотек;
  • баланс initial/async chunks.

Пример плохого splitChunks

{
  "name": "vendors-node_modules_react-dom_index_js.js",
  "size": 2400000
}

Огромный vendor chunk ухудшает:

  • initial load;
  • TTI;
  • caching strategy.

Анализ duplicated modules

Иногда одна библиотека попадает в сборку несколько раз.

Пример:

{
  "name": "./node_modules/lodash/lodash.js",
  "chunks": [1]
}

и:

{
  "name": "./node_modules/lodash-es/lodash.js",
  "chunks": [5]
}

или разные версии:

node_modules/react
node_modules/some-lib/node_modules/react

Это увеличивает:

  • размер бандла;
  • время парсинга;
  • memory usage.

Анализ entrypoints

Раздел:

"entrypoints": {
  "main": {
    "assets": [
      {
        "name": "main.js"
      }
    ]
  }
}

показывает:

  • какие assets относятся к entry;
  • initial загрузку;
  • зависимости entrypoint.

namedChunkGroups

Показывает группы chunk.

Особенно важно при:

  • dynamic imports;
  • lazy loading;
  • splitChunks.

Пример:

"namedChunkGroups": {
  "main": {
    "chunks": [179]
  }
}

Анализ времени сборки

Поле:

"time": 3456

означает время сборки в миллисекундах.

Но более полезен детальный profiling.


Профилирование

Генерация:

webpack --profile --json > stats.json

После этого появляются:

  • profile
  • factory
  • building
  • dependencies

profile

Пример:

"profile": {
  "factory": 12,
  "building": 342
}

factory

Время создания модуля.


building

Время обработки loader’ами.

Большие значения обычно означают:

  • тяжёлый Babel;
  • сложный TypeScript;
  • медленные loaders;
  • source maps.

Анализ loader performance

Пример проблемного модуля:

{
  "name": "./src/app.ts",
  "profile": {
    "building": 5321
  }
}

Причины:

  • ts-loader в full typecheck режиме;
  • сложные source maps;
  • babel-polyfill;
  • heavy transforms.

Анализ source maps

Source maps сильно влияют на:

  • время сборки;
  • размер assets;
  • memory consumption.

В stats.json можно увидеть рост:

  • chunk size;
  • asset size;
  • build time.

devtool и влияние на сборку

eval

Очень быстрый.


source-map

Очень медленный, но качественный.


cheap-module-source-map

Компромисс между скоростью и качеством.


Анализ async chunks

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

import('./admin');

Webpack создаёт async chunk.

В stats.json:

{
  "initial": false
}

Проверка lazy loading

Необходимо убедиться:

  • код действительно вынесен;
  • модуль не попал в initial chunk;
  • отсутствует дублирование.

Анализ cache groups

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

splitChunks: {
  cacheGroups: {
    vendors: {
      test: /node_modules/
    }
  }
}

в stats.json можно анализировать:

  • корректность группировки;
  • размеры;
  • пересечения;
  • shared dependencies.

Анализ orphan modules

Иногда встречаются orphan modules.

Это модули:

  • без chunk;
  • без использования;
  • исключённые оптимизацией.

Анализ sideEffects

Webpack учитывает:

"sideEffects": false

в package.json.

Это влияет на tree shaking.


Пример эффективного sideEffects

{
  "sideEffects": false
}

Позволяет удалять:

  • unused imports;
  • unused reexports;
  • unused utility functions.

Проблемы интерпретации stats.json

Размер модуля не равен размеру в итоговом JS

size обычно показывает размер до:

  • минификации;
  • gzip;
  • brotli.

Один модуль может входить в несколько chunks

Например:

"chunks": [1, 5, 8]

Vendor chunks искажают картину

Большой chunk не всегда проблема.

Иногда:

  • хороший кеш;
  • редкие обновления;
  • long-term caching

делают крупный vendor bundle допустимым.


Практический сценарий анализа

Шаг 1. Поиск самых тяжёлых assets

Сортировка по:

"size"

Шаг 2. Анализ крупных chunks

Проверяются:

  • initial chunks;
  • vendors;
  • async bundles.

Шаг 3. Анализ модулей

Поиск:

  • moment;
  • lodash;
  • duplicated libs;
  • editor libraries;
  • charting libraries.

Шаг 4. Проверка tree shaking

Анализируются:

  • usedExports;
  • optimizationBailout;
  • CommonJS dependencies.

Шаг 5. Анализ async splitting

Проверяется:

  • lazy loading;
  • dynamic import;
  • cacheGroups.

Инструменты для визуализации stats.json

webpack-bundle-analyzer

Самый популярный инструмент.

Показывает:

  • treemap;
  • размеры модулей;
  • структуру chunks.

webpack analyse

Простой визуальный анализ.


speed-measure-webpack-plugin

Анализ времени loader’ов и plugins.


statoscope

Глубокий анализ Webpack internals.


Пример интерпретации проблемной сборки

Предположим:

{
  "name": "main.js",
  "size": 8400000
}

Дальнейший анализ показывает:

{
  "name": "./node_modules/moment/locale",
  "size": 1200000
}

и:

{
  "optimizationBailout": [
    "CommonJS bailout"
  ]
}

Выводы:

  • moment подключён полностью;
  • locale не удаляются;
  • tree shaking не работает;
  • CommonJS ухудшает оптимизацию.

Интерпретация warnings

В stats.json присутствуют:

"warnings": []

Часто встречаются:

  • asset size lim it;
  • entrypoint size lim it;
  • conflicting order;
  • deoptimization warnings.

Asset size limit

Пример:

"asset size limit"

Webpack предупреждает о слишком крупном asset.


Entrypoint size limit

Слишком тяжёлый initial entrypoint.


Интерпретация errors

Раздел:

"errors": []

может содержать:

  • loader errors;
  • parse errors;
  • module resolution errors;
  • chunk conflicts.

Важность production stats

Development stats часто:

  • содержат eval;
  • включают HMR;
  • не используют minimization;
  • имеют искажённые размеры.

Анализировать необходимо production build.


Интерпретация runtime modules

Webpack 5 добавляет runtime modules.

Пример:

{
  "moduleType": "runtime"
}

Они отвечают за:

  • загрузку chunks;
  • module cache;
  • publicPath;
  • federation runtime.

Интерпретация concatenated modules

При scope hoisting:

"modules": [
  {
    "modules": []
  }
]

Webpack объединяет несколько модулей в один.

Это улучшает:

  • parsing speed;
  • execution speed;
  • compression ratio.

Анализ ModuleConcatenation bailout

Если scope hoisting не сработал:

"ModuleConcatenation bailout"

Причины:

  • CommonJS;
  • dynamic exports;
  • eval;
  • unsupported syntax.

Анализ runtime chunk

При:

optimization: {
  runtimeChunk: 'single'
}

в stats.json появляется отдельный runtime chunk.

Это улучшает:

  • long-term caching;
  • cache invalidation;
  • стабильность hash.