前端工程化发展到今天,环境变量管理已经成为项目配置的基石能力。去年在重构一个中型SaaS平台时,我深刻体会到:当项目需要对接3套后端环境、5个CDN域名和7组第三方服务密钥时,如果没有完善的环境变量机制,代码中到处散落的硬编码配置就像定时炸弹。
Vite作为新一代构建工具,其环境变量系统设计极具巧思。与Webpack的dotenv方案不同,Vite原生支持了开发/生产环境的变量隔离、客户端安全注入、TypeScript智能提示等特性。最近在将公司内部组件库迁移到Vite时,我完整梳理了这套机制的最佳实践。
Vite严格遵循着.env.[mode]的文件命名约定。当执行vite --mode staging命令时,工具会依次加载:
.env.staging.local (最高优先级).env.staging.env.local.env (基础配置)重要提示:所有包含
.local后缀的文件应当加入.gitignore,这些通常包含敏感信息
实测发现一个易错点:如果同时存在.env.production和.env.development,运行vite build时会优先采用production模式的变量,这与开发时的逻辑不同,需要特别注意。
通过前缀控制变量可见性:
VITE_开头的变量会暴露给客户端代码例如:
bash复制# 客户端可访问
VITE_API_BASE=https://api.example.com
# 仅构建时使用
INTERNAL_SECRET=123456
在TS项目中,我们可以通过env.d.ts增强类型:
typescript复制interface ImportMetaEnv {
readonly VITE_API_BASE: string
// 其他自定义变量...
}
很多项目需要同时连接测试环境和Mock服务。我的方案是在package.json配置多脚本:
json复制{
"scripts": {
"dev:mock": "vite --mode mock",
"dev:test": "vite --mode test",
"dev:prod": "vite --mode production"
}
}
对应的.env.mock文件示例:
bash复制VITE_API_BASE=http://localhost:3000/mock
VITE_USE_MOCK=true
常规环境变量修改需要重启服务,但通过vite-plugin-environment可以实现动态加载:
javascript复制import environment from 'vite-plugin-environment'
export default {
plugins: [
environment(['VITE_EXPERIMENTAL_FEATURE'])
]
}
这样在修改VITE_EXPERIMENTAL_FEATURE值时,浏览器会自动刷新而不丢失当前状态。
Docker部署时常常需要动态传入变量,可以通过--语法传递:
bash复制vite build -- --VITE_VERSION=$CI_COMMIT_SHA
对应的vite.config.js需要做适配:
javascript复制export default ({ mode }) => {
return {
define: {
__APP_VERSION__: JSON.stringify(process.env.VITE_VERSION)
}
}
}
为防止敏感信息泄露,建议采取以下措施:
vite.config.js中添加过滤逻辑:javascript复制const safeEnv = Object.keys(process.env)
.filter(key => key.startsWith('VITE_'))
.reduce((env, key) => {
env[key] = process.env[key]
return env
}, {})
export default {
define: {
'process.env': safeEnv
}
}
bash复制vite build --mode production --profile
大型项目推荐采用config/目录集中管理:
code复制config/
├── dev.env
├── test.env
├── uat.env
└── prod.env
通过vite.config.js动态加载:
javascript复制import { parse } from 'dotenv'
import fs from 'fs'
const envFile = fs.readFileSync(`config/${process.env.NODE_ENV}.env`)
const envConfig = parse(envFile)
export default {
define: {
'process.env': envConfig
}
}
添加env-validator.js确保关键变量存在:
javascript复制const requiredVars = ['VITE_API_BASE', 'VITE_SENTRY_DSN']
export default function validateEnv() {
const missing = requiredVars.filter(key => !process.env[key])
if (missing.length) {
throw new Error(`缺少必需环境变量: ${missing.join(', ')}`)
}
}
在构建脚本中前置调用:
json复制{
"scripts": {
"build": "node env-validator.js && vite build"
}
}
VITE_前缀--mode参数是否与文件名对应node_modules/.vite缓存目录当出现TS类型报错时,检查以下三点:
env.d.ts是否在tsconfig.json的include范围内ImportMetaEnv接口import.meta.env类型对于大型应用,可以动态加载环境配置:
javascript复制const env = import.meta.glob('../config/*.env', {
eager: true,
import: 'default'
})
通过条件编译移除开发代码:
javascript复制if (import.meta.env.MODE === 'development') {
setupMockWorker()
}
构建后这段代码会被自动移除,减少产物体积。
yaml复制build_production:
stage: build
script:
- echo "VITE_COMMIT_SHA=$CI_COMMIT_SHA" >> .env.production
- npm run build
artifacts:
paths:
- dist/
yaml复制- name: Build with Vite
run: |
echo "VITE_API_BASE=${{ secrets.PROD_API_BASE }}" >> .env.production
npm run build
env:
NODE_ENV: production
建议在应用初始化时检查关键变量:
javascript复制function validateRuntimeEnv() {
const required = ['API_BASE', 'AUTH_DOMAIN']
required.forEach(key => {
if (!import.meta.env[`VITE_${key}`]) {
console.error(`[ENV ERROR] Missing ${key}`)
// 上报到Sentry等监控系统
}
})
}
从Webpack迁移时,按以下步骤改造:
重命名环境文件:
.env → 保持不变.env.development → 保持不变.env.production → 保持不变替换变量前缀:
REACT_APP_ → VITE_VUE_APP_ → VITE_更新客户端访问方式:
diff复制- process.env.REACT_APP_API_BASE
+ import.meta.env.VITE_API_BASE
调整类型声明文件(针对TS项目)
在Jest等测试环境中,需要手动设置环境变量:
javascript复制// jest.setup.js
process.env.VITE_API_BASE = 'http://test-api.example.com'
或者使用jest-environment-jsdom-global包:
javascript复制// jest.config.js
module.exports = {
testEnvironment: 'jest-environment-jsdom-global'
}
建议创建src/core/env.ts统一管理:
typescript复制class EnvService {
get apiBase(): string {
return import.meta.env.VITE_API_BASE || ''
}
get isProduction(): boolean {
return import.meta.env.MODE === 'production'
}
}
export const env = new EnvService()
对于需要运行时获取的配置:
typescript复制async function loadRemoteConfig() {
const res = await fetch('/config.json')
window.__APP_CONFIG__ = await res.json()
}
在vite.config.js中配置:
javascript复制export default {
server: {
headers: {
"Content-Security-Policy": "script-src 'self'"
}
}
}
对于高安全要求场景,建议:
javascript复制import { encrypt } from 'company-crypto'
const secureEnv = {
API_KEY: encrypt(import.meta.env.VITE_API_KEY)
}
通过customEvent传递:
javascript复制// 主应用
window.dispatchEvent(new CustomEvent('env-update', {
detail: {
API_BASE: import.meta.env.VITE_API_BASE
}
}))
// 子应用
window.addEventListener('env-update', (e) => {
Object.assign(import.meta.env, e.detail)
})
在vite.config.js中配置:
javascript复制export default {
plugins: [
federation({
shared: ['env-config']
})
]
}
在capacitor.config.json中注入:
json复制{
"plugins": {
"Env": {
"API_BASE": "$VITE_API_BASE"
}
}
}
通过vite-plugin-mobile同步环境变量:
javascript复制import mobile from 'vite-plugin-mobile'
export default {
plugins: [
mobile({
envPrefix: 'VITE_'
})
]
}
推荐使用这些工具增强管理:
vite-plugin-inspect:查看解析后的环境变量
bash复制vite --inspect
@vitejs/plugin-legacy:处理传统浏览器环境变量
vite-plugin-dynamic-import:实现按需加载环境配置
在Sentry中跟踪环境信息:
javascript复制import * as Sentry from '@sentry/vue'
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
environment: import.meta.env.MODE,
release: import.meta.env.VITE_VERSION
})
在nuxt.config.js中配置:
javascript复制export default {
vite: {
define: {
'process.env': process.env
}
}
}
创建ssrEnv.js处理环境差异:
javascript复制export function getServerEnv() {
return {
...process.env,
...globalThis.__SSR_ENV__
}
}
在electron/main.js中:
javascript复制process.env = {
...process.env,
...require('dotenv').config().parsed
}
tauri.conf.json配置:
json复制{
"build": {
"env": {
"VITE_API_BASE": "$VITE_API_BASE"
}
}
}
Vite团队正在规划的环境变量改进包括:
.env.json格式建议关注Vite RFC仓库获取最新动态。在实际项目中,我已经开始尝试通过插件实现部分特性,比如环境变量分组管理,这对复杂微前端架构特别有帮助。