Типы для import.meta.env и Vite API

В стандартном JavaScript объект import.meta содержит метаданные текущего ES-модуля. В среде Vite этот механизм расширяется дополнительными возможностями:

  • доступ к переменным окружения через import.meta.env;
  • специальные флаги режима разработки и production;
  • HMR API (import.meta.hot);
  • служебные данные Vite;
  • типизация среды выполнения.

Vite внедряет значения в процессе сборки и разработки, поэтому import.meta.env работает как compile-time API.


Структура import.meta.env

Vite автоматически предоставляет объект:

console.log(import.meta.env)

Пример содержимого:

{
  BASE_URL: "/",
  MODE: "development",
  DEV: true,
  PROD: false,
  SSR: false,
  VITE_API_URL: "https://api.site.com"
}

Встроенные типы Vite

По умолчанию Vite поставляет декларации типов для:

import.meta.env.MODE
import.meta.env.BASE_URL
import.meta.env.DEV
import.meta.env.PROD
import.meta.env.SSR

Чтобы TypeScript понимал эти типы, подключаются типы клиента Vite.


Подключение типов Vite

Создаётся файл:

src/vite-env.d.ts

Содержимое:

/// <reference types="vite/client" />

Эта директива подключает:

  • типы ImportMeta;
  • типы ImportMetaEnv;
  • HMR API;
  • специальные возможности Vite.

Без этого TypeScript часто выдаёт ошибки:

Property 'env' does not exist on type 'ImportMeta'

Интерфейс ImportMetaEnv

После подключения vite/client становится доступен интерфейс:

interface ImportMetaEnv {
  readonly BASE_URL: string
  readonly MODE: string
  readonly DEV: boolean
  readonly PROD: boolean
  readonly SSR: boolean
}

Типизация пользовательских переменных окружения

Vite экспортирует в клиент только переменные с префиксом VITE_.

Пример .env:

VITE_API_URL=https://api.site.com
VITE_APP_TITLE=Dashboard
VITE_ENABLE_CACHE=true

Без ручной типизации TypeScript видит их как:

string | boolean | undefined

или даже:

any

Расширение интерфейса ImportMetaEnv

Файл:

src/vite-env.d.ts

Пример:

/// <reference types="vite/client" />

interface ImportMetaEnv {
  readonly VITE_API_URL: string
  readonly VITE_APP_TITLE: string
  readonly VITE_ENABLE_CACHE: string
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

Теперь TypeScript корректно понимает:

const api = import.meta.env.VITE_API_URL

Тип:

string

Почему булевы значения остаются строками

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

Даже:

VITE_ENABLE_CACHE=true

будет:

"type" === string

Проверка:

if (import.meta.env.VITE_ENABLE_CACHE === 'true') {
  // ...
}

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

Часто создают отдельный конфигурационный слой.

Пример:

export const config = {
  apiUrl: import.meta.env.VITE_API_URL,
  enableCache: import.meta.env.VITE_ENABLE_CACHE === 'true',
  timeout: Number(import.meta.env.VITE_TIMEOUT)
}

Типы становятся безопаснее:

config.enableCache // boolean

Типизация режимов приложения

Vite поддерживает режимы:

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

Можно ограничить допустимые значения.


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

Пример:

interface ImportMetaEnv {
  readonly MODE: 'development' | 'production' | 'staging'
}

Теперь:

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

получает полноценную поддержку TypeScript.


Типизация URL и endpoint-конфигурации

Пример:

interface ImportMetaEnv {
  readonly VITE_API_URL: `https://${string}`
}

Теперь TypeScript запрещает:

VITE_API_URL=http://localhost

если ожидается HTTPS.


Типизация enum-подобных значений

Пример:

interface ImportMetaEnv {
  readonly VITE_THEME: 'light' | 'dark'
}

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

const theme = import.meta.env.VITE_THEME

TypeScript:

"light" | "dark"

Типизация числовых параметров

Несмотря на то что значения строковые, можно явно описывать намерение:

interface ImportMetaEnv {
  readonly VITE_PORT: string
}

Далее:

const port = Number(import.meta.env.VITE_PORT)

Иногда создают utility-функции.


Централизованная env-конфигурация

Пример:

function required(name: string, value: string | undefined): string {
  if (!value) {
    throw new Error(`Missing env: ${name}`)
  }

  return value
}

export const env = {
  apiUrl: required(
    'VITE_API_URL',
    import.meta.env.VITE_API_URL
  ),

  port: Number(import.meta.env.VITE_PORT),

  isDev: import.meta.env.DEV
}

Проверка env во время запуска

Часто используют runtime-валидацию.

Пример с Zod:

import { z } from 'zod'

const schema = z.object({
  VITE_API_URL: z.string().url(),
  VITE_TIMEOUT: z.string()
})

schema.parse(import.meta.env)

Разделение серверных и клиентских env

В Vite существуют ограничения безопасности.

Экспортируются только переменные:

VITE_*

Например:

SECRET_KEY=123

не попадёт в браузер.


Почему это важно

Без префикса:

DATABASE_PASSWORD=123

не будет доступен:

import.meta.env.DATABASE_PASSWORD

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


Типизация SSR-флагов

В SSR-режиме:

import.meta.env.SSR

Тип:

boolean

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

if (import.meta.env.SSR) {
  // серверный код
}

Типизация DEV и PROD

Vite внедряет compile-time константы.

Пример:

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

Во время production-сборки лишний код может быть удалён Rollup и esbuild.


Как работает tree-shaking

Пример:

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

Во время dev:

if (false) {
}

не удаляется.

Во время production:

if (true) {
  enableAnalytics()
}

или наоборот.

Это позволяет эффективно вырезать debug-код.


Типизация import.meta

Vite расширяет стандартный интерфейс:

interface ImportMeta {
  url: string
  env: ImportMetaEnv
  hot?: ViteHotContext
}

Типы для HMR API

Vite предоставляет Hot Module Replacement API.

Пример:

if (import.meta.hot) {
  import.meta.hot.accept()
}

Типизация доступна автоматически через:

vite/client

Тип ViteHotContext

Упрощённо:

interface ViteHotContext {
  accept(): void
  dispose(cb: () => void): void
  invalidate(): void
}

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

Пример:

if (import.meta.hot) {
  import.meta.hot.accept((module) => {
    console.log(module)
  })
}

TypeScript понимает:

  • наличие API;
  • nullable-проверки;
  • методы hot context.

Типизация кастомных HMR-событий

Vite поддерживает пользовательские события.

Пример:

if (import.meta.hot) {
  import.meta.hot.on('custom:update', (data) => {
    console.log(data)
  })
}

Можно расширить типы вручную.


Расширение типов HMR

Пример:

interface CustomEvents {
  'custom:update': {
    version: string
  }
}

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

Стандартное API ES-модулей:

console.log(import.meta.url)

Тип:

string

Использование URL-конструктора

Vite активно использует:

new URL('./image.png', import.meta.url)

Пример:

const imageUrl = new URL(
  './assets/logo.png',
  import.meta.url
).href

Типизация asset URL

TypeScript понимает:

const imageUrl: string

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

  • Web Workers;
  • динамических asset;
  • WASM;
  • lazy resources.

Типы для Web Worker API

Vite поддерживает:

new Worker(
  new URL('./worker.ts', import.meta.url),
  { type: 'module' }
)

TypeScript корректно обрабатывает:

  • URL;
  • Worker;
  • module workers.

Типизация glob imports

Vite поддерживает:

const modules = import.meta.glob('./pages/*.ts')

Тип результата glob

Тип:

Record<string, () => Promise<unknown>>

Пример:

const pages = import.meta.glob('./pages/*.ts')

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

Можно указать generic:

const modules = import.meta.glob<{
  default: unknown
}>('./pages/*.ts')

Типизация eager glob

Пример:

const modules = import.meta.glob(
  './pages/*.ts',
  { eager: true }
)

Тип:

Record<string, unknown>

Типизация default export

Пример:

const pages = import.meta.glob<{
  default: Component
}>('./pages/*.vue')

Типизация markdown-модулей

Пример:

const posts = import.meta.glob<{
  metadata: PostMeta
}>('./posts/*.md')

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

TypeScript объединяет интерфейсы.

Пример:

interface ImportMetaEnv {
  readonly VITE_API_URL: string
}

дополняет встроенные типы Vite, а не заменяет их.


Где размещать vite-env.d.ts

Обычно:

src/vite-env.d.ts

или:

types/vite-env.d.ts

Главное условие — файл должен попадать в tsconfig.json.


Настройка tsconfig.json

Пример:

{
  "include": [
    "src",
    "src/vite-env.d.ts"
  ]
}

Частые ошибки типизации

Ошибка: Property env does not exist

Причина:

/// <reference types="vite/client" />

не подключён.


Ошибка: env имеет тип any

Причина:

  • TypeScript не видит declaration file;
  • файл лежит вне include;
  • отсутствует расширение .d.ts.

Ошибка: переменная undefined

Причина:

API_URL=...

вместо:

VITE_API_URL=...

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

Vite объявляет env как immutable.

Пример:

interface ImportMetaEnv {
  readonly VITE_API_URL: string
}

Попытка:

import.meta.env.VITE_API_URL = 'x'

вызовет ошибку TypeScript.


Отличие Vite от Node.js env

В Node.js:

process.env

В Vite:

import.meta.env

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

Node.js Vite
Runtime Compile-time
process.env import.meta.env
Сервер Браузер
dynamic access statically replaced

Почему Vite использует import.meta.env

Причины:

  • соответствие стандарту ESM;
  • отсутствие polyfill process;
  • лучшая оптимизация;
  • корректный tree-shaking;
  • минимальный runtime overhead.

Статическая замена значений

Во время сборки:

import.meta.env.DEV

заменяется на:

false

или:

true

Это позволяет bundler выполнять агрессивную оптимизацию.


Интеграция с TypeScript strict mode

При включённом:

{
  "strict": true
}

типизация env становится особенно важной.

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

  • possibly undefined;
  • any;
  • отсутствие autocomplete.

Практический пример полной типизации

/// <reference types="vite/client" />

interface ImportMetaEnv {
  readonly VITE_API_URL: string
  readonly VITE_APP_NAME: string
  readonly VITE_THEME: 'light' | 'dark'
  readonly VITE_TIMEOUT: string
  readonly VITE_ENABLE_LOGS: 'true' | 'false'
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

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

export const env = {
  apiUrl: import.meta.env.VITE_API_URL,

  appName: import.meta.env.VITE_APP_NAME,

  theme: import.meta.env.VITE_THEME,

  timeout: Number(import.meta.env.VITE_TIMEOUT),

  enableLogs:
    import.meta.env.VITE_ENABLE_LOGS === 'true'
}

Архитектурный подход для крупных проектов

Часто структура выглядит так:

src/
├── config/
│   ├── env.ts
│   ├── runtime.ts
│   └── constants.ts
├── types/
│   └── vite-env.d.ts

Преимущества строгой типизации Vite API

  • autocomplete для env;
  • защита от опечаток;
  • безопасный refactoring;
  • предсказуемые режимы;
  • строгий контроль конфигурации;
  • корректная работа strict mode;
  • безопасная работа с HMR;
  • улучшенная поддержка IDE;
  • более стабильная SSR-интеграция;
  • уменьшение runtime-ошибок.