Форматы конфигурации: JS, TS, MJS

Vite использует конфигурационный файл для управления поведением dev-сервера, системы сборки, плагинов, алиасов, переменных окружения, CSS-препроцессоров, Rollup-настроек и других аспектов проекта.

По умолчанию Vite ищет конфигурацию в корне проекта. Поддерживаются несколько форматов:

  • vite.config.js
  • vite.config.mjs
  • vite.config.ts
  • vite.config.cjs
  • vite.config.mts
  • vite.config.cts

Наиболее распространёнными считаются:

  • JavaScript-конфигурация (.js)
  • TypeScript-конфигурация (.ts)
  • ES-модульная конфигурация (.mjs)

Конфигурация в формате JS

Базовая структура

Файл vite.config.js является самым простым вариантом конфигурации.

import { defineConfig } from 'vite'

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

Vite автоматически загружает этот файл при запуске команд:

vite
vite dev
vite build
vite preview

Почему используется defineConfig

Функция defineConfig() не является обязательной, однако она предоставляет несколько преимуществ:

  • улучшенная типизация
  • автодополнение в IDE
  • корректная работа JSDoc
  • более точная проверка структуры конфигурации

Без defineConfig() конфигурация также работает:

export default {
    server: {
        port: 3000
    }
}

Но IDE теряет часть информации о типах.


CommonJS и ES Modules

Исторически Node.js использовал систему CommonJS:

const path = require('path')

module.exports = {
    resolve: {
        alias: {
            '@': path.resolve(__dirname, 'src')
        }
    }
}

Современный Vite ориентирован на ES Modules.

Поэтому чаще используется синтаксис:

import path from 'path'

export default {
    resolve: {
        alias: {
            '@': path.resolve('src')
        }
    }
}

Как Node.js определяет тип модуля

Поведение файла .js зависит от поля type в package.json.

Вариант CommonJS

{
    "type": "commonjs"
}

или отсутствие поля type.

Тогда .js трактуется как CommonJS.


Вариант ES Modules

{
    "type": "module"
}

Тогда .js считается ES Module.

Это напрямую влияет на работу vite.config.js.


Проблемы при неправильном типе модулей

Если проект работает в режиме CommonJS, а конфигурация написана как ES Module:

import { defineConfig } from 'vite'

export default defineConfig({})

Node.js выдаст ошибку:

Cannot use import statement outside a module

Когда использовать vite.config.js

Формат .js подходит в случаях:

  • небольшие проекты
  • простая конфигурация
  • отсутствие TypeScript
  • минимальная настройка Vite
  • учебные проекты
  • быстрые прототипы

Формат MJS

Что такое .mjs

Расширение .mjs принудительно указывает Node.js, что файл является ES Module независимо от поля type.

Файл:

vite.config.mjs

всегда работает как ES Module.


Пример vite.config.mjs

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

export default defineConfig({
    resolve: {
        alias: {
            '@': path.resolve('src')
        }
    }
})

Преимущество .mjs

Главное преимущество — предсказуемость.

Даже если в package.json отсутствует:

{
    "type": "module"
}

конфигурация всё равно будет работать как ES Module.


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

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

В больших monorepo разные пакеты могут использовать:

  • CommonJS
  • ESM
  • смешанную архитектуру

.mjs устраняет неоднозначность.


Старые Node.js-проекты

Во многих legacy-проектах используется CommonJS:

require()
module.exports

Но Vite лучше работает с ESM.

В этом случае удобно оставить проект на CommonJS, а Vite-конфигурацию вынести в .mjs.


Отличие .js и .mjs

vite.config.js

Поведение зависит от:

"type": "module"

vite.config.mjs

Всегда:

import/export

без зависимости от package.json.


Ограничения .mjs

Внутри .mjs недоступны CommonJS-глобалы:

__dirname
__filename
require
module.exports

Аналог __dirname в ESM

Вместо __dirname используется:

import { fileURLToPath } from 'url'
import { dirname } from 'path'

const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)

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

import { defineConfig } from 'vite'
import { fileURLToPath } from 'url'
import { dirname, resolve } from 'path'

const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)

export default defineConfig({
    resolve: {
        alias: {
            '@': resolve(__dirname, 'src')
        }
    }
})

Формат TS

Зачем использовать TypeScript-конфигурацию

Файл:

vite.config.ts

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

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

  • строгая типизация
  • автодополнение
  • проверка ошибок
  • удобство крупных проектов
  • безопасный рефакторинг

Базовый пример

import { defineConfig } from 'vite'

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

На первый взгляд код почти не отличается от JavaScript.

Но теперь TypeScript проверяет корректность конфигурации.


Проверка ошибок типов

Например:

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

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

Type 'string' is not assignable to type 'number'

Работа с типами плагинов

TypeScript особенно полезен при подключении сложных плагинов.

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

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

IDE понимает типы:

  • параметров плагина
  • возвращаемых значений
  • доступных опций

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

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

import { defineConfig } from 'vite'

export default defineConfig(({ mode }) => {
    return {
        server: {
            port: mode === 'development'
                ? 3000
                : 8080
        }
    }
})

Типизация mode, command и env

TypeScript знает типы аргументов:

import { defineConfig } from 'vite'

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

    return {}
})

command

Может быть:

'serve'

или:

'build'

mode

Обычно:

development
production

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

vite --mode staging

Поддержка TypeScript внутри Vite

Нужно ли компилировать vite.config.ts

Нет.

Vite автоматически обрабатывает TypeScript-конфигурацию.

