Улучшенный алгоритм генерации идентификаторов чанков

Webpack разбивает приложение на набор чанков — отдельных логических частей графа модулей. Каждый чанк получает собственный идентификатор (chunk id), который используется:

  • во внутреннем runtime;
  • при генерации имён файлов;
  • в механизмах ленивой загрузки;
  • в кэшировании;
  • при построении dependency graph;
  • в SplitChunksPlugin;
  • в Module Federation;
  • при работе HMR;
  • в source map и статистике сборки.

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


Проблема старых алгоритмов генерации chunk id

Ранние версии Webpack использовали простую стратегию:

chunk.id = incrementalNumber++;

В результате:

main.js      -> id 0
vendors.js   -> id 1
profile.js   -> id 2

Подобная схема имела несколько серьёзных недостатков.

Нестабильность между сборками

Добавление нового чанка в середину графа меняет все последующие идентификаторы:

Сборка #1:
0 -> main
1 -> vendors
2 -> profile

Сборка #2:
0 -> main
1 -> analytics
2 -> vendors
3 -> profile

Даже если содержимое vendors не изменилось, его id уже другой.

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

  • инвалидированию browser cache;
  • изменению имён файлов;
  • лишним повторным загрузкам;
  • ухудшению long-term caching.

Эволюция алгоритмов chunk id

Webpack постепенно внедрял более сложные схемы генерации идентификаторов.

Natural ids

Алгоритм основан на естественном порядке обнаружения чанков.

Пример:

optimization: {
  chunkIds: 'natural'
}

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

  • простая генерация;
  • минимальные вычисления;
  • нестабильность при изменении графа.

Подходит:

  • для development;
  • быстрых локальных сборок;
  • отладки.

Named ids

Webpack начал использовать имена чанков:

optimization: {
  chunkIds: 'named'
}

Результат:

main
vendors
profile

Преимущества:

  • читаемая статистика;
  • удобство анализа;
  • более стабильные значения.

Недостатки:

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

Deterministic ids

Современный production-подход.

optimization: {
  chunkIds: 'deterministic'
}

Webpack генерирует короткий, но стабильный идентификатор на основе содержимого и структуры графа.

Пример:

a1
b4
3f

Главная цель — сохранить id неизменным между сборками, если чанк логически не изменился.


Принцип работы deterministic chunk ids

Алгоритм использует несколько источников информации:

  • путь к модулям;
  • структуру dependency graph;
  • имена чанков;
  • runtime связи;
  • hash-сигнатуры;
  • порядок соединений;
  • split points.

Упрощённая схема:

chunk graph
   ↓
serialization
   ↓
hash generation
   ↓
base encoding
   ↓
final chunk id

Сериализация chunk graph

Webpack преобразует информацию о чанке в строковое представление.

Например:

src/pages/profile.js
src/components/avatar.js
src/api/user.js

Либо:

runtime=main
group=profile
async=true

Генерация hash

После сериализации Webpack создаёт hash:

f83a91dcb7...

Затем используется сокращённая версия.

Например:

f83a

Устойчивость идентификаторов

Если структура чанка не изменилась:

src/pages/profile.js
src/components/avatar.js

то id останется прежним даже после:

  • добавления других чанков;
  • изменения unrelated modules;
  • появления новых entry points.

Это критически важно для:

  • CDN;
  • immutable caching;
  • service workers;
  • HTTP cache;
  • edge caching.

Влияние chunk ids на long-term caching

Одна из главных задач improved chunk ids — минимизация cache invalidation.


Проблема каскадной инвалидации

Без стабильных идентификаторов:

main.js
vendors.js
profile.js

Добавление нового lazy chunk:

analytics.js

может изменить:

  • runtime;
  • mapping таблицы;
  • имена файлов;
  • import manifest.

В итоге браузер повторно скачивает почти всё приложение.


Стабильные runtime mappings

Webpack хранит таблицу соответствия:

{
  14: '/assets/profile.a1.js',
  15: '/assets/vendors.b4.js'
}

Deterministic ids позволяют не менять mapping без необходимости.


Связь с contenthash

Обычно используется:

