原文地址

dotenv 的作用

dotenv
Dotenv 是一个零依赖模块,可将 .env 文件中的环境变量加载到 process.env 中。
如果需要使用变量,则配合如下扩展包使用。
dotenv-expand
众所周知,.env 文件在我们项目中非常常见,在 vue-cli 和 create-react-app 中都有使用。
vue-cli .env
create-react-app .env

.env 文件使用

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

  1. NAME=若川
  2. AGE=18
  3. BLOG=https://lxchuan12.gitee.io
  4. MP_WEIXIN='若川视野'
  5. ACTIVITY=每周一起学200行左右的源码共读活动
  6. WEIXIN=加我微信 ruochuan12 参与

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

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

    简单实现

    根据分析问题,我们最终可以简单把代码实现如下: ```javascript const fs = require(‘fs’); const path = require(‘path’);

const parse = function parse(src){ const obj = {}; // 用换行符 分割 // 比如 /**

  1. * NAME=若川
  2. * AGE=18
  3. * MP_WEIXIN=若川视野
  4. * BLOG=https://lxchuan12.gitee.io
  5. * ACTIVITY=每周一起学200行左右的源码共读活动
  6. * WEIXIN=加我微信 ruochuan12 参与
  7. */
  8. src.toString().split('\n').forEach(function(line, index){
  9. // 用等号分割
  10. const keyValueArr = line.split('=');
  11. // NAME
  12. key = keyValueArr[0];
  13. // 若川
  14. val = keyValueArr[1] || '';
  15. obj[key] = val;
  16. });
  17. // { NAME: '若川', ... }
  18. return obj;

}

const config = function(){ // 读取 node 执行的当前路径下的 .env 文件 let dotenvPath = path.resolve(process.cwd(), ‘.env’); // 按 utf-8 解析文件,得到对象 // { NAME: ‘若川’, … } const parsed = parse(fs.readFileSync(dotenvPath, ‘utf-8’));

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

};

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

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

  1. <a name="boJgy"></a>
  2. ## 6. 继续完善 config 函数
  3. 简版的 config 函数还缺失挺多功能,比如:

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

  1. 根据功能,我们很容易实现以下代码:
  2. ```javascript
  3. function resolveHome (envPath) {
  4. return envPath[0] === '~' ? path.join(os.homedir(), envPath.slice(1)) : envPath
  5. }
  6. const config = function(options){
  7. // 读取 node 执行的当前路径下的 .env 文件
  8. let dotenvPath = path.resolve(process.cwd(), '.env');
  9. // utf8
  10. let encoding = 'utf8';
  11. // debug 模式,输出提示等信息
  12. let debug = false;
  13. // 对象
  14. if (options) {
  15. if (options.path != null) {
  16. // 解析路径
  17. dotenvPath = resolveHome(options.path)
  18. }
  19. // 使用配置的编码方式
  20. if (options.encoding != null) {
  21. encoding = options.encoding
  22. }
  23. // 有配置就设置为 true
  24. if (options.debug != null) {
  25. debug = true
  26. }
  27. }
  28. try {
  29. // 按 utf-8 解析文件,得到对象
  30. // { NAME: '若川', ... }
  31. // debug 传递给 parse 函数 便于
  32. const parsed = parse(fs.readFileSync(dotenvPath, { encoding }), { debug });
  33. // 键值对形式赋值到 process.env 变量上,原先存在的不赋值
  34. Object.keys(parsed).forEach(function(key){
  35. if(!Object.prototype.hasOwnProperty.call(process.env, key)){
  36. process.env[key] = parsed[key];
  37. } else if (debug) {
  38. console.log(`"${key}" is already defined in \`process.env\` and will not be overwritten`);
  39. }
  40. });
  41. // 返回对象
  42. return parsed;
  43. }
  44. catch (e) {
  45. return { error: e };
  46. }
  47. };

dotenv 源码中,parse 函数主要是一些正则和单双引号、跨平台等细致处理。这里就暂时不阐述,读者朋友可以查看dotenv 源码

7. 总结

鉴于文章不宜过长,文章只比较深入的分析了 config 函数。parse 函数目前没有深入分析。
一句话总结 dotenv 库的原理。用 fs.readFileSync 读取 .env 文件,并解析文件为键值对形式的对象,将最终结果对象遍历赋值到 process.env 上
我们也可以不看 dotenv 源码,根据 api 倒推,自己来实现这样的功能。最终看看和 dotenv 源码本身有什么差别。这样也许更能锻炼自己。或者用 ts 重构它。
本文同时也给我们启发:围绕工作常用的技术包和库值得深入学习,做到知其然,知其所以然
值得一提的是:dotenv 源码使用的是 flow 类型。vue2 源码也是用的 flow。vue3 源码改用 ts了。