前端工程化发展到今天,环境变量管理早已不是简单的配置切换,而是贯穿项目全生命周期的重要基础设施。最近在重构一个企业级中台项目时,我深刻体会到Vite环境变量系统的精妙设计——它既保留了传统dotenv文件的简洁性,又通过原生ESM支持实现了编译时优化,彻底解决了Webpack时代需要插件介入的性能损耗问题。
举个例子,我们项目中有个典型场景:开发环境调用Mock API,测试环境连接内网服务,生产环境走CDN加速域名。传统方案需要在webpack.config.js里写一堆if-else判断,而Vite通过.env.[mode]文件配合智能加载机制,让不同环境的配置管理变得优雅而高效。下面这张表格对比了两种方案的差异:
| 特性 | Webpack方案 | Vite方案 |
|---|---|---|
| 配置文件加载 | 需要dotenv-webpack插件 | 原生支持.env文件 |
| 变量注入方式 | 构建时通过DefinePlugin替换 | 编译时静态替换 |
| 热更新影响 | 修改后需重启构建 | 部分变量支持HMR |
| 类型支持 | 需手动声明类型 | 自动生成d.ts类型定义 |
Vite会按照特定顺序加载环境变量文件,这个机制很多开发者容易忽略。假设我们执行vite --mode staging命令,加载顺序实际是:
.env - 基础配置(所有环境共享).env.local - 本地覆盖配置(git忽略).env.staging - 指定模式配置.env.staging.local - 模式本地配置重要提示:.local文件应始终加入.gitignore,避免敏感信息泄露。我曾见过有团队误将.local文件提交后导致数据库密码泄露的事故。
模式(mode)的概念特别值得展开。默认情况下Vite提供development和production两种模式,但我们可以通过--mode参数自定义。比如添加staging预发布环境:
bash复制# package.json
{
"scripts": {
"build:staging": "vite build --mode staging"
}
}
Vite只注入以VITE_开头的变量,这是其安全机制的重要设计。比如:
env复制# .env
DB_PASSWORD=123456 # 不会被注入
VITE_API_URL=/api # 会被注入
这种设计带来三个好处:
在代码中使用时,通过import.meta.env对象访问:
js复制console.log(import.meta.env.VITE_API_URL)
与Webpack的运行时注入不同,Vite在编译阶段就会静态替换环境变量。观察下面这段代码:
js复制// 源代码
const apiUrl = import.meta.env.VITE_API_URL
// 生产环境编译后(假设VITE_API_URL="https://api.example.com")
const apiUrl = "https://api.example.com"
这种静态替换带来的性能优势非常明显:
一个典型的多环境配置目录结构如下:
code复制.env # 全局默认配置
.env.development # 开发环境覆盖配置
.env.staging # 预发布环境配置
.env.production # 生产环境配置
.env.local # 本地覆盖配置
各文件内容示例:
env复制# .env
VITE_APP_NAME=MyApp
VITE_BASE_URL=/api
# .env.development
VITE_BASE_URL=http://localhost:3000/api
# .env.production
VITE_BASE_URL=https://api.myapp.com/v1
Vite会自动生成env.d.ts文件,但我们还可以进一步强化类型检查:
ts复制// src/env.d.ts
interface ImportMetaEnv {
readonly VITE_APP_NAME: string
readonly VITE_BASE_URL: string
// 其他自定义变量...
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
配合TS检查,可以避免拼写错误和类型不匹配问题。我曾在一个大型项目中通过这种方式发现了17处潜在的类型安全问题。
对于需要动态计算的环境变量,可以使用vite.config.js进行预处理:
js复制// vite.config.js
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), 'VITE_')
return {
define: {
__APP_VERSION__: JSON.stringify(process.env.npm_package_version),
__BUILD_TIME__: JSON.stringify(new Date().toISOString())
}
}
})
这样可以在代码中访问这些编译时注入的常量:
js复制console.log(`当前版本:${__APP_VERSION__}`)
生产环境部署时要特别注意:
js复制// 生产环境变量检查
if (import.meta.env.PROD) {
if (!import.meta.env.VITE_CDN_URL) {
throw new Error('Missing required production variables')
}
}
通过环境变量启用不同的优化策略:
js复制// vite.config.js
export default {
build: {
minify: import.meta.env.VITE_USE_TERSER === 'true' ? 'terser' : 'esbuild',
sourcemap: import.meta.env.VITE_SOURCEMAP === 'true'
}
}
结合Docker实现环境隔离:
dockerfile复制# Dockerfile
ARG APP_ENV=production
ENV VITE_MODE=$APP_ENV
COPY .env.$APP_ENV .env
RUN npm run build
部署时指定环境:
bash复制docker build --build-arg APP_ENV=staging -t myapp:staging .
典型场景:开发环境用代理,生产环境用绝对路径
js复制// src/api/client.js
const baseURL = import.meta.env.DEV
? import.meta.env.VITE_PROXY_URL
: import.meta.env.VITE_API_URL
常见错误:在Docker构建阶段无法访问运行时环境变量。正确做法:
dockerfile复制# 构建时传入变量
ARG VITE_API_URL
ENV VITE_API_URL=$VITE_API_URL
启动容器时注入:
bash复制docker run -e VITE_API_URL=https://api.example.com myapp
添加pre-build检查脚本:
json复制// package.json
{
"scripts": {
"check-env": "node scripts/verifyEnv.js",
"build": "npm run check-env && vite build"
}
}
验证脚本示例:
js复制// scripts/verifyEnv.js
const requiredVars = ['VITE_API_URL', 'VITE_SENTRY_DSN']
requiredVars.forEach(varName => {
if (!process.env[varName]) {
console.error(`Missing required environment variable: ${varName}`)
process.exit(1)
}
})
对于微前端架构,可以创建共享环境配置包:
code复制shared-config/
├── .env.shared
├── package.json
└── src/
└── index.ts
通过npm link或私有仓库共享基础配置。
对敏感变量进行前端可解密处理:
js复制// vite.config.js
import { encrypt } from './scripts/crypto'
export default {
define: {
__ENCRYPTED_CONFIG__: JSON.stringify(
encrypt(process.env.VITE_SENSITIVE_DATA)
)
}
}
在业务代码中解密使用:
js复制import { decrypt } from '@/utils/crypto'
const data = decrypt(__ENCRYPTED_CONFIG__)
这套方案在我参与的金融级项目中成功通过了安全审计。关键是要确保解密密钥不会暴露在前端代码中,可以通过运行时接口获取。