output: {
  filename: '[name].[contenthash].js',
  chunkFilename: '[name].[contenthash].js'
}

Но даже при contenthash нестабильные chunk ids способны менять runtime.

Improved chunk ids уменьшают вероятность изменения:

  • bootstrap runtime;
  • manifest;
  • chunk maps.

Алгоритм оптимизации идентификаторов

Webpack применяет несколько внутренних эвристик.


Сортировка по размеру

Некоторые алгоритмы отдают приоритет большим чанкам.

Причина:

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

Минимизация длины id

Webpack пытается сохранить короткие идентификаторы:

a
b
c
d

вместо:

chunk-profile-page
chunk-dashboard-layout

Это уменьшает:

  • размер runtime;
  • объём manifest;
  • размер bootstrap-кода.

Разрешение коллизий

Hash может совпасть:

a1
a1

Webpack автоматически удлиняет идентификатор:

a1
a1f

или:

a1
a1b7

Адаптивная длина id

Webpack не использует фиксированную длину hash.

Чем больше проект:

  • тем длиннее ids;
  • тем ниже вероятность коллизий.

Маленький проект:

a
b
c

Крупный monorepo:

a91
f3d
bb7

Runtime и chunk ids

Во время выполнения Webpack runtime использует chunk ids для загрузки файлов.

Пример:

__webpack_require__.e(14)

Runtime ищет:

14 -> profile.a1.js

После чего создаётся:

<script src="/assets/profile.a1.js">

JSONP chunk loading

Классический runtime:

webpackChunk.push([
  [14],
  modules
]);

Число 14 — идентификатор чанка.

Если ids нестабильны:

  • меняется runtime;
  • меняется bootstrap;
  • ломается caching.

Chunk graph и стабильность идентификаторов

Современный Webpack использует полноценный chunk graph.


Старый подход

Ранее использовалась линейная модель:

entry -> modules

Она плохо работала:

  • с async imports;
  • dynamic imports;
  • nested chunks;
  • shared chunks.

Новый chunk graph

Теперь используется сложная графовая модель:

Entry
 ├── vendors
 ├── dashboard
 │    └── charts
 └── profile

Алгоритм генерации id анализирует:

  • связи;
  • зависимости;
  • точки разделения;
  • reuse shared chunks.

Влияние SplitChunksPlugin

SplitChunksPlugin напрямую влияет на генерацию chunk ids.


Shared chunks

Пример:

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

Webpack создаёт:

vendors
common
shared

Для них особенно важна стабильность.


Повторное использование chunk ids

Webpack пытается сохранить идентификатор shared chunk, если:

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

Deterministic ids и Module Federation

В микрофронтендах стабильность id становится критически важной.


Remote chunks

Remote container может загружать чанки динамически:

import('shop/ProductPage');

При изменении chunk ids:

  • ломаются remote mappings;
  • invalidates federation cache;
  • усложняется prefetching.

Независимость сборок

Deterministic ids позволяют:

  • собирать приложения независимо;
  • уменьшать конфликт runtime;
  • сохранять стабильность remote manifests.

Chunk ids и tree shaking

Tree shaking изменяет состав модулей внутри чанков.

Пример:

import { a } from './utils';

Unused exports удаляются:

before:
a b c d

after:
a

Это способно изменить hash чанка.

Webpack старается минимизировать влияние:

  • unrelated chunks не получают новые ids;
  • локальные изменения не затрагивают весь graph.

Влияние dynamic import

Каждый import() создаёт потенциальный async chunk.

import('./dashboard');

Webpack генерирует:

dashboard.chunk.js

или:

391.js

Improved ids уменьшают вероятность того, что:

  • новый async import изменит старые чанки;
  • произойдёт каскадный сдвиг идентификаторов.

Chunk naming strategy

Можно комбинировать deterministic ids и naming.


Magic comments

import(
  /* webpackChunkName: "dashboard" */
  './dashboard'
);

Webpack использует:

dashboard.js

или:

dashboard.a91.js

Комбинированный подход

Часто применяется:

output: {
  chunkFilename: '[name].[contenthash].js'
}

