Skip to main content

Node.js应用集成dotenvx

首先使用dotenvx命令行在目录下创建对应的.env.env.keys文件,命令如下:

$ dotenvx init
$ dotenvx set HELLO "World"
$ dotenvx encrypt

安装dotenvx npm,命令如下:

$ npm add @dotenvx/dotenvx

创建index.js文件,代码如下:

require('@dotenvx/dotenvx').config()
// or import '@dotenvx/dotenvx/config' if you're using esm

console.log(`Hello ${process.env.HELLO}`)

Run the application:

$ node index.js

即可看到对应的输出结果:Hello World

如果要在服务器或者其他环境中运行该应用,请设置DOTENV_PRIVATE_KEY环境变量,其值来之于.env.keys文件。

preload 方式

Bun

如果你使用Bun,那么使用以下代码来加载加密后的.env文件,命令行为: bun --preload=./preload-dotenvx-bun.js dotenvx-demo.ts

import {$} from "bun";

async function load_dotenvx() {
const env_variables = await $`dotenvx decrypt --dump`.json();
for (const [key, value] of Object.entries(env_variables)) {
process.env[key] = value;
}
}

await load_dotenvx();

Node.js

通过node -r我们可以提前加载并运行相关的代码,这样就可以在应用启动时自动加载dotenvx的配置。 命令行为:node -r ./dotenvx-preload-nodejs.cjs dotenvx-demo.js

const {execSync} = require('node:child_process');

function load_dotenvx() {
try {
const stdout = execSync('dotenvx decrypt --dump');
const env_variables = JSON.parse(stdout);
for (const [key, value] of Object.entries(env_variables)) {
process.env[key] = value;
}
} catch (error) {
console.error(`Error: ${error}`);
}
}

load_dotenvx();

Deno

借助deno run --preload命令,我们可以在Deno中预加载dotenvx的配置,命令如下:

$ deno run --allow-run --allow-env --preload dotenvx-preload-deno.js demo.ts

样例代码如下:

async function load_dotenvx() {
let cmd = new Deno.Command("dotenvx", {args: ["decrypt", "--stdout", "--format", "json"]});
const {code, stdout, stderr} = await cmd.output();
const env_variables = JSON.parse(new TextDecoder().decode(stdout));
for (const [key, value] of Object.entries(env_variables)) {
process.env[key] = value;
}
}

await load_dotenvx();

对应Node.js应用,我应该选择哪种方式集成Dotenvx?

对于一个典型的Node.js应用,目前下来有三种方式来集成dotenvx:

  • npm开发包
  • preload方式
  • dotenvx run -- node index.js命令行方式

那么你应该选择哪种方式来集成呢? 这里有几点给大家参考一下:

  • npm方式:虽然简单,但是有一个问题就是dotenvx的npm包比较大,如果你使用esbuild bundle一下,你会发现最终的文件达到405K左右,这个对于一个小型的Node.js应用来说,实在有点大啦,不值当。
  • preload方式:如果是serverless场景,就非常合适,主要是本来就有preload.js文件来初始化环境,所以增加dotenvx的preload代码也很简单。
  • 命令行方式:如果你的应该是通过shell脚本启动,那么调用一下eval $( dotenvx decrypt --format shell --stdout ) 可以直接转换为环境变量。。

我个人是不建议npm方式,主要是dotenvx涉及到加解密,npm包的大小会比较大,这样无需会增加应用的体积和消耗的资源,这个要慎重。 至于preload和命令行方式,这个就看你项目具体的情况啦。

npm方式

如果你使用npm方式运行相关的Node.js应用,那么你需要在package.json中添加以下脚本:

"scripts": {
"dev": "eval $(dotenvx decrypt -f .env --stdout --format shell); tsx watch src/index.ts",
"build": "tsc",
"start": "eval $(dotenvx decrypt -f .env --stdout --format shell); node dist/index.js"
},

解释:

  • "dev": "eval $(dotenvx decrypt -f .env --stdout --format shell); tsx watch src/index.ts": Debugger友好,开发模式下使用
  • dotenvx run -- node dist/index.js: 简洁模式

其实就是就是通过dotenvx Rust命令行,将.env文件解密为shell格式的环境变量,然后通过eval命令来设置环境变量。 这种方式非常简单,不会占用什么资源,而且可以直接在shell脚本中使用。

参考样例仓库: https://github.com/linux-china/dotenvx-hono-demo

Volta和Dotenvx集成

不少同学使用Volta来管理Node.js版本和工具, Volta管理Node.js多版本和工具,都是通过一个shim工具来实现的,也就是$HOME/.volta/bin/volta-shim命令, 然后会会创建一个node的软连接,指向$HOME/.volta/bin/volta-shim,在执行node命令时,实际上是执行的volta-shim命令, volta-shim会根据当前目录的package.json文件,来决定使用哪个版本的Node.js,然后再执行对应版本的Node.js解析器。

接下来我们要做的工作就是让volta-shim支持dotenvx特性,这样就可以让Node.js天生支持dotenvx特性了。 Volta是用Rust语言编写的, Dotenvx的命令行也是用Rust编写的,所以我们只需要在volta-shim中调用dotenvx-rs的API即可, 也就是一行代码的事:dotenvx_rs::dotenv().ok();,这样就可以加载.env文件了,然后再执行对应版本的Node.js解析器即可。

让我们看一下具体的操作。 首先安装Volta, 然后下载Volta Dotenvx Edition, 覆盖$HOME/.volta/bin/下的命令行即可。

注意: 使用Mac的同学可能要执行一下sudo xattr -r -d com.apple.quarantine volta*解除安全隔离。

如果是Bun或者Deno,你可以创建一个bunw或者denow的脚本,由改脚本负责调用dotenvx加载.env文件,然后再调用具体的Bun或者Deno解析器即可。 然后将该脚本保存在bundeno命令所在目录下即可。 样例脚本如下:

#!/bin/bash

# load .env by dotenvx
eval $( dotenvx decrypt --stdout --format shell )
# Execute bun command with arguments
$HOME/.bun/bin/bun "$@"
#$HOME/.deno/bin/deno "$@"