Экспорт объекта и функции defineConfig

Конфигурация в Vite строится вокруг файла vite.config.js, vite.config.ts или их ESM-вариантов. Главная задача этого файла — экспортировать объект конфигурации, который Vite использует при запуске dev-сервера, сборке проекта и preview-режиме.

Базовый вариант конфигурации выглядит так:

export default {
  server: {
    port: 3000
  }
}

После запуска Vite автоматически считывает экспортируемый объект и применяет настройки.

Поддерживаются разные форматы экспорта:

  • экспорт обычного объекта;
  • экспорт через defineConfig;
  • экспорт функции;
  • экспорт асинхронной функции;
  • условная генерация конфигурации.

Прямой экспорт объекта

Самый простой вариант — экспортировать объект напрямую.

export default {
  base: '/',
  server: {
    port: 5173
  },
  build: {
    outDir: 'dist'
  }
}

Такой подход подходит для:

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

Vite интерпретирует объект как итоговую конфигурацию.


Проблемы прямого экспорта

Несмотря на простоту, прямой экспорт имеет ограничения.

Отсутствие типизации

В JavaScript IDE не всегда корректно понимает структуру конфигурации.

Например:

export default {
  servre: {
    port: 3000
  }
}

Опечатка servre не будет обнаружена автоматически.

Ограниченная поддержка IntelliSense

Без дополнительных подсказок IDE хуже автодополняет:

  • server;
  • build;
  • resolve;
  • css;
  • plugins;
  • optimizeDeps;
  • define.

Сложности с динамической логикой

При росте проекта конфигурация начинает зависеть от:

  • режима;
  • переменных окружения;
  • платформы;
  • CI/CD;
  • SSR;
  • dev/prod окружения.

Обычный объект становится неудобным.


Функция defineConfig

Vite предоставляет вспомогательную функцию defineConfig.

import { defineConfig } from 'vite'

export default defineConfig({
  server: {
    port: 3000
  }
})

Технически defineConfig почти ничего не делает во время выполнения. Основная задача функции — улучшение типизации и DX (Developer Experience).


Зачем нужен defineConfig

Улучшенная типизация

IDE начинает понимать структуру конфигурации.

Ошибки обнаруживаются сразу:

export default defineConfig({
  servre: {
    port: 3000
  }
})

Редактор покажет, что servre не существует.


Автодополнение

При вводе появляются подсказки:

export default defineConfig({
  server: {
    // подсказки:
    // host
    // port
    // open
    // proxy
  }
})

Это особенно важно в крупных проектах.


Типобезопасность в TypeScript

В TypeScript defineConfig раскрывает все типы конфигурации:

import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    sourcemap: true
  }
})

IDE знает допустимые значения и типы свойств.


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

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

function defineConfig(config) {
  return config
}

Но благодаря TypeScript функция использует generics и специальные типы Vite.

Именно поэтому IDE понимает структуру объекта.


Экспорт функции

Vite позволяет экспортировать не только объект, но и функцию.

import { defineConfig } from 'vite'

export default defineConfig(() => {
  return {
    server: {
      port: 3000
    }
  }
})

Такой подход нужен для динамической конфигурации.


Аргументы функции конфигурации

Vite передаёт в функцию объект контекста.

import { defineConfig } from 'vite'

export default defineConfig(({ command, mode }) => {
  console.log(command)
  console.log(mode)

  return {}
})

Доступны параметры:

Параметр Описание
command текущая команда (serve или build)
mode активный режим
isSsrBuild SSR-сборка
isPreview preview-режим

Параметр command

command показывает, какая команда запущена.

Режим разработки

vite

или:

vite dev

Тогда:

command === 'serve'

Production-сборка

vite build

Тогда:

command === 'build'

Условная конфигурация

На основе command можно разделять настройки.

import { defineConfig } from 'vite'

export default defineConfig(({ command }) => {
  if (command === 'serve') {
    return {
      server: {
        port: 3000
      }
    }
  }

  return {
    build: {
      minify: 'esbuild'
    }
  }
})

Параметр mode

mode определяет режим приложения.

По умолчанию:

Команда Mode
vite development
vite build production

Пользовательские режимы

Можно запускать:

vite build --mode staging

Тогда:

mode === 'staging'

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

import { defineConfig } from 'vite'

export default defineConfig(({ mode }) => {
  return {
    define: {
      __APP_MODE__: JSON.stringify(mode)
    }
  }
})

Разделение dev и production настроек

Одна из главных причин использования функции — разделение окружений.

Пример dev/prod конфигурации

import { defineConfig } from 'vite'

export default defineConfig(({ command }) => {
  const isDev = command === 'serve'

  return {
    server: {
      port: isDev ? 3000 : 8080
    },

    build: {
      sourcemap: isDev
    }
  }
})

