Улучшения в SplitChunksPlugin

SplitChunksPlugin — механизм оптимизации Webpack, предназначенный для разделения кода на независимые чанки. Основная цель плагина заключается в уменьшении объёма загружаемых файлов, устранении дублирования модулей и улучшении кэширования.

До появления современного SplitChunksPlugin в Webpack активно использовался CommonsChunkPlugin, однако он имел сложную конфигурацию и множество ограничений. Начиная с Webpack 4, новый механизм автоматического разделения модулей стал значительно гибче и эффективнее.

Плагин работает автоматически при использовании режима production:

module.exports = {
  mode: 'production'
};

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


Основные проблемы без разделения чанков

При отсутствии SplitChunksPlugin возникают несколько типичных проблем.

Дублирование библиотек

Если несколько entry points используют одинаковые зависимости, библиотека может попасть в каждый итоговый bundle.

Пример:

// admin.js
import lodash from 'lodash';

// profile.js
import lodash from 'lodash';

Без разделения кода lodash окажется сразу в двух бандлах.


Плохое кэширование

Даже небольшое изменение приложения может приводить к полной инвалидизации кэша браузера.

Например:

import './button';

После изменения button.js меняется hash всего bundle, включая сторонние библиотеки.


Большой размер initial bundle

Монолитные бандлы ухудшают:

  • время загрузки;
  • First Contentful Paint;
  • Time To Interactive;
  • скорость мобильных устройств;
  • эффективность CDN-кэширования.

Базовая конфигурация SplitChunksPlugin

Простейшая настройка:

module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all'
    }
  }
};

Параметр:

chunks: 'all'

означает:

  • разделять synchronous chunks;
  • разделять asynchronous chunks;
  • анализировать все типы импортов.

Варианты параметра chunks

async

Разделяются только динамические импорты.

splitChunks: {
  chunks: 'async'
}

Пример:

import('./dashboard');

Синхронные импорты не затрагиваются.


initial

Разделяются только initial chunks.

splitChunks: {
  chunks: 'initial'
}

Dynamic import игнорируется.


all

Наиболее популярный вариант.

splitChunks: {
  chunks: 'all'
}

Webpack анализирует весь граф зависимостей.


Как работает алгоритм разделения

Webpack оценивает:

  • размер модулей;
  • количество повторных использований;
  • тип чанка;
  • лимиты запросов;
  • cache groups;
  • приоритеты;
  • размер итоговых файлов.

На основе этих данных создаётся оптимальная структура чанков.


minSize

Минимальный размер чанка для разделения.

splitChunks: {
  minSize: 20000
}

Значение указывается в байтах.

Если размер потенциального чанка меньше minSize, разделение не произойдёт.


Пример

splitChunks: {
  minSize: 30000
}

Если общий код весит 10 KB:

10 KB < 30 KB

Webpack не создаст отдельный chunk.


maxSize

Позволяет ограничить максимальный размер чанков.

splitChunks: {
  maxSize: 100000
}

Если chunk превышает лимит, Webpack пытается разбить его дополнительно.

Это особенно полезно:

  • для HTTP/2;
  • долгосрочного кэширования;
  • CDN;
  • lazy loading.

minChunks

Минимальное количество использований модуля.

splitChunks: {
  minChunks: 2
}

Модуль должен использоваться минимум дважды.


Пример

// a.js
import './utils';

// b.js
import './utils';

utils.js может быть вынесен в отдельный chunk.


maxAsyncRequests

Ограничение количества параллельных async-запросов.

splitChunks: {
  maxAsyncRequests: 30
}

Webpack старается не превышать это количество.


maxInitialRequests

Максимальное количество initial requests.

splitChunks: {
  maxInitialRequests: 30
}

Особенно важно для:

  • HTTP/1.1;
  • старых браузеров;
  • медленных сетей.

automaticNameDelimiter

Разделитель имён чанков.

splitChunks: {
  automaticNameDelimiter: '~'
}

Результат:

vendors~main.js

automaticNameMaxLength

Максимальная длина имени чанка.

