dotenv
Dotenv 是一个零依赖模块,它将 .env
文件中的环境变量加载到 process.env
中。将配置与代码分开存储在环境中基于 十二要素应用程序 方法论。
🌱 安装
# 本地安装(推荐)
npm install dotenv --save
或者使用 yarn 安装?yarn add dotenv
🏗️ 使用
在项目根目录创建一个 .env
文件(如果使用像 apps/backend/app.js
这样的单体仓库结构,请将其放在运行 app.js
进程的文件夹根目录):
S3_BUCKET="YOURS3BUCKET"
SECRET_KEY="YOURSECRETKEYGOESHERE"
在应用程序中尽早导入和配置 dotenv:
require('dotenv').config()
console.log(process.env) // 确认工作正常后删除此行
.. 或者使用 ES6?
import 'dotenv/config'
就是这样。process.env
现在包含了你在 .env
文件中定义的键和值:
require('dotenv').config()
...
s3.getBucketCors({Bucket: process.env.S3_BUCKET}, function(err, data) {})
多行值
如果你需要多行变量,例如私钥,现在支持带换行符的多行值(>= 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"
注释
可以在单独的行或内联添加注释:
# 这是一条注释
SECRET_KEY=YOURSECRETKEYGOESHERE # 注释
SECRET_HASH="something-with-a-#-hash"
注释从 #
开始,所以如果你的值包含 #
,请用引号包裹它。这是 >= v15.0.0
及以后版本的一个重大变化。
解析
解析包含环境变量的文件内容的引擎可供使用。它接受一个字符串或 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
而不是预加载。我现在正在使用(并推荐)这种方式。它具有相同的目的(你不需要 require 和加载 dotenv),增加了更好的调试功能,并且适用于任何语言、框架或平台。 – motdotla
你可以使用 --require
(-r
) 命令行选项 来预加载 dotenv。这样做,你就不需要在应用程序代码中 require 和加载 dotenv。
$ node -r dotenv/config your_script.js
以下配置选项支持作为命令行参数,格式为 dotenv_config_<option>=value
$ node -r dotenv/config your_script.js dotenv_config_path=/custom/path/to/.env dotenv_config_debug=true
此外,你可以使用环境变量来设置配置选项。命令行参数将优先于这些。
$ DOTENV_CONFIG_<OPTION>=value node -r dotenv/config your_script.js
$ DOTENV_CONFIG_ENCODING=latin1 DOTENV_CONFIG_DEBUG=true node -r dotenv/config your_script.js dotenv_config_path=/custom/path/to/.env
变量扩展
你需要在一个变量中添加另一个变量的值?使用 dotenv-expand。
命令替换
使用 dotenvx 来进行命令替换。
在 .env 文件中的某个变量中添加命令的输出。
# .env
DATABASE_URL="postgres://$(whoami)@localhost/my_database"
// index.js
console.log('DATABASE_URL', process.env.DATABASE_URL)
$ dotenvx run --debug -- node index.js
[dotenvx@0.14.1] injecting env (1) from .env
DATABASE_URL postgres://yourusername@localhost/my_database
同步
你需要在机器、环境或团队成员之间保持 .env
文件同步?使用 dotenvx 加密你的 .env
文件,并安全地将它们包含在源代码控制中。这仍然遵循十二要素应用程序规则,通过生成与代码分开的解密密钥。
多环境
使用 dotenvx 生成 .env.ci
、.env.production
文件等。
部署
你需要以云无关的方式部署你的秘密?使用 dotenvx 生成一个私有解密密钥,并在生产服务器上设置。
🌴 管理多环境
使用 dotenvx
在本地运行任何环境。创建一个 .env.ENVIRONMENT
文件,并使用 --env-file
加载它。简单直接,但灵活。
$ echo "HELLO=production" > .env.production
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run --env-file=.env.production -- node index.js
Hello production
> ^^
或者使用多个 .env 文件
$ echo "HELLO=local" > .env.local
$ echo "HELLO=World" > .env
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run --env-file=.env.local --env-file=.env -- node index.js
Hello local
🚀 部署
使用 dotenvx。
只需一个命令即可为你的 .env
文件添加加密。传递 --encrypt
标志。
$ dotenvx set HELLO Production --encrypt -f .env.production
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ DOTENV_PRIVATE_KEY_PRODUCTION="<.env.production 私钥>" dotenvx run -- node index.js
[dotenvx] injecting env (2) from .env.production
Hello Production
📚 示例
查看使用dotenv与各种框架、语言和配置的示例。
- nodejs
- nodejs (调试开启)
- nodejs (覆盖开启)
- nodejs (processEnv覆盖)
- esm
- esm (预加载)
- typescript
- typescript解析
- typescript配置
- webpack
- webpack (插件)
- react
- react (typescript)
- express
- nestjs
- fastify
📖 文档
Dotenv暴露了四个函数:
config
parse
populate
decrypt
Config
config
会读取你的.env
文件,解析内容,将其分配给process.env
,并返回一个包含已加载内容的parsed
键或失败时的error
键的对象。
const result = dotenv.config()
if (result.error) {
throw result.error
}
console.log(result.parsed)
你还可以向config
传递选项。
选项
path
默认值:path.resolve(process.cwd(), '.env')
如果包含环境变量的文件位于其他位置,可以指定自定义路径。
require('dotenv').config({ path: '/custom/path/to/.env' })
默认情况下,config
会在当前工作目录中查找名为.env的文件。
可以将多个文件作为数组传入,它们将按顺序解析并与process.env
(或设置了option.processEnv
时的对应对象)合并。变量的第一个设置值将生效,除非设置了options.override
标志,在这种情况下最后设置的值将生效。如果process.env
中已存在某个值且未设置options.override
标志,则该值不会被更改。
require('dotenv').config({ path: ['.env.local', '.env'] })
encoding
默认值:utf8
指定包含环境变量的文件的编码。
require('dotenv').config({ encoding: 'latin1' })
debug
默认值:false
开启日志记录以帮助调试某些键或值未按预期设置的原因。
require('dotenv').config({ debug: process.env.DEBUG })
override
默认值:false
用.env文件中的值覆盖机器上已经设置的任何环境变量。如果在option.path
中提供了多个文件,覆盖也将在每个文件与下一个文件合并时使用。如果未设置override
,则第一个值生效。设置了override
后,最后一个值生效。
require('dotenv').config({ override: true })
processEnv
默认值:process.env
指定一个对象来写入你的密钥。默认为process.env
环境变量。
const myObject = {}
require('dotenv').config({ processEnv: myObject })
console.log(myObject) // .env中的值
console.log(process.env) // 这没有被更改或写入
Parse
解析包含环境变量的文件内容的引擎可供使用。它接受一个字符串或Buffer,并返回一个包含解析后的键和值的对象。
const dotenv = require('dotenv')
const buf = Buffer.from('BASIC=basic')
const config = dotenv.parse(buf) // 将返回一个对象
console.log(typeof config, config) // object { BASIC : 'basic' }
选项
debug
默认值:false
开启日志记录以帮助调试某些键或值未按预期设置的原因。
const dotenv = require('dotenv')
const buf = Buffer.from('hello world')
const opt = { debug: true }
const config = dotenv.parse(buf, opt)
// 期望看到一条调试消息,因为缓冲区不是KEY=VAL格式
Populate
将.env文件内容填充到process.env
的引擎可供使用。它接受一个目标、一个源和选项。这对于想要提供自己对象的高级用户很有用。
例如,自定义源:
const dotenv = require('dotenv')
const parsed = { HELLO: 'world' }
dotenv.populate(process.env, parsed)
console.log(process.env.HELLO) // world
例如,自定义源和目标:
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' }
选项
Debug
默认值:false
开启日志记录以帮助调试某些键或值未按预期填充的原因。
override
默认值:false
覆盖任何已经设置的环境变量。
❓ 常见问题
为什么.env
文件没有成功加载我的环境变量?
很可能你的.env
文件位置不正确。参见这个Stack Overflow问题。
打开调试模式再试一次..
require('dotenv').config({ debug: true })
你会在控制台看到一条有用的错误输出。
我应该提交我的.env
文件吗?
不。我们强烈建议不要将你的.env
文件提交到版本控制。它应该只包含特定环境的值,如数据库密码或API密钥。你的生产数据库应该有一个与开发数据库不同的密码。
我应该有多个.env
文件吗?
我们建议为每个环境创建一个.env
文件。对本地/开发使用.env
,对生产使用.env.production
,以此类推。这仍然遵循十二因素原则,因为每个文件都单独归属于其自己的环境。避免使用以某种方式继承的自定义设置(例如,.env.production
从.env
继承值)。如果需要,最好在每个.env.environment
文件中复制值。
在十二因素应用中,环境变量是细粒度的控制,每个变量完全独立于其他环境变量。它们从不被归类为"环境",而是为每次部署独立管理。这是一个随着应用自然扩展到更多部署而平滑扩展的模型。
– 十二因素应用
解析引擎遵循哪些规则?
解析引擎目前支持以下规则:
BASIC=basic
变为{BASIC: 'basic'}
- 空行被跳过
- 以
#
开头的行被视为注释 #
标记注释的开始(除非值被引号包裹)- 空值变为空字符串(
EMPTY=
变为{EMPTY: ''}
) - 内部引号保持不变(考虑JSON)(
JSON={"foo": "bar"}
变为{JSON:"{\"foo\": \"bar\"}"
) - 未加引号的值两端的空白会被移除(参见
trim
)(FOO= some value
变为{FOO: 'some value'}
) - 单引号和双引号值会被转义(
SINGLE_QUOTE='quoted'
变为{SINGLE_QUOTE: "quoted"}
) - 单引号和双引号值保留两端的空白(
FOO=" some value "
变为{FOO: ' some value '}
) - 双引号值展开新行(
MULTILINE="new\nline"
变为
{MULTILINE: 'new
line'}
- 支持反引号(
BACKTICK_KEY=`This has 'single' and "double" quotes inside of it.`
)
已经设置的环境变量会发生什么?
默认情况下,我们永远不会修改任何已经设置的环境变量。特别是,如果你的.env
文件中有一个变量与已存在的环境变量冲突,那么该变量将被跳过。
如果你想覆盖process.env
,请使用override
选项。
require('dotenv').config({ override: true })
为什么我的环境变量在React中不显示?
你的React代码在Webpack中运行,其中fs
模块甚至process
全局变量本身都无法开箱即用。process.env
只能通过Webpack配置注入。
如果你使用的是通过create-react-app
分发的react-scripts
,它内置了dotenv,但有一个特点。在你的环境变量前加上REACT_APP_
前缀。有关更多详细信息,请参阅这个Stack Overflow问题。
如果你使用其他框架(如Next.js、Gatsby等),你需要查阅它们的文档,了解如何将环境变量注入到客户端。
我可以为dotenv自定义/编写插件吗?
可以!dotenv.config()
返回一个表示已解析.env
文件的对象。这为你提供了继续在process.env
上设置值所需的一切。例如:
const dotenv = require('dotenv')
const variableExpansion = require('dotenv-expand')
const myEnv = dotenv.config()
variableExpansion(myEnv)
如何在import
中使用dotenv?
简单来说..
// index.mjs (ESM)
import 'dotenv/config' // 参见 https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import
import express from 'express'
一些背景..
当你运行包含
import
声明的模块时,它导入的模块会先被加载,然后每个模块的主体会按照依赖图的深度优先遍历顺序执行,通过跳过已执行的内容来避免循环。– ES6深入:模块
用简单的话来说,这意味着你可能会认为以下代码可以正常工作,但实际上并不行。
errorReporter.mjs
:
import { Client } from 'best-error-reporting-service'
export default new Client(process.env.API_KEY)
index.mjs
:
// 注意:这是错误的,不会生效
import * as dotenv from 'dotenv'
dotenv.config()
import errorReporter from './errorReporter.mjs'
errorReporter.report(new Error('documented example'))
process.env.API_KEY
将会是空白的。
相反,index.mjs
应该这样写:
import 'dotenv/config'
import errorReporter from './errorReporter.mjs'
errorReporter.report(new Error('documented example'))
这样理解了吗?这有点不直观,但这就是ES6模块导入的工作方式。这里有一个这种陷阱的实际例子。
还有两种替代方法:
- 预加载dotenv:
node --require dotenv/config index.js
(注意:使用这种方法不需要import
dotenv) - 创建一个单独的文件,首先执行
config
,如这个关于#133的评论中所述
为什么我会收到Module not found: Error: Can't resolve 'crypto|os|path'
这个错误?
你在前端使用dotenv时没有包含polyfill。Webpack 5以下版本曾为你包含这些。请执行以下操作:
npm install node-polyfill-webpack-plugin
将你的webpack.config.js
配置为类似以下内容:
require('dotenv').config()
const path = require('path');
const webpack = require('webpack')
const NodePolyfillPlugin = require('node-polyfill-webpack-plugin')
module.exports = {
mode: 'development',
entry: './src/index.ts',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
plugins: [
new NodePolyfillPlugin(),
new webpack.DefinePlugin({
'process.env': {
HELLO: JSON.stringify(process.env.HELLO)
}
}),
]
};
或者,直接使用dotenv-webpack,它会在后台为你处理这些事情。
变量扩展怎么办?
如何同步和保护.env文件?
使用dotenvx
如果我不小心将.env
文件提交到代码中怎么办?
删除它,删除git历史,然后安装git pre-commit钩子以防止这种情况再次发生。
brew install dotenvx/brew/dotenvx
dotenvx precommit --install
如何防止将.env
文件提交到Docker构建中?
# Dockerfile
...
RUN curl -fsS https://dotenvx.sh/ | sh
...
RUN dotenvx prebuild
CMD ["dotenvx", "run", "--", "node", "index.js"]
贡献指南
更新日志
请参阅CHANGELOG.md
谁在使用dotenv?
扩展它的项目通常在npm上使用"dotenv"关键词。