Dotenv源码学习

项目开发者 .env 的作用

.env文件是用来自定义配置的一个简单方法,可以将一些不能在代码中存储的敏感/账号数据从代码中剥离出来,作为环境变量存储在环境中。

.env 的使用方法

介绍

.env 文件通常不包含在版本控制内,它可能包含敏感的 API Key 或者 密码。

所有需要环境变量定义(不敏感的定义)的项目都需要创建一个.env.example 文件,这个环境变量包含他们自己定义的环境变量或者联合开发包含的环境变量。

项目合作开发者可以独立的复制 .env.example并且重命名为.env,并且修改为正确的本地环境配置,存储密码key或者提供他们必要的值。

在这个使用方法中 .env 文件应该添加到.gitignore文件中并且永远不会被项目的合作者签入/签出。

这个方法确保里边没有敏感的 API Key 或者 密码在版本控制中出现从而减少了安全风险,并且开发环境中的配置永远不会告知合作开发者。

配置规则


DB_HOST=127.0.0.1
DB_NAME=timerDabase"
DB_PORT=4442
DB_USER=vincent
DB_UNSECURE=true


DB_HOST=127.0.0.1
DB_NAME=timerDabase"
DB_PORT=4442
DB_USER=vincent
DB_UNSECURE=true

引入 dotenv


let dotenv = require('dotenv');
const env = process.env.ENV
dotenv.config(`./env.${env}`);
console.log(process.env);

let dotenv = require('dotenv');
const env = process.env.ENV
dotenv.config(`./env.${env}`);
console.log(process.env);

Dotenv

dotenv 是一个npm包, 作用是将.env的文件解析成json对象。并且把这个json对象解析到process.env的环境变量上。 之后就可以在代码中通过process.env[key]来获取环境变量。

源码地址

源码

const fs = require('fs')
const path = require('path')
const os = require('os')

function log (message) {
  console.log(`[dotenv][DEBUG] ${message}`)
}

const NEWLINE = '\n'
const RE_INI_KEY_VAL = /^\s*([\w.-]+)\s*=\s*("[^"]*"|'[^']*'|[^#]*)?(\s*|\s*#.*)?$/
const RE_NEWLINES = /\\n/g
const NEWLINES_MATCH = /\r\n|\n|\r/

// 把读取的字符串解析成对象 
function parse (src, options) {
  const debug = Boolean(options && options.debug)
  const obj = {}

  // 在分割成行和处理之前转换缓冲区
  src.toString().split(NEWLINES_MATCH).forEach(function (line, idx) {
    // 在“KEY=VAL”中匹配“KEY”和“VAL”
    const keyValueArr = line.match(RE_INI_KEY_VAL)
    // 匹配到了?
    if (keyValueArr != null) {
      const key = keyValueArr[1]
      // 默认值为空字符串,未定义或缺少值
      let val = (keyValueArr[2] || '')
      const end = val.length - 1
      const isDoubleQuoted = val[0] === '"' && val[end] === '"'
      const isSingleQuoted = val[0] === "'" && val[end] === "'"

      // 如果是单引号或双引号,删除引号
      if (isSingleQuoted || isDoubleQuoted) {
        val = val.substring(1, end)

        // 如果双引号,展开换行
        if (isDoubleQuoted) {
          val = val.replace(RE_NEWLINES, NEWLINE)
        }
      } else {
        // 清除周围的空格
        val = val.trim()
      }

      obj[key] = val
    } else if (debug) {
      const trimmedLine = line.trim()

      // 忽略空行和注释行
      if (trimmedLine.length && trimmedLine[0] !== '#') {
        log(`failed to match key and value when parsing line ${idx + 1}: ${line}`)
      }
    }
  })

  return obj
}

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)

  if (options) {
    if (options.path != null) {
      dotenvPath = resolveHome(options.path)
    }
    if (options.encoding != null) {
      encoding = options.encoding
    }
  }

  try {
    // 指定编码将返回字符串而不是缓冲区
    const parsed = parse(fs.readFileSync(dotenvPath, { encoding }), { debug })

    Object.keys(parsed).forEach(function (key) {
      if (!Object.prototype.hasOwnProperty.call(process.env, key)) {
        process.env[key] = parsed[key]
      } else if (debug) {
        log(`"${key}" is already defined in \`process.env\` and will not be overwritten`)
      }
    })

    return { parsed }
  } catch (e) {
    console.error(e);
    return { error: e }
  }
}

module.exports.config = config
module.exports.parse = parse

const fs = require('fs')
const path = require('path')
const os = require('os')

function log (message) {
  console.log(`[dotenv][DEBUG] ${message}`)
}

const NEWLINE = '\n'
const RE_INI_KEY_VAL = /^\s*([\w.-]+)\s*=\s*("[^"]*"|'[^']*'|[^#]*)?(\s*|\s*#.*)?$/
const RE_NEWLINES = /\\n/g
const NEWLINES_MATCH = /\r\n|\n|\r/

