Расширение конфига через mergeConfig

Функция mergeConfig в Vite предназначена для объединения нескольких конфигурационных объектов Vite в один итоговый конфиг. На практике она используется при создании плагинов, библиотек, внутренних CLI-инструментов, корпоративных шаблонов и многослойных конфигураций.

mergeConfig позволяет:

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

Наиболее часто функция применяется в экосистемных инструментах поверх Vite.


Импорт функции

import { mergeConfig } from 'vite'

Функция принимает два объекта:

mergeConfig(configA, configB)

Второй конфиг расширяет первый.


Базовый принцип объединения

Пример:

import { mergeConfig } from 'vite'

const baseConfig = {
    server: {
        port: 3000
    }
}

const customConfig = {
    server: {
        host: true
    }
}

const config = mergeConfig(baseConfig, customConfig)

console.log(config)

Результат:

{
    server: {
        port: 3000,
        host: true
    }
}

Без mergeConfig пришлось бы вручную объединять вложенные объекты.


Проблема поверхностного объединения

Обычный оператор spread работает только на верхнем уровне:

const config = {
    ...baseConfig,
    ...customConfig
}

Если оба объекта содержат server, второй объект полностью перезапишет первый:

{
    server: {
        host: true
    }
}

Свойство port исчезнет.

mergeConfig выполняет глубокое объединение вложенных структур.


Объединение resolve.alias

Одна из самых частых задач — расширение алиасов.

Базовая конфигурация:

const baseConfig = {
    resolve: {
        alias: {
            '@': '/src'
        }
    }
}

Дополнительная:

const testConfig = {
    resolve: {
        alias: {
            '@mocks': '/tests/mocks'
        }
    }
}

Объединение:

const config = mergeConfig(baseConfig, testConfig)

Результат:

{
    resolve: {
        alias: {
            '@': '/src',
            '@mocks': '/tests/mocks'
        }
    }
}

Расширение define

const baseConfig = {
    define: {
        __DEV__: true
    }
}

const prodConfig = {
    define: {
        __API_URL__: '"https://api.site.com"'
    }
}

const config = mergeConfig(baseConfig, prodConfig)

Результат:

{
    define: {
        __DEV__: true,
        __API_URL__: '"https://api.site.com"'
    }
}

Объединение массивов

Поведение массивов

mergeConfig не всегда просто заменяет массивы. Для некоторых структур используется специальная логика.

Пример с плагинами:

const baseConfig = {
    plugins: [
        react()
    ]
}

const customConfig = {
    plugins: [
        legacy()
    ]
}

Результат:

{
    plugins: [
        react(),
        legacy()
    ]
}

Массивы плагинов объединяются.


Расширение Rollup-настроек

const baseConfig = {
    build: {
        rollupOptions: {
            external: ['vue']
        }
    }
}

const libConfig = {
    build: {
        rollupOptions: {
            external: ['react']
        }
    }
}

После объединения:

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

Это особенно важно при создании библиотек.


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

Наиболее распространённый сценарий:

import { defineConfig, mergeConfig } from 'vite'

const baseConfig = defineConfig({
    server: {
        port: 3000
    }
})

export default mergeConfig(baseConfig, {
    server: {
        host: true
    }
})

Разделение конфигурации по файлам

Базовый конфиг

// vite.base.js

export default {
    resolve: {
        alias: {
            '@': '/src'
        }
    }
}

Development-конфиг

// vite.dev.js

export default {
    server: {
        port: 5173
    }
}

Production-конфиг

// vite.prod.js

export default {
    build: {
        minify: 'esbuild'
    }
}

Итоговое объединение

import { mergeConfig } from 'vite'

import baseConfig from './vite.base'
import devConfig from './vite.dev'

export default mergeConfig(baseConfig, devConfig)

Каскадное объединение

Можно строить многоуровневую конфигурацию.

const config = mergeConfig(
    baseConfig,
    mergeConfig(
        frameworkConfig,
        userConfig
    )
)

Порядок имеет значение.

Последний конфиг имеет приоритет.


Приоритет свойств

Если свойства конфликтуют:

const a = {
    server: {
        port: 3000
    }
}

const b = {
    server: {
        port: 8080
    }
}

Результат:

{
    server: {
        port: 8080
    }
}

Значение из второго конфига перезаписывает первое.


Расширение конфигурации в плагинах

Многие плагины Vite динамически расширяют конфигурацию через mergeConfig.

Пример:

import { mergeConfig } from 'vite'

export default function customPlugin() {
    return {
        name: 'custom-plugin',

        config(userConfig) {
            return mergeConfig(userConfig, {
                define: {
                    __CUSTOM__: true
                }
            })
        }
    }
}

Генерация конфигурации на основе режима

import { defineConfig, mergeConfig } from 'vite'

const baseConfig = {
    resolve: {
        alias: {
            '@': '/src'
        }
    }
}

const devConfig = {
    server: {
        port: 3000
    }
}

const prodConfig = {
    build: {
        sourcemap: false
    }
}

export default defineConfig(({ mode }) => {
    if (mode === 'development') {
        return mergeConfig(baseConfig, devConfig)
    }

    return mergeConfig(baseConfig, prodConfig)
})

Объединение серверных настроек

