Доступ к окружению внутри хуков плагина

Система окружений в Vite тесно связана с механизмом работы плагинов. Каждый плагин может выполняться в разных контекстах:

  • dev server;
  • production build;
  • SSR;
  • preview;
  • middleware mode;
  • worker context.

Во время выполнения хуков Vite предоставляет объект окружения, содержащий сведения о режиме запуска, конфигурации, SSR-контексте, текущей команде и других параметрах. Доступ к этим данным позволяет создавать адаптивные плагины, изменяющие поведение в зависимости от среды выполнения.

Наиболее важным этапом становится понимание того, какие данные доступны:

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

Доступ к окружению через config

Первым источником информации об окружении является хук config.

export default function myPlugin() {
    return {
        name: 'my-plugin',

        config(config, env) {
            console.log(env.command)
            console.log(env.mode)
        }
    }
}

Объект env содержит:

{
    command: 'serve',
    mode: 'development',
    isSsrBuild: false,
    isPreview: false
}

Значение command

Поле command определяет тип запуска:

serve

или

build

Это позволяет разделять логику:

config(config, env) {
    if (env.command === 'serve') {
        console.log('dev server')
    }

    if (env.command === 'build') {
        console.log('production build')
    }
}

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

mode определяет активный режим окружения.

Примеры:

vite --mode development
vite --mode production
vite --mode staging
vite --mode testing

Внутри плагина:

config(config, env) {
    console.log(env.mode)
}

Пример адаптации поведения:

config(config, env) {
    if (env.mode === 'staging') {
        return {
            define: {
                __API__: '"https://staging-api.local"'
            }
        }
    }
}

SSR-контекст в окружении

Во время production build Vite может запускаться в SSR-режиме.

Проверка:

config(config, env) {
    if (env.isSsrBuild) {
        console.log('SSR build')
    }
}

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

  • externalization;
  • node-only зависимостей;
  • server transforms;
  • отключения browser APIs;
  • генерации server bundles.

Пример:

config(config, env) {
    if (env.isSsrBuild) {
        return {
            build: {
                target: 'node18'
            }
        }
    }
}

Получение окружения через configResolved

Хук configResolved предоставляет полностью обработанную конфигурацию.

export default function myPlugin() {
    let resolvedConfig

    return {
        name: 'my-plugin',

        configResolved(config) {
            resolvedConfig = config
        }
    }
}

Этот объект содержит:

  • итоговый mode;
  • root;
  • plugins;
  • resolve options;
  • build options;
  • server options;
  • envDir;
  • envPrefix;
  • logger;
  • cacheDir;
  • command;
  • SSR flags.

Структура ResolvedConfig

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

configResolved(config) {
    console.log(config.command)
    console.log(config.mode)
    console.log(config.root)
    console.log(config.base)
}

Доступные свойства:

config.server
config.build
config.resolve
config.css
config.define
config.plugins

Пример использования:

configResolved(config) {
    if (config.command === 'serve') {
        console.log('development mode')
    }
}

Хранение окружения внутри плагина

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

export default function myPlugin() {
    let config

    return {
        name: 'my-plugin',

        configResolved(resolvedConfig) {
            config = resolvedConfig
        },

        transform(code, id) {
            console.log(config.mode)

            return code
        }
    }
}

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


Доступ к окружению внутри transform

Хук transform не получает объект окружения напрямую.

Поэтому окружение обычно сохраняется через configResolved.

export default function plugin() {
    let config

    return {
        name: 'env-transform-plugin',

        configResolved(resolved) {
            config = resolved
        },

        transform(code, id) {
            if (config.command === 'serve') {
                console.log('dev transform')
            }

            return code
        }
    }
}

Разделение логики dev и build

Практический пример:

transform(code) {
    if (config.command === 'serve') {
        return code.replace(
            '__DEV__',
            'true'
        )
    }

    return code.replace(
        '__DEV__',
        'false'
    )
}

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

Для ограничения окружения Vite предоставляет поле apply.

export default function plugin() {
    return {
        name: 'build-only',

        apply: 'build'
    }
}

Варианты:

apply: 'serve'
apply: 'build'

Динамический apply

apply может быть функцией:

apply(config, env) {
    return env.mode === 'staging'
}

Пример:

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

        apply(config, env) {
            return env.mode === 'staging'
        }
    }
}

Доступ к process.env

В Node.js-плагинах доступно стандартное окружение:

process.env.NODE_ENV
process.env.API_URL
process.env.PORT

Пример:

transform(code) {
    console.log(process.env.NODE_ENV)

    return code
}

Однако необходимо понимать различие между:

  • окружением Node.js;
  • Vite env system;
  • client-side env variables.

Различие между mode и NODE_ENV

Эти значения не всегда совпадают.

Пример:

vite build --mode development

Результат:

mode = development
NODE_ENV = production

Поэтому внутри плагинов предпочтительнее использовать:

config.mode

или

env.mode

Доступ к .env внутри плагина

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

import { loadEnv } from 'vite'

export default function plugin() {
    let env

    return {
        name: 'env-plugin',

        config(config, viteEnv) {
            env = loadEnv(
                viteEnv.mode,
                process.cwd(),
                ''
            )
        }
    }
}

Пример чтения .env

API_URL=https://api.local
APP_VERSION=1.0.0

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

config(config, viteEnv) {
    const env = loadEnv(
        viteEnv.mode,
        process.cwd(),
        ''
    )

    console.log(env.API_URL)
}

Почему loadEnv лучше process.env

process.env не учитывает автоматически:

  • .env.production;
  • .env.development;
  • .env.local;
  • .env.staging;
  • mode-specific env files.

loadEnv загружает env-файлы в соответствии с режимом Vite.


