拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

dotenv 完全指南:用零依赖模块在 Node.js 中加载 .env 环境变量(含 CLI 与源码级解析)

dotenv 完全指南:用零依赖模块在 Node.js 中加载 .env 环境变量(含 CLI 与源码级解析)
  • 开发工具
  • 后端

【免费下载链接】dotenv

Loads environment variables from .env for nodejs projects.

项目地址:https://gitcode.com/gh_mirrors/do/dotenv
点击查看免费下载

dotenv 是一个零依赖的 Node.js 模块,用于将.env文件中的环境变量加载进process.env为核心骨架,结合 lib/main.js、cli.js、lib/config-options.js 等源码与 tests/ 下的测试用例,系统讲解安装接入、CLI 用法、全部配置项、解析引擎规则,以及底层实现原理。读完本文,你将能独立完成.env文件的创建、加载、多文件合并、调试与 CLI 注入,并能按需启用fast快解析器或编写基于parse/populate的扩展插件。

快速开始

安装

在项目根目录执行:

npm install dotenv --save

dotenv无任何运行时依赖,包体积与解析成本都保持极简(见 package.json 的dependencies为空)。本包自带 CLI 命令,bin字段指向dist/index.cjs,安装后npx dotenv或dotenv均可直接调用。

创建 .env 文件

在项目根目录新建.env:

# .env HELLO="Dotenv" OPENAI_API_KEY="your-api-key-goes-here"

加载并读取

在应用代码中尽早导入并配置:

// index.js require('dotenv').config() // 或使用 ESM 方式:import 'dotenv/config' console.log(`Hello ${process.env.HELLO}`)

运行:

$ node index.js ◇ injected env (2) from .env Hello Dotenv

◇ injected env (2) from .env是加载成功后的注入提示,表示从.env中注入了 2 个变量。提示信息输出到stderr而非 stdout,因此不会污染管道化的标准输出(见 CHANGELOG.md v18.0.0 变更记录)。

从源码看,加载流程在 lib/main.js 的configDotenv中实现:先按默认路径path.resolve(process.cwd(), '.env')解析,fs.readFileSync读取文件,再交给DotenvModule.parse解析,最后DotenvModule.populate写入process.env,并把实际写入的键值集合(populated)数量打印出来。

CLI 用法

dotenv 从 v18.0.0 起自带 CLI(见 CHANGELOG.md 的 v18.0.0 条目),非常适合在启动命令前注入环境变量,也便于 CI、Docker 与 coding agent 使用。

// index.js console.log(`Hello ${process.env.HELLO}`)
$ npx dotenv run -- node index.js ◇ injected env (2) from .env Hello Dotenv

--分隔符是可选的;dotenv 自己的选项必须放在命令之前,命令之后的所有参数原样透传给目标命令:

$ dotenv run node index.js $ dotenv run -q node index.js $ dotenv run --override --debug -- node index.js $ dotenv run -f .env.local,.env node index.js

CLI 的完整入口在 cli.js 的run()函数:parseRunArgs负责解析参数,loadEnvFiles读取并合并各文件,随后通过 lib/spawn-command.js 的spawnCommand派生子进程执行命令,并把子进程的退出码原样转发(见 tests/test-cli.js 中“preserves child exit code”用例)。

多文件加载与优先级

-f/--file支持一次选择多个.env文件,用逗号分隔或重复该标志均可,文件按给定顺序加载:

$ dotenv run --file .env.local,.env node index.js ◇ injected env (2) from .env.local, .env
  • 未指定--override时:先加载的值优先(第一个值胜出),已存在于process.env中的变量不会被覆盖;
  • 指定--override时:后加载的值覆盖先前的值(最后一个值胜出),并覆盖已存在的环境变量。

CLI 在 cli.js 的loadEnvFiles中先解析各文件到临时对象parsedAll,最后一次性 populate 进process.env;合并顺序与override语义和 SDK 的config({ path: [...] })完全一致。

退出码与文件缺失行为

