首頁 > 軟體

dotenv原始碼解讀從.env檔案中讀取環境變數

2022-12-27 14:01:39

引言

dotenv.env檔案中讀取環境變數,然後將其新增到process.env中。這是一個非常簡單的庫,但是它在開發中非常有用,因為它允許你在.env檔案中儲存敏感資訊,而不是將其儲存在程式碼中。

現在很多庫都支援.env檔案,例如create-react-appvue-clinext.js等。

原始碼地址:github.com/motdotla/do…

使用

根據READMEdotenv只有兩個方法:

  • config:讀取.env檔案並將其新增到process.env中。
  • parse:解析一段包含環境變數的字串或Buffer,並返回一個物件。
const dotenv = require('dotenv')
// 讀取.env檔案並將其新增到process.env中
dotenv.config()
// 解析一段包含環境變數的字串或Buffer,返回一個物件
const config1 = dotenv.parse('FOO=barnBAR=foo')
console.log(config1) // { FOO: 'bar', BAR: 'foo' }
const buffer = Buffer.from('FOO=barnBAR=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*(?:exports+)?([w.-]+)(?:s*=s*?|:s+?)(s*'(?:'|[^'])*'|s*"(?:"|[^"])*"|s*`(?:`|[^`])*`|[^#rn]+)?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(/rn?/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(/^(['"`])([sS]*)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

可以看到最後匯出的是一個物件,包含了configparse兩個方法。

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是檔案的編碼
  • debugoverride分別表示是否開啟偵錯模式和是否覆蓋已有的環境變數。
if (options) {
  if (options.path != null) {
    dotenvPath = _resolveHome(options.path)
  }
  if (options.encoding != null) {
    encoding = options.encoding
  }
}

然後判斷了一下options是否存在,如果存在的話,就會根據options的值來修改dotenvPathencoding的值。

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*(?:exports+)?([w.-]+)(?:s*=s*?|:s+?)(s*'(?:'|[^'])*'|s*"(?:"|[^"])*"|s*`(?:`|[^`])*`|[^#rn]+)?s*(?:#.*)?(?:$|$)/mg
function parse (src) {
  const obj = {}
  // Convert buffer to string
  let lines = src.toString()
  // Convert line breaks to same format
  lines = lines.replace(/rn?/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(/^(['"`])([sS]*)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檔案讀取環境變數的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com