Ограничение префиксов окружения

Vite по умолчанию экспортирует клиенту только переменные:

VITE_*

Но внутри плагинов можно читать любые значения:

SECRET_KEY=hidden
DATABASE_URL=internal
const env = loadEnv(mode, process.cwd(), '')

Пустой префикс:

''

означает загрузку всех переменных.


Окружение внутри configureServer

Хук configureServer предоставляет dev server.

configureServer(server) {
    console.log(server.config.mode)
}

Доступно:

server.config
server.middlewares
server.ws
server.httpServer
server.moduleGraph

Использование окружения внутри middleware

configureServer(server) {
    server.middlewares.use((req, res, next) => {
        if (server.config.mode === 'development') {
            console.log(req.url)
        }

        next()
    })
}

SSR-флаг внутри transform

В некоторых хуках присутствует SSR-флаг.

transform(code, id, options) {
    console.log(options?.ssr)
}

Значения:

true
false
undefined

Пример:

transform(code, id, options) {
    if (options?.ssr) {
        return code.replace(
            '__TARGET__',
            '"server"'
        )
    }

    return code.replace(
        '__TARGET__',
        '"client"'
    )
}

Доступ к окружению внутри resolveId

resolveId(source, importer, options) {
    if (options?.ssr) {
        console.log('SSR resolve')
    }
}

Это особенно полезно для:

  • server aliases;
  • node polyfills;
  • browser replacements;
  • platform-specific imports.

Доступ к окружению внутри load

load(id, options) {
    if (options?.ssr) {
        return 'export default "server"'
    }

    return 'export default "client"'
}

Использование this.meta

Некоторые Rollup-хуки предоставляют метаинформацию.

buildStart() {
    console.log(this.meta.watchMode)
}

watchMode:

true
false

Пример:

buildStart() {
    if (this.meta.watchMode) {
        console.log('watch mode enabled')
    }
}

Комбинирование окружений

Часто используется комбинация нескольких источников:

export default function plugin() {
    let config
    let env

    return {
        name: 'advanced-plugin',

        configResolved(resolved) {
            config = resolved

            env = loadEnv(
                resolved.mode,
                resolved.root,
                ''
            )
        },

        transform(code, id, options) {
            const isSSR = options?.ssr
            const isDev = config.command === 'serve'
            const isProd = config.command === 'build'

            console.log({
                isSSR,
                isDev,
                isProd,
                api: env.API_URL
            })

            return code
        }
    }
}

Работа с несколькими окружениями

Плагины могут адаптироваться под:

  • browser;
  • SSR;
  • edge runtime;
  • workers;
  • electron;
  • testing environments.

Пример:

transform(code, id, options) {
    if (options?.ssr) {
        return code.replace(
            '__RUNTIME__',
            '"node"'
        )
    }

    return code.replace(
        '__RUNTIME__',
        '"browser"'
    )
}

Создание environment-aware плагинов

Хорошо спроектированный плагин:

  • не полагается только на NODE_ENV;
  • учитывает SSR;
  • различает serve/build;
  • использует configResolved;
  • корректно обрабатывает custom modes;
  • не смешивает client и server environment.

Частая ошибка: чтение env слишком рано

Неверный подход:

const mode = process.env.MODE

Во время загрузки модуля значения ещё могут быть недоступны.

Правильный подход:

config(config, env) {
    console.log(env.mode)
}

или:

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

Частая ошибка: глобальное состояние

Неверно:

let config

за пределами фабрики плагина.

Правильно:

export default function plugin() {
    let config

    return {
        name: 'plugin',

        configResolved(resolved) {
            config = resolved
        }
    }
}

Иначе несколько инстансов Vite могут конфликтовать между собой.


Частая ошибка: смешивание SSR и client transforms

Неверно:

transform(code) {
    return code.replace(
        'window.',
        ''
    )
}

Такой код может ломать SSR.

Корректный вариант:

transform(code, id, options) {
    if (options?.ssr) {
        return code
    }

    return code.replace(
        'window.',
        ''
    )
}

Частая ошибка: игнорирование custom mode

Неверный код:

if (mode === 'production')

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

  • staging;
  • testing;
  • qa;
  • preview;
  • local;
  • benchmark.

Более гибкий вариант:

const isDev = command === 'serve'
const isBuild = command === 'build'

Практическая архитектура environment-aware плагина

import { loadEnv } from 'vite'

export default function plugin() {
    let config
    let env

    return {
        name: 'full-env-plugin',

        configResolved(resolved) {
            config = resolved

            env = loadEnv(
                resolved.mode,
                resolved.root,
                ''
            )
        },

        transform(code, id, options) {
            const context = {
                mode: config.mode,
                command: config.command,
                ssr: options?.ssr,
                api: env.API_URL
            }

            console.log(context)

            return code
        },

        configureServer(server) {
            console.log(server.config.mode)
        }
    }
}

Наиболее важные источники окружения внутри плагинов

Источник Назначение
env.mode текущий mode
env.command serve/build
configResolved() итоговая конфигурация
options.ssr SSR-контекст хука
server.config окружение dev server
loadEnv() загрузка .env
process.env Node.js environment
this.meta.watchMode watch mode

Когда использовать разные механизмы

env.command

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

  • разделения dev/build;
  • включения dev-only логики;
  • отключения production transforms.

env.mode

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

  • staging;
  • qa;
  • testing;
  • feature environments.

options.ssr

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

  • SSR transforms;
  • server-only imports;
  • browser API guards.

loadEnv

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

  • API URLs;
  • feature flags;
  • secrets;
  • custom env values.

configResolved

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

  • долгоживущего доступа к окружению;
  • shared plugin state;
  • complex plugin architecture.