CD's blog CD's blog
首页
  • HTMLCSS
  • JavaScript
  • Vue
  • TypeScript
  • React
  • Node
  • Webpack
  • Git
  • Nestjs
  • 小程序
  • 浏览器网络
  • 学习笔记

    • 《TypeScript 从零实现 axios》
    • Webpack笔记
  • JS/TS教程

    • 《现代JavaScript》教程
🔧工具方法
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

CD_wOw

内卷的行情,到不了的梦
首页
  • HTMLCSS
  • JavaScript
  • Vue
  • TypeScript
  • React
  • Node
  • Webpack
  • Git
  • Nestjs
  • 小程序
  • 浏览器网络
  • 学习笔记

    • 《TypeScript 从零实现 axios》
    • Webpack笔记
  • JS/TS教程

    • 《现代JavaScript》教程
🔧工具方法
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 基础原理及工具方法

  • Babel

  • WebComponent

  • 专项知识汇总

    • 图片类型、优化、处理知识点汇总
    • 如何统一 npm 包管理
    • JS如何做请求并发限制
    • JS如何做实现动画序列播放
    • dotenv如何实现env环境变量注入
      • 实现
      • 小结
  • JavaScript笔记
  • 专项知识汇总
CD
2022-10-31
目录

dotenv如何实现env环境变量注入

我们常常看到,在使用 cli 的时候可以通过配置.xxx.env ,.env 之类的文件,将文件内的常量注入到 process.env 或是 process.xx 上,其实现原理其实简单,这里我们通过解析 dotenv 的源码来学习其中原理.

dotenv (opens new window) 从功能的角度出发,我们可以将功能分为以下方面:

  1. 查找并读取 .env 文件
  2. 解析 .env 文件内容,将其拆成键值对的对象形式
  3. 将拆分好的值赋到 process.env 上
  4. 最后返回解析后得到的对象

# 实现

先看.env 文件的写法:

// .env
REDIS_NAME=redis-EkmW
REDIS_HOST=127.0.0.1
REDIS_PORT=55000
REDIS_PASSWORD=123456
REDIS_CACHE_TIME = 3600
1
2
3
4
5
6

再看简单实现:

interface Src {
  src: string; // parse函数的src为fs读取的.env文件的字符串内容, 为Buffer数据流,可解码为string
}

// 搜索字符串中满足条件xxx=yyy的数据
const LINE =
  /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gm;

function parse(src: string) {
  const obj = {};

  //  buffer 转为 string
  let lines = src.toString();

  let match;
  // exec() 方法在一个指定字符串中执行一个搜索匹配。返回一个结果数组或 null, 例如:
  /* match: ['REDIS_NAME=redis-EkmW',
    'REDIS_NAME',
    'redis-EkmW', ...]
    */
  while ((match = LINE.exec(lines)) != null) {
    // 拿到键
    const key = match[1];
    // 拿到值
    let value = match[2] || "";

    // 移除两端空格
    value = value.trim();

    // 判断值是否为 "xx" 的情况
    const maybeQuote = value[0];

    // 移除value最外层的'"`符号
    value = value.replace(/^(['"`])([\s\S]*)\1$/gm, "$2");

    // 如果双引号,则展开换行
    if (maybeQuote === '"') {
      value = value.replace(/\\n/g, "\n");
      value = value.replace(/\\r/g, "\r");
    }

    obj[key] = value;
  }

  return obj;
}

function _resolveHome(envPath) {
  return envPath[0] === "~"
    ? path.join(os.homedir(), envPath.slice(1))
    : envPath;
}

interface Options {
  path?: string; // .env文件的路径
  encoding?: string; // 解码方式。默认为 utf-8
  override?: boolean; // 默认为false,为true时.env文件的值覆盖其应用上设置的任何环境变量
}
function config(options: Options) {
  let dotenvPath = path.resolve(process.cwd(), ".env");
  let encoding = "utf8";
  const override = Boolean(options && options.override);

  if (options) {
    // 自定义.env路径
    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读出的buffer解码为字符串
    const parsed = parse(fs.readFileSync(dotenvPath, { encoding }))
    console.log(parsed)
    Object.keys(parsed).forEach(function (key) {
      // 为process.env配值
      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`)
          }
        }
      }
    })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98

我们可以看到, 其根本就是用 fs.readFileSync 读取 .env 文件,并解析文件为键值对形式的对象,将最终结果对象遍历赋值到 process.env 上, 最终实现了功能。

# 小结

dotenv 把环境变量加载进 process.env 对于前端项目来说还不够,因为浏览器环境是访问不到 process 的,需要通过 webpack 的 DefinePlugin 在构建阶段把变量替换为对应的值。

new webpack.DefinePlugin({
  PRODUCTION: JSON.stringify(true),
  VERSION: JSON.stringify("5fa3b9"),
  BROWSER_SUPPORTS_HTML5: true,
  TWO: "1+1",
  "typeof window": JSON.stringify("object"),
  "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
});
1
2
3
4
5
6
7
8

例如我们可以采用dotenv-webpack (opens new window), 其原理与 dotenv 类似,只是最终将处理后的值填入了 webpack.DefinePlugin 中去,最终以实现:

// file1.js
console.log(process.env.DB_HOST);
// '127.0.0.1'

// bundle.js
console.log("127.0.0.1");
1
2
3
4
5
6

这样的效果。

编辑 (opens new window)
#JavaScript
上次更新: 2022/12/11, 20:19:48
JS如何做实现动画序列播放

← JS如何做实现动画序列播放

最近更新
01
gsap动画库学习笔记 - 持续~
06-05
02
远程组件加载方案笔记
05-03
03
小程序使用笔记
03-29
更多文章>
Theme by Vdoing | Copyright © 2020-2023 CD | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式