const baseConfig = {
    server: {
        port: 3000,
        open: true
    }
}

const localConfig = {
    server: {
        host: '0.0.0.0'
    }
}

После объединения:

{
    server: {
        port: 3000,
        open: true,
        host: '0.0.0.0'
    }
}

Работа с optimizeDeps

const baseConfig = {
    optimizeDeps: {
        include: ['axios']
    }
}

const uiConfig = {
    optimizeDeps: {
        include: ['lodash']
    }
}

Результат:

{
    optimizeDeps: {
        include: ['axios', 'lodash']
    }
}

Комбинирование конфигураций монорепозитория

В монорепозиториях mergeConfig используется особенно часто.

Общий конфиг

// configs/vite.base.js

export default {
    resolve: {
        alias: {
            '@shared': '/packages/shared'
        }
    }
}

Конфиг приложения

// apps/admin/vite.config.js

import { mergeConfig } from 'vite'
import baseConfig from '../. ./configs/vite.base'

export default mergeConfig(baseConfig, {
    server: {
        port: 4000
    }
})

Объединение тестовых конфигураций

При использовании тестовых окружений:

const viteConfig = {
    resolve: {
        alias: {
            '@': '/src'
        }
    }
}

const testConfig = {
    test: {
        globals: true
    }
}
const config = mergeConfig(viteConfig, testConfig)

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

Экосистема Vitest активно использует mergeConfig.

import { mergeConfig } from 'vite'
import viteConfig from './vite.config'

export default mergeConfig(viteConfig, {
    test: {
        environment: 'jsdom'
    }
})

Отличие от Object.assign

Object.assign

Object.assign(a, b)

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

  • поверхностное объединение;
  • вложенные объекты перезаписываются;
  • массивы заменяются;
  • не учитываются особенности структуры Vite.

mergeConfig

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

  • глубокое объединение;
  • корректная работа с вложенными объектами;
  • специальная логика для массивов;
  • понимание структуры конфигурации Vite;
  • безопасное расширение конфигов.

Внутренний принцип работы

Упрощённо алгоритм выглядит так:

  1. Проверяется тип значения.

  2. Если значение — объект:

    • выполняется рекурсивное объединение.
  3. Если значение — массив:

    • применяется стратегия объединения.
  4. Если значение примитивное:

    • используется значение второго конфига.

Потенциальные проблемы

Дублирование плагинов

plugins: [
    react(),
    react()
]

mergeConfig не удаляет дубликаты автоматически.


Конфликты алиасов

alias: {
    '@': '/src'
}

и

alias: {
    '@': '/app'
}

Результат:

alias: {
    '@': '/app'
}

Последний конфиг перезапишет предыдущий.


Неочевидное объединение массивов

Некоторые массивы объединяются, а некоторые заменяются в зависимости от внутренней стратегии Vite.

Поэтому при сложных конфигурациях важно проверять итоговый объект.


Практический шаблон масштабируемой архитектуры

Базовый конфиг

// configs/base.js

export default {
    resolve: {
        alias: {
            '@': '/src'
        }
    }
}

React-конфиг

// configs/react.js

import react from '@vitejs/plugin-react'

export default {
    plugins: [react()]
}

Production-конфиг

// configs/prod.js

export default {
    build: {
        sourcemap: false,
        minify: 'esbuild'
    }
}

Итоговый конфиг

import { mergeConfig } from 'vite'

import base from './configs/base'
import react from './configs/react'
import prod from './configs/prod'

export default mergeConfig(
    base,
    mergeConfig(
        react,
        prod
    )
)

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

Корпоративные шаблоны

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


Внутренние CLI

CLI генерирует часть конфига автоматически.


Плагины

Плагин расширяет пользовательскую конфигурацию без полного перезаписывания.


Монорепозитории

Каждый пакет наследует общую конфигурацию.


Многослойная архитектура

Конфиг собирается из независимых модулей.


Сравнение ручного объединения и mergeConfig

Ручное объединение

const config = {
    ...baseConfig,

    server: {
        ...baseConfig.server,
        ...customConfig.server
    }
}

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


mergeConfig

const config = mergeConfig(baseConfig, customConfig)

Код компактнее, безопаснее и лучше масштабируется.


Использование с асинхронной конфигурацией

import { defineConfig, mergeConfig } from 'vite'

const baseConfig = {
    server: {
        port: 3000
    }
}

export default defineConfig(async () => {
    const remoteConfig = await loadConfig()

    return mergeConfig(baseConfig, remoteConfig)
})

Комбинирование пользовательского и системного конфига

function createConfig(userConfig = {}) {
    const systemConfig = {
        build: {
            sourcemap: true
        }
    }

    return mergeConfig(systemConfig, userConfig)
}

Типизация в TypeScript

import { mergeConfig, type UserConfig } from 'vite'

const baseConfig: UserConfig = {
    server: {
        port: 3000
    }
}

const customConfig: UserConfig = {
    build: {
        sourcemap: true
    }
}

export default mergeConfig(baseConfig, customConfig)

Архитектурная роль mergeConfig

mergeConfig является одним из ключевых механизмов масштабирования конфигурации в Vite. Через него строятся:

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

Без mergeConfig поддержка крупных конфигураций Vite быстро превращается в сложную систему ручного копирования и перезаписи свойств.