Различие трансформаций по окружению

В процессе сборки приложения разные окружения требуют различного поведения кода. Разработка ориентирована на скорость, удобство отладки и поддержку HMR, тогда как production-сборка требует минимизации размера, оптимизации производительности и удаления служебной логики. Vite предоставляет механизм разделения поведения через переменные окружения, режимы (mode) и условные трансформации.

Трансформации по окружению позволяют:

  • подключать разные плагины;
  • изменять поведение Rollup;
  • подменять API-адреса;
  • удалять debug-код;
  • изменять структуру сборки;
  • выполнять SSR-специфичную обработку;
  • включать или отключать экспериментальные возможности.

Понятие окружения в Vite

Vite использует несколько уровней окружений:

Тип Назначение
development dev-сервер
production production-сборка
test тестирование
пользовательские режимы staging, qa, preview и др.

Ключевую роль играет параметр mode.

Пример запуска:

vite --mode staging

или:

vite build --mode production

Текущее окружение доступно через:

import.meta.env.MODE

Пример:

console.log(import.meta.env.MODE)

Объект import.meta.env

Vite внедряет набор специальных переменных:

Переменная Назначение
DEV режим разработки
PROD production
MODE имя режима
BASE_URL базовый URL
SSR SSR-режим

Пример:

if (import.meta.env.DEV) {
    console.log('Режим разработки')
}

Во время production-сборки такие условия статически анализируются и оптимизируются.


Статическое удаление кода

Одно из главных преимуществ Vite — возможность удаления неиспользуемого кода на этапе сборки.

Пример:

if (import.meta.env.PROD) {
    enableAnalytics()
}

Во время development:

if (false) {
    enableAnalytics()
}

Rollup и esbuild удаляют мёртвые ветки.

Аналогично:

if (import.meta.env.DEV) {
    console.log('debug')
}

В production этот код полностью исчезает.

Это называется:

  • dead code elimination;
  • tree shaking;
  • compile-time replacement.

Различие поведения в dev и build

Development

Во время работы dev-сервера Vite:

  • не создаёт полноценный bundle;
  • использует ESM-модули напрямую;
  • трансформирует файлы по запросу;
  • выполняет HMR;
  • хранит кэш зависимостей.

Трансформации выполняются динамически.

Production

Во время vite build:

  • запускается Rollup;
  • создаётся bundle;
  • выполняется минификация;
  • удаляются debug-ветки;
  • происходит tree-shaking;
  • вычисляются хэши файлов.

Трансформации становятся статическими.


Условные трансформации в vite.config.js

Конфигурация может зависеть от окружения.

Пример:

import { defineConfig } from 'vite'

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

    const isDev = command === 'serve'
    const isProd = command === 'build'

    return {
        build: {
            sourcemap: isDev
        }
    }
})

Параметры:

Параметр Значение
command serve или build
mode текущее окружение

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

Часто окружения требуют полностью разных настроек.

Пример:

export default defineConfig(({ mode }) => {

    if (mode === 'development') {
        return {
            server: {
                port: 3000
            }
        }
    }

    if (mode === 'production') {
        return {
            build: {
                minify: 'esbuild'
            }
        }
    }

})

Использование env-файлов

Vite автоматически загружает:

Файл Назначение
.env общие переменные
.env.local локальные
.env.development development
.env.production production

Пример:

VITE_API_URL=https://api.dev.local

Production:

VITE_API_URL=https://api.example.com

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

fetch(import.meta.env.VITE_API_URL)

Префикс VITE_

В браузер попадают только переменные с префиксом VITE_.

Допустимо:

VITE_API_URL=https://example.com

Недопустимо:

SECRET_KEY=123

SECRET_KEY останется доступной только внутри Node.js.

Это предотвращает случайную утечку секретов.


Различие трансформаций для SSR

SSR требует отдельной логики.

Пример:

if (import.meta.env.SSR) {
    loadFromDatabase()
}

Клиентская версия:

if (!import.meta.env.SSR) {
    initBrowserAPI()
}

Во время SSR-сборки Vite заменяет флаги статически.


SSR и браузерные API

Некоторые API отсутствуют на сервере:

  • window
  • document
  • localStorage

Неправильный код:

localStorage.setItem('theme', 'dark')

Без проверки SSR приложение завершится ошибкой.

Правильный вариант:

if (!import.meta.env.SSR) {
    localStorage.setItem('theme', 'dark')
}

Условное подключение модулей

Трансформации позволяют исключать модули из bundle.

Пример:

if (import.meta.env.DEV) {
    import('./debug.js')
}

Production-сборка может исключить debug.js.


define и compile-time константы

Vite поддерживает внедрение compile-time значений.

Пример:

export default defineConfig({
    define: {
        __APP_VERSION__: JSON.stringify('1.0.0')
    }
})

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

console.log(__APP_VERSION__)

Во время сборки происходит простая текстовая подстановка.


Различие define по окружению

Пример:

export default defineConfig(({ mode }) => ({
    define: {
        __DEVTOOLS__: mode === 'development'
    }
}))

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

if (__DEVTOOLS__) {
    startDevtools()
}

В production ветка удалится.


Условные плагины

Плагины могут включаться только в нужных режимах.

Пример:

import legacy from '@vitejs/plugin-legacy'

