当前位置: 首页 > news >正文

Vite 环境变量与运行时配置最佳实践:从 .env 到 一份包 跑多环境,以及开发环境代理、请求配置等

一、背景

在前端项目开发中,我们经常需要针对不同环境(开发 / 测试 / 预发 / 生产)切换 API 地址或应用配置。
Vite 提供了 .env 文件机制在构建时注入环境变量到 import.meta.env,但构建后的产物配置是静态的、不可再修改

在实际生产部署中,常常需要“一份包跑多个环境”,例如相同构建产物要部署到 测试 / 预发 / 生产
这就需要 运行时配置覆盖 来实现灵活性。

本文结合 .envconfig.js,总结一套 开发方便 + 部署灵活 的最佳实践方案。


二、.env 文件加载规则

Vite 会根据 mode 加载对应的 .env 文件,并按顺序合并:

文件名加载范围场景
.env所有模式通用配置
.env.local所有模式,本机专用本地私密,不提交 git
.env.[mode]指定模式区分 dev / prod / staging
.env.[mode].local指定模式,本机专用本机覆盖,敏感配置

加载顺序(后覆盖前):

.env → .env.local → .env.[mode] → .env.[mode].local

与命令的关系

{"scripts": {"dev": "vite",                     // 默认 mode=development"build": "vite build",             // 默认 mode=production"build:staging": "vite build --mode staging", // 使用 .env.staging"preview": "vite preview"          // 预览 dist 产物}
}
  • npm run dev.env.development
  • npm run build.env.production
  • npm run build:staging.env.staging
  • npm run preview → 仅预览打包产物(不会重新加载 .env

三、环境变量命名规则

1. 必须以 VITE_ 开头才会注入前端

VITE_API_BASE_URL="https://api.example.com"
  • VITE_ 开头的变量才会注入到 import.meta.env
  • 不以 VITE_ 开头的变量仍可写在 .env,但只能在 vite.config.js 中通过 loadEnv 读取(loadEnv使用见下文)

⚠️ 注意:不要把私密信息(token、密码)放在 VITE_ 变量里,会暴露到前端。


2. 注释与基本语法

  • 注释用 #
  • 值里有空格/特殊字符 → 加引号
  • 也支持 export KEY=value

3. 所有值都是字符串

const enabled = import.meta.env.VITE_FEATURE_ENABLED === 'true'
const timeout = Number(import.meta.env.VITE_TIMEOUT_MS || '5000')

如需对象:

VITE_FLAGS='{"newUI":true,"abTest":0.3}'
const flags = JSON.parse(import.meta.env.VITE_FLAGS || '{}')

4. 变量展开(dotenv-expand)

允许在 .env 中引用已有变量,避免重复:

VITE_API_HOST="https://api.example.com"
VITE_API_VERSION="v1"
VITE_API_BASE_URL="${VITE_API_HOST}/${VITE_API_VERSION}"

结果:VITE_API_BASE_URL=https://api.example.com/v1


5. 内置变量

Vite 提供一些内置变量:

import.meta.env.MODE    // "development" | "production" | "staging"
import.meta.env.DEV     // true | false
import.meta.env.PROD    // true | false
import.meta.env.BASE_URL // 部署时的基础路径

6. 在 vite.config.js 中读取 loadEnv

Vite 提供了 loadEnv(mode, root, prefix) 方法来读取 .env 文件中的变量。

import { defineConfig, loadEnv } from 'vite'export default defineConfig(({ mode }) => {const env = loadEnv(mode, process.cwd(), '') // 读取全部变量console.log(env) // 打印所有环境变量return {server: {proxy: {'/api': {target: env.VITE_API_PROXY_TARGET,changeOrigin: true,rewrite: (p) => p.replace(/^\/api/, ''),},},},}
})
参数说明
  • mode
    当前运行模式,例如 developmentproductionstaging
    Vite 会根据这个值去加载对应的 .env.[mode] 文件。

  • root
    项目根目录路径。
    一般写 process.cwd(),表示当前 Node.js 进程的工作目录。
    这告诉 Vite 从哪里开始查找 .env 文件。

  • prefix
    用于过滤变量前缀。默认是 'VITE_',只会返回 VITE_ 开头的变量。
    如果写成 ''(空字符串),则返回所有 .env 文件中的变量,包括非 VITE_ 的。

为什么用 process.cwd()
  • process.cwd() 是 Node.js API,表示当前执行命令的工作目录。
  • 当你在项目根目录执行 npm run dev 时,它就是项目的根目录。
  • 所以配合 loadEnv(mode, process.cwd(), ''),就能确保 Vite 从项目根目录读取 .env.* 文件。
示例

.env.development

NODE_ENV=development
APP_SECRET=123456   # 不会注入客户端
VITE_API_BASE_URL=http://localhost:3000/api

vite.config.js

export default defineConfig(({ mode }) => {const env = loadEnv(mode, process.cwd(), '')console.log(env.NODE_ENV)          // "development"console.log(env.APP_SECRET)        // "123456"console.log(env.VITE_API_BASE_URL) // "http://localhost:3000/api"return {define: {__APP_SECRET__: JSON.stringify(env.APP_SECRET), // 可选择性注入}}
})

7. 自定义暴露前缀

除了默认的 VITE_,你还可以通过 vite.config.js 中的 envPrefix 配置,允许其它前缀的变量也暴露到客户端代码中:

export default defineConfig({envPrefix: ['VITE_', 'APP_']
})

这样,在 .env 文件中写:

APP_TITLE="我的应用"
VITE_API_BASE_URL="https://api.example.com"

就可以在前端代码里直接访问:

console.log(import.meta.env.APP_TITLE)        // "我的应用"
console.log(import.meta.env.VITE_API_BASE_URL) // "https://api.example.com"

envPrefix vs loadEnv 的区别

它们看起来相似,但作用范围不同

  1. envPrefix(vite.config.js 配置项)

    • 控制哪些变量可以被注入到 客户端代码(import.meta.env) 中。
    • 默认只允许 VITE_ 前缀的变量暴露给前端。
    • 修改 envPrefix 可以增加其它前缀,例如 APP_
    • 影响:运行时前端能不能访问。
  2. loadEnv(mode, root, prefix)(函数参数)

    • 用于在 vite.config.js 里读取 .env 文件。
    • prefix 参数决定返回的变量前缀过滤:
      • 'VITE_' → 只返回 VITE_ 开头的变量。
      • ''(空字符串) → 返回所有变量(包括非 VITE_ 的)。
    • 影响:配置阶段你能拿到哪些变量。

举个例子 🚀

.env.development

SECRET_KEY=123456
APP_TITLE="我的应用"
VITE_API_BASE_URL="http://localhost:3000/api"

vite.config.js

import { defineConfig, loadEnv } from 'vite'export default defineConfig(({ mode }) => {const env = loadEnv(mode, process.cwd(), '') // 拿到全部变量console.log(env.SECRET_KEY)        // 123456console.log(env.APP_TITLE)         // 我的应用console.log(env.VITE_API_BASE_URL) // http://localhost:3000/apireturn {envPrefix: ['VITE_', 'APP_'] // 允许 APP_ 前缀变量暴露到前端}
})

前端代码

console.log(import.meta.env.VITE_API_BASE_URL) // ✅ 可访问
console.log(import.meta.env.APP_TITLE)         // ✅ 可访问
console.log(import.meta.env.SECRET_KEY)        // ❌ undefined (没有被暴露)

📌 总结

  • loadEnv(..., prefix) → 影响 vite.config.js 构建阶段能读到哪些变量。
  • envPrefix → 影响 import.meta.env 暴露到客户端的变量范围。
  • 两者不冲突,而是配合使用。
    • 开发阶段你可能需要读取全部变量(loadEnv(mode, root, ''))。
    • 但你只想暴露部分变量给前端(通过 envPrefix 控制)。

四、开发环境代理

推荐在开发时:

  • .env.development 里写 VITE_API_BASE_URL=/api
  • 通过 vite.config.js 代理转发到后端

.env.development

VITE_API_BASE_URL="/api"
VITE_API_PROXY_TARGET="http://localhost:8080"

vite.config.js

server: {proxy: {'/api': {target: env.VITE_API_PROXY_TARGET,changeOrigin: true,rewrite: (p) => p.replace(/^\/api/, ''),},},
}

👉 前端请求 /api/user → 开发时代理到 http://localhost:8080/user
👉 生产环境直接用 .env.production 里的完整地址。


五、运行时配置(config.js 覆盖)

打包后,.env 已经固化,想“一份包跑多环境”就要在 运行时覆盖

public/config.js

window.__APP_CONFIG__ = {API_BASE_URL: "https://runtime-api.example.com",APP_TITLE: "My App (Runtime)"
}

index.html 中尽早引入:

<!doctype html>
<html><head><meta charset="utf-8" /><title>My App</title><script src="/config.js"></script> <!-- 必须在应用脚本之前加载 --></head><body><div id="app"></div><script type="module" src="/src/main.js"></script></body>
</html>

在项目中统一封装:

// src/config/appConfig.js
const runtime = window.__APP_CONFIG__ || {}export const APP_CONFIG = {API_BASE_URL: runtime.API_BASE_URL || import.meta.env.VITE_API_BASE_URL,APP_TITLE: runtime.APP_TITLE || import.meta.env.VITE_APP_TITLE,
}

六、Axios 封装

import axios from 'axios'
import { APP_CONFIG } from '@/config/appConfig'const request = axios.create({baseURL: APP_CONFIG.API_BASE_URL,timeout: 10000,
})request.interceptors.response.use((res) => res.data,(err) => {console.error('请求失败:', err.message)return Promise.reject(err)}
)export default request

七、推荐项目结构(示例)与完整代码片段

project/
├─ public/
│  └─ config.js
├─ src/
│  ├─ config/
│  │  └─ appConfig.js
│  ├─ utils/
│  │  └─ request.js
│  └─ main.js
├─ .env
├─ .env.development
├─ .env.production
├─ .env.staging
├─ package.json
└─ vite.config.js

package.json scripts

{"scripts": {"dev": "vite","build": "vite build","build:staging": "vite build --mode staging","preview": "vite preview"}
}

示例 .env.development

VITE_API_BASE_URL="/api"
VITE_API_PROXY_TARGET="http://localhost:8080"
VITE_APP_TITLE="My App (Dev)"

示例 .env.production

VITE_API_BASE_URL="https://api.example.com"
VITE_APP_TITLE="My App (Prod)"

public/config.js(部署时可覆盖)

window.__APP_CONFIG__ = {API_BASE_URL: "https://staging-api.example.com",APP_TITLE: "My App (Staging)"
}

src/config/appConfig.js

const runtime = (window && window.__APP_CONFIG__) || {}export const APP_CONFIG = {API_BASE_URL: runtime.API_BASE_URL || import.meta.env.VITE_API_BASE_URL,APP_TITLE: runtime.APP_TITLE || import.meta.env.VITE_APP_TITLE,
}

src/utils/request.js

import axios from 'axios'
import { APP_CONFIG } from '@/config/appConfig'
import { ElMessage } from 'element-plus'const request = axios.create({baseURL: APP_CONFIG.API_BASE_URL,timeout: 10000,headers: { 'Content-Type': 'application/json' },
})request.interceptors.response.use((res) => res.data,(err) => {ElMessage.error(err.response?.data?.message || err.message || '请求失败')return Promise.reject(err)}
)export default request

八、流程图

请添加图片描述

请求流转过程:

.envvite.config.js(dev 代理) → axios(request.js)config.js 覆盖 → 后端服务


九、常见坑 & 总结

.env 中的所有值都是字符串,需要自己转换
✅ 只有 VITE_ 开头的才会注入前端
.local 文件不要提交到 git
vite preview 不会重新读取 .env
✅ 可以用 ${VAR} 引用已有变量(dotenv-expand)
✅ 敏感信息不要放到 VITE_


📌 最终方案

  • 开发.env.development + Vite 代理
  • 生产.env.production 打包
  • 运行时config.js 覆盖 → 实现“一份包跑多环境”

文章转载自:

http://ufapqaRg.ryjqh.cn
http://pBlsP9Kv.ryjqh.cn
http://DPtuptHZ.ryjqh.cn
http://ShHBeonu.ryjqh.cn
http://SrYuvLy5.ryjqh.cn
http://pRq7S9mY.ryjqh.cn
http://yRdyBqWG.ryjqh.cn
http://Zw5vmE1T.ryjqh.cn
http://zelQ5as8.ryjqh.cn
http://wclbllzt.ryjqh.cn
http://eucNm6B9.ryjqh.cn
http://TuAUMPvN.ryjqh.cn
http://PTlI1cvz.ryjqh.cn
http://t8rxj8tP.ryjqh.cn
http://feygJS20.ryjqh.cn
http://7MBrAowQ.ryjqh.cn
http://TCKR7Xea.ryjqh.cn
http://xrx2NkV1.ryjqh.cn
http://arQz5J4j.ryjqh.cn
http://8QyMM17t.ryjqh.cn
http://2y8X1i6o.ryjqh.cn
http://Xfz2avwi.ryjqh.cn
http://TvRBZxjZ.ryjqh.cn
http://RQB50YZG.ryjqh.cn
http://FITN393k.ryjqh.cn
http://wmVSdNK4.ryjqh.cn
http://GJ3iBhlS.ryjqh.cn
http://iNix8vw1.ryjqh.cn
http://biJM4SYO.ryjqh.cn
http://Zva7rKBU.ryjqh.cn
http://www.dtcms.com/a/377221.html

相关文章:

  • 【大前端】前端生成二维码
  • 重学前端014 --- 响应式网页设计 CSS动画
  • YOLO 模型从 PyTorch 转换为 ONNX 并优化
  • Vue:条件渲染 (Conditional Rendering)
  • 手机版数字人分身系统源码搭建与定制化开发指南
  • 深入理解 Java 内存模型(JMM)
  • 9.10网编——项目1机械臂,TFTP手写
  • Spring Cloud Alibaba快速入门02-Nacos配置中心(下)
  • 3. 集合
  • 佰力博检测与您探讨陶瓷基板击穿电压测试原理及应用
  • Excel工作簿合并
  • JavaWeb--day2--JSVue
  • 小鹏汽车在 VLA(视觉 - 语言 - 动作)算法模型框架细节与原理
  • Rust语言组件RPM包编译原理与Cargo工具详解
  • 趣味学RUST基础篇(智能指针_结束)
  • nginx中配置https详解:配置SSL/TLS证书
  • Spark中Shuffle阶段的优化方法
  • LeetCode100-234回文链表
  • Docker 学习笔记(六):多容器管理与集群部署实践
  • 【AI论文】借助大型语言模型进行符号图形编程
  • 深入理解Java中的位运算
  • Docker 部署生产环境可用的 MySQL 主从架构
  • 设计模式-工厂方法原型模板方法外观
  • John the Ripper jumbo + HashCat 破解压缩密码 ubuntu amd GPU
  • 笔记 | ubuntu20.04离线安装Docker
  • 4.1.多线程JUC-什么是多线程?
  • 硅基计划4.0 算法 模拟
  • Android调用系统内置的UiAutomator工具实现自动化测试
  • vim 编辑器
  • RAG原理是什么?