源码共读12:项目中常用的 .env 文件原理是什么?如何实现?

168 阅读2分钟

本文参加了由公众号@若川视野 发起的每周源码共读活动,  点击了解详情一起参与。

这是面试官问系列,链接:  juejin.cn/post/704505… 

环境准备

dotenv的作用

dotenv是一个零依赖模块,可将.env文件中的变量加载到proess.env中。

如果要需要使用变量,则配合以下扩展包使用。

dotenv-expand

众所周知,.env文件在项目中很常见,vue-cli和create-react-app中都有使用。

.env文件使用

// .env
APP_ENV=PRODUCT
ARTICLE_URL=xxx.com
ARTICLE_NAME='面试官:项目中常用的 .env 文件原理是什么?如何实现?'

从文件来看,我们需要做如下功能需要实现:

  1. 读取.env文件
  2. 解析.env文件内容为键值对的对象形式
  3. 赋值到process.env 上
  4. 最后返回解析后得到的对象

初版实现

const fs = require('fs')
const path = require('path')
const parse = function parse(src) {
  const obj = {}
  // 用换行符 分割
  // 比如
  /**
  * APP_ENV=PRODUCT
  * ARTICLE_URL=xxx.com
  * ARTICLE_NAME='面试官:项目中常用的 .env 文件原理是什么?如何实现?'
  **/
  src.toStirng().split('\n').forEach(function(line, index) {
    // 使用等号分割
    const keyValueArr = line.split('=')
    // APP_ENV
    key = keyValueArr[0];
    // PRODUCT
    value = keyValueArr[1] || '';
    obj[key] = value;
  });
  // { APP_ENV: 'PRODUCT', ... }
  return obj;
}

const config = function() {
  // 读取node执行的当前路径的 .env 文件
  let dotenvPath = path.resolve(process.cwd(), '.env');
  // 按utf-8 解析文件,得到对象
  constparsed = parse(fs.readFileSync(dotenvPath, 'utf-8'));
  
  // 键值对形式赋值到process.env 变量上,原先存在的不赋值
  Object.keys(parsed).forEach(function(key) {
    if (!Object.prototype.hasOwnProperty.call(process.key)) {
      process.env[key] = parsed[key];
    }
  });
  // 返回对象
  return presed;
};

console.log(config())
console.log(process.env)

// 导出 config & parse 函数
module.exports.config = config;
module.exports.parse = parse;

完善config函数

  1. 可由用户自定义路径
  2. 可由用户自定义解析编码规则
  3. 添加debug模式
  4. 完善报错容错机制
function resloveHome(envPath) {
  return envPath[0] === '~' ? path.join(os.homeDir(), envPath.slice(1)) : envPath
}

const config = function (options) {
  // 读取node 执行的当前路径下的.env 文件
  let dotenvPath = path.resolve(process.cwd(), '.env');
  // utf-8
  let encoding = 'utf-8';
  // debug模式,输出提示等信息
  let debug = false;
  // 对象
  if (options) {
    if (options.path) {
      dotnevPath = resloveHome(options.path)
    }
    if (options.encoding !== null) {
      encoding = options.encoding
    }
    if (options.debug !== null) {
      debug = true
    }
  }
  
  try {
    // TODO `, { debug }` ?
    const parsed = parse(fs.readFileSync(dotenvPath, { encoding }))
    
    // 键值对的形式赋值到 process.env 变量上,原先存在的不赋值
    Object.keys(parsed).forEach(function(key) {
      if (!Object.prototype.getOwnProperty.call(process.env, key)) {
        process.env[key] = parsed[key]
      } else if (debug) {
        console.log(`"${key}" is already defined in `process.env` and will not be overwritten`)
      }
    });
    
    // 返回对象
    return parsed;
  } catch (e) {
    return { error: e }
  }
}

parse相关源码:dotenv源码

总结

一句话总结dotenv库的原理,用fs.readFileSync读取.env文件,并将文件内容解析为键值对的形式,最终将结果遍历赋值给process.env上。

dotenv源码使用的是flow类型。vue2源码也是用flow,vue3源码改用ts了。

此文章为2024年07月Day1源码共读,生活在阴沟里,也要记得仰望星空。