5.1 import.meta.env 是什么
Vite 在 ESM 标准的 import.meta 上挂了一个 .env 对象,所有环境变量都从这里读:
console.log(import.meta.env.MODE); // 'development' / 'production'
console.log(import.meta.env.DEV); // boolean
console.log(import.meta.env.PROD);
console.log(import.meta.env.SSR);
console.log(import.meta.env.BASE_URL); // 相当于 config.base
console.log(import.meta.env.VITE_API_URL); // 自定义变量
这是安全设计:避免你把 DB_PASSWORD、JWT_SECRET 之类的服务端密钥误打包进浏览器 bundle。任何不以 VITE_ 开头的环境变量在客户端代码里都不存在。
5.2 .env 文件链
Vite 在项目根按以下顺序加载(后加载的覆盖先加载的):
| 文件 | 加载时机 | 是否应提交 |
|---|---|---|
.env | 所有模式 | ✅ 可以 |
.env.local | 所有模式(仅本地) | ❌ 放进 .gitignore |
.env.[mode] | 特定模式(development/production/staging...) | ✅ 可以 |
.env.[mode].local | 特定模式(仅本地) | ❌ 放进 .gitignore |
# .env (提交,共享默认值)
VITE_APP_NAME=My App
VITE_API_URL=https://api.example.com
# .env.development (提交,开发环境覆盖)
VITE_API_URL=http://localhost:3000
# .env.local (不提交,个人临时值)
VITE_API_URL=http://192.168.1.100:3000
VITE_MY_TOKEN=sk-xxxxx
5.3 模式(mode)
$ vite # 默认 mode=development
$ vite build # 默认 mode=production
$ vite build --mode staging # 自定义 mode → 加载 .env.staging
典型做法是区分 development / staging / production 三个模式:
package.json
"scripts": {
"dev": "vite",
"build:stg": "vite build --mode staging",
"build:prod": "vite build"
}
5.4 TypeScript 类型补全
Vite 默认 import.meta.env 是 ImportMetaEnv,只含内置字段。自定义变量要自己补声明:
// src/vite-env.d.ts(脚手架已有,往里加)
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_APP_NAME: string;
readonly VITE_API_URL: string;
readonly VITE_FEATURE_FLAG_NEW_UI: 'on' | 'off';
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
现在 import.meta.env.VITE_API_URL 有自动补全,拼错会报错。
5.5 在 vite.config.ts 里读环境变量
配置文件本身是 Node 环境,不能直接用 import.meta.env。用官方 loadEnv:
import { defineConfig, loadEnv } from 'vite';
export default defineConfig(({ mode }) => {
// 第 3 参 '' 表示加载所有前缀,不限 VITE_
const env = loadEnv(mode, process.cwd(), '');
return {
server: {
port: Number(env.VITE_DEV_PORT) || 5173,
proxy: {
'/api': { target: env.VITE_API_URL, changeOrigin: true },
},
},
};
});
5.6 特殊内置变量
- import.meta.env.MODE
- 当前模式字符串。
- import.meta.env.DEV / PROD
- 布尔值,会被 dead-code elimination 消除。例如
if (import.meta.env.DEV) logDebug()在生产构建里整段被删。 - import.meta.env.SSR
- 当前代码是否运行在服务端(SSR 构建)。
- import.meta.env.BASE_URL
- 配置里的
base值,默认/。
5.7 运行时 vs 构建时
Vite 的环境变量在 构建时替换——生产包里 import.meta.env.VITE_API_URL 已经是字面字符串。这带来两个影响:
好处:tree-shaking 友好
if (import.meta.env.PROD) {
initSentry(); // 开发时整段被删,Sentry SDK 不会打进 dev bundle
}
代价:一次构建只对应一套环境
你不能用同一份 dist/ 切换 staging/production 的 API 地址。解决方案:
方案 A:各环境独立构建
$ VITE_API_URL=https://stg.example.com vite build
$ VITE_API_URL=https://api.example.com vite build
方案 B:运行时注入 window.__CONFIG__
index.html 里放一个可被服务端模板替换的全局变量:
<script>
window.__CONFIG__ = {
API_URL: "{{ API_URL }}", // Nginx/Express 部署时替换
};
</script>
5.8 .env 安全原则
前端代码会下发到浏览器——VITE_ 前缀的任何变量都能被用户在 devtools 里看到。API key、JWT secret、数据库密码一律放后端 .env 并通过 API 代理。
.gitignore 应该包含
.env.local
.env.*.local
.env.production.local
团队共享 .env 模板
提交一个 .env.example 作为变量清单,但不写真实值:
# .env.example(提交到 git)
VITE_APP_NAME=
VITE_API_URL=
VITE_SENTRY_DSN=
新人 clone 后 cp .env.example .env.local 再填自己的值。
5.9 全部示例:多环境项目结构
my-app/
├── .env # 所有环境共用默认
├── .env.development # dev
├── .env.staging # staging
├── .env.production # prod
├── .env.local # 个人(不提交)
├── .env.example # 变量清单模板(提交)
├── .gitignore # 含 .env.local / .env.*.local
├── src/
└── vite.config.ts
5.10 小结
VITE_前缀的变量注入到客户端;其他的只能在 vite.config / 服务端用。.env.[mode].local层级加载,local 结尾的都要 .gitignore。- 配置里用
loadEnv读环境变量;TS 里给ImportMetaEnv补 interface。 - 变量是"构建时替换",多环境要独立构建或走 window 注入。