Вынос vendor-кода в отдельный чанк

В современных сборках JavaScript-приложений значительная часть итогового бандла состоит не из прикладного кода, а из сторонних зависимостей. Эти зависимости — React, Vue, Lodash, Axios, UI-библиотеки и прочие пакеты из node_modules — обладают характерной особенностью: они изменяются значительно реже, чем бизнес-логика приложения. Именно это свойство используется при выделении vendor-кода в отдельный чанк.

Vendor-кодом считается любой код, поступающий из внешних зависимостей проекта, обычно расположенных в директории node_modules. В контексте Webpack он рассматривается как стабильный слой приложения, который может быть закэширован браузером отдельно от основного кода.

Ключевые свойства vendor-кода:

  • высокая стабильность между релизами
  • слабая связь с бизнес-логикой
  • крупный объём относительно прикладного кода
  • возможность повторного использования между страницами и модулями

Разделение этого слоя позволяет снизить стоимость повторных загрузок приложения и улучшить эффективность HTTP-кэширования.

Механизм разделения в Webpack

Webpack предоставляет встроенный механизм разделения кода через SplitChunksPlugin. В современных версиях (Webpack 4 и 5) он активирован по умолчанию в production-режиме, но для vendor-чанков требуется явная настройка.

Базовая идея заключается в выделении модулей, удовлетворяющих определённым критериям (обычно — происхождение из node_modules) в отдельные чанки через cacheGroups.

Базовая конфигурация vendor-чанка

Основная конфигурация выполняется через 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, содержащий весь внешний код.

Работа SplitChunksPlugin

Механизм разделения работает на этапе оптимизации графа модулей. Webpack:

  1. Строит dependency graph
  2. Анализирует каждый модуль на предмет принадлежности к cacheGroups
  3. Группирует модули по совпавшим правилам
  4. Создаёт отдельные чанки
  5. Переписывает зависимости в исходных чанках

Важным аспектом является то, что один модуль может принадлежать только одной группе с наивысшим приоритетом.

Управление приоритетами групп

При наличии нескольких правил разделения используется параметр 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: all, async и initial

Параметр chunks определяет, какие типы модулей анализируются:

  • initial — только синхронные импорты
  • async — только динамические import()
  • all — оба типа одновременно

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

Автоматическое разделение в Webpack 5

Webpack 5 улучшил алгоритмы дефолтного code splitting. Без дополнительной конфигурации уже создаются отдельные чанки для:

  • крупных библиотек
  • повторно используемых модулей
  • асинхронных зависимостей

Однако vendor-чанк как единая сущность всё ещё требует явного описания, если необходима строгая контрольная структура.

Контроль размера чанков

Параметры, влияющие на агрегацию vendor-кода:

splitChunks: {
  minSize: 20000,
  maxSize: 200000,
  chunks: 'all'
}
  • minSize задаёт минимальный размер чанка
  • maxSize позволяет дробить крупные vendor-бандлы на части

Разбиение по maxSize используется для оптимизации параллельной загрузки в HTTP/2 и HTTP/3 окружениях.

Кэширование и стабильность хэшей

Выделение vendor-чанка тесно связано с системой хэширования файлов. При изменении прикладного кода vendor-часть остаётся неизменной, что позволяет браузеру повторно использовать закэшированные ресурсы.

Ключевые механизмы:

  • contenthash в имени файла
  • долгоживущие зависимости
  • стабильные идентификаторы модулей

Пример конфигурации output:

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

В результате изменение бизнес-логики не приводит к перекачиванию vendor-кода.

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

Для максимальной эффективности часто выделяется runtime-часть Webpack:

optimization: {
  runtimeChunk: 'single'
}

Runtime содержит логику загрузки модулей и их связывания. Его отделение предотвращает изменение vendor-хэшей при пересборке.

Проблемы дублирования зависимостей

Без корректной настройки splitChunks возможны следующие проблемы:

  • повторное включение одной библиотеки в разные чанки
  • увеличение общего размера загрузки
  • нарушение кэш-стратегии

Особенно часто это проявляется при использовании динамических импортов, когда одна и та же зависимость встречается в нескольких асинхронных ветках графа.

Мульти-entry и vendor-чанки

В проектах с несколькими точками входа vendor-логика становится особенно критичной. Без разделения каждый entry-point включает собственную копию зависимостей.

Пример структуры:

entry: {
  app: './src/app.js',
  admin: './src/admin.js'
}

Без splitChunks:

  • app.bundle.js содержит React
  • admin.bundle.js содержит React

С vendor-чанком:

  • vendors.js содержит React
  • app.js и admin.js ссылаются на общий модуль

Динамические импорты и влияние на vendor

Использование import() влияет на граф зависимостей и может приводить к созданию дополнительных чанков, содержащих vendor-код:

import('lodash').then(({ default: _ }) => {
  _.debounce(fn);
});

Если библиотека используется только в одном асинхронном модуле, она может оказаться изолированной внутри его чанка, а не в общем vendors-бандле.

Стратегии группировки vendor-кода

Существуют несколько подходов к организации vendor-чанков:

Общий vendor-чанк

  • вся внешняя логика в одном файле
  • простая конфигурация
  • менее гибкое кэширование

Разделение по библиотекам

  • react-vendor
  • ui-vendor
  • utils-vendor

Позволяет обновлять части зависимостей независимо.

Автоматическое группирование

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

Приоритеты и стабильность сборки

Ключевым фактором стабильности vendor-чанков является правильная настройка:

  • priority
  • reuseExistingChunk
  • enforce

Пример:

cacheGroups: {
  vendor: {
    test: /node_modules/,
    name: 'vendors',
    chunks: 'all',
    enforce: true
  }
}

enforce отключает проверку минимального размера и гарантирует создание чанка.

Влияние tree-shaking на vendor-код

Tree-shaking применяется до этапа формирования чанков. Это означает, что:

  • неиспользуемые части библиотек исключаются
  • vendor-чанк содержит только реально используемые экспорты
  • размер значительно уменьшается при ES modules

Однако эффективность зависит от формата библиотек (ESM vs CommonJS).

Итоговое поведение в production-сборке

При корректной настройке Webpack формируется структура:

  • отдельный runtime chunk
  • vendor chunk с зависимостями
  • application chunks с бизнес-логикой
  • async chunks для ленивых модулей

Такое разделение обеспечивает предсказуемое кэширование, уменьшение повторной загрузки и улучшение времени повторного визита пользователя.