用 node --env-file 临时加载 .env

25 次浏览7 条回复

Node 20.6+ 开始自带了一个挺顺手的参数,小脚本想读 .env 时,不一定要先装 dotenv。

环境:Node.js 20.6+,项目根目录有 .env:

API_BASE=https://example.test
DEBUG_FLAG=1

然后直接跑:

node --env-file=.env scripts/check-config.mjs

脚本里照常读:

console.log(process.env.API_BASE)
console.log(process.env.DEBUG_FLAG)

它只是启动这个 Node 进程时加载环境变量,不会改文件。比较适合本地调试、跑一次性脚本这种场景。

有个小点要注意:如果 shell 里已经有同名环境变量,实际值按 Node 当前版本规则来,别把生产配置和本地 .env 混着猜,跑之前可以先打印确认一下。

这个放到 package.json 脚本里也挺省心:

{
  "scripts": {
    "check:config": "node --env-file=.env scripts/check-config.mjs"
  }
}

这样别人拉项目以后直接 npm run check:config,不用记这个参数。就是项目里如果有人还是 Node 18,会直接不认识这个参数,README 里顺手标一下版本会稳一点。

补一个小坑:如果脚本里要区分本地和 CI,尽量别把文件名写死成 .env 以外的私有配置。比如可以约定只读 .env.example 里的键名,然后本地自己放 .env。

还有就是这个参数只影响当前 node 进程,子进程能不能拿到这些变量,要看你启动子进程时有没有继承 process.env。排查时先打一下关键变量,能少猜很多。

还有个容易忽略的点:从 .env 进来的值全都是字符串。像 DEBUG_FLAG=0、PORT=3000 这种,脚本里最好自己做一次显式转换,别直接拿来当 boolean 或 number 用。

小脚本里我会写得笨一点:

const debug = process.env.DEBUG_FLAG === '1'
const port = Number(process.env.PORT ?? 3000)

这样以后换人改 .env,不太容易被真假值坑到。

我会顺手在脚本入口加个必填检查,不然 .env 没加载成功时,后面请求报错会绕很远。

const required = ['API_BASE']
for (const key of required) {
  if (!process.env[key]) throw new Error(`missing env: ${key}`)
}

尤其是放进 npm scripts 以后,别人换目录跑、或者文件名写错,能早点炸出来。小脚本里这种笨检查反而挺省时间。

还有一点是它只在进程启动时读一次。.env 后面改了,已经跑起来的脚本不会自动刷新。

如果是 node --watch --env-file=.env ... 这种开发命令,改业务代码会重启,但只改 .env 不一定按你预期触发,所以调配置时我一般直接停掉重跑,少一点玄学。

如果是从 dotenv 迁过来,我觉得最好先拿现有 .env 跑一遍最小脚本确认解析结果。

node --env-file=.env -e "console.log(process.env.API_BASE)"

主要是注释、引号、空值这些边角写法,不同工具细节可能不完全一样。配置文件越老越值得先验一下,别迁完才发现某个值其实没读成预期。

这个参数里的路径也别忘了是按启动命令时的当前目录来找的,不是按脚本文件所在目录。

比如人在项目根目录跑:

node --env-file=.env scripts/check-config.mjs

和先 cd scripts 再跑,找的就不是同一个 .env 了。要放进 npm scripts 还好,一般 cwd 在项目根;如果是别的工具间接调用,最好先打印一下 process.cwd(),不然挺容易以为配置没生效。