совместно с:

optimization: {
  chunkIds: 'deterministic'
}

Это обеспечивает:

  • читаемость;
  • стабильность;
  • эффективный caching.

Влияние на размер runtime

Runtime содержит таблицы соответствия ids и файлов.

Большие ids увеличивают:

  • bootstrap size;
  • initial payload;
  • memory usage.

Webpack балансирует между:

  • стабильностью;
  • длиной ids;
  • вероятностью коллизий.

Сравнение алгоритмов chunk ids

Алгоритм Стабильность Размер runtime Production
natural низкая минимальный плохо
named средняя высокий ограниченно
deterministic высокая низкий оптимально

Практическая production-конфигурация

Базовая схема

module.exports = {
  mode: 'production',

  optimization: {
    chunkIds: 'deterministic',
    moduleIds: 'deterministic',
    runtimeChunk: 'single',

    splitChunks: {
      chunks: 'all'
    }
  },

  output: {
    filename: '[name].[contenthash].js',
    chunkFilename: '[name].[contenthash].js'
  }
};

Runtime chunk isolation

optimization: {
  runtimeChunk: 'single'
}

Позволяет изолировать:

  • manifest;
  • mapping ids;
  • bootstrap runtime.

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


Анализ стабильности chunk ids

Webpack предоставляет статистику:

webpack --json > stats.json

Можно анализировать:

  • chunk graph;
  • chunk ids;
  • reused chunks;
  • invalidated chunks.

Признаки проблемной генерации ids

Частые симптомы:

  • изменение всех contenthash;
  • рост runtime после мелких изменений;
  • постоянная перезагрузка vendor chunks;
  • нестабильные async bundles.

Распространённые ошибки

Использование natural ids в production

optimization: {
  chunkIds: 'natural'
}

Приводит к:

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

Отсутствие runtimeChunk

Без изоляции runtime:

runtimeChunk: false

изменение одного чанка способно менять bootstrap.


Нестабильный SplitChunks

Слишком агрессивный splitting:

splitChunks: {
  minSize: 0
}

создаёт множество мелких чанков.

Это повышает:

  • вероятность изменения graph;
  • нестабильность ids;
  • объём runtime mappings.

Внутренние механизмы Webpack 5

Webpack 5 существенно переработал систему идентификаторов.


ChunkGraph API

Появился отдельный API:

compilation.chunkGraph

Он хранит:

  • зависимости;
  • ownership;
  • runtime relationships;
  • module connections.

Separate module/chunk id layers

Webpack разделяет:

  • module ids;
  • chunk ids.

Это уменьшает каскадные изменения.


DeterministicChunkIdsPlugin

Внутренний плагин:

DeterministicChunkIdsPlugin

реализует:

  • hashing;
  • collision handling;
  • adaptive sizing;
  • stable serialization.

Производительность генерации ids

Сложные алгоритмы увеличивают:

  • CPU usage;
  • hashing operations;
  • graph traversal.

Но выигрыш в caching обычно значительно важнее.

Особенно:

  • в SPA;
  • enterprise frontend;
  • microfrontend architecture;
  • крупных monorepo;
  • CDN-heavy deployments.

Поведение при incremental builds

В watch mode Webpack старается:

  • переиспользовать ids;
  • не перестраивать runtime;
  • сохранять стабильность mapping.

Это уменьшает:

  • rebuild time;
  • invalidation;
  • объём HMR updates.

Связь chunk ids с HMR

Hot Module Replacement использует:

  • module ids;
  • chunk ids;
  • update manifests.

Стабильные идентификаторы уменьшают:

  • вероятность конфликтов;
  • объём hot updates;
  • количество invalidated chunks.

Улучшения Webpack 5 по сравнению с Webpack 4

Webpack 5:

  • лучше сохраняет ids;
  • эффективнее работает с graph;
  • уменьшает runtime churn;
  • стабилизирует async chunks;
  • снижает cache invalidation.

Особенно заметно это в проектах с:

  • dynamic import;
  • Module Federation;
  • aggressive code splitting;
  • persistent caching;
  • большим количеством vendor dependencies.