export default defineConfig(({ mode }) => ({
    plugins: [
        mode === 'production' && legacy()
    ]
}))

Фильтрация falsy-значений

Обычно используется:

plugins: [
    isProd && legacy()
].filter(Boolean)

Это удаляет:

  • false
  • null
  • undefined

из массива плагинов.


Различие alias по окружению

Можно подменять реализации модулей.

Development:

resolve: {
    alias: {
        '@api': '/src/api/mock.js'
    }
}

Production:

resolve: {
    alias: {
        '@api': '/src/api/real.js'
    }
}

Различие proxy-настроек

Dev-сервер часто использует proxy.

Пример:

server: {
    proxy: {
        '/api': {
            target: 'http://localhost:5000'
        }
    }
}

В production proxy обычно отсутствует, поскольку запросы идут через CDN, nginx или backend.


Условная минификация

Пример:

build: {
    minify: mode === 'production'
}

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

Значение Описание
false без минификации
esbuild быстрая
terser более глубокая

Различие sourcemap

Development требует sourcemap для отладки.

Production часто отключает их.

Пример:

build: {
    sourcemap: mode !== 'production'
}

Иногда production sourcemap сохраняются только для Sentry.


Условное разделение чанков

Production может использовать агрессивный code splitting.

Пример:

build: {
    rollupOptions: {
        output: {
            manualChunks: mode === 'production'
                ? {
                    vendor: ['vue']
                }
                : undefined
        }
    }
}

Различие target

Development может использовать современные API браузера.

Production иногда требует совместимости.

Пример:

build: {
    target: mode === 'production'
        ? 'es2018'
        : 'esnext'
}

Трансформации через плагины

Плагины получают информацию об окружении.

Пример:

export default function myPlugin() {

    return {
        name: 'my-plugin',

        transform(code, id) {

            if (process.env.NODE_ENV === 'production') {
                return code.replace('__DEBUG__', 'false')
            }

            return code
        }
    }
}

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

Плагин может получить финальную конфигурацию.

Пример:

configResolved(config) {
    console.log(config.mode)
}

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


Различие transformIndexHtml

HTML также может трансформироваться по окружению.

Пример:

transformIndexHtml(html) {

    if (process.env.NODE_ENV === 'production') {
        return html.replace(
            '</head>',
            '<script src="/analytics.js"></script></head>'
        )
    }

    return html
}

Условное удаление логирования

Production-сборка часто удаляет console-вызовы.

Пример:

esbuild: {
    drop: ['console', 'debugger']
}

Иногда:

drop: mode === 'production'
    ? ['console']
    : []

Различие optimizeDeps

Development использует pre-bundling.

Production — нет.

Пример:

optimizeDeps: {
    include: mode === 'development'
        ? ['lodash']
        : []
}

Различие HMR-поведения

HMR существует только в dev.

Пример:

if (import.meta.hot) {

    import.meta.hot.accept(() => {
        console.log('Обновление модуля')
    })

}

Production-код HMR не содержит.


Условная загрузка аналитики

Пример:

if (import.meta.env.PROD) {

    import('./analytics.js').then(module => {
        module.init()
    })

}

Development не загружает analytics-код.


Условное подключение mock API

Development:

if (import.meta.env.DEV) {
    enableMockAPI()
}

Production:

if (import.meta.env.PROD) {
    disableMockAPI()
}

Использование mode вместо NODE_ENV

В Vite предпочтительно использовать:

import.meta.env.MODE

а не:

process.env.NODE_ENV

Причины:

  • Vite ориентирован на ESM;
  • process.env не всегда доступен;
  • mode поддерживает пользовательские режимы;
  • поведение становится предсказуемым.

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

Допустимы собственные режимы:

vite build --mode staging

Файл:

.env.staging

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

if (import.meta.env.MODE === 'staging') {
    enablePreviewBanner()
}

Отличие command и mode

Это разные сущности.

command

Показывает тип запуска:

Значение Описание
serve dev-сервер
build production build

mode

Показывает логическое окружение:

Значение Описание
development разработка
production production
staging staging
qa тестирование

Пример:

vite build --mode development

В этом случае:

Поле Значение
command build
mode development

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

Использование process.env в браузере

Неправильно:

console.log(process.env.API_URL)

Правильно:

console.log(import.meta.env.VITE_API_URL)

Отсутствие префикса VITE_

Неправильно:

API_URL=https://example.com

Правильно:

VITE_API_URL=https://example.com

Попытка изменить import.meta.env

Неправильно:

import.meta.env.DEV = false

Это compile-time значения.


Использование SSR API в браузере

Неправильно:

fs.readFileSync()

в клиентском коде.

Необходимо разделять окружения.


Архитектурные подходы

Крупные проекты часто используют:

Подход Назначение
feature flags включение функций
mock layers тестовые API
environment adapters адаптеры окружений
runtime config серверная конфигурация
build profiles разные сборки

Feature flags

Пример:

define: {
    __NEW_UI__: true
}

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

if (__NEW_UI__) {
    renderNewUI()
}

Production может содержать полностью другой интерфейс.


Runtime и build-time различия

Важно различать:

Тип Когда применяется
build-time во время сборки
runtime во время выполнения

import.meta.env относится к build-time.

Backend API-конфигурация часто относится к runtime.