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,
включая сторонние библиотеки.
Монолитные бандлы ухудшают:
Простейшая настройка:
module.exports = {
optimization: {
splitChunks: {
chunks: 'all'
}
}
};
Параметр:
chunks: 'all'
означает:
Разделяются только динамические импорты.
splitChunks: {
chunks: 'async'
}
Пример:
import('./dashboard');
Синхронные импорты не затрагиваются.
Разделяются только initial chunks.
splitChunks: {
chunks: 'initial'
}
Dynamic import игнорируется.
Наиболее популярный вариант.
splitChunks: {
chunks: 'all'
}
Webpack анализирует весь граф зависимостей.
Webpack оценивает:
На основе этих данных создаётся оптимальная структура чанков.
Минимальный размер чанка для разделения.
splitChunks: {
minSize: 20000
}
Значение указывается в байтах.
Если размер потенциального чанка меньше minSize,
разделение не произойдёт.
splitChunks: {
minSize: 30000
}
Если общий код весит 10 KB:
10 KB < 30 KB
Webpack не создаст отдельный chunk.
Позволяет ограничить максимальный размер чанков.
splitChunks: {
maxSize: 100000
}
Если chunk превышает лимит, Webpack пытается разбить его дополнительно.
Это особенно полезно:
Минимальное количество использований модуля.
splitChunks: {
minChunks: 2
}
Модуль должен использоваться минимум дважды.
// a.js
import './utils';
// b.js
import './utils';
utils.js может быть вынесен в отдельный chunk.
Ограничение количества параллельных async-запросов.
splitChunks: {
maxAsyncRequests: 30
}
Webpack старается не превышать это количество.
Максимальное количество initial requests.
splitChunks: {
maxInitialRequests: 30
}
Особенно важно для:
Разделитель имён чанков.
splitChunks: {
automaticNameDelimiter: '~'
}
Результат:
vendors~main.js
Максимальная длина имени чанка.
splitChunks: {
automaticNameMaxLength: 50
}
Предотвращает слишком длинные имена файлов.
Принудительное разделение при достижении размера.
splitChunks: {
enforceSizeThreshold: 50000
}
Даже если остальные ограничения не соблюдены, Webpack создаст новый chunk.
cacheGroups — главный механизм управления разделением
кода.
Именно через cache groups задаются:
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
};
Все зависимости из node_modules попадут в
vendors.js.
Условие попадания модуля в группу.
Пример через RegExp:
test: /[\\/]node_modules[\\/]/
test(module) {
return module.resource.includes('shared');
}
Имя итогового чанка.
name: 'vendors'
Если модуль подходит под несколько групп, используется приоритет.
cacheGroups: {
react: {
priority: 20
},
vendors: {
priority: 10
}
}
Модуль попадёт в react.
Повторное использование существующего чанка.
reuseExistingChunk: true
Позволяет избегать дублирования файлов.
Игнорирование стандартных ограничений.
enforce: true
Webpack выполнит разделение независимо от:
Одна из самых популярных практик.
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: -10
}
}
}
}
};
Сторонние библиотеки изменяются редко:
Выделение их в отдельный chunk улучшает:
Крупные приложения часто выделяют framework отдельно.
cacheGroups: {
react: {
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
name: 'react',
priority: 20
}
}
cacheGroups: {
vue: {
test: /[\\/]node_modules[\\/](vue|vue-router)[\\/]/,
name: 'vue',
priority: 20
}
}
Большие UI-framework могут значительно увеличивать размер bundle.
cacheGroups: {
ui: {
test: /[\\/]node_modules[\\/](antd|@mui)[\\/]/,
name: 'ui',
priority: 15
}
}
Webpack runtime тоже может быть вынесен отдельно.
optimization: {
runtimeChunk: 'single'
}
Создаётся отдельный runtime bundle:
runtime.js
Без runtime chunk:
SplitChunksPlugin тесно связан с lazy loading.
import('./settings');
Webpack создаёт отдельный async chunk.
cacheGroups: {
charts: {
test: /chart.js/,
name: 'charts',
chunks: 'async'
}
}
Библиотека графиков загрузится только при необходимости.
С HTTP/2 подход к чанкам изменился.
Раньше старались минимизировать число запросов.
С HTTP/2 важнее:
Вместо:
1 огромный bundle
предпочтительно:
много независимых чанков
Одна из ключевых задач SplitChunksPlugin.
Если весь код находится в одном файле:
app.js
любое изменение меняет hash:
app.3456.js
runtime.js
vendors.js
main.js
Теперь изменение бизнес-логики не затрагивает vendor bundle.
Webpack 5 значительно улучшил стабильность чанков.
optimization: {
moduleIds: 'deterministic'
}
optimization: {
chunkIds: 'deterministic'
}
Это уменьшает количество изменений hash между сборками.
Обе технологии тесно связаны.
Удаляет неиспользуемый код:
import { debounce } from 'lodash-es';
Разделяет оставшиеся модули по чанкам.
Комбинация технологий позволяет:
Избыточное дробление тоже опасно.
Например:
150 чанков по 5 KB
может работать хуже, чем:
20 чанков по 40 KB
Каждый chunk содержит:
Браузеру сложнее:
Для анализа структуры используют:
Установка:
npm install webpack-bundle-analyzer --save-dev
Подключение:
const BundleAnalyzerPlugin =
require('webpack-bundle-analyzer')
.BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
};
Инструмент визуализирует:
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 существенно переработал стратегию chunk splitting.
Более стабильные hash между сборками.
Меньше ситуаций, когда изменяется hash vendor chunks.
Webpack 5 эффективнее:
Кэширование результатов сборки ускоряет rebuild.
cache: {
type: 'filesystem'
}
Обычно используются:
runtimeChunk: 'single'
chunks: 'all'
и отдельный vendor chunk.
Рекомендуется:
Важно:
Допустимо:
Проблема:
vendors.js = 8 MB
Любое изменение dependency tree может инвалидировать огромный chunk.
Избыточная детализация усложняет:
Без bundle analyzer невозможно понять:
Без выделения runtime ухудшается долгосрочное кэширование.
Разделение наиболее эффективно именно в сочетании с lazy loading:
import('./admin-panel');
Наиболее стабильная схема:
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
Такая архитектура обеспечивает: