В стандартном JavaScript объект import.meta содержит
метаданные текущего ES-модуля. В среде Vite этот механизм расширяется
дополнительными возможностями:
import.meta.env;import.meta.hot);Vite внедряет значения в процессе сборки и разработки, поэтому
import.meta.env работает как compile-time API.
import.meta.envVite автоматически предоставляет объект:
console.log(import.meta.env)
Пример содержимого:
{
BASE_URL: "/",
MODE: "development",
DEV: true,
PROD: false,
SSR: false,
VITE_API_URL: "https://api.site.com"
}
По умолчанию Vite поставляет декларации типов для:
import.meta.env.MODE
import.meta.env.BASE_URL
import.meta.env.DEV
import.meta.env.PROD
import.meta.env.SSR
Чтобы TypeScript понимал эти типы, подключаются типы клиента Vite.
Создаётся файл:
src/vite-env.d.ts
Содержимое:
/// <reference types="vite/client" />
Эта директива подключает:
ImportMeta;ImportMetaEnv;Без этого 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
Можно ограничить допустимые значения.
Пример:
interface ImportMetaEnv {
readonly MODE: 'development' | 'production' | 'staging'
}
Теперь:
if (import.meta.env.MODE === 'staging') {
// ...
}
получает полноценную поддержку TypeScript.
Пример:
interface ImportMetaEnv {
readonly VITE_API_URL: `https://${string}`
}
Теперь TypeScript запрещает:
VITE_API_URL=http://localhost
если ожидается HTTPS.
Пример:
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-функции.
Пример:
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
}
Часто используют 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)
В Vite существуют ограничения безопасности.
Экспортируются только переменные:
VITE_*
Например:
SECRET_KEY=123
не попадёт в браузер.
Без префикса:
DATABASE_PASSWORD=123
не будет доступен:
import.meta.env.DATABASE_PASSWORD
Это предотвращает случайную утечку секретов.
В SSR-режиме:
import.meta.env.SSR
Тип:
boolean
Использование:
if (import.meta.env.SSR) {
// серверный код
}
Vite внедряет compile-time константы.
Пример:
if (import.meta.env.DEV) {
console.log('debug')
}
Во время production-сборки лишний код может быть удалён Rollup и esbuild.
Пример:
if (import.meta.env.PROD) {
enableAnalytics()
}
Во время dev:
if (false) {
}
не удаляется.
Во время production:
if (true) {
enableAnalytics()
}
или наоборот.
Это позволяет эффективно вырезать debug-код.
import.metaVite расширяет стандартный интерфейс:
interface ImportMeta {
url: string
env: ImportMetaEnv
hot?: ViteHotContext
}
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
}
Пример:
if (import.meta.hot) {
import.meta.hot.accept((module) => {
console.log(module)
})
}
TypeScript понимает:
Vite поддерживает пользовательские события.
Пример:
if (import.meta.hot) {
import.meta.hot.on('custom:update', (data) => {
console.log(data)
})
}
Можно расширить типы вручную.
Пример:
interface CustomEvents {
'custom:update': {
version: string
}
}
import.meta.urlСтандартное API ES-модулей:
console.log(import.meta.url)
Тип:
string
Vite активно использует:
new URL('./image.png', import.meta.url)
Пример:
const imageUrl = new URL(
'./assets/logo.png',
import.meta.url
).href
TypeScript понимает:
const imageUrl: string
Это особенно важно для:
Vite поддерживает:
new Worker(
new URL('./worker.ts', import.meta.url),
{ type: 'module' }
)
TypeScript корректно обрабатывает:
URL;Worker;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')
Пример:
const modules = import.meta.glob(
'./pages/*.ts',
{ eager: true }
)
Тип:
Record<string, unknown>
Пример:
const pages = import.meta.glob<{
default: Component
}>('./pages/*.vue')
Пример:
const posts = import.meta.glob<{
metadata: PostMeta
}>('./posts/*.md')
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.
Пример:
{
"include": [
"src",
"src/vite-env.d.ts"
]
}
Property env does not existПричина:
/// <reference types="vite/client" />
не подключён.
Причина:
include;.d.ts.Причина:
API_URL=...
вместо:
VITE_API_URL=...
Vite объявляет env как immutable.
Пример:
interface ImportMetaEnv {
readonly VITE_API_URL: string
}
Попытка:
import.meta.env.VITE_API_URL = 'x'
вызовет ошибку TypeScript.
В Node.js:
process.env
В Vite:
import.meta.env
Основные различия:
| Node.js | Vite |
|---|---|
| Runtime | Compile-time |
| process.env | import.meta.env |
| Сервер | Браузер |
| dynamic access | statically replaced |
import.meta.envПричины:
process;Во время сборки:
import.meta.env.DEV
заменяется на:
false
или:
true
Это позволяет bundler выполнять агрессивную оптимизацию.
При включённом:
{
"strict": true
}
типизация env становится особенно важной.
Без деклараций появляются:
possibly undefined;any;/// <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