Code splitting в Rollup представляет собой механизм разбиения
исходного графа модулей на несколько отдельных бандлов. В отличие от
Webpack, где код-сплиттинг часто включается автоматически через
динамические импорты, Rollup делает упор на предсказуемость и явную
конфигурацию. Одним из ключевых инструментов управления разбиением кода
является параметр manualChunks, позволяющий разработчику
напрямую управлять тем, какие модули попадут в отдельные чанки.
Rollup строит граф зависимостей начиная с entry points, после чего
пытается создать минимально возможный набор бандлов без дублирования
кода. При наличии динамических импортов (import()) он
автоматически формирует отдельные чанки для ленивых зависимостей. Однако
при статических зависимостях Rollup стремится объединять всё в один
бандл, если не заданы дополнительные правила.
Именно в этом месте появляется необходимость ручного управления разбиением:
Опция output.manualChunks позволяет явно указать Rollup,
как распределять модули по чанкам. Она может быть задана в двух
формах:
Объектный вариант используется, когда структура чанков заранее известна.
export default {
input: 'src/main.js',
output: {
dir: 'dist',
format: 'es',
manualChunks: {
vendor: ['lodash', 'axios'],
utils: ['src/utils/math.js', 'src/utils/date.js']
}
}
};
В этом случае Rollup принудительно создаст:
vendor с библиотеками lodash и
axiosutils с указанными локальными модулямиОсобенность подхода заключается в полной предсказуемости результата. Однако он плохо масштабируется в больших приложениях, где зависимости часто меняются.
Наиболее гибкий способ управления код-сплиттингом — функция:
manualChunks(id) {
if (id.includes('node_modules')) {
return 'vendor';
}
}
Функция вызывается для каждого модуля в графе зависимостей. Параметр
id представляет абсолютный путь к файлу. Возвращаемое
значение определяет имя чанка.
Если функция возвращает undefined, Rollup самостоятельно
решает, куда включить модуль.
Наиболее распространённый сценарий — выделение зависимостей из
node_modules в отдельный чанк:
manualChunks(id) {
if (id.includes('node_modules')) {
return 'vendor';
}
}
Такой подход даёт несколько преимуществ:
В реальных проектах часто требуется более тонкая сегментация:
manualChunks(id) {
if (id.includes('node_modules')) {
const parts = id.split('node_modules/')[1].split('/');
const pkgName = parts[0].startsWith('@')
? parts.slice(0, 2).join('/')
: parts[0];
return `vendor-${pkgName}`;
}
}
Такой подход создаёт отдельный чанк для каждого пакета, улучшая гранулярность кеширования.
В приложениях с выраженной архитектурой (например, feature-based structure) manualChunks используется для разбиения по доменам:
manualChunks(id) {
if (id.includes('/src/features/auth/')) {
return 'auth';
}
if (id.includes('/src/features/dashboard/')) {
return 'dashboard';
}
}
Такой подход позволяет:
Функция manualChunks не ограничена простыми условиями.
Она может реализовывать сложную логику на основе анализа путей:
manualChunks(id) {
const match = id.match(/src\/modules\/([^/]+)\//);
if (match) {
return match[1];
}
}
В этом случае каждый модуль внутри src/modules/*
превращается в отдельный чанк, имя которого соответствует названию
директории.
Такой подход особенно полезен в системах с plugin-архитектурой.
Rollup строит граф модулей до этапа генерации выходных файлов.
manualChunks вмешивается на стадии partitioning, когда:
Это означает, что manualChunks не влияет на tree-shaking
напрямую, но может косвенно изменить итоговый результат, если один
модуль включается в разные чанки.
Rollup избегает дублирования модулей между чанками. Если модуль используется в нескольких чанках, он выносится в общий разделяемый chunk или остаётся в зависимости от стратегии построения графа.
При использовании manualChunks важно учитывать:
manualChunks работает совместно с динамическими
импортами. При наличии конструкции:
import('./feature.js');
Rollup создаёт отдельный чанк автоматически. Однако
manualChunks может переопределять его структуру:
manualChunks(id) {
if (id.includes('feature')) {
return 'feature-bundle';
}
}
Это позволяет объединять несколько динамических импортов в один общий chunk.
Создание чанка на каждый файл приводит к избыточному числу HTTP-запросов и ухудшению производительности загрузки.
Использование случайных или изменяющихся значений приводит к потере кеширования:
manualChunks(id) {
return `chunk-${Date.now()}`;
}
Такой подход полностью разрушает механизм долгосрочного кеша.
Разделение без учёта реального графа приводит к дублированию кода и увеличению общего размера бандла.
В крупных приложениях применяются устойчивые стратегии:
vendor — все зависимости node_modulesapp — основная логикаКаждая крупная библиотека получает отдельный chunk
Комбинация доменного и библиотечного разделения:
manualChunks(id) {
if (id.includes('node_modules')) {
return 'vendor';
}
if (id.includes('/features/auth/')) {
return 'auth';
}
if (id.includes('/features/dashboard/')) {
return 'dashboard';
}
}
Правильно настроенный manualChunks напрямую влияет
на:
Особенно важно учитывать баланс между количеством запросов и размером каждого чанка. Rollup не оптимизирует это автоматически — ответственность полностью лежит на конфигурации.
manualChunks влияет только на структуру модулей, но не
управляет именами файлов напрямую. Имена формируются через:
output.entryFileNamesoutput.chunkFileNamesoutput.assetFileNamesТипичная конфигурация:
output: {
dir: 'dist',
format: 'es',
entryFileNames: '[name].js',
chunkFileNames: '[name]-[hash].js'
}
Сочетание стабильных chunk names и хеширования позволяет добиться эффективного кеширования.
Если модуль подходит под несколько условий manualChunks,
приоритет определяется порядком выполнения логики функции. Например:
manualChunks(id) {
if (id.includes('node_modules/react')) {
return 'react';
}
if (id.includes('node_modules')) {
return 'vendor';
}
}
В этом случае React всегда попадёт в отдельный чанк, несмотря на
общее правило для node_modules.
Механизм остаётся исключительно инструментом оптимизации сборки, а не архитектурной абстракцией приложения.