splitVendorChunkPlugin — встроенный плагин Vite,
предназначенный для автоматического разделения зависимостей
(node_modules) на отдельные vendor-чанки. Основная задача
плагина — улучшение кэширования, ускорение повторной загрузки страниц и
уменьшение количества изменений в основных bundle-файлах приложения.
Плагин появился как удобная стратегия автоматического code splitting
без необходимости вручную конфигурировать manualChunks в
Rollup.
Пример подключения:
import { defineConfig, splitVendorChunkPlugin } from 'vite'
export default defineConfig({
plugins: [
splitVendorChunkPlugin()
]
})
После подключения Vite начинает автоматически выносить зависимости из
node_modules в отдельные чанки.
Без разделения vendor-зависимостей сборщик может создавать единый большой JavaScript-файл:
dist/
├── assets/
│ ├── index-8fd91.js
│ └── style-2ac1.css
Внутри index-8fd91.js оказываются:
При любом изменении приложения hash-файл изменяется полностью:
index-8fd91.js
↓
index-a9c44.js
Браузер повторно загружает весь bundle, даже если изменился только один компонент.
После использования splitVendorChunkPlugin структура
становится другой:
dist/
├── assets/
│ ├── index-7ad21.js
│ ├── vendor-91ca2.js
│ └── style-2ac1.css
Теперь:
index содержит код приложения;vendor содержит зависимости.При изменении бизнес-логики vendor-файл может остаться неизменным.
Это улучшает:
Внутри Vite плагин использует стратегию Rollup
manualChunks.
Фактически он автоматически генерирует аналог подобной конфигурации:
manualChunks(id) {
if (id.includes('node_modules')) {
return 'vendor'
}
}
Все зависимости из node_modules группируются в vendor
chunk.
Стандартная конфигурация:
import { defineConfig, splitVendorChunkPlugin } from 'vite'
export default defineConfig({
plugins: [
splitVendorChunkPlugin()
]
})
Результат:
Предположим, приложение использует:
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-файл может храниться в кэше месяцами.
Поскольку зависимости обновляются редко, повторные визиты становятся значительно быстрее.
Например:
Admin dashboard обычно содержит:
Размер vendor chunk может достигать нескольких мегабайт.
Где:
Конфигурация:
import { defineConfig } from 'vite'
export default defineConfig({})
Сборка:
index-1a2b3.js → 1.8 MB
Внутри:
Конфигурация:
import { defineConfig, splitVendorChunkPlugin } from 'vite'
export default defineConfig({
plugins: [
splitVendorChunkPlugin()
]
})
Сборка:
vendor-6a1f2.js → 1.5 MB
index-8b2c1.js → 300 KB
Теперь изменения UI затрагивают только index.
Важно понимать:
splitVendorChunkPlugin не уменьшает общий объём
JavaScript.
Он:
На первом посещении пользователь всё равно загружает оба чанка.
Браузер кэширует ресурсы по URL:
vendor-123.js
Hash-файл меняется только при изменении содержимого.
Если зависимости не обновились:
vendor-123.js
остаётся тем же.
Раньше множество чанков могло ухудшать производительность из-за большого количества HTTP-запросов.
HTTP/2 изменил ситуацию:
Поэтому разделение vendor chunk стало более эффективным.
splitVendorChunkPlugin хорошо работает совместно с lazy
loading.
Пример:
const AdminPage = () => import('./AdminPage')
Vite создаёт:
vendor.js
admin.js
main.js
В результате:
В dev-режиме плагин практически не влияет на работу приложения.
Причина:
Основной эффект проявляется только при vite build.
Плагин совместим с дополнительной Rollup-конфигурацией.
Пример:
import {
defineConfig,
splitVendorChunkPlugin
} from 'vite'
export default defineConfig({
plugins: [
splitVendorChunkPlugin()
],
build: {
rollupOptions: {
output: {
entryFileNames: 'js/[name].[hash].js'
}
}
}
})
Если используется собственный manualChunks, необходимо
понимать приоритеты.
Пример:
build: {
rollupOptions: {
output: {
manualChunks: {
vue: ['vue']
}
}
}
}
В такой ситуации ручная конфигурация может переопределять стратегию плагина.
Плагин — это упрощённая стратегия.
Для сложных проектов обычно применяют собственный
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'
}
}
Это позволяет:
Один vendor bundle иногда становится слишком большим:
vendor.js → 4 MB
Проблемы:
Иногда эффективнее:
react.js
charts.js
editor.js
vendor.js
Вместо:
vendor.js
Это позволяет:
После build полезно анализировать output:
vite build
или:
npm run build
Результат:
dist/assets/
Размеры чанков помогают определить:
Часто применяется визуальный анализ bundle.
Установка:
npm install -D rollup-plugin-visualizer
Конфигурация:
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
plugins: [
splitVendorChunkPlugin(),
visualizer()
]
})
После сборки генерируется интерактивная карта bundle.
dist/
├── assets/
│ ├── vendor.js
│ ├── react.js
│ ├── charts.js
│ ├── admin.js
│ ├── editor.js
│ └── main.js
splitVendorChunkPlugin не заменяет tree shaking.
Tree shaking:
Vendor splitting:
Обе стратегии работают совместно.
Иногда bundle содержит дубликаты:
lodash-es
lodash
или:
date-fns
moment
Vendor splitting не устраняет эту проблему автоматически.
Необходим:
Пример:
resolve: {
dedupe: ['react', 'react-dom']
}
Это уменьшает размер vendor chunk.
optimizeDeps влияет на dev optimization, а не на
production splitting.
Пример:
optimizeDeps: {
include: ['lodash']
}
Не определяет структуру production chunks.
Например:
Иногда единый bundle быстрее.
Если проект уже использует:
manualChunks()
Плагин может быть избыточным.
Некоторые SSR-решения требуют полностью контролируемого chunk graph.
Даже при хорошем network cache остаётся стоимость:
Большой vendor bundle:
vendor.js → 5 MB
может тормозить мобильные устройства даже после кэширования.
Для крупных приложений эффективнее route-based splitting.
Пример:
const Dashboard = () => import('./Dashboard')
const Reports = () => import('./Reports')
const Admin = () => import('./Admin')
Тогда Vite создаёт независимые route chunks.
На практике часто используется комбинация:
splitVendorChunkPlugin;Vite автоматически генерирует preload hints:
<link rel="modulepreload">
Это ускоряет загрузку vendor chunks.
Высокий приоритет загрузки:
<link rel="preload">
Низкий приоритет:
<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'
}
}
}
}
}
})
Результат:
index.js → 8 MB
200 маленьких chunk-файлов
Это создаёт:
vendor.js → 10 MB
Требуется дополнительное разделение.
Оптимальная стратегия обычно выглядит так:
DevTools позволяют анализировать:
Особенно полезны вкладки:
Плагин активно используется с:
В разных версиях Vite стратегия chunk splitting могла изменяться.
Некоторые версии:
Поэтому при обновлении Vite полезно повторно анализировать build output.
Vendor chunk влияет на:
Особенно заметно в больших monorepo.
В monorepo vendor splitting становится сложнее:
packages/
apps/
shared/
Некоторые shared-зависимости могут дублироваться между приложениями.
Требуются:
Крупные приложения обычно используют:
splitVendorChunkPlugin часто выступает лишь стартовой
стратегией, поверх которой строится более сложная архитектура bundle
splitting.