.env 的作用
💡 加载 .env
文件中的变量到 process.env
中
.env
文件是用来自定义配置的一个简单方法。我们可以将一些不能在代码中存储的 敏感信息 / 账号数据 从代码中剥离出来,作为环境变量存储在 .env
文件中。
.env 的使用方法
.env
文件通常不包含在版本控制内,因为它可能包含敏感的 API Key 或 密码。所有需要环境变量定义的项目都可以创建一个 .env.example
文件。项目合作开发者可以独立的复制 .env.example
并重命名为 .env
,同时将其修改为正确的本地环境配置:API Key 或者其他必要的值。 在这个使用方法中 .env
文件应该添加到 .gitignore
文件中保证永远不会出现在 git 中。
-
在根目录下添加
.env
文件,eg:DB_HOST=127.0.0.1 DB_NAME=timeseriesmonitor DB_PORT=5432 DB_USER=tsm DB_UNSECURE=true
-
引入
dotenv
:npm install dotenv
const dotenv = require('dotenv'); dotenv.config('./env'); // .env 中的环境变量被加载到 process.env 中 console.log(process.env);
-
打印log如下:
{ ... DB_HOST: '127.0.0.1', DB_NAME: 'timeseriesmonitor', DB_PORT: '5432', DB_UNSECURE: 'true', DB_USER: 'tsm', ... }
.env 源码解读
核心代码逻辑在 lib/main.js 中,可以看到刚开始先初始化了几个正则表达式
正则表达式
const RE_INI_KEY_VAL = /^\s*([\w.-]+)\s*=\s*(.*)?\s*$/
const RE_NEWLINES = /\\n/g
const NEWLINES_MATCH = /\r\n|\n|\r/
我们可以通过 regexr.com 来交互式查看表达式各部分的含义:
-
const RE_INI_KEY_VAL = /^\s*([\w.-]+)\s*=\s*(.*)?\s*$/
匹配.env
文件中的环境变量,如DB_HOST=127.0.0.1
,注意到表达式中有两个部分被()
包起来了,这是为了后续正则匹配的时候方便提取出匹配的字符串,即环境变量key
,value
的值 -
const RE_NEWLINES = /\\n/g
匹配\n
字符串,\\
:前面的\
将后面的\
转义掉了,所以这里匹配的是\n
字符串,而不是换行符。 -
const NEWLINES_MATCH = /\r\n|\n|\r/
匹配换行符:三种换行符是为了兼容各操作系统,不同操作系统换行符有所不同:\n
: Unix系统,\r\n
: Windows系统,\r
: Mac系统- 回车
\r
本义是光标重新回到本行开头,r 的英文 return,控制字符为 CR,即 Carriage Return; - 换行
\n
本义是光标往下一行(不一定到下一行行首),n 的英文 newline,控制字符为 LF,即 Line Feed;
- 回车
parse
函数
💡 将 .env
中的字符串转换成 Object
核心逻辑简化如下:
function parse(src) {
const obj = {}
// 用 NEWLINES_MATCH 分割每行表达式,再 forEach 依次处理
src.split(NEWLINES_MATCH).forEach(function (line, idx) {
// 用 RE_INI_KEY_VAL 匹配 'KEY=VAL' 中的 'KEY' 和 'VAL'
const keyValueArr = line.match(RE_INI_KEY_VAL)
// matched?
if (keyValueArr != null) {
const key = keyValueArr[1]
const val = (keyValueArr[2] || '').trim()
obj[key] = val
}
})
return obj
}
其关键是用了 match
方法匹配 'KEY=VAL'
中的 'KEY'
和 'VAL'
。match
方法若匹配到了 line
中的键值对则会返回一个数组,这个数组的第一项是整个正则表达式所匹配的字符串,后面会接表达式中用 ()
包围起来的正则表达式匹配的字符串。所以 key = keyValueArr[1]
,val = keyValueArr[2]
。
config
函数
💡 将 .env
中的环境变量加载到 process.env
中
核心逻辑简化如下:
function config(options) {
let dotenvPath = path.resolve(process.cwd(), '.env')
const parsed = parse(fs.readFileSync(dotenvPath, { encoding: 'utf8' }))
Object.keys(parsed).forEach(function (key) {
if (!Object.prototype.hasOwnProperty.call(process.env, key)) {
process.env[key] = parsed[key]
}
})
return { parsed }
}
config
函数分三步:
- 获取
.env
文件的路径并读取文件 - 将文件的字符串传入
parse
函数中解析成 Object - 遍历 Object 将其加载到
process.env
中
注意代码中使用 Object.prototype.hasOwnProperty.call(process.env, key)
判断 key
是否已经存在于 process.env
中,若存在,则不进行覆盖。使用 Object.prototype.hasOwnProperty
是避免原型链查找,只判断 key
是否存在于 process.env
中,而不是其原型链上,这样做可以省去原型链查找的耗时。
总结
dotenv
源码非常简短只有 118 行,但其有 14.5k star(截止至本文写稿时间),源码简单易懂,建议自己动手看看。