專案中常用的 .env 檔案原理原始碼分析

roc_guo發表於2023-01-30

dotenv 是一個用於載入環境變數的庫,在 Node.js 應用程式中可以使用它來簡化對環境變數的訪問。在日常開發中起到了很重要的作用。

如何使用

使用 dotenv 庫,可以在應用程式中建立一個名為 .env 的檔案,並在該檔案中儲存環境變數。然後,可以使用 dotenv 庫將這些變數載入到 Node.js 應用程式中。

例如,您可以在 .env 檔案中儲存以下內容:

DB_HOST=localhost
DB_USERNAME=user
DB_PASSWORD=password

使用以下程式碼將這些變數載入到應用程式中:

require('dotenv').config();
const dbHost = process.env.DB_HOST;
const dbUsername = process.env.DB_USERNAME;
const dbPassword = process.env.DB_PASSWORD;
原始碼解析

閱讀原始碼之前,可以猜測 dotenv 所做的工作有如下幾點。

  • 讀取 .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)
  if (options) {
    if (options.path != null) {
      dotenvPath = _resolveHome(options.path)
    }
    if (options.encoding != null) {
      encoding = options.encoding
    }
  }
}

程式碼中定義了一個變數 dotenvPath,並將其賦值為使用 path.resolve 函式處理後的路徑。

path.resolve 函式會從右到左依次遍歷引數,並返回一個絕對路徑。函式的第一個引數是 process.cwd,它返回 Node.js 程式的當前工作目錄。第二個引數是字串 '.env',它表示要在當前工作目錄中查詢的檔名。

之後會進行一些引數的判斷,如果引數中有path這個變數,則使用_resolveHome函式處理:

function _resolveHome (envPath) {
  return envPath[0] === '~' ? path.join(os.homedir(), envPath.slice(1)) : envPath
}

os.homedir 函式返回當前使用者的主目錄路徑。

_resolveHome 函式可用於將以波浪號開頭的路徑解析為主目錄的實際路徑。例如,如果 envPath 等於 '~/documents/file.txt',則函式將返回 '/home/user/documents/file.txt'(在基於 Unix 的系統上)或 'C:\Users\user\documents\file.txt'(在 Windows 上)。

解析檔案
// 使用 `fs.readFileSync` 函式以指定的編碼方式從檔案系統中讀取檔案內容
const parsed = DotenvModule.parse(fs.readFileSync(dotenvPath, { encoding }))
// 解析檔案
function parse (src) {
  const obj = {}
  // 轉為string型別
  let lines = src.toString()
  // 將換行符轉換為相同的格式
  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 來匹配字串 lines 中的內容。

const LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg

這個正規表示式的目的是匹配類似於環境變數的行。它可以匹配以下格式的行:

VARNAME=value
VARNAME: value
export VARNAME=value
export VARNAME: value

最後會返回一個包含所有變數的物件。

賦值操作
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 }
}

拿到解析後的物件,使用 Object.keys(parsed) 獲取所有的鍵,然後使用forEach迴圈將所有的鍵新增到process.env 中。

dotenv 的功能用一句話來概括就是:解析env檔案將其變數新增到process.env中,其中解析部分主要是使用了正規表示式來匹配4種格式的鍵值對。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69901823/viewspace-2933335/,如需轉載,請註明出處,否則將追究法律責任。

相關文章