CLI 会转发子命令的退出状态。默认.env缺失时允许继续运行(ENOENT被吞掉);但用-f显式指定的文件缺失时会停止执行并报错(对应 cli.js 中options.defaultPath的判断逻辑)。

进阶用法

ES6 导入

import 'dotenv/config'

DOTENV_ENCODING、DOTENV_PATH、DOTENV_QUIET、DOTENV_DEBUG、DOTENV_OVERRIDE、DOTENV_FAST为config()和dotenv run提供默认值。优先级从高到低为:直接传入的选项/标志 >DOTENV_*环境变量 > 旧的DOTENV_CONFIG_*名称。注意:空值和 false 值不会触发回退到旧名称。

其他包管理器

bun add dotenv yarn add dotenv pnpm add dotenv deno add dotenv

Monorepo 场景

对于apps/backend/app.js这类结构,把.env放在app.js进程实际运行的目录下即可:

# app/backend/.env S3_BUCKET="YOURS3BUCKET" SECRET_KEY="YOURSECRETKEYGOESHERE"

因为默认路径是基于process.cwd()(当前工作目录)解析的。

多行值

从 v15.0.0 起支持多行变量(例如私钥),可以直接换行书写:

PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- ... Kh9NV... ... -----END RSA PRIVATE KEY-----"

也可以双引号包裹并使用\n转义:

PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nKh9NV...\n-----END RSA PRIVATE KEY-----\n"

注意:只有双引号包裹的值才会展开\n换行。这在 tests/test-parse.js 中有明确验证:EXPAND_NEWLINES="expand\nnew\nlines"会展开为真实换行,而单引号或未引用的DONT_EXPAND_UNQUOTED、DONT_EXPAND_SQUOTED会保留字面\n。

注释

.env支持整行注释和行内注释:

# This is a comment SECRET_KEY=YOURSECRETKEYGOESHERE # comment SECRET_HASH="something-with-a-#-hash"

从 v15.0.0 起(破坏性变更):只要出现#即视为注释开始,因此值里若含#必须用引号包裹。

解析引擎(Parse)

解析引擎可独立使用,接受 String 或 Buffer,返回键值对象:

const dotenv = require('dotenv') const buf = Buffer.from('BASIC=basic') const config = dotenv.parse(buf) // 返回对象 console.log(typeof config, config) // object { BASIC : 'basic' }

使用 dotenvx 实现变量展开、命令替换、加密与多环境

以下高级能力由 dotenvx 提供(dotenv本身刻意保持单文件解析、不做展开/加密):

  • 变量展开:引用并展开本机已有变量,写入.env:

    # .env USERNAME="username" DATABASE_URL="postgres://${USERNAME}@localhost/my_database"
    $ dotenvx run --debug -- node index.js ⟐ injected env (2) from .env · dotenvx@1.59.1 DATABASE_URL postgres://username@localhost/my_database
  • 命令替换:把命令输出塞进变量:

    # .env DATABASE_URL="postgres://$(whoami)@localhost/my_database"
  • 加密:一条命令给.env文件加解密:

    $ dotenvx set HELLO Production -f .env.production $ echo "console.log('Hello ' + process.env.HELLO)" > index.js $ DOTENV_PRIVATE_KEY_PRODUCTION="<.env.production private key>" dotenvx run -- node index.js ⟐ injected env (2) from .env.production · dotenvx@1.59.1 Hello Production
  • 多环境:按环境建文件,用-f加载:

    $ echo "HELLO=production" > .env.production $ dotenvx run -f=.env.production -- node index.js Hello production

    多个.env文件时,先指定的优先:

    $ echo "HELLO=local" > .env.local $ echo "HELLO=World" > .env $ dotenvx run -f=.env.local -f=.env -- node index.js Hello local
  • 生产部署:创建.env.production→dotenvx encrypt -f .env.production加密 → 在服务器上设置解密密钥DOTENV_PRIVATE_KEY_PRODUCTION→ 把加密后的.env.production提交进代码库并部署 → 运行时由dotenvx run -- node index.js自动解密注入。

  • 同步(Syncing):用dotenvx encrypt -f .env加密后随 git 安全同步,解密密钥与代码分离,依然符合 Twelve-Factor 原则。

