【源码学习】第19期 | 工作中常见的env文件是干嘛用的?实现原理?


theme: cyanosis
highlight: a11y-dark

前言

    日常开发中,相信不少项目都会有.env或者.env.development这样的文件,那么这些文件的作用是什么呢?今天就一起来学习探讨一下~

收获清单

  • [x] env文件的作用
  • [x] dotenv 原理和实现
  • [x] 如何使用fs模块获取文件并解析

env文件的作用

    提到env不难会联想到环境变量,为什么要设置环境变量呢?简言之,当你想要同一套代码应用到不同的环境并在某些功能上有所区分时,区分这些不同的环境就需要用到环境变量。如下图 vue-cli 中的环境变量。在env文件设置完变量后通过项目代码中通过process.env来访问变量,那么是什么把env文件加载到process.env中的呢?就是今天的主角dotenv了!

图片.png

源码下载

git clone https://github.com/motdotla/dotenv
cd dotenv
dotnev简介

    使用教程可以看README,官方简介是Dotenv是一个零依赖模块,它将.env文件中的环境变量加载到process.env中。将配置与代码分开存储在环境中是基于十二因素应用程序方法的。

开启调试

图片.png

调试截图

图片.png

源码解析

引入模块
// 文件模块
const fs = require('fs')
// 路径模块
const path = require('path')
// os模块,提供基本的系统操作函数
const os = require('os')
config函数
function config (options) {
//获取env文件当前Node.js进程执行时的文件夹地址——工作目录
  let dotenvPath = path.resolve(process.cwd(), '.env')

  let encoding = 'utf8'

  const debug = Boolean(options && options.debug)

  const override = Boolean(options && options.override)
// 获取options配置:路径、编码方式等
  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(dotenvPath, { encoding })读取文件然后进行解析
    const parsed = DotenvModule.parse(fs.readFileSync(dotenvPath, { encoding }))

  // 把解析出的env文件值遍历赋值到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 }

  }

}

_resolveHome函数

// os.homedir()方法是os模块的内置应用程序编程接口,用于获取当前用户的主目录路径。
function _resolveHome (envPath) {

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

}

parse 函数

const LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\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(/\r\n?/mg, '\n')
  let match
// 检索解析内容是否匹配正则形如**=**,exec返回匹配则返回函数值
  while ((match = LINE.exec(lines)) != null) {
    // 对应键
    const key = match[1]
    
    // Default undefined or null to empty string
    // 对应值,.trim()删除两端空格
    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
}

总结

    今天调试分析了dotenv的源码,总结一下原理就是:先利用fs.readFileSync读取env文件内容,然后借助正则解析返回键值对组成的对象赋值到process.env。本期源码主要函数仅108行,除了匹配正则有点晦涩难懂外整体理解起来不难,况且涉及了node.js的文件读取、正则应用、Object.keys遍历对象key值等实用知识,同时又是工作中常见又容易被忽略的,值得一学~

© 版权声明
THE END
喜欢就支持一下吧
点赞6 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容