引言
dotenv
从.env
文件中读取环境变量,然后将其添加到process.env
中。这是一个非常简单的库,但是它在开发中非常有用,因为它允许你在.env
文件中存储敏感信息,而不是将其存储在代码中。
现在很多库都支持.env
文件,例如create-react-app
,vue-cli
,next.js
等。
源码地址:github.com/motdotla/do…
使用
根据README
,dotenv
只有两个方法:
config
:读取.env
文件并将其添加到process.env
中。parse
:解析一段包含环境变量的字符串或Buffer
,并返回一个对象。
const dotenv = require('dotenv')
// 读取.env文件并将其添加到process.env中
dotenv.config()
// 解析一段包含环境变量的字符串或Buffer,返回一个对象
const config1 = dotenv.parse('FOO=bar\nBAR=foo')
console.log(config1) // { FOO: 'bar', BAR: 'foo' }
const buffer = Buffer.from('FOO=bar\nBAR=foo')
const config2 = dotenv.parse(buffer)
console.log(config2) // { FOO: 'bar', BAR: 'foo' }
可以看到,dotenv
的使用非常简单,通常我们只需要调用config
方法即可。
还有一种方法是预加载,直接通过node -r dotenv/config
来运行脚本,这样就不需要在脚本中引入dotenv
了。
源码
源码在lib/main.js
中,先来看一下全部的代码:
const fs = require('fs')
const path = require('path')
const os = require('os')
const packageJson = require('../package.json')
const version = packageJson.version
const LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\'|[^'])*'|\s*"(?:\"|[^"])*"|\s*`(?:\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg
// Parser src into an Object
function parse (src) {
const obj = {}
// Convert buffer to string
let lines = src.toString()
// Convert line breaks to same format
lines = lines.replace(/\r\n?/mg, '\n')
let match
while ((match = LINE.exec(lines)) != null) {
const key = match[1]
// Default undefined or null to empty string
let value = (match[2] || '')
// Remove whitespace
value = value.trim()
// Check if double quoted
const maybeQuote = value[0]
// Remove surrounding quotes
value = value.replace(/^(['"`])([\s\S]*)\1$/mg, '$2')
// Expand newlines if double quoted
if (maybeQuote === '"') {
value = value.replace(/\n/g, '\n')
value = value.replace(/\r/g, '\r')
}
// Add to object
obj[key] = value
}
return obj
}
function _log (message) {
console.log(`[dotenv@${version}][DEBUG] ${message}`)
}
function _resolveHome (envPath) {
return envPath[0] === '~' ? path.join(os.homedir(), envPath.slice(1)) : envPath
}
// Populates process.env from .env file
function config (options) {
let dotenvPath = path.resolve(process.cwd(), '.env')
let encoding = 'utf8'
const debug = Boolean(options && options.debug)
const override = Boolean(options && options.override)
if (options) {
if (options.path != null) {
dotenvPath = _resolveHome(options.path)
}
if (options.encoding != null) {
encoding = options.encoding
}
}
try {
// Specifying an encoding returns a string instead of a buffer
const parsed = DotenvModule.parse(fs.readFileSync(dotenvPath, { encoding }))
Object.keys(parsed).forEach(function (key) {
if (!Object.prototype.hasOwnProperty.call(process.env, key)) {
process.env[key] = parsed[key]
} else {
if (override === true) {
process.env[key] = parsed[key]
}
if (debug) {
if (override === true) {
_log(`"${key}" is already defined in `process.env` and WAS overwritten`)
} else {
_log(`"${key}" is already defined in `process.env` and was NOT overwritten`)
}
}
}
})
return { parsed }
} catch (e) {
if (debug) {
_log(`Failed to load ${dotenvPath} ${e.message}`)
}
return { error: e }
}
}
const DotenvModule = {
config,
parse
}
module.exports.config = DotenvModule.config
module.exports.parse = DotenvModule.parse
module.exports = DotenvModule
可以看到最后导出的是一个对象,包含了config
和parse
两个方法。
config
config
方法的作用是读取.env
文件,并将其添加到process.env
中。
function config (options) {
let dotenvPath = path.resolve(process.cwd(), '.env')
let encoding = 'utf8'
const debug = Boolean(options && options.debug)
const override = Boolean(options && options.override)
}
首先定义了一些变量:
dotenvPath
是.env
文件的路径encoding
是文件的编码debug
和override
分别表示是否开启调试模式和是否覆盖已有的环境变量。
if (options) {
if (options.path != null) {
dotenvPath = _resolveHome(options.path)
}
if (options.encoding != null) {
encoding = options.encoding
}
}
然后判断了一下options
是否存在,如果存在的话,就会根据options
的值来修改dotenvPath
和encoding
的值。
const parsed = DotenvModule.parse(fs.readFileSync(dotenvPath, { encoding }))
然后是调用parse
方法来解析.env
文件,parse
方法的实现在下面会讲到。
这里是只用fs.readFileSync
来读取.env
文件,然后将其传入parse
方法中,接着往下:
Object.keys(parsed).forEach(function (key) {
if (!Object.prototype.hasOwnProperty.call(process.env, key)) {
process.env[key] = parsed[key]
} else {
if (override === true) {
process.env[key] = parsed[key]
}
if (debug) {
if (override === true) {
_log(`"${key}" is already defined in `process.env` and WAS overwritten`)
} else {
_log(`"${key}" is already defined in `process.env` and was NOT overwritten`)
}
}
}
})
这里是遍历parsed
对象,然后将其添加到process.env
中,如果process.env
中已经存在了该环境变量,那么就会根据override
的值来决定是否覆盖。
debug
的值表示是否开启调试模式,如果开启了调试模式,那么就会打印一些日志。
最后就是直接返回parsed
对象。
parse
parse
方法的作用是解析.env
文件,将其转换为一个对象。
const LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\'|[^'])*'|\s*"(?:\"|[^"])*"|\s*`(?:\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg
function parse (src) {
const obj = {}
// Convert buffer to string
let lines = src.toString()
// Convert line breaks to same format
lines = lines.replace(/\r\n?/mg, '\n')
let match
while ((match = LINE.exec(lines)) != null) {
const key = match[1]
// Default undefined or null to empty string
let value = (match[2] || '')
// Remove whitespace
value = value.trim()
// Check if double quoted
const maybeQuote = value[0]
// Remove surrounding quotes
value = value.replace(/^(['"`])([\s\S]*)\1$/mg, '$2')
// Expand newlines if double quoted
if (maybeQuote === '"') {
value = value.replace(/\n/g, '\n')
value = value.replace(/\r/g, '\r')
}
// Add to object
obj[key] = value
}
return obj
}
首先定义了一个正则表达式LINE
,用来匹配.env
文件中的每一行。
然后是将src
转换为字符串,然后将换行符统一为\n
。
接着就是核心,通过正则表达式的特性通过while
循环来匹配每一行。
这个正则着实有点复杂,我是正则渣渣,可以在regex101查看一下。
这个正则上面标出了三种颜色,和下面的匹配的值的颜色相互对应,然后右边会展示匹配的值。
这里我不过多解读,可以自己去看一下,然后输入不同的值对比一下结果。
通过上面的截图可以看到匹配会捕获两个值,第一个是环境变量的名称,第二个是环境变量的值。
然后对值进行处理,首先去掉首尾的空格,然后通过正则去掉首尾的引号,最后再将转义的换行符转换还原。
经过上面的处理,就可以将每一行的环境变量添加到obj
对象中了,最后返回obj
对象。
总结
dotenv
真的是非常惊艳的一个库,没有任何依赖,只有一个文件,而且功能也非常强大。
如果你将README
中的内容全部看完,你还会发现dotenv
还有很多其他的功能,都是一些很实用的功能,并且还有很多引导你如何使用的例子。
以上就是dotenv源码解读从.env文件中读取环境变量的详细内容,更多关于dotenv .env文件读取环境变量的资料请关注编程网其它相关文章!