Принудительное включение зависимостей в optimizeDeps.include

Механизм optimizeDeps в Vite отвечает за предварительную оптимизацию зависимостей во время запуска dev-сервера. Эта оптимизация выполняется через esbuild и предназначена для ускорения холодного старта проекта, уменьшения количества HTTP-запросов и ускорения обработки CommonJS-модулей.

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

  • подключаться динамически;
  • импортироваться косвенно;
  • находиться внутри monorepo;
  • генерироваться во время выполнения;
  • использовать нестандартную структуру экспорта;
  • быть скрыты за alias;
  • подключаться через виртуальные модули плагинов.

Для подобных случаев существует параметр optimizeDeps.include.


Базовый синтаксис

import { defineConfig } from 'vite'

export default defineConfig({
  optimizeDeps: {
    include: ['lodash', 'axios']
  }
})

Все зависимости, перечисленные в массиве include, будут принудительно включены в этап предварительной оптимизации.


Что делает optimizeDeps.include

Во время запуска dev-сервера Vite:

  1. Сканирует исходный код.
  2. Находит импорты зависимостей.
  3. Передаёт найденные пакеты в esbuild.
  4. Создаёт оптимизированные ESM-файлы в кэше .vite.

optimizeDeps.include вмешивается в этот процесс и говорит Vite:

«Оптимизируй эти зависимости независимо от результатов автоматического анализа».

Это особенно важно для зависимостей, которые не обнаруживаются статическим анализатором.


Проблемы автоматического сканирования зависимостей

Динамические импорты

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

Проблемный пример:

const moduleName = 'lodash'
const lib = await import(moduleName)

Статический анализ здесь невозможен.

Решение:

export default defineConfig({
  optimizeDeps: {
    include: ['lodash']
  }
})

Импорт внутри условий

if (window.innerWidth > 1000) {
  import('chart.js')
}

Во время анализа Vite может не включить пакет в pre-bundling.

Принудительное включение:

optimizeDeps: {
  include: ['chart.js']
}

Импорт через alias

import api from '@shared/api'

Если alias указывает на пакет внутри workspace или symlink-зависимость, Vite может пропустить оптимизацию.


Зависимости monorepo

В monorepo-проектах часто используются локальные пакеты:

packages/
  ui/
  core/
  app/

Пакет может подключаться как:

import { Button } from '@company/ui'

Однако Vite воспринимает workspace-пакет как исходный код, а не dependency.

Иногда это приводит к:

  • медленному HMR;
  • повторной трансформации;
  • проблемам CommonJS;
  • лишним запросам.

Решение:

optimizeDeps: {
  include: ['@company/ui']
}

Как работает pre-bundling

Во время оптимизации Vite создаёт единый ESM-бандл зависимости.

Например:

import _ from 'lodash'

может быть преобразован во внутренний файл:

node_modules/.vite/deps/lodash.js

Этот файл:

  • уже преобразован в ESM;
  • содержит объединённые внутренние модули;
  • кэшируется между запусками;
  • быстрее загружается браузером.

Когда optimizeDeps.include особенно полезен

CommonJS-зависимости

Некоторые библиотеки публикуются только как CommonJS:

const moment = require('moment')

Vite автоматически конвертирует их через esbuild, но иногда пакет пропускается анализатором.

Пример:

optimizeDeps: {
  include: ['moment']
}

Большие UI-библиотеки

Крупные библиотеки компонентов:

  • antd
  • element-plus
  • primevue
  • vuetify

могут содержать множество вложенных импортов.

Предварительная оптимизация уменьшает нагрузку на dev-сервер.

optimizeDeps: {
  include: ['antd']
}

Библиотеки с множеством внутренних файлов

Например:

import debounce from 'lodash/debounce'

или:

import { format } from 'date-fns'

Без pre-bundling браузер может получать десятки отдельных модулей.


Включение вложенных зависимостей

Vite позволяет указывать вложенные entry-point.

Пример:

optimizeDeps: {
  include: [
    'lodash/debounce',
    'lodash/throttle'
  ]
}

Это особенно важно для библиотек с tree-shaking-структурой.


optimizeDeps.include и plugins

Некоторые плагины генерируют виртуальные модули:

virtual:my-module

или скрытые импорты.

Например, плагины для:

  • Markdown;
  • GraphQL;
  • i18n;
  • SVG;
  • auto-import;
  • filesystem routing.

Автоматический анализатор может не увидеть реальные зависимости.

В этом случае пакеты подключаются вручную:

optimizeDeps: {
  include: [
    'graphql',
    'vue-i18n'
  ]
}

Отличие optimizeDeps.include от build.rollupOptions.external

Это принципиально разные механизмы.

optimizeDeps.include

Используется только:

  • в dev-режиме;
  • для pre-bundling;
  • через esbuild.

Цель:

  • ускорение разработки;
  • преобразование CommonJS;
  • уменьшение количества запросов.

external

Используется:

  • во время production build;
  • через Rollup;
  • для исключения зависимости из бандла.

Пример:

build: {
  rollupOptions: {
    external: ['vue']
  }
}

Отличие include от exclude

include

Принудительно оптимизирует зависимость.

include: ['axios']

exclude

Запрещает оптимизацию.

exclude: ['my-library']

Когда нужен exclude

Некоторые пакеты:

  • уже ESM;
  • содержат side effects;
  • ломаются после pre-bundling;
  • используют environment-specific code.

Тогда их исключают:

optimizeDeps: {
  exclude: ['problematic-lib']
}

Оптимизация deep imports

Некоторые библиотеки активно используют deep import:

import Button from 'library/components/Button'

Каждый deep import может стать отдельным HTTP-запросом.

