【第22期】dotenv 解析 .env 文件

808 阅读2分钟

本次源码分析的内容是 dotenv, 它的作用是将项目中 .env 环境文件里定义的变量"键=值对", 放入到 process.env 中, 这样在项目里就可以使用了.

process 的官方解释:

process 对象是一个全局变量,提供了有关当前 Node.js 进程的信息并对其进行控制。 作为全局变量,它始终可供 Node.js 应用程序使用,无需使用 require()。 它也可以使用 require() 显式地访问。

process.env的解释:

process.env 属性会返回包含用户环境的对象

vue-cli 中就有相应的配置

除了通过环境文件.env的方式添加环境变量, 我们在执行 npm 命令的时候 可以携带一些参数, 比如 npm run serve --type=dev, 打印 process.env 对象后能发现在其中添加了如下对象

npm_config_type: 'dev

dotenv 的源码

// 引入 fs/path/os 三个node模块
const fs = require('fs')
const path = require('path')
const os = require('os')
​
// 定义了一个正则表达式, 
const LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\'|[^'])*'|\s*"(?:\"|[^"])*"|\s*`(?:\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg// Parser src into an Object, 将src解析成一个Object
function parse (src) {
  const obj = {}
​
  // Convert buffer to string, 将buffer转换成string, src 一般是通过例如fs.readFileSync('tests/.env', { encoding: 'utf8' }) 等方式读取的数据
  let lines = src.toString()
​
  // Convert line breaks to same format, 统一换行方式
  lines = lines.replace(/\r\n?/mg, '\n')
​
  let match
  // exec() 方法在一个指定字符串中执行一个搜索匹配。返回一个结果数组或 null
  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
}
​
// 封装了一个log方法
function _log (message) {
  console.log(`[dotenv][DEBUG] ${message}`)
}
​
function _resolveHome (envPath) {
  // os.homedir() 用于获取当前用户的主目录路径。
  return envPath[0] === '~' ? path.join(os.homedir(), envPath.slice(1)) : envPath
}
​
// Populates process.env from .env file
function config (options) {
  // process.cwd()方法是流程模块的内置应用程序编程接口,用于获取node.js流程的当前工作目录。默认获取 .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) {
      dotenvPath = _resolveHome(options.path)
    }
    if (options.encoding != null) {
      encoding = options.encoding
    }
  }
​
  try {
    // Specifying an encoding returns a string instead of a buffer, 读取环境文件并解析
    // fs.readFileSync 同步读取文件内容
    const parsed = DotenvModule.parse(fs.readFileSync(dotenvPath, { encoding }))
​
    // 遍历解析后对象
    Object.keys(parsed).forEach(function (key) {
      // 如果 process.env 上没有key 则赋值
      if (!Object.prototype.hasOwnProperty.call(process.env, key)) {
        process.env[key] = parsed[key]
      } else {
        // 如果需要覆盖再覆盖
        if (override === true) {
          process.env[key] = parsed[key]
        }
        
        // debug 模式下输出每个属性的赋值情况 是不是被覆盖的
        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

综上 本次源码的难度偏简单, 比较费劲儿的是理解这里面用到的正则表达式

比如 LINE 过于复杂 还没理解