FAQ 速查

问题结论
应该提交.env文件吗?不建议。除非用 dotenvx 加密,加密后反而推荐提交。
变量展开怎么做?使用 dotenvx。
要不要多个.env文件?每个环境一个文件(.env本地、.env.production生产),避免自定义继承式配置,必要时跨环境复制值。
用import怎么写?import 'dotenv/config'置于读取环境变量的模块之前;或dotenv run -- node index.mjs。
可以写插件吗?可以。dotenv.config()返回包含parsed键的对象,可直接传给dotenv-expand等插件继续加工。
已存在的环境变量怎么办?默认绝不覆盖(冲突时跳过.env中的同名项);需要覆盖时用override选项。
变量在 React 里不生效?React 跑在 Webpack 中,process.env只能通过 Webpack 配置注入;react-scripts内置 dotenv 但要求变量以REACT_APP_前缀。
.env加载失败?多半是文件位置不对。开启debug: true查看控制台错误。
报Module not found: Can't resolve 'os|path'?前端场景缺少 polyfill,安装node-polyfill-webpack-plugin并在webpack.config.js中配置,或直接使用 dotenv-webpack。

解析引擎规则清单(来自 README FAQ,测试逐一验证)

  • BASIC=basic→{BASIC: 'basic'}
  • 空行跳过
  • 以#开头的行视为注释
  • #标记注释开始(值被引号包裹时除外)
  • 空值变成空字符串(EMPTY=→{EMPTY: ''})
  • 保留内部引号(类 JSON):JSON={"foo": "bar"}→{JSON:"{\"foo\": \"bar\"}"}
  • 未引用值的两端空白被去除:FOO= some value→{FOO: 'some value'}
  • 单双引号包裹的值会被脱引号:SINGLE_QUOTE='quoted'→{SINGLE_QUOTE: "quoted"}
  • 单双引号包裹的值保留两端空白:FOO=" some value "→{FOO: ' some value '}
  • 双引号值展开换行:MULTILINE="new\nline"→{MULTILINE: 'new\nline'}
  • 支持反引号:BACKTICK_KEY=`This has 'single' and "double" quotes inside of it.`

这些规则与 tests/.env 测试样本及 tests/test-parse.js 中的断言一一对应,例如INLINE_COMMENTS、EQUAL_SIGNS=equals==、SPACED_KEY、export EXPORT_IS_DECLARED=parsed(export关键字会被忽略)等。

SDK 参考:config / parse / populate

dotenv 暴露三个函数:config、parse、populate(类型声明见 lib/main.d.ts)。

config()

读取.env→ 解析 → 写入process.env,返回包含parsed或error键的对象:

const result = dotenv.config() if (result.error) { throw result.error } console.log(result.parsed)
选项:path
  • 默认:path.resolve(process.cwd(), '.env')
  • 指定自定义路径(也支持URL对象):
require('dotenv').config({ path: '/custom/path/to/.env' }) const fileUrl = new URL('file:///custom/path/to/.env') require('dotenv').config({ path: fileUrl })
  • 传数组可加载多个文件,按顺序解析并与process.env(或option.processEnv)合并;未设override时首个值胜出,设了则末个值胜出:
require('dotenv').config({ path: ['.env.local', '.env'] })
选项:quiet
  • 默认:false
  • 抑制运行时日志。dotenv/config导入与 preload 方式默认即为true;可用DOTENV_QUIET=false(或旧名DOTENV_CONFIG_QUIET=false)在 shell 里重新开启启动信息。
require('dotenv').config({ quiet: false }) // 改为 true 可抑制输出
选项:encoding
  • 默认:utf8
  • 指定.env文件编码:
require('dotenv').config({ encoding: 'latin1' })
选项:debug
  • 默认:false
  • 开启日志,定位键值未被按预期写入的原因:
