.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 dotenvconst 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(截止至本文写稿时间),源码简单易懂,建议自己动手看看。