环境变量注入的差异
Vue CLI 的 process.env
Node 环境构建阶段(vue.config.js等)
可访问 process.env 中所有 Node.js 内建变量,包括
.env
文件中任意自定义的变量。客户端代码运行阶段(main.js等)
① Node.js 内建变量只能访问 NODE_ENV,其他的为 undefined:
process.env.NODE_ENV:反映当前模式,与 --mode 参数一致。
为什么
NODE_ENV
可以同时在构建阶段和运行时都可以访问?构建时注入: 在构建阶段,NODE_ENV 的值会被 Vue CLI 和 Webpack 直接注入到代码中。Vue CLI 会根据当前运行的命令(npm run serve 或 npm run build)自动设置 NODE_ENV 的值。这个值在构建过程中会被传递到客户端的 JavaScript 代码中。
客户端访问: 当你在客户端代码中使用 process.env.NODE_ENV 时,Webpack 会根据你设置的构建模式(开发模式或生产模式)来替换 process.env.NODE_ENV,从而保证你能够在浏览器端获取正确的环境信息。
② 默认情况下,可访问以
VUE_APP_
开头的变量:.env
文件中自定义的变量,默认情况下,只能访问以VUE_APP_
为前缀的变量。⚠️ 注意
默认情况下,只有
NODE_ENV
,BASE_URL
和以VUE_APP_
开头的变量将通过 webpack.DefinePlugin 静态地嵌入到客户端侧的代码中。这是为了避免意外公开机器上可能具有相同名称的私钥。process.env.BASE_URL
是由 Vue CLI 内部处理的,而不是 Node.js 的内建变量或你的自定义变量。它的值在构建时由 Vue CLI 根据 vue.config.js 中的 publicPath 动态设置。示例:
变量定义如下:
dotenv# .env.development VUE_APP_NO=1 NO=2
测试结果如下:
js// vue.config.js console.log(process.env.NODE_ENV) // development console.log(process.env.USERNAME) // Administrator console.log(process.env.BASE_URL) // undefined console.log(process.env.VUE_APP_NO) // 1 console.log(process.env.NO) // 2
js// src/main.js console.log(process.env.NODE_ENV) // development console.log(process.env.USERNAME) // undefined console.log(process.env.BASE_URL) // '' console.log(process.env.VUE_APP_NO) // 1 console.log(process.env.NO) // undefined
Vite 的 import.meta.env 与 process.env
Node 环境构建阶段(vite.config.js等)
- 只能访问 process.env 中的 Node.js 原生内建环境变量,
VITE_
开头的自定义变量和BASE_URL
无法被访问。 - import.meta.env 在此阶段值为 undefined。
- 只能访问 process.env 中的 Node.js 原生内建环境变量,
客户端代码运行阶段(main.js等)
- 可访问 import.meta.env 中的内建变量,以及以
VITE_
开头的变量。 - 无法访问 process.env:直接访问该变量或其内建变量会抛出错误:process is not defined。
- 特殊情况,process.env.NODE_ENV 可正常访问:是因为构建工具的支持:大多数构建工具(比如 Webpack、Babel 等)都默认支持 NODE_ENV。所以 Vite 为了兼容部分依赖此变量的工具和库,会自动将 NODE_ENV 单独暴露为静态值。
- 可访问 import.meta.env 中的内建变量,以及以
为什么 Vite 客户端侧代码中 process.env 是 undefined?
Vite 是以现代 ES 模块为基础设计,客户端代码不依赖 Node.js 环境。process.env 的直接访问会报错是因为浏览器中没有 process 对象。
- 示例:
变量定义如下:
# .env.development
VITE_aaa=123
bbb=123
测试结果如下:
// vite.config.js
console.log( process.env.NODE_ENV) // development
console.log(process.env.APPDATA) // C:\Users\Administrator\AppData\Roaming
console.log('process.env: ', process.env) // {...}
console.log(process.env.VITE_aaa) // undefined
console.log(process.env.bbb) // undefined
console.log(process.env.BASE_URL) // undefined
console.log(process.env.VITE_API_BASE_URL) // undefined
console.log(import.meta.env) // undefined
// src/main.js
// console.log(process.env.APPDATA) // process is not defined
// console.log(process.env) // process is not defined
// console.log(process.env.VITE_aaa) // process is not defined
// console.log(process.env.bbb) // process is not defined
// console.log(process.env.BASE_URL) // process is not defined
console.log(process.env.NODE_ENV) // development
console.log(import.meta.env) // {...} 只能访问内建变量和VITE_前缀开头的变量
console.log(import.meta.env.BASE_URL) // '/'
Vue CLI 环境变量注入原理
webpack 构建阶段
- Vue CLI 使用 Webpack 的 DefinePlugin 插件,将 process.env 注入到客户端代码中。
- 在客户端代码中,process.env 并非真实的 Node.js 环境变量,而是静态替换的值。例如:javascript
new DefinePlugin({ 'process.env': { NODE_ENV: JSON.stringify('production'), BASE_URL: JSON.stringify('/'), 'VUE_APP_API_URL': JSON.stringify('https://api.example.com'), }, })
- 仅以
VUE_APP_
为前缀的变量会被注入到客户端代码中,防止不必要的敏感信息暴露。
运行时行为
- 在 vue.config.js 中运行的代码处于 Node.js 环境,所以 process.env 可以访问 .env 文件中所有变量。
Vite 环境变量注入原理
- import.meta.env 的生成
- Vite 使用 ESBuild / 插件系统处理代码,在构建时生成静态替换。
- 内建变量(如 MODE, BASE_URL)和以
VITE_
开头的变量被注入到 import.meta.env,其余变量不会暴露到客户端代码中。javascriptconst importMetaEnv = { MODE: 'production', BASE_URL: '/', VITE_API_URL: 'https://api.example.com', }
- 特殊变量 process.env.NODE_ENV 的暴露
- Vite 会手动将 process.env.NODE_ENV 替换为静态值,便于兼容使用该变量的库(如 Vue 本身)。
- Node 环境的运行
- 在 vite.config.js 中,process.env 仅包含 Node.js 环境变量,而不包括 .env 文件中的变量。
- import.meta.env 在该阶段为 undefined,因为其仅在客户端侧生效。
Vue CLI 自定义前缀变量
配置方法
- 修改 vue.config.js 的 chainWebpack 配置
- 使用 DefinePlugin 手动注入自定义前缀变量。
- 示例:
const dotenv = require('dotenv')
const path = require('path')
// 读取.env文件和相关变体(包括模式和本地模式),将其解析为一个对象返回
function getEnvCustomVariable() {
// 使用 path.resolve 生成绝对路径以确保文件定位正确
const getAbsolutePathOfFile = (file) => path.resolve(__dirname, file)
// 使用 dotenv 加载指定路径的 .env 文件
const getEnvVariable = (file) =>
dotenv.config({ path: getAbsolutePathOfFile(file) })?.parsed ?? {}
const envVariableOfGlobal = '.env'
const envVariableOfMode = `.env.${process.env.NODE_ENV}`
const envVariableOfLocalMode = `.env.${process.env.NODE_ENV}.local`
// 使用数组存储文件路径,并动态生成文件列表以支持自定义优先级
const files = [envVariableOfGlobal, envVariableOfMode, envVariableOfLocalMode]
return files.reduce((acc, file) => ({ ...acc, ...getEnvVariable(file) }), {})
}
module.exports = {
chainWebpack(config) {
const envVariables = getEnvCustomVariable()
// 支持多前缀
const prefixes = ['MY_PREFIX_', 'ANOTHER_PREFIX_']
Object.keys(envVariables).forEach((key) => {
// 遍历环境变量的键值对,检查是否符合特定前缀条件
if (prefixes.some((prefix) => key.startsWith(prefix))) {
// 通过 define 插件动态修改全局定义
config.plugin('define').tap((definitions) => {
definitions[0]['process.env'][key] = JSON.stringify(envVariables[key])
return definitions
})
}
})
}
}
访问方法
- 在客户端代码中可以通过
process.env.MY_PREFIX_XXX
访问自定义变量。
Vite 自定义前缀变量
配置方法
- 使用 vite.config.js 中的 define 配置,将自定义前缀变量注入到 import.meta.env。
- 示例:
import { defineConfig, loadEnv } from 'vite'
export default defineConfig(({ mode }) => {
// 定义需要加载的前缀(多个前缀可以单独调用 loadEnv)
const prefixes = ['MY_PREFIX_', 'ANOTHER_PREFIX_']
// 加载环境变量合并
const env = prefixes.reduce((acc, prefix) => {
// 使用 Vite 的内置 loadEnv 方法加载指定前缀的环境变量。
// loadEnv:不需要手动解析 .env 文件,会根据模式自动加载
// .env 文件、.env.[mode] 文件、.env.local.[mode] 文件
const prefixEnv = loadEnv(mode, process.cwd(), prefix)
return { ...acc, ...prefixEnv } // 合并结果
}, {})
// 将变量转为 vite 的 define 格式
const defineEnv = Object.keys(env).reduce((acc, key) => {
// 挂载全局变量
acc[key] = JSON.stringify(env[key])
// 或:挂载到 import.meta.env 中
acc[`import.meta.env.${key}`] = JSON.stringify(env[key])
return acc
})
return {
define: defineEnv
}
})
env[key] 的值默认就是字符串类型,为什么需要用 JSON.stringify 处理 env[key]?
- 不加构建阶段就会报错:
# 无法为导入分析解析源代码,因为内容包含无效的JS语法。如果您正在使用JSX,请确保使用.jsx或.tsx扩展名来命名文件
[vite] Pre-transform error: Failed to parse source for import analysis because the content contains invalid JS syntax. If you are using JSX, make sure to name the file with the .jsx or .tsx extension.
- 为什么需要 JSON.stringify ?
在 Vite 配置中的 define 选项,传递给客户端代码的变量值,需是合法的 JavaScript 表达式。define 配置选项的作用是将代码中的变量值替换为实际的字符串或数字等值。然而,如果你直接将环境变量传递给 define,例如:
define: {
'MY_PREFIX_API_URL': env.MY_PREFIX_API_URL, // 这将会导致错误
}
生成的代码将是:
MY_PREFIX_API_URL = http://example.com;
这会是非法的,因为 http://example.com 被认为是一个标识符,而不是一个字符串。
为了避免这个问题,你必须使用 JSON.stringify 来确保生成的代码是合法的 JavaScript 字符串字面量:
- 总结:
在 vite.config.js 中,如果你要使用 define 配置来替换代码中的变量,你需要使用 JSON.stringify 来确保传递给客户端的变量是有效的 JavaScript 字符串字面量。
在 server.proxy 中 target 属性应用 loadEnv 中的变量,需要使用 JSON.parse 对变量进行解析后才能使用
- loadEnv 加载的环境变量始终是 字符串 类型,无论你在 .env 文件中定义的是数字、布尔值,还是 JSON 字符串。而前面我们对自定义变量又进行了一层 JSON.stringify 处理。
- 所以在 server.proxy.target 配置中,如果你希望使用一个 JSON 格式的字符串 作为 target,你需要使用 JSON.parse 来将它解析成一个有效的 JavaScript 对象或字符串。
server: {
proxy: {
'/api': {
target: defineEnv.MY_PREFIX_API_BASE_URL, // => '"http://xxx"'
target: JSON.parse(defineEnv.MY_PREFIX_API_BASE_URL), // => 'http://xxx'
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
访问方法
- 挂载到全局的变量:直接访问变量名,如
MY_PREFIX_XXX
。 - 挂载到 import.meta.env 中的变量:在客户端代码中可以通过
import.meta.env.MY_PREFIX_XXX
访问变量。