nodejs环境变量 、.env文件以及dotenv的使用
环境变量的添加、查看与删除
以bash中的操作为例:
# 设置一个名为FOO的环境变量 值为foo:
export FOO=foo
# 查看名为FOO的环境变量值
echo $FOO
# 清除名为FOO的环境变量
unset FOO
我们启动一个项目的时候,或是执行webpack打包的时候,可能会在命令行中提前声明:
export SOME_ENV_VAL = foo
npm run build
这样一来,在整个node服务运行的过程中,都会存在名为SOME_ENV_VAL,值为foo的一个环境变量。
NODE中获取环境变量
nodejs允许我们通过 process.env
获取当前运行环境中的所有环境变量。例如我们想取到上文中声明的SOME_ENV_VAL
变量:
console.log(process.env.SOME_ENV_VAL) // foo
.env文件
当我们的项目需要声明很多环境变量的时候,命令行声明的形式显然过于繁琐,而且难以管理。
.env文件允许我们将所有项目需要的环境变量放在一个单独的文件中,然后一并加载进process.env
我们可以自己编写脚本去加载.env文件,不过更加简便和推荐的方式是使用dotenv
使用dotenv加载.env文件
npm install dotenv --save
dotenv是一个零依赖模块,它将环境变量从 .env 文件加载到 process.env 中。dotenv提供许多的方法,最常用的是dotenv.config()
。
dotenv.config()
读取一个.env文件,解析其内容,将.env文件中声明的环境变量合并进process.env,然后返回一个对象result
。result.parsed
是解析出的内容,result.error
是在解析失败的时候返回的一个标识。 通常我们只需要进行dotenv.config()
操作,不需要关心result
。
先在.env文件中声明环境变量
# .env
NAME = foo
AGE = 20
为了能够尽早把.env中的环境变量加载进process.env
,我们应该尽量在项目入口顶部的位置执行dotenv.config()
。比较推荐的做法是单独抽出一个config/env.js文件来处理环境变量逻辑,然后在项目服务的入口文件中引入config/env.js。
// config/env.js
require('dotenv').config()
console.log(process.env.NAME); // foo
console.log(process.env.AGE); // AGE
dotenv加载优先级
dotenv.config()
方法可以接收一个option
对象,其中option.path
代表我们期望加载的.env*文件路径。
需要注意的是,只要一个环境变量已经被设置过,dotenv就不会修改它 。也就是说,dotenv始终以先加载到的变量声明为更高优先级
我们在项目的根目录下建三个文件,各自声明对应环境的环境变量:
# .env
NAME = foo
AGE = 30
COUNTRY = China
# .env.development
NAME = bar
AGE = 20
# .env.local
NAME = zhangsan
LOCAL_ENV = local
我们期望的效果是,.env.local文件中定义的环境变量获得最高优先级,.env.development其次,.env中的通用配置优先级最低,由此我们可以在config/env.js中进行如下的处理:
// config/env.js
const path = require("path");
const fs = require("fs");
const dotEnv = require("dotenv");
// 先构造出.env*文件的绝对路径
const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = (relativePath) => path.resolve(appDirectory, relativePath);
const pathsDotenv = resolveApp(".env");
// 按优先级由高到低的顺序加载.env文件
dotEnv.config({ path: `${pathsDotenv}.local` }) // 加载.env.local
dotEnv.config({ path: `${pathsDotenv}.development` }) // 加载.env.development
dotEnv.config({ path: `${pathsDotenv}` }) // 加载.env
// 打印一下此时的process.env
console.log(process.env.NAME); // zhangsan
console.log(process.env.AGE); // 20
console.log(process.env.COUNTRY); // China
console.log(process.env.LOCAL_ENV); // local
使用dotenv-expand 开启.env文件的模板字符串语法
有时我们希望将某几个环境变量拼接为一个新的环境变量,可能会考虑如下的写法:
NAME = lisi
AGE = 40
NAME_AND_AGE = ${NAME}-is-${AGE}-years-old
我们期望注入到环境变量中的NAME_AND_AGE
是 lisi-is-40-years-old
。
然而直接dotenv.config()
,发现结果是 ${NAME}-is-${AGE}-years-old
。这说明dotenv.config()是没办法解析${NAME}
和${AGE}
这种模板字符串语法的
想要使用模板字符串语法的话,就要用到dotenv-expand了。
npm install dotenv-expand --save
使用方法:接收一个 dotenv.config()
的返回结果作为参数,如果.env*文件中有${XXX}
这种模板语法的话,自动将其解析为同文件中已声明的环境变量XXX
的值:
require('dotenv-expand')(require('dotenv').config());
console.log(process.env.NAME_AND_AGE); // lisi-is-40-years-old
高阶技巧
覆写已声明的环境变量
前面我们提到过,dotenv.config()
遇到已经加载过的环境变量,则会跳过加载,导致后加载的.env*文件中声明的重复环境变量被忽略。
如果我们一定要让后加载的环境变量覆盖已存在的环境变量,就需要显式对原有环境变量进行赋值操作。
举个例子:
const fs = require('fs')
const dotenv = require('dotenv')
// 解析.env.override中的配置项为一个对象
const envConfig = dotenv.parse(fs.readFileSync('.env.override'))
for (const k in envConfig) {
// 强制修改process.env下所有envConfig中存在的属性
process.env[k] = envConfig[k]
}
不过官方并不推荐这样操作,随意修改process.env
的话,后期可能会造成维护的困难。
webpack打包时将环境变量注入前端代码
有时我们希望项目运行时也能取到编译时的环境变量,例如在一个业务代码文件中:
// page.jsx
if (process.env.REACT_APP_NODE_ENV === 'development') {
// 如果运行在development环境,则执行一些操作
}
我们可以直接参考一个createReactApp项目中,React帮我们生成的config/env.js里的操作:
// config/env.js
// ........其他配置........
// 定义规则:REACT_APP_开头的环境变量会被注入前端
// 可以在前端代码中通过process.env.REACT_APP_XXX取到
const REACT_APP = /^REACT_APP_/i;
function getClientEnvironment(publicUrl) {
// 取所有的环境变量,然后过滤出以REACT_APP_开头的环境变量
// 返回一个环境变量配置对象,提供给webpack InterpolateHtmlPlugin插件
const raw = Object.keys(process.env)
.filter((key) => REACT_APP.test(key))
.reduce(
(env, key) => {
env[key] = process.env[key];
return env;
},
{
// 再合并一些自定义的前端环境变量
NODE_ENV: process.env.NODE_ENV || "development",
PUBLIC_URL: publicUrl,
}
);
// 字符串形式的process.env 提供给webpack DefinePlugin使用
// 可以将前端代码中的process.env.XXX替换为对应的实际环境变量值
const stringified = {
"process.env": Object.keys(raw).reduce((env, key) => {
env[key] = JSON.stringify(raw[key]);
return env;
}, {}),
};
return { raw, stringified };
}
module.exports = getClientEnvironment;
对应的webpack打包配置:
// config/webpack.config.js
module.exports = function (webpackEnv) {
// ......其他配置
return {
// ......其他配置
plugins: [
// 使模板html可以取到PUBLIC_URL等环境变量
// 形如<link rel="icon" href="%PUBLIC_URL%/favicon.ico">
new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
// 使前端可以取到process.env.REACT_APP_XXXX 、
// process.env.NODE_ENV等环境变量
new webpack.DefinePlugin(env.stringified),
]
}
}
转载自:https://juejin.cn/post/6993224664705138702