В современных сборках JavaScript-приложений значительная часть
итогового бандла состоит не из прикладного кода, а из сторонних
зависимостей. Эти зависимости — React, Vue, Lodash, Axios, UI-библиотеки
и прочие пакеты из node_modules — обладают характерной
особенностью: они изменяются значительно реже, чем бизнес-логика
приложения. Именно это свойство используется при выделении vendor-кода в
отдельный чанк.
Vendor-кодом считается любой код, поступающий из внешних зависимостей
проекта, обычно расположенных в директории node_modules. В
контексте Webpack он рассматривается как стабильный слой приложения,
который может быть закэширован браузером отдельно от основного кода.
Ключевые свойства vendor-кода:
Разделение этого слоя позволяет снизить стоимость повторных загрузок приложения и улучшить эффективность HTTP-кэширования.
Webpack предоставляет встроенный механизм разделения кода через
SplitChunksPlugin. В современных версиях (Webpack 4 и 5) он
активирован по умолчанию в production-режиме, но для vendor-чанков
требуется явная настройка.
Базовая идея заключается в выделении модулей, удовлетворяющих
определённым критериям (обычно — происхождение из
node_modules) в отдельные чанки через
cacheGroups.
Основная конфигурация выполняется через
optimization.splitChunks:
module.exports = {
mode: 'production',
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
};
В данной конфигурации:
test определяет, какие модули попадают в группу (все из
node_modules)name задаёт имя результирующего чанкаchunks: 'all' означает анализ как синхронных, так и
асинхронных импортовРезультатом становится отдельный файл vendors.js,
содержащий весь внешний код.
Механизм разделения работает на этапе оптимизации графа модулей. Webpack:
Важным аспектом является то, что один модуль может принадлежать только одной группе с наивысшим приоритетом.
При наличии нескольких правил разделения используется параметр
priority:
splitChunks: {
cacheGroups: {
react: {
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
name: 'react-vendor',
chunks: 'all',
priority: 20
},
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
priority: 10
}
}
}
В данном случае React и ReactDOM выделяются в отдельный чанк с более высоким приоритетом, чем общий vendor.
Параметр chunks определяет, какие типы модулей
анализируются:
initial — только синхронные импортыasync — только динамические import()all — оба типа одновременноИспользование all обеспечивает наиболее полное
разделение vendor-кода, особенно в приложениях с ленивой загрузкой
маршрутов.
Webpack 5 улучшил алгоритмы дефолтного code splitting. Без дополнительной конфигурации уже создаются отдельные чанки для:
Однако vendor-чанк как единая сущность всё ещё требует явного описания, если необходима строгая контрольная структура.
Параметры, влияющие на агрегацию vendor-кода:
splitChunks: {
minSize: 20000,
maxSize: 200000,
chunks: 'all'
}
minSize задаёт минимальный размер чанкаmaxSize позволяет дробить крупные vendor-бандлы на
частиРазбиение по maxSize используется для оптимизации
параллельной загрузки в HTTP/2 и HTTP/3 окружениях.
Выделение vendor-чанка тесно связано с системой хэширования файлов. При изменении прикладного кода vendor-часть остаётся неизменной, что позволяет браузеру повторно использовать закэшированные ресурсы.
Ключевые механизмы:
Пример конфигурации output:
output: {
filename: '[name].[contenthash].js',
clean: true
}
В результате изменение бизнес-логики не приводит к перекачиванию vendor-кода.
Для максимальной эффективности часто выделяется runtime-часть Webpack:
optimization: {
runtimeChunk: 'single'
}
Runtime содержит логику загрузки модулей и их связывания. Его отделение предотвращает изменение vendor-хэшей при пересборке.
Без корректной настройки splitChunks возможны следующие проблемы:
Особенно часто это проявляется при использовании динамических импортов, когда одна и та же зависимость встречается в нескольких асинхронных ветках графа.
В проектах с несколькими точками входа vendor-логика становится особенно критичной. Без разделения каждый entry-point включает собственную копию зависимостей.
Пример структуры:
entry: {
app: './src/app.js',
admin: './src/admin.js'
}
Без splitChunks:
С vendor-чанком:
Использование import() влияет на граф зависимостей и
может приводить к созданию дополнительных чанков, содержащих
vendor-код:
import('lodash').then(({ default: _ }) => {
_.debounce(fn);
});
Если библиотека используется только в одном асинхронном модуле, она может оказаться изолированной внутри его чанка, а не в общем vendors-бандле.
Существуют несколько подходов к организации vendor-чанков:
Позволяет обновлять части зависимостей независимо.
Webpack сам формирует чанки на основе частоты использования и пересечений модулей.
Ключевым фактором стабильности vendor-чанков является правильная настройка:
priorityreuseExistingChunkenforceПример:
cacheGroups: {
vendor: {
test: /node_modules/,
name: 'vendors',
chunks: 'all',
enforce: true
}
}
enforce отключает проверку минимального размера и
гарантирует создание чанка.
Tree-shaking применяется до этапа формирования чанков. Это означает, что:
Однако эффективность зависит от формата библиотек (ESM vs CommonJS).
При корректной настройке Webpack формируется структура:
Такое разделение обеспечивает предсказуемое кэширование, уменьшение повторной загрузки и улучшение времени повторного визита пользователя.