主流的CSR前端SPA项目通常会通过 process.env
(构建时) 或 import.meta.env
来访问环境变量,而环境变量通常会使用 .env文件、cicd流程时注入、Dockerfile、cross-env等方式在执行dev
或build
命令时设定并构建到前端代码中,也就是build后的代码已经不能随部署运行时修改环境变量了,这是一个正常的现象。
我们通常会在执行针对不同部署环境的构建指令时会读取不同的.env文件,或使用不同的ci/cd流程等切换构建时的环境变量。
而部署一个node服务的时候,我们可以在部署执行启动命令时才进行指定环境变量,并通过process.env
访问。
Nuxt3通用渲染时环境变量配置
因Nuxt3项目在生产环境部署时需要build的,构建后需要执行 node .output/server/index.mjs
入口文件启动服务 (部署 · 快速入门 Nuxt),则会有构建时 和运行时 的区别,其中执行build
指令时为构建时,执行node .output/server/index.mjs
时为运行时。
模拟需求 :当部署项目执行
node .output/server/index.mjs
时,使用一个环境变量HOST_ENV
来指定当前部署到哪个环境,默认为dev
,部署到预发布环境时值为stag
,生产环境为prod
client端直接访问环境变量
如client端代码访问process.env.HOST_ENV
vue
<!-- app.vue -->
<template>
<div></div>
</template>
<script setup lang="ts">
consolt.log('HOST_ENV ---- ', process.env.HOST_ENV) // 打印
</script>
使用cross-env或在目录中加入.env文件,并指定 HOST_ENV=dev,在命令行中执行dev
指令启动nuxt服务,并在浏览器进行访问。默认的通用渲染模式下,代码会现在server端执行一次(SSR),在浏览器端水合也会执行一次,所以会在命令行窗口(serve端)和浏览器控制台(client端)各打印一次
可以发现server端执行时值是dev
,而到client端时变为undefined
。原因很明显,process
是node环境下的,在nuxt3中client端执行时访问的process
是经过如下处理的,并且process.env
中有且仅有NODE_ENV
这一个key
json
{
"dev": true,
"test": false,
"env": {
"NODE_ENV": "development"
},
"server": false,
"client": true,
"browser": true,
"nitro": false,
"prerender": false
}
那么如果使用 import.meta.env
来访问呢?在这种情况下实际上是可以访问的。当然,默认情况下只会读取VITE_
开头的环境变量,所以需要把环境变量改名为VITE_HOST_ENV
进行访问。
但是,这个访问方式和开篇赘述时的情况一致,import.meta.env.xxx
的访问是会在构建时被静态替换的,clinet端访问环境变量的值会停留在执行构建时的值。
runtimeConfig对环境变量和nirto的相关配置
对于上述的情况,已经看到多个平台不少相关的文章均有提到过类似的环境变量访问的问题,但对于nuxt3的runtimeConfig
的配置描述得不到位,或使用场景不清晰的问题。
也许是和官方文档的描述不清晰有关系,其环境变量相关的文档描述分了3个部分
其中有一段描述:
特定命名的环境变量可以覆盖运行时配置属性。也就是说,以
NUXT_
开头的大写环境变量,使用_
分隔键和大小写变化。
引出一个普遍的疑惑:既然我需要写 NUXT_
开头的环境变量去覆盖runtimeConfig
中的值,那么我为何不直接为runtimeConfig
赋值为访问环境变量? 或者我在代码中直接访问环境变量,为何还须通过useRuntimeConfig()
去访问?
对于server端的代码来说是的,环境变量可以直接通过process.env
访问。
但runtimeConfig
可以被NUXT_
开头的环境变量覆盖的意义就在于,可在启动服务运行时指定环境变量去进行覆盖,而不仅仅是构建时 。所以对于client端的代码来说,要获取运行时的环境变量配置,只能使用这种途径并通过useRuntimeConfig()
进行访问。
注意: 下面这种方式,也只能读取到构建时的值
js
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
public:{
HOST_ENV: process.env.HOST_ENV // 读取不到运行时的环境变量
}
}
})
英文版文档中有这一段描述了上面的情况: Runtime Config · Nuxt Advanced
Setting the default of
runtimeConfig
values to differently named environment variables (for example settingmyVar
toprocess.env.OTHER_VARIABLE
) will only work during build-time and will break on runtime. It is advised to use environment variables that match the structure of yourruntimeConfig
object.
所以我们需要依赖NUXT_
开头的环境变量进行运行时覆盖,我们需要把变量名HOST_ENV
更名为NUXT_PUBLIC_HOST_ENV
,根据转换规则,会覆盖runtimeConfig.public.hostEnv
,并且必须要预先声明这个key,否则不会被运行时环境变量注入
js
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
public:{
hostEnv: 'dev' // 必须在代码中先声明这个key,否则不会被覆盖,可以设定默认值
}
}
})
client端访问
vue
<!-- app.vue -->
<template>
<div></div>
</template>
<script setup lang="ts">
// consolt.log('HOST_ENV ---- ', process.env.HOST_ENV)
// 更换为对runtimeConfig的访问
console.log('HOST_ENV ----', useRuntimeConfig().public.hostEnv) // 打印
</script>
执行build
进行构建后,这里为方便测试,在scripts中新增启动服务指令start
,其中直接使用cross-env指定运行时环境变量NUXT_PUBLIC_HOST_ENV=prod
json
// package.json
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"start": "cross-env NUXT_PUBLIC_HOST_ENV=prod node .output/server/index.mjs"
}
启动服务后查看打印结果,server端执行时和client端执行时该值均被成功设置为prod
优化实践
若希望改变注入的环境变量前缀,不使用NUXT_
开头,可以进行如下配置(nuxt、nitro文档均没有提及这一点)
js
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
nitro: {
envPrefix: 'Prefix_' // 更改为自定义的前缀,替换NUXT_这个匹配规则
},
}
})
如果使用上述的环境变量名转换的话,可能在使用中会有3个问题
- 在别的项目迁移到NUXT3时,或之前的版本中已经编写好多个环境变量,现在要进行运行时覆盖的改造等情况时,需要强制更改环境变量名或新增一个业务意义相同的NUXT_变量名。
- 当在
runtimeConfig
中对象层级深的情况下,要设置的环境变量名可能会非常长,增加了拼写错误的风险并增加了复杂性 - 限制了
runtimeConfig
中key的命名
为解决这个问题,nitro在2.9.0版本后新增了一个特性(需要把nuxt项目升级到对应的支持版本),可以让任意的环境变量名覆盖到runtimeConfig
中的任意key和值中。PR描述
- 首先在配置中声明开启这个功能,在nitro配置项中,或在
runtimeConfig
中的nirto配置项中均可开启实验性的envExpansion
功能 runtimeConfig
中使用{{ENV_NAME}}
的插值语法声明要读取的环境变量名
js
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
experimental: {
envExpansion: true, // 可以在这里开启
},
},
runtimeConfig: {
nitro: {
envExpansion: true // 也可以在这里开启
},
public: {
// 声明这个位置要的字符串要使用环境变量HOST_ENV的值
HOST_ENV: '{{HOST_ENV}}',
// 插值语法的灵活性,可以拼接字符串和其他环境变量
COMBIN_ENV: '{{HOST_ENV}} ---- {{APP_NAME}}'
},
},
})
同样使用cross-env指定环境变量并运行start
启动服务
json
// package.json
{
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"start": "cross-env HOST_ENV=prod APP_NAME=juejin node .output/server/index.mjs"
}
}
查看打印结果无误
使用这种方法时,就不能直接在runtimeConfig
里面设定默认值了,在本地开发时,可以使用开发环境的.env文件进行指定环境变量默认值