Дополнительная настройка обычно не требуется.


Как Vite исполняет TS-конфигурацию

Внутри используется механизм трансформации TypeScript в JavaScript перед выполнением.

При запуске:

vite

происходит:

  1. чтение vite.config.ts
  2. трансформация TS → JS
  3. запуск результата в Node.js

Ограничения TypeScript-конфигурации

Нельзя использовать browser API

Конфигурация выполняется в Node.js, а не в браузере.

Недоступны:

window
document
localStorage

Конфигурация выполняется сервером

Файл vite.config.ts — это backend-код среды Node.js.

Он не попадает в браузерный bundle.


Формат MTS

Что такое .mts

Файл:

vite.config.mts

является TypeScript-версией .mjs.

Он всегда работает как ES Module.


Отличие .ts и .mts

vite.config.ts

Поведение зависит от настроек TypeScript и Node.js.


vite.config.mts

Всегда ESM.


Когда использовать .mts

.mts полезен при:

  • строгом ESM-подходе
  • monorepo
  • смешанных CommonJS/ESM проектах
  • сложной Node.js-инфраструктуре

Формат CJS и CTS

vite.config.cjs

Принудительный CommonJS:

const { defineConfig } = require('vite')

module.exports = defineConfig({
    server: {
        port: 3000
    }
})

vite.config.cts

TypeScript + CommonJS:

import { defineConfig } from 'vite'

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

Как Vite выбирает конфигурацию

При запуске Vite ищет конфигурацию в определённом порядке.

Например:

vite.config.ts
vite.config.mts
vite.config.js
vite.config.mjs

Точный внутренний приоритет зависит от версии Vite.


Явное указание конфигурации

Можно выбрать файл вручную:

vite --config vite.admin.config.ts

Несколько конфигураций

В крупных проектах встречаются:

vite.client.config.ts
vite.admin.config.ts
vite.ssr.config.ts

Запуск:

vite build --config vite.ssr.config.ts

Практическое сравнение форматов

JS

Подходит для:

  • простых проектов
  • минимальных настроек
  • быстрого старта

Плюсы:

  • простота
  • минимум синтаксиса
  • не нужен TypeScript

Минусы:

  • слабая типизация
  • меньше проверок

TS

Подходит для:

  • крупных проектов
  • enterprise-разработки
  • сложных плагинов
  • monorepo

Плюсы:

  • строгая типизация
  • автодополнение
  • безопасный рефакторинг

Минусы:

  • дополнительная сложность TypeScript

MJS

Подходит для:

  • чистого ESM
  • гибридных Node.js-проектов
  • устранения конфликтов CommonJS

Плюсы:

  • однозначное поведение
  • современный ESM-подход

Минусы:

  • сложности с __dirname
  • необходимость ESM-синтаксиса

MTS

Подходит для:

  • TypeScript + ESM
  • сложной архитектуры
  • monorepo

Плюсы:

  • строгая типизация
  • гарантированный ESM

Минусы:

  • наиболее сложный вариант настройки

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

Конфигурация может быть динамической.

import { defineConfig } from 'vite'

export default defineConfig(({ command, mode }) => {
    const isBuild = command === 'build'

    return {
        build: {
            sourcemap: !isBuild
        }
    }
})

Асинхронная конфигурация

Поддерживается async-конфигурация.

import { defineConfig } from 'vite'

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

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

async function loadConfig() {
    return {
        api: 'https://api.site.com'
    }
}

Работа с import.meta.url

В ESM-конфигурациях активно используется:

import.meta.url

Пример:

import { fileURLToPath } from 'url'

const filename = fileURLToPath(import.meta.url)

Это замена старых CommonJS-механизмов.


Совместимость с Node.js

Современные версии Vite ориентированы на актуальные версии Node.js.

Особенно это важно для:

  • ESM
  • .mjs
  • .mts
  • import.meta.url

Старые версии Node.js могут некорректно работать с модульной системой ES Modules.


Типичный современный вариант

Наиболее распространённая конфигурация современных проектов:

// vite.config.ts

import { defineConfig } from 'vite'

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

Пример сложной TypeScript-конфигурации

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

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

    return {
        plugins: [vue()],

        resolve: {
            alias: {
                '@': path.resolve(__dirname, './src')
            }
        },

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

        build: {
            sourcemap: mode === 'development'
        }
    }
})

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

Смешивание CommonJS и ESM

Ошибка:

const path = require('path')

export default {}

Нельзя смешивать:

  • require
  • export default

Неправильный __dirname в ESM

Ошибка:

console.log(__dirname)

в .mjs или .mts.


Неверное поле type

Конфликт:

{
    "type": "commonjs"
}

при использовании ESM-конфигурации.


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

Ошибка:

localStorage.getItem('token')

Конфигурация запускается в Node.js, а не в браузере.


Рекомендации по выбору формата

Для большинства современных проектов

Оптимальный вариант:

vite.config.ts

Для чистого ESM

Лучше использовать:

vite.config.mjs

или:

vite.config.mts

Для legacy Node.js-проектов

Подходят:

vite.config.cjs

или:

vite.config.mjs

в зависимости от архитектуры проекта.


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

Формат конфигурации практически не влияет на скорость работы Vite.

Разница между:

  • .js
  • .ts
  • .mjs
  • .mts

обычно незначительна.

Основное различие связано:

  • с удобством разработки
  • типизацией
  • совместимостью модульной системы
  • архитектурой Node.js-проекта