require('dotenv').config({ debug: process.env.DEBUG })
选项:override
  • 默认:false
  • 用.env的值覆盖本机已设置的环境变量;多文件时配合path数组逐文件生效。未设置时首个值胜出,设置后末个值胜出:
require('dotenv').config({ override: true })
选项:fast
  • 默认:false
  • 启用约 2 倍速的字符扫描解析器(character-scanner parser),默认仍是经典正则解析器:
require('dotenv').config({ fast: true })
选项:processEnv
  • 默认:process.env
  • 指定一个自定义对象作为写入目标,默认写process.env:
const myObject = {} require('dotenv').config({ processEnv: myObject }) console.log(myObject) // 来自 .env 的值 console.log(process.env) // 未被改动

parse()

解析引擎独立可用,接受 String 或 Buffer,返回键值对象(可选{ fast: true }):

const dotenv = require('dotenv') const buf = Buffer.from('BASIC=basic') const config = dotenv.parse(buf) console.log(typeof config, config) // object { BASIC : 'basic' }

parse支持debug选项,输入不合规时会输出调试信息:

const dotenv = require('dotenv') const buf = Buffer.from('hello world') const opt = { debug: true } const config = dotenv.parse(buf, opt) // 因输入不是 KEY=VAL 形式,会看到一条 debug 消息

populate()

把解析结果写入目标的引擎,接受 target、source 与 options,适合自定义对象的高级用户:

const dotenv = require('dotenv') const parsed = { HELLO: 'world' } dotenv.populate(process.env, parsed) console.log(process.env.HELLO) // world

自定义 source 与 target,并开启override与debug:

const dotenv = require('dotenv') const parsed = { HELLO: 'universe' } const target = { HELLO: 'world' } dotenv.populate(target, parsed, { override: true, debug: true }) console.log(target) // { HELLO: 'universe' }

populate支持两个选项:debug(默认false,输出排查日志)与override(默认false,是否覆盖已存在的变量)。

从 lib/main.js 的实现看,populate逐键遍历parsed:若目标对象已存在该键且override为真则覆盖,否则跳过;不存在的键直接写入,并统计实际写入的populated集合返回。若 target/source 不是对象,会抛出code为OBJECT_REQUIRED的错误(对应 tests/test-populate.js 的断言)。

CLI 参考

dotenv命令随包自带,可用npx dotenv或直接在 npm scripts 中使用。

run

从.env加载环境变量,然后运行命令:

npx dotenv run [options] -- <command> [args...]
npx dotenv run -- node index.js npx dotenv run -f .env.local -- node index.js npx dotenv run -f .env.local -f .env -- npm test

dotenv 选项放在命令之前;--分隔符可选,命令后的所有参数透传给目标命令(tests/test-cli.js 验证了空格、引号、空参数与 shell 元字符的透传保真)。

CLI 选项表

选项说明
-f, --file <paths>加载一个或多个文件。可重复标志或用逗号分隔。默认.env。
-q, --quiet抑制 “injected environment variables” 提示消息。
--debug开启 debug 日志。
--override覆盖已有环境变量。加载多文件时最后一个值胜出。
--fast使用更快的字符扫描解析器。
-h, --help显示帮助。也可用dotenv --help。

未设--override时,已有环境变量优先,多文件间先加载的值胜出;设了--override则文件值覆盖已有变量,且后加载的文件覆盖先加载的。

环境变量默认值表

可用环境变量设置 CLI 默认值(CLI 标志优先于它们):

变量默认说明
DOTENV_PATH.env要加载的文件路径。
DOTENV_ENCODINGutf8文件编码。
DOTENV_QUIETfalse抑制注入提示消息。
DOTENV_DEBUGfalse开启 debug 日志。
DOTENV_OVERRIDEfalse覆盖已有环境变量。
DOTENV_FASTfalse使用更快的解析器。

旧的DOTENV_CONFIG_*名称在对应DOTENV_*未设置时作为回退。布尔类设置中,false、0、no、off与空值均视为关闭。

源码级原理