splitChunks: {
  automaticNameMaxLength: 50
}

Предотвращает слишком длинные имена файлов.


enforceSizeThreshold

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

splitChunks: {
  enforceSizeThreshold: 50000
}

Даже если остальные ограничения не соблюдены, Webpack создаст новый chunk.


Cache Groups

cacheGroups — главный механизм управления разделением кода.

Именно через cache groups задаются:

  • правила группировки;
  • приоритеты;
  • кастомные чанки;
  • vendor bundles;
  • выделение framework-кода.

Базовый пример cacheGroups

module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',

      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all'
        }
      }
    }
  }
};

Все зависимости из node_modules попадут в vendors.js.


test

Условие попадания модуля в группу.

Пример через RegExp:

test: /[\\/]node_modules[\\/]/

Пример через функцию

test(module) {
  return module.resource.includes('shared');
}

name

Имя итогового чанка.

name: 'vendors'

priority

Если модуль подходит под несколько групп, используется приоритет.

cacheGroups: {
  react: {
    priority: 20
  },

  vendors: {
    priority: 10
  }
}

Модуль попадёт в react.


reuseExistingChunk

Повторное использование существующего чанка.

reuseExistingChunk: true

Позволяет избегать дублирования файлов.


enforce

Игнорирование стандартных ограничений.

enforce: true

Webpack выполнит разделение независимо от:

  • minSize;
  • minChunks;
  • request limits.

Выделение vendor bundles

Одна из самых популярных практик.


Классическая конфигурация vendors

module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',

      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          priority: -10
        }
      }
    }
  }
};

Почему vendor chunk важен

Сторонние библиотеки изменяются редко:

  • React;
  • Vue;
  • Lodash;
  • Axios;
  • RxJS.

Выделение их в отдельный chunk улучшает:

  • browser cache hit rate;
  • долгосрочное кэширование;
  • скорость повторной загрузки страниц.

Разделение framework-кода

Крупные приложения часто выделяют framework отдельно.


React bundle

cacheGroups: {
  react: {
    test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
    name: 'react',
    priority: 20
  }
}

Vue bundle

cacheGroups: {
  vue: {
    test: /[\\/]node_modules[\\/](vue|vue-router)[\\/]/,
    name: 'vue',
    priority: 20
  }
}

Разделение UI-библиотек

Большие UI-framework могут значительно увеличивать размер bundle.


Отдельный chunk для UI

cacheGroups: {
  ui: {
    test: /[\\/]node_modules[\\/](antd|@mui)[\\/]/,
    name: 'ui',
    priority: 15
  }
}

Разделение runtime

Webpack runtime тоже может быть вынесен отдельно.

optimization: {
  runtimeChunk: 'single'
}

Создаётся отдельный runtime bundle:

runtime.js

Почему runtimeChunk полезен

Без runtime chunk:

  • hash основного bundle меняется чаще;
  • кэш браузера инвалидируется;
  • уменьшается эффективность CDN.

SplitChunks и Dynamic Import

SplitChunksPlugin тесно связан с lazy loading.


Dynamic import

import('./settings');

Webpack создаёт отдельный async chunk.


Комбинация с cacheGroups

cacheGroups: {
  charts: {
    test: /chart.js/,
    name: 'charts',
    chunks: 'async'
  }
}

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


Оптимизация для HTTP/2

С HTTP/2 подход к чанкам изменился.

Раньше старались минимизировать число запросов.

С HTTP/2 важнее:

  • эффективное кэширование;
  • независимые чанки;
  • долгосрочная стабильность hash;
  • granular caching.

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

Вместо:

1 огромный bundle

предпочтительно:

много независимых чанков

Long Term Caching

Одна из ключевых задач SplitChunksPlugin.


Проблема cache invalidation

Если весь код находится в одном файле:

app.js

любое изменение меняет hash:

app.3456.js

Правильная архитектура

runtime.js
vendors.js
main.js

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


Deterministic Chunking

Webpack 5 значительно улучшил стабильность чанков.


moduleIds

optimization: {
  moduleIds: 'deterministic'
}

chunkIds