// 把读取的字符串解析成对象 
function parse (src, options) {
  const debug = Boolean(options && options.debug)
  const obj = {}

  // 在分割成行和处理之前转换缓冲区
  src.toString().split(NEWLINES_MATCH).forEach(function (line, idx) {
    // 在“KEY=VAL”中匹配“KEY”和“VAL”
    const keyValueArr = line.match(RE_INI_KEY_VAL)
    // 匹配到了?
    if (keyValueArr != null) {
      const key = keyValueArr[1]
      // 默认值为空字符串,未定义或缺少值
      let val = (keyValueArr[2] || '')
      const end = val.length - 1
      const isDoubleQuoted = val[0] === '"' && val[end] === '"'
      const isSingleQuoted = val[0] === "'" && val[end] === "'"

      // 如果是单引号或双引号,删除引号
      if (isSingleQuoted || isDoubleQuoted) {
        val = val.substring(1, end)

        // 如果双引号,展开换行
        if (isDoubleQuoted) {
          val = val.replace(RE_NEWLINES, NEWLINE)
        }
      } else {
        // 清除周围的空格
        val = val.trim()
      }

      obj[key] = val
    } else if (debug) {
      const trimmedLine = line.trim()

      // 忽略空行和注释行
      if (trimmedLine.length && trimmedLine[0] !== '#') {
        log(`failed to match key and value when parsing line ${idx + 1}: ${line}`)
      }
    }
  })

  return obj
}

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)

  if (options) {
    if (options.path != null) {
      dotenvPath = resolveHome(options.path)
    }
    if (options.encoding != null) {
      encoding = options.encoding
    }
  }

  try {
    // 指定编码将返回字符串而不是缓冲区
    const parsed = parse(fs.readFileSync(dotenvPath, { encoding }), { debug })

    Object.keys(parsed).forEach(function (key) {
      if (!Object.prototype.hasOwnProperty.call(process.env, key)) {
        process.env[key] = parsed[key]
      } else if (debug) {
        log(`"${key}" is already defined in \`process.env\` and will not be overwritten`)
      }
    })

    return { parsed }
  } catch (e) {
    console.error(e);
    return { error: e }
  }
}

module.exports.config = config
module.exports.parse = parse

parse

parse把传入的字符串转成对象并输出

// Parses src into an Object
function parse (src, options) {
  const debug = Boolean(options && options.debug)
  const obj = {}

  // convert Buffers before splitting into lines and processing
  src.toString().split(NEWLINES_MATCH).forEach(function (line, idx) {
    // matching "KEY' and 'VAL' in 'KEY=VAL'
    const keyValueArr = line.match(RE_INI_KEY_VAL)
    // matched?
    if (keyValueArr != null) {
      const key = keyValueArr[1]
      // default undefined or missing values to empty string
      let val = (keyValueArr[2] || '')
      const end = val.length - 1
      const isDoubleQuoted = val[0] === '"' && val[end] === '"'
      const isSingleQuoted = val[0] === "'" && val[end] === "'"

      // if single or double quoted, remove quotes
      if (isSingleQuoted || isDoubleQuoted) {
        val = val.substring(1, end)

        // if double quoted, expand newlines
        if (isDoubleQuoted) {
          val = val.replace(RE_NEWLINES, NEWLINE)
        }
      } else {
        // remove surrounding whitespace
        val = val.trim()
      }

      obj[key] = val
    } else if (debug) {
      const trimmedLine = line.trim()

      // ignore empty and commented lines
      if (trimmedLine.length && trimmedLine[0] !== '#') {
        log(`failed to match key and value when parsing line ${idx + 1}: ${line}`)
      }
    }
  })

  return obj
}

// Parses src into an Object
function parse (src, options) {
  const debug = Boolean(options && options.debug)
  const obj = {}

  // convert Buffers before splitting into lines and processing
  src.toString().split(NEWLINES_MATCH).forEach(function (line, idx) {
    // matching "KEY' and 'VAL' in 'KEY=VAL'
    const keyValueArr = line.match(RE_INI_KEY_VAL)
    // matched?
    if (keyValueArr != null) {
      const key = keyValueArr[1]
      // default undefined or missing values to empty string
      let val = (keyValueArr[2] || '')
      const end = val.length - 1
      const isDoubleQuoted = val[0] === '"' && val[end] === '"'
      const isSingleQuoted = val[0] === "'" && val[end] === "'"

      // if single or double quoted, remove quotes
      if (isSingleQuoted || isDoubleQuoted) {
        val = val.substring(1, end)

        // if double quoted, expand newlines
        if (isDoubleQuoted) {
          val = val.replace(RE_NEWLINES, NEWLINE)
        }
      } else {
        // remove surrounding whitespace
        val = val.trim()
      }

      obj[key] = val
    } else if (debug) {
      const trimmedLine = line.trim()

      // ignore empty and commented lines
      if (trimmedLine.length && trimmedLine[0] !== '#') {
        log(`failed to match key and value when parsing line ${idx + 1}: ${line}`)
      }
    }
  })

  return obj
}

config