Стратегии splitVendorChunkPlugin

splitVendorChunkPlugin — встроенный плагин Vite, предназначенный для автоматического разделения зависимостей (node_modules) на отдельные vendor-чанки. Основная задача плагина — улучшение кэширования, ускорение повторной загрузки страниц и уменьшение количества изменений в основных bundle-файлах приложения.

Плагин появился как удобная стратегия автоматического code splitting без необходимости вручную конфигурировать manualChunks в Rollup.

Пример подключения:

import { defineConfig, splitVendorChunkPlugin } from 'vite'

export default defineConfig({
    plugins: [
        splitVendorChunkPlugin()
    ]
})

После подключения Vite начинает автоматически выносить зависимости из node_modules в отдельные чанки.


Проблема монолитного bundle

Без разделения vendor-зависимостей сборщик может создавать единый большой JavaScript-файл:

dist/
├── assets/
│   ├── index-8fd91.js
│   └── style-2ac1.css

Внутри index-8fd91.js оказываются:

  • код приложения;
  • Vue/React;
  • router;
  • state manager;
  • utility-библиотеки;
  • сторонние пакеты.

При любом изменении приложения hash-файл изменяется полностью:

index-8fd91.js
↓
index-a9c44.js

Браузер повторно загружает весь bundle, даже если изменился только один компонент.


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

После использования splitVendorChunkPlugin структура становится другой:

dist/
├── assets/
│   ├── index-7ad21.js
│   ├── vendor-91ca2.js
│   └── style-2ac1.css

Теперь:

  • index содержит код приложения;
  • vendor содержит зависимости.

При изменении бизнес-логики vendor-файл может остаться неизменным.

Это улучшает:

  • browser cache;
  • CDN cache;
  • cold start;
  • repeat visit performance.

Принцип работы плагина

Внутри Vite плагин использует стратегию Rollup manualChunks.

Фактически он автоматически генерирует аналог подобной конфигурации:

manualChunks(id) {
    if (id.includes('node_modules')) {
        return 'vendor'
    }
}

Все зависимости из node_modules группируются в vendor chunk.


Использование с defineConfig

Стандартная конфигурация:

import { defineConfig, splitVendorChunkPlugin } from 'vite'

export default defineConfig({
    plugins: [
        splitVendorChunkPlugin()
    ]
})

Результат:

  • автоматически создаётся vendor bundle;
  • Vite самостоятельно определяет зависимости;
  • уменьшается размер entry chunk.

Влияние на HTTP-кэширование

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

react
react-dom
axios
lodash

После сборки:

vendor-aaa111.js
app-bbb222.js

Изменение компонента:

<button>Save</button>
↓
<button>Submit</button>

Меняется только:

app-ccc333.js

Vendor bundle остаётся прежним:

vendor-aaa111.js

Браузер использует кэшированный файл.


Разделение и долгосрочное кэширование

Стратегия особенно полезна для production CDN.

Например:

Cache-Control: max-age=31536000, immutable

Vendor-файл может храниться в кэше месяцами.

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


Когда splitVendorChunkPlugin особенно полезен

Большие SPA-приложения

Например:

  • React;
  • Vue;
  • Svelte;
  • Angular через Vite.

Панели управления

Admin dashboard обычно содержит:

  • chart libraries;
  • rich text editors;
  • date pickers;
  • UI frameworks.

Размер vendor chunk может достигать нескольких мегабайт.

SaaS-приложения

Где:

  • высокая повторяемость посещений;
  • критична скорость загрузки;
  • используется aggressive caching.

Пример без splitVendorChunkPlugin

Конфигурация:

import { defineConfig } from 'vite'

export default defineConfig({})

Сборка:

index-1a2b3.js → 1.8 MB

Внутри:

  • React;
  • ReactDOM;
  • Redux;
  • приложение;
  • charts;
  • router.

Пример со splitVendorChunkPlugin

Конфигурация:

import { defineConfig, splitVendorChunkPlugin } from 'vite'

export default defineConfig({
    plugins: [
        splitVendorChunkPlugin()
    ]
})

Сборка:

vendor-6a1f2.js → 1.5 MB
index-8b2c1.js → 300 KB

Теперь изменения UI затрагивают только index.


Разделение initial load

Важно понимать:

splitVendorChunkPlugin не уменьшает общий объём JavaScript.