双解析器:正则解析与 fast 字符扫描

lib/main.js 内置两条解析路径(tests/test-parse-fast.js 专门验证 fast 模式):

  • 默认parseRegex(lib/main.js):基于LINE正则逐行匹配KEY=VAL,处理export前缀、引号脱壳、双引号\n/\r展开。
  • parseFast(lib/main.js):手写字符扫描器(源自 PR #1010),用KEY_CHAR查找表(A-Za-z0-9._-)扫描键名,逐字符判断注释、export前缀、引号配对,无正则回溯开销,官方标注约 2 倍速,通过{ fast: true }、CLI--fast或DOTENV_FAST=true开启。两套解析器对同一输入产生一致结果,只是性能路径不同。

配置合并链路:config-options.js

lib/config-options.js 是配置层的枢纽:

  • parseBoolean:把字符串'false' | '0' | 'no' | 'off' | ''归一为false,其余字符串为true,非字符串走Boolean()。
  • optionsFromEnv:按ENCODING / PATH / QUIET / DEBUG / OVERRIDE / FAST顺序读取DOTENV_*,缺失时回退DOTENV_CONFIG_*;其中ENCODING与PATH保留字符串,其余经parseBoolean转布尔。

configDotenv通过{ ...optionsFromEnv(), ...options }合并:环境变量提供默认值,显式传入的 options 覆盖前者(tests/test-config-options.js 验证了别名优先级与显式 false/空值不回退的行为)。

CLI 信号与进程管理

cli.js 在派生子进程后做了完整的信号管理:

  • 交互式终端(stdin.isTTY)下首次 Ctrl-C 交给前台子进程处理,第二次转发SIGTERM、第三次SIGKILL;
  • 非交互(CI/服务)场景用独立进程组,SIGINT/SIGTERM/SIGHUP/SIGQUIT均转发到整个进程组,避免遗留孙进程;
  • 子进程退出码原样透传(process.exit(exitCode)),被信号终止时向自身重发信号保持退出语义。

Windows 下由 lib/spawn-command.js 负责cmd.exe参数转义(quoteWindowsArgument、protectShellToken)、PATHEXT可执行文件解析与.bat/.cmd/npm shim 的双重转义,相关行为在 tests/test-cli.js 的 Windows 用例中覆盖。

类型声明

lib/main.d.ts 为 TypeScript 用户提供了完整类型:DotenvConfigOptions(path: string | string[] | URL、encoding、quiet、debug、override、fast、processEnv)、DotenvConfigOutput({ error?, parsed? })以及parse/populate的签名,类型测试见 tests/types/test.ts。

相关工具

  • dotenv-expand:展开.env中的变量引用。
  • dotenv-vscode:在 VS Code 中隐藏/管理密钥。
  • dotenvx:为.env提供加密、多环境与同步能力(.env.vault支持在 v18.0.0 已移除,见 CHANGELOG.md)。

完整变更历史见 CHANGELOG.md:v18.0.0 引入 CLI 与 fast 解析器,v18.0.3 修复.env内设置DOTENV_QUIET的问题,v18.0.4 让import dotenv/config默认静默(quiet: true)。

小结

从.env文件的创建、config()加载、CLIdotenv run注入,到parse/populate独立引擎与双解析器原理,dotenv 以极小的 API 面覆盖了环境变量管理的完整闭环。实际使用建议:本地/开发用.env,生产单独维护.env.production并配合 dotenvx 加密;遇到加载异常优先开启debug: true定位;追求启动性能时启用fast解析器。仓库内 tests/ 目录保留了全部行为契约,可作为深入研读的起点。

  • 开发工具
  • 后端

【免费下载链接】dotenv

Loads environment variables from .env for nodejs projects.

项目地址:https://gitcode.com/gh_mirrors/do/dotenv
点击查看免费下载
上一篇:d2s-editor:5分钟掌握暗黑破坏神2存档编辑的完整指南
下一篇:5分钟快速上手:用Markdown Viewer打造极致浏览器阅读体验

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

返回列表