Работа с переменными окружения

Vite предоставляет функцию loadEnv.

import { defineConfig, loadEnv } from 'vite'

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd())

  return {
    server: {
      port: Number(env.VITE_PORT)
    }
  }
})

Что делает loadEnv

Функция загружает:

  • .env;
  • .env.local;
  • .env.production;
  • .env.development;
  • .env.staging.

Пример env-файла

VITE_PORT=4000

Преобразование типов

Все env-переменные загружаются как строки.

Поэтому:

port: Number(env.VITE_PORT)

а не:

port: env.VITE_PORT

Асинхронный экспорт конфигурации

Vite поддерживает async-функции.

import { defineConfig } from 'vite'

export default defineConfig(async () => {
  const data = await fetchSomeConfig()

  return {
    define: {
      __CONFIG__: JSON.stringify(data)
    }
  }
})

Когда нужен async-конфиг

Асинхронная конфигурация полезна при:

  • чтении удалённых настроек;
  • генерации build-параметров;
  • загрузке внешних JSON;
  • работе с CMS;
  • динамическом определении окружения.

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

Крупные проекты часто разделяют конфигурацию на части.

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

// vite.base.js

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

Dev-конфигурация

// vite.dev.js

export const devConfig = {
  build: {
    sourcemap: true
  }
}

Финальная сборка

import { defineConfig } from 'vite'
import { baseConfig } from './vite.base'
import { devConfig } from './vite.dev'

export default defineConfig({
  ...baseConfig,
  ...devConfig
})

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

Vite предоставляет встроенную функцию объединения.

import { defineConfig, mergeConfig } from 'vite'

Пример mergeConfig

import { defineConfig, mergeConfig } from 'vite'

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

const productionConfig = {
  build: {
    minify: 'terser'
  }
}

export default defineConfig(
  mergeConfig(baseConfig, productionConfig)
)

Конфигурация через TypeScript

Vite отлично работает с TypeScript.

vite.config.ts

import { defineConfig } from 'vite'

export default defineConfig({
  server: {
    port: 3000
  }
})

Преимущества TypeScript-конфига

Строгая типизация

server: {
  port: '3000'
}

IDE сразу покажет ошибку типа.


Улучшенный рефакторинг

TypeScript помогает:

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

Экспорт без defineConfig в TypeScript

TypeScript позволяет типизировать объект вручную.

import type { UserConfig } from 'vite'

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

export default config

Почему чаще используют defineConfig

Подход с defineConfig короче:

export default defineConfig({
  server: {
    port: 3000
  }
})

Кроме того, функция лучше работает с infer-типами.


Использование plugins внутри defineConfig

Обычно плагины подключаются именно внутри defineConfig.

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

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

Условное подключение плагинов

Функциональный экспорт особенно полезен для плагинов.

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig(({ command }) => {
  return {
    plugins: [
      vue(),

      command === 'serve'
        ? devOnlyPlugin()
        : null
    ].filter(Boolean)
  }
})

Использование defineConfig в монорепозиториях

В monorepo-конфигурациях defineConfig используется практически всегда.

Причины:

  • сложная структура;
  • разные режимы сборки;
  • SSR;
  • library mode;
  • shared-конфиги;
  • workspace-пути.

Пример динамического root

import { defineConfig } from 'vite'
import path from 'path'

export default defineConfig(({ mode }) => {
  return {
    root: path.resolve(__dirname, `apps/${mode}`)
  }
})

Конфигурация library mode

Экспорт функции удобен и для библиотек.

import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    lib: {
      entry: 'src/index.js',
      name: 'MyLibrary',
      fileName: 'my-library'
    }
  }
})

SSR-конфигурации

При SSR-сборке появляется isSsrBuild.

import { defineConfig } from 'vite'

export default defineConfig(({ isSsrBuild }) => {
  return {
    build: {
      minify: !isSsrBuild
    }
  }
})

Preview-режим

Параметр isPreview показывает запуск preview-сервера.

import { defineConfig } from 'vite'

export default defineConfig(({ isPreview }) => {
  return {
    server: {
      open: isPreview
    }
  }
})

Практически рекомендуемый формат

Наиболее распространённый и рекомендуемый вариант:

import { defineConfig } from 'vite'

export default defineConfig(({ mode, command }) => {
  const isDev = command === 'serve'

  return {
    server: {
      port: isDev ? 3000 : 8080
    },

    build: {
      sourcemap: isDev
    },

    define: {
      __MODE__: JSON.stringify(mode)
    }
  }
})

Такой формат:

  • хорошо масштабируется;
  • поддерживает типизацию;
  • удобен для env;
  • подходит для plugin ecosystem;
  • используется в большинстве production-проектов.