浅析.env文件的工作原理以及它是如何实现的

  我们在项目中使用环境变量时,经常会使用到 .env 文件,那么关于 .env 文件中的内容是如何设置的?

  VUE_APP_URL 是在 .env.* 文件中定义的,在项目启动时,vue-cli 会将以 VUE_APP_ 开头的变量读取至环境变量,这是 vue-cli 强制要求的“你想用我的工具,就得遵循我定的规则”,其实这背后是一系列工具链(webpack + dotenv + webpack DefinePlugin)相互作用的成果。

一、dotenv 的作用

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

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

二、.env 文件的使用与实现

  我们项目中经常会用到 .env 文件写法

NODE_ENV = production
VUE_APP_DOMAIN = http://dev.modb.cc:9090
VUE_APP_BUCKET_PRIVATE = oss-estest-private
VUE_APP_BUCKET_PUBLIC = oss-estest-public
VUE_APP_MODB = https://dev.modb.cc

  单从这个文件来看,我们可以知道有如下功能需要实现:

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

  根据分析问题,我们最终可以简单把代码实现如下

复制代码
const fs = require('fs');
const path = require('path');

const parse = function parse(src){
    const obj = {};
    // 用换行符分割
    src.toString().split('\n').forEach(function(line, index){
        // 用等号分割
        const keyValueArr = line.split('=');
        key = keyValueArr[0];       // NAME
        val = keyValueArr[1] || '';  // VALUE
        obj[key] = val;
    });
    // { VUE_APP_DOMAIN: 'http://dev.modb.cc:9090', ... }
    return obj;
}

const config = function(){
    // 读取 node 执行的当前路径下的 .env 文件
    let dotenvPath = path.resolve(process.cwd(), '.env');
    // 按 utf-8 解析文件,得到对象 { VUE_APP_DOMAIN: 'http://dev.modb.cc:9090', ... }
    const parsed = parse(fs.readFileSync(dotenvPath, 'utf-8'));

    // 键值对形式赋值到 process.env 变量上,原先存在的不赋值
    Object.keys(parsed).forEach(function(key){
        if(!Object.prototype.hasOwnProperty.call(process.env, key)){
            process.env[key] = parsed[key];
        }
    });
    // 返回对象
    return parsed;
};

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

// 导出 config parse 函数
module.exports.config = config;
module.exports.parse = parse;
复制代码

三、继续完善config函数

  我们看下源码config函数,然后对比下我们简版的 config 函数还缺失挺多功能,比如:

  1. 可由用户自定义路径
  2. 可由用户自定义解析编码规则
  3. 添加 debug 模式
  4. 完善报错输出,用户写的env文件自由度比较大,故需添加容错机制

  根据功能,我们很容易实现以下代码:

复制代码
function resolveHome (envPath) {
    return envPath[0] === '~' ? path.join(os.homedir(), envPath.slice(1)) : envPath
}
const config = function(options){
    let dotenvPath = path.resolve(process.cwd(), '.env'); //读取node执行的当前路径下的.env文件
    let encoding = 'utf8';  // utf8
    let debug = false;   // debug 模式,输出提示等信息
    if (options) {  // 传入配置对象
        if (options.path != null) {
            dotenvPath = resolveHome(options.path)  // 解析路径
        }
        if (options.encoding != null) {// 使用配置的编码方式
            encoding = options.encoding
        }
        if (options.debug != null) {  // 有配置就设置为 true
            debug = true
        }
    }
    try {
        // 按 utf-8 解析文件,得到对象// debug 传递给 parse 函数 便于
        const parsed = parse(fs.readFileSync(dotenvPath, { encoding }), { debug });

        // 键值对形式赋值到 process.env 变量上,原先存在的不赋值
        Object.keys(parsed).forEach(function(key){
            if(!Object.prototype.hasOwnProperty.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 };
    }
};
复制代码

  dotenv源码中,parse函数主要是一些正则和单双引号、跨平台等细致处理,详情可以查看 dotenv 源码

  一句话总结 dotenv 库的原理:

  用 fs.readFileSync 读取 .env 文件,并解析文件为键值对形式的对象,将最终结果对象遍历赋值到 process.env 上。

  我们也可以不看 dotenv 源码,根据API倒推,自己来实现这样的功能。最终看看和 dotenv 源码本身有什么差别。这样也许更能锻炼自己,或者用ts重构它。同时也能给我们启发:围绕工作常用的技术包和库值得深入学习,做到知其然,并知其所以然。

posted @   古兰精  阅读(8364)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
历史上的今天:
2017-07-31 JavaScript事件代理和事件委托
2017-07-31 JavaScript事件冒泡机制和阻止事件冒泡及默认事件
点击右上角即可分享
微信分享提示