Он:

  • меняет структуру загрузки;
  • улучшает caching strategy;
  • уменьшает invalidation.

На первом посещении пользователь всё равно загружает оба чанка.


Связь с browser caching

Браузер кэширует ресурсы по URL:

vendor-123.js

Hash-файл меняется только при изменении содержимого.

Если зависимости не обновились:

vendor-123.js

остаётся тем же.


Взаимодействие с HTTP/2

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

HTTP/2 изменил ситуацию:

  • multiplexing;
  • parallel loading;
  • header compression.

Поэтому разделение vendor chunk стало более эффективным.


Использование вместе с dynamic import

splitVendorChunkPlugin хорошо работает совместно с lazy loading.

Пример:

const AdminPage = () => import('./AdminPage')

Vite создаёт:

vendor.js
admin.js
main.js

В результате:

  • основная страница загружается быстрее;
  • admin chunk подгружается позже;
  • vendor dependencies кэшируются отдельно.

Поведение в development

В dev-режиме плагин практически не влияет на работу приложения.

Причина:

  • dev server использует native ESM;
  • bundle-файлы не генерируются;
  • Rollup-сборка отсутствует.

Основной эффект проявляется только при vite build.


Использование с build.rollupOptions

Плагин совместим с дополнительной Rollup-конфигурацией.

Пример:

import {
    defineConfig,
    splitVendorChunkPlugin
} from 'vite'

export default defineConfig({
    plugins: [
        splitVendorChunkPlugin()
    ],

    build: {
        rollupOptions: {
            output: {
                entryFileNames: 'js/[name].[hash].js'
            }
        }
    }
})

Конфликт с manualChunks

Если используется собственный manualChunks, необходимо понимать приоритеты.

Пример:

build: {
    rollupOptions: {
        output: {
            manualChunks: {
                vue: ['vue']
            }
        }
    }
}

В такой ситуации ручная конфигурация может переопределять стратегию плагина.


splitVendorChunkPlugin и manualChunks

Плагин — это упрощённая стратегия.

Для сложных проектов обычно применяют собственный manualChunks.

Пример:

manualChunks(id) {
    if (id.includes('node_modules/react')) {
        return 'react'
    }

    if (id.includes('node_modules/lodash')) {
        return 'lodash'
    }

    if (id.includes('node_modules/chart.js')) {
        return 'charts'
    }
}

Это позволяет:

  • изолировать тяжёлые библиотеки;
  • уменьшать invalidation;
  • оптимизировать lazy loading.

Недостатки единого vendor chunk

Один vendor bundle иногда становится слишком большим:

vendor.js → 4 MB

Проблемы:

  • медленный initial download;
  • долгий parse time;
  • высокий memory usage;
  • плохой mobile performance.

Стратегия granular chunking

Иногда эффективнее:

react.js
charts.js
editor.js
vendor.js

Вместо:

vendor.js

Это позволяет:

  • не загружать editor на главной странице;
  • изолировать редко используемые библиотеки;
  • улучшать TTI.

Анализ результата сборки

После build полезно анализировать output:

vite build

или:

npm run build

Результат:

dist/assets/

Размеры чанков помогают определить:

  • необходимость дополнительного splitting;
  • oversized vendor bundles;
  • duplicate dependencies.

Использование rollup-plugin-visualizer

Часто применяется визуальный анализ bundle.

Установка:

npm install -D rollup-plugin-visualizer

Конфигурация:

import { visualizer } from 'rollup-plugin-visualizer'

export default defineConfig({
    plugins: [
        splitVendorChunkPlugin(),
        visualizer()
    ]
})

После сборки генерируется интерактивная карта bundle.


Пример структуры после сложного splitting

dist/
├── assets/
│   ├── vendor.js
│   ├── react.js
│   ├── charts.js
│   ├── admin.js
│   ├── editor.js
│   └── main.js

Влияние на Tree Shaking

splitVendorChunkPlugin не заменяет tree shaking.

Tree shaking:

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

Vendor splitting:

  • разделяет bundle на чанки.

Обе стратегии работают совместно.


Проблема duplicate packages

Иногда bundle содержит дубликаты:

lodash-es
lodash

или:

date-fns
moment

Vendor splitting не устраняет эту проблему автоматически.

Необходим:

  • dependency audit;
  • dedupe;
  • aliasing.

Использование resolve.dedupe

Пример:

resolve: {
    dedupe: ['react', 'react-dom']
}

Это уменьшает размер vendor chunk.


Связь с optimizeDeps

optimizeDeps влияет на dev optimization, а не на production splitting.

Пример:

optimizeDeps: {
    include: ['lodash']
}

Не определяет структуру production chunks.


Когда splitVendorChunkPlugin не нужен

Маленькие приложения

Например:

  • landing page;
  • небольшой widget;
  • micro app.

Иногда единый bundle быстрее.


Очень агрессивный manualChunks

Если проект уже использует:

manualChunks()

Плагин может быть избыточным.


SSR-проекты со специальной архитектурой

Некоторые SSR-решения требуют полностью контролируемого chunk graph.


Производительность parse/eval

Даже при хорошем network cache остаётся стоимость:

  • parse;
  • compile;
  • evaluate.

Большой vendor bundle:

vendor.js → 5 MB

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


Split by route

Для крупных приложений эффективнее route-based splitting.

Пример:

const Dashboard = () => import('./Dashboard')
const Reports = () => import('./Reports')
const Admin = () => import('./Admin')

Тогда Vite создаёт независимые route chunks.


Реальная стратегия production-приложений

На практике часто используется комбинация:

  • splitVendorChunkPlugin;
  • lazy loading;
  • route splitting;
  • custom manualChunks;
  • CDN caching;
  • preload/prefetch.

Использование preload

Vite автоматически генерирует preload hints:

<link rel="modulepreload">

Это ускоряет загрузку vendor chunks.


Разница между preload и prefetch

preload

Высокий приоритет загрузки:

<link rel="preload">

prefetch

Низкий приоритет:

<link rel="prefetch">

Vendor chunk обычно preload’ится.


Практический пример архитектуры

Крупное React-приложение:

src/
├── pages/
├── widgets/
├── shared/
├── admin/
├── charts/
└── editor/

Конфигурация:

import {
    defineConfig,
    splitVendorChunkPlugin
} from 'vite'

export default defineConfig({
    plugins: [
        splitVendorChunkPlugin()
    ],

    build: {
        rollupOptions: {
            output: {
                manualChunks(id) {
                    if (id.includes('chart.js')) {
                        return 'charts'
                    }

                    if (id.includes('monaco-editor')) {
                        return 'editor'
                    }
                }
            }
        }
    }
})

Результат:

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

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

Полное отсутствие splitting

index.js → 8 MB

Чрезмерное дробление

200 маленьких chunk-файлов

Это создаёт:

  • overhead;
  • сложный dependency graph;
  • лишние network requests.

Огромный vendor.js

vendor.js → 10 MB

Требуется дополнительное разделение.


Стратегия balanced chunking

Оптимальная стратегия обычно выглядит так:

  • framework chunk;
  • vendor chunk;
  • route chunks;
  • async feature chunks;
  • isolated heavy libraries.

Проверка network waterfall

DevTools позволяют анализировать:

  • порядок загрузки;
  • blocking requests;
  • cache hits;
  • chunk dependencies.

Особенно полезны вкладки:

  • Network;
  • Coverage;
  • Performance.

Совместимость с frameworks

Плагин активно используется с:

  • React;
  • Vue;
  • Preact;
  • Svelte;
  • SolidJS.

Изменения в новых версиях Vite

В разных версиях Vite стратегия chunk splitting могла изменяться.

Некоторые версии:

  • улучшали cache invalidation;
  • меняли алгоритм группировки;
  • оптимизировали dependency graph.

Поэтому при обновлении Vite полезно повторно анализировать build output.


Влияние на CI/CD

Vendor chunk влияет на:

  • build cache;
  • Docker layer caching;
  • CDN invalidation;
  • deployment size.

Особенно заметно в больших monorepo.


Использование в monorepo

В monorepo vendor splitting становится сложнее:

packages/
apps/
shared/

Некоторые shared-зависимости могут дублироваться между приложениями.

Требуются:

  • dependency dedupe;
  • workspace optimization;
  • shared vendor strategy.

Подход enterprise-приложений

Крупные приложения обычно используют:

  • route-level splitting;
  • feature-level splitting;
  • framework isolation;
  • granular vendors;
  • performance budgets;
  • bundle analysis automation.

splitVendorChunkPlugin часто выступает лишь стартовой стратегией, поверх которой строится более сложная архитектура bundle splitting.