optimization: {
  chunkIds: 'deterministic'
}

Это уменьшает количество изменений hash между сборками.


SplitChunks и Tree Shaking

Обе технологии тесно связаны.


Tree Shaking

Удаляет неиспользуемый код:

import { debounce } from 'lodash-es';

SplitChunks

Разделяет оставшиеся модули по чанкам.

Комбинация технологий позволяет:

  • уменьшить размер bundle;
  • улучшить кэширование;
  • ускорить загрузку.

Проблемы слишком агрессивного разделения

Избыточное дробление тоже опасно.


Слишком много запросов

Например:

150 чанков по 5 KB

может работать хуже, чем:

20 чанков по 40 KB

Overhead runtime

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

  • metadata;
  • bootstrap;
  • manifest;
  • runtime references.

Ухудшение preload

Браузеру сложнее:

  • прогнозировать загрузку;
  • строить dependency graph;
  • оптимизировать network pipeline.

Анализ split chunks

Для анализа структуры используют:

  • webpack-bundle-analyzer;
  • stats.json;
  • Chrome DevTools;
  • Lighthouse.

webpack-bundle-analyzer

Установка:

npm install webpack-bundle-analyzer --save-dev

Подключение:

const BundleAnalyzerPlugin =
  require('webpack-bundle-analyzer')
    .BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin()
  ]
};

Что показывает analyzer

Инструмент визуализирует:

  • размер модулей;
  • состав чанков;
  • дублирование;
  • vendor dependencies;
  • gzip size;
  • parsed size.

Реальная production-конфигурация

module.exports = {
  optimization: {
    runtimeChunk: 'single',

    splitChunks: {
      chunks: 'all',

      minSize: 20000,
      maxSize: 240000,

      cacheGroups: {
        react: {
          test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
          name: 'react',
          priority: 30
        },

        vendors: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          priority: 10
        },

        common: {
          minChunks: 2,
          name: 'common',
          priority: 5,
          reuseExistingChunk: true
        }
      }
    }
  }
};

Изменения в Webpack 5

Webpack 5 существенно переработал стратегию chunk splitting.


Улучшенные deterministic ids

Более стабильные hash между сборками.


Улучшенный cache invalidation

Меньше ситуаций, когда изменяется hash vendor chunks.


Более умное разделение

Webpack 5 эффективнее:

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

Поддержка persistent caching

Кэширование результатов сборки ускоряет rebuild.

cache: {
  type: 'filesystem'
}

Практические рекомендации

Для SPA

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

runtimeChunk: 'single'
chunks: 'all'

и отдельный vendor chunk.


Для больших enterprise-приложений

Рекомендуется:

  • выделять framework отдельно;
  • выделять UI-kit отдельно;
  • использовать granular chunking;
  • анализировать cache hit rate.

Для микрофронтендов

Важно:

  • избегать дублирования React;
  • стабилизировать vendor bundles;
  • контролировать shared dependencies.

Для HTTP/2

Допустимо:

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

Типичные ошибки

Один giant vendors.js

Проблема:

vendors.js = 8 MB

Любое изменение dependency tree может инвалидировать огромный chunk.


Слишком много cacheGroups

Избыточная детализация усложняет:

  • поддержку;
  • debugging;
  • caching strategy.

Отсутствие анализа bundle

Без bundle analyzer невозможно понять:

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

Игнорирование runtime chunk

Без выделения runtime ухудшается долгосрочное кэширование.


Использование splitChunks без dynamic imports

Разделение наиболее эффективно именно в сочетании с lazy loading:

import('./admin-panel');

Архитектура эффективного code splitting

Наиболее стабильная схема:

runtime
framework
vendors
common
feature chunks
async chunks

Пример итоговой структуры

runtime.a1.js
react.b2.js
vendors.c3.js
common.d4.js
dashboard.e5.js
settings.f6.js

Такая архитектура обеспечивает:

  • минимальную инвалидизацию кэша;
  • независимое обновление частей приложения;
  • уменьшение initial load;
  • эффективную lazy loading-стратегию;
  • стабильные production-сборки.