dotenv源碼解讀從.env文件中讀取環(huán)境變量
引言
dotenv從.env文件中讀取環(huán)境變量,然后將其添加到process.env中。這是一個非常簡單的庫,但是它在開發(fā)中非常有用,因為它允許你在.env文件中存儲敏感信息,而不是將其存儲在代碼中。
現(xiàn)在很多庫都支持.env文件,例如create-react-app,vue-cli,next.js等。
使用
根據(jù)README,dotenv只有兩個方法:
config:讀取.env文件并將其添加到process.env中。parse:解析一段包含環(huán)境變量的字符串或Buffer,并返回一個對象。
const dotenv = require('dotenv')
// 讀取.env文件并將其添加到process.env中
dotenv.config()
// 解析一段包含環(huán)境變量的字符串或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分別表示是否開啟調試模式和是否覆蓋已有的環(huán)境變量。
if (options) {
if (options.path != null) {
dotenvPath = _resolveHome(options.path)
}
if (options.encoding != null) {
encoding = options.encoding
}
}
然后判斷了一下options是否存在,如果存在的話,就會根據(jù)options的值來修改dotenvPath和encoding的值。
const parsed = DotenvModule.parse(fs.readFileSync(dotenvPath, { encoding }))
然后是調用parse方法來解析.env文件,parse方法的實現(xiàn)在下面會講到。
這里是只用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中已經(jīng)存在了該環(huán)境變量,那么就會根據(jù)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轉換為字符串,然后將換行符統(tǒng)一為\n。
接著就是核心,通過正則表達式的特性通過while循環(huán)來匹配每一行。
這個正則著實有點復雜,我是正則渣渣,可以在regex101查看一下。

這個正則上面標出了三種顏色,和下面的匹配的值的顏色相互對應,然后右邊會展示匹配的值。
這里我不過多解讀,可以自己去看一下,然后輸入不同的值對比一下結果。
通過上面的截圖可以看到匹配會捕獲兩個值,第一個是環(huán)境變量的名稱,第二個是環(huán)境變量的值。
然后對值進行處理,首先去掉首尾的空格,然后通過正則去掉首尾的引號,最后再將轉義的換行符轉換還原。
經(jīng)過上面的處理,就可以將每一行的環(huán)境變量添加到obj對象中了,最后返回obj對象。
總結
dotenv真的是非常驚艷的一個庫,沒有任何依賴,只有一個文件,而且功能也非常強大。
如果你將README中的內容全部看完,你還會發(fā)現(xiàn)dotenv還有很多其他的功能,都是一些很實用的功能,并且還有很多引導你如何使用的例子。
以上就是dotenv源碼解讀從.env文件中讀取環(huán)境變量的詳細內容,更多關于dotenv .env文件讀取環(huán)境變量的資料請關注腳本之家其它相關文章!
相關文章
總結JavaScript中BigIn函數(shù)常見的屬性
本文基于JavaScript基礎,介紹了 BigInt 函數(shù),常見的屬性,通過 BigInt 函數(shù)進行數(shù)字運算符的比較。布爾運算等等,通過按案例的分析進行詳細的講解,需要的朋友可以參考一下2021-10-10