Vite позволяет заранее оптимизировать их:

optimizeDeps: {
  include: [
    'library/components/Button',
    'library/components/Input'
  ]
}

Работа с linked packages

Если используется npm link, pnpm link или symlink-зависимости, Vite может считать пакет исходным кодом приложения.

Следствия:

  • отсутствие pre-bundling;
  • медленный HMR;
  • повторные трансформации;
  • проблемы с CommonJS.

Принудительное включение помогает:

optimizeDeps: {
  include: ['shared-lib']
}

optimizeDeps.include в SSR

При использовании SSR часть зависимостей должна быть предобработана отдельно.

Например:

export default defineConfig({
  optimizeDeps: {
    include: ['some-cjs-package']
  },
  ssr: {
    noExternal: ['some-cjs-package']
  }
})

Особенности pnpm

Менеджер пакетов pnpm использует symlink-структуру node_modules.

Из-за этого некоторые зависимости:

  • могут определяться как linked packages;
  • не попадать в optimizeDeps;
  • вызывать дублирование экземпляров библиотек.

Особенно часто проблема возникает с:

  • React;
  • Vue;
  • Zustand;
  • MobX;
  • RxJS.

optimizeDeps.include и React

В больших React-проектах часто оптимизируют:

optimizeDeps: {
  include: [
    'react',
    'react-dom',
    'react/jsx-runtime'
  ]
}

Это уменьшает количество отдельных модулей во время разработки.


optimizeDeps.include и Vue

Для Vue-проектов:

optimizeDeps: {
  include: [
    'vue',
    'vue-router',
    'pinia'
  ]
}

Особенно полезно в monorepo.


optimizeDeps.include и TypeScript

TypeScript напрямую не влияет на optimizeDeps, поскольку анализ выполняется после разрешения импортов.

Однако indirect imports через:

  • path aliases;
  • tsconfig paths;
  • generated types;
  • virtual modules

могут мешать автоматическому обнаружению зависимостей.


Влияние на производительность

Ускорение cold start

Без pre-bundling:

  • браузер загружает множество файлов;
  • CommonJS преобразуется на лету;
  • dev server делает больше трансформаций.

С include:

  • зависимости заранее собраны;
  • уменьшается количество запросов;
  • ускоряется HMR.

Слишком большое include

Чрезмерное количество зависимостей в include может:

  • увеличить время старта;
  • повысить расход памяти;
  • создать огромный pre-bundle.

Плохой пример:

include: [
  'lodash',
  'rxjs',
  'three',
  'd3',
  'monaco-editor',
  'firebase'
]

если половина библиотек почти не используется.


Проверка результатов optimizeDeps

После запуска dev-сервера Vite создаёт каталог:

node_modules/.vite

или:

node_modules/.vite/deps

Там находятся оптимизированные зависимости.


Очистка кэша

После изменения optimizeDeps иногда требуется очистка:

rm -rf node_modules/.vite

или запуск:

vite --force

Флаг --force заставляет Vite пересоздать pre-bundle.


Типичный пример конфигурации

import { defineConfig } from 'vite'

export default defineConfig({
  optimizeDeps: {
    include: [
      'axios',
      'lodash',
      'vue-i18n',
      '@company/ui',
      'chart.js'
    ],
    exclude: [
      'legacy-lib'
    ]
  }
})

Диагностика проблем

Симптомы отсутствия optimizeDeps.include

Часто наблюдаются:

  • медленный старт dev server;
  • постоянные повторные rebuild;
  • ошибки CommonJS;
  • бесконечные запросы модулей;
  • проблемы HMR;
  • дублирование React/Vue;
  • нестабильная работа linked packages.

Ошибки CommonJS

Типичный пример:

Failed to resolve entry for package

или:

does not provide an export named

Часто решается через:

optimizeDeps: {
  include: ['problem-package']
}

Внутренний механизм Vite

Во время pre-bundling Vite использует:

  • dependency crawler;
  • esbuild scanner;
  • import analysis;
  • module graph.

optimizeDeps.include добавляет зависимости напрямую в graph preprocessing phase до запуска основного dev pipeline.

Это гарантирует:

  • раннюю обработку;
  • преобразование CommonJS;
  • единый ESM output;
  • стабильную работу dev server.

Практический пример для monorepo

import { defineConfig } from 'vite'

export default defineConfig({
  optimizeDeps: {
    include: [
      '@workspace/ui',
      '@workspace/utils',
      '@workspace/icons'
    ]
  }
})

Преимущества:

  • уменьшение повторной компиляции;
  • ускорение HMR;
  • более стабильный dependency graph;
  • единый экземпляр библиотек.

Практический пример для React + pnpm

export default defineConfig({
  optimizeDeps: {
    include: [
      'react',
      'react-dom',
      'scheduler',
      'react/jsx-runtime'
    ]
  }
})

Это помогает избежать:

  • duplicate React;
  • invalid hook call;
  • медленного dev startup.

Практический пример для библиотек с dynamic import

export default defineConfig({
  optimizeDeps: {
    include: [
      'monaco-editor',
      'chart.js',
      'highlight.js'
    ]
  }
})

Такие библиотеки часто загружают внутренние модули динамически и плохо анализируются автоматически.


Когда optimizeDeps.include не нужен

Во многих небольших проектах Vite прекрасно работает без ручной настройки.

Автоматического анализа достаточно, если:

  • используются обычные ESM-пакеты;
  • нет monorepo;
  • нет symlink-зависимостей;
  • отсутствуют dynamic imports;
  • зависимости имеют корректный package.json;
  • нет сложных плагинов.

Ручная настройка требуется преимущественно в крупных или нестандартных проектах.