项目中的.env文件到底有什么用

444 阅读3分钟

在一些项目中存在.env文件,但是这个文件到底有什么作用呢?从名字上看一定是和环境相关的配置或者变量。有时一些项目是一套通用的代码,但是对于不同的公司和局点都是有一些定制化的要求,所以有一些配置文件是不一致的,区分这些不同环境就需要用到环境变量,设置不同的环境变量需要通过process.env来访问,那么这些变量是怎么存放到process.env中的。

为了解决上面的问题,那么就需要用到一个包---dotenv。

使用方式

.env文件是怎么样的,怎么使用的。 创建一个.env文件然后填写类似下面的内容(键值对的方式进行赋值) 这样就可以将需要的配置填写到文件中了

S3_BUCKET="YOURS3BUCKET"
SECRET_KEY="YOURSECRETKEYGOESHERE"

填写了对应的配置文件,那么要如何添加到环境变量中呢?使用如下的config函数就可以将.env中的变量添加到process.env中了。

require('dotenv').config()
console.log(process.env) 

源码解读

读源码看package.json,可以知道入口文件为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

function parse (src) {}

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

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

function config (options) {}

const DotenvModule = {
  config,
  parse
}

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

看了上面的代码其实主要就暴露了两个函数分别为config和parse,config函数是用来加载配置环境变量到process.env中的,parse函数是将.env文件读到内容 解析为对象格式返回。 那下面来分析一下这些函数的作用。

config

function config (options) {
  // 读取 node 执行的当前路径下的 .env 文件
  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) {
      // 如果存在路径信息时,就对路径进行解析,保证读取对应路径下的.env配置文件
      dotenvPath = _resolveHome(options.path)
    }
    if (options.encoding != null) {
      encoding = options.encoding
    }
  }

  try {
    // 使用parse函数解析读取到的配置文件
    const parsed = DotenvModule.parse(fs.readFileSync(dotenvPath, { encoding }))
    // 将键值对赋值到process.env上
    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 }
  }
}

其实代码很简单,就是先将用户的配置选项进行解析包括路径,打印与否,重写与否,编码格式。然后fs.readFileSync(dotenvPath, { encoding })读取对应的文件,并使用parse函数进行解析成键值对,使用循环将键值对赋值到process.env上

parse

function parse (src) {
  const obj = {}
  // 将文件的内容string化
  let lines = src.toString()

  // 将内容中的换行符进行替换
  lines = lines.replace(/\r\n?/mg, '\n')

  let match
  // 检索解析内容是否匹配正则形如key=value,exec返回匹配则返回函数值
  while ((match = LINE.exec(lines)) != null) {
    const key = match[1]
    let value = (match[2] || '')
    // trim()删除两端空格
    value = value.trim()

    // 获取字符串的第一个值,主要是为了判断是否为'"'
    const maybeQuote = value[0]

    // 删除周围的"
    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')
    }

    // 将值赋值到对象上,返回一个对象
    obj[key] = value
  }

  return obj
}

parse主要就是进行解析文件返回键值对,其实很简单,主要是里面牵扯正则以及字符串的处理比较复杂,因为考虑的情况比较多,所以看起来较为复杂,如果不考虑对特殊情况的处理,应该会简单很多,这也就是我们和开源作者的区别