# 来,让我们一起来盘盘 Nodejs 环境变量(process.env)

额,仅仅讨论 process.env 属性,项目上常用的 NODE_ENV 啊,自己定义的 SERVER_PATH、BASEURL、VUE_APP_TITLE 啥的,怎么修改,怎么方便快速切换环境,一起来盘盘

# 首先 process.env 是什么?

官方解释:process 对象是一个全局变量,提供了有关当前 Node.js 进程的信息并对其进行控制;而 process.env 属性会返回包含用户环境的对象,如下所示(官方示例):

{
  TERM: 'xterm-256color',
  SHELL: '/usr/local/bin/bash',
  USER: 'nodejscn',
  PATH: '~/.bin/:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin',
  PWD: '/Users/nodejscn',
  EDITOR: 'vim',
  SHLVL: '1',
  HOME: '/Users/nodejscn',
  LOGNAME: 'nodejscn',
  _: '/usr/local/bin/node'
}
1
2
3
4
5
6
7
8
9
10
11
12

对,它是一个对象,不是我的对象,也不是你的对象~

它是 Nodejs 应用程序中,process.env 属性,返回包含用户环境的对象,所以它不可以在客户端侧代码中使用,简单得来说就是浏览器上,打包后的静态资源。

这时候就有人说了(假如):不对啊,人家在写 Vue 项目的时候,可以在使用啊;比如 VUE_APP_TITLE,可以这样访问:

console.log(process.env.VUE_APP_TITLE)
1

懂的同学马上就懂了,不懂的同学你来看:VUE_APP_TITLE,标重点了,VUE_APP

是不是有那么一丝明悟,对的,在写 Vue 项目时,在客户端侧代码中,只有以 VUE_APP_ 开头的变量会被 webpack.DefinePlugin 静态嵌入到客户端侧的包中,你才可以在应用的代码中访问它们;当然,还有两个特殊的变量也可以在应用中访问:NODE_ENVBASE_URL;来自官方的解释:传送门 (opens new window)

同样在 React 应用中,除了一些内置变量( NODE_ENVPUBLIC_URL )之外,变量名必须以 REACT_APP_ 开头才能使用。

# 如何修改?

要好好的使用 process.env,当然要知道如何修改它,就比如交往了一个女朋友,好吧,我没有女朋友。

言归正传,怎么修改呢?

答:直接赋值。

process.env.girlfriend = 100
console.log(process.env.girlfriend)
1
2

咦,怎么打印出来的是字符串?聪明的同学已经发现了。

我们不推荐这样的行为,不要做渣男,要一心一意。

嗯,我们确实不推荐这样的行为,因为在 process.env 上为属性赋值会隐式地将值转换为字符串:

process.env.girlfriend = true;
console.log(process.env.girlfriend);
// => 'true'

process.env.girlfriend = 1;
console.log(process.env.girlfriend);
// => '1'

process.env.girlfriend = null;
console.log(process.env.girlfriend);
// => 'null'

process.env.girlfriend = undefined;
console.log(process.env.girlfriend);
// => 'undefined'

process.env.girlfriend = {a: 'aa'};
console.log(process.env.girlfriend);
// => '[object Object]'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

并且,当值不是字符串、数字或布尔值时,Node.js 未来的版本可能会抛出错误。

增删改查,如何删呢?直接 delete 就行了:

process.env.girlfriend = 1;
delete process.env.girlfriend
console.log(process.env.girlfriend);
// => undefined
1
2
3
4

注意,在 Windows 操作系统上,环境变量没有大小写之分:

process.env.GIRLFRIEND = 1;
console.log(process.env.girlfriend);
// => 'q'
1
2
3

# 如何方便的切换环境?

在公司中,一个项目一般会有开发版本、测试版本、线上版本,也许还有预上线版本,每个版本对应不同参数,比如请求的 API 地址,或者图像字体资源地址等等;

为了方便管理,我们通常做成配置文件的形式,根据不同的环境,加载不同的文件。毕竟我还要找女朋友,不能太 low 了。

# 修改 package.json 文件

比如我们现在要设置的环境变量 girlfriend

{
  ...,
  "scripts": {
    "normal": "export girlfriend='woman' && node friend.js",
    "sages": "export girlfriend='monster' && node friend.js" // 在windows上export要换成set
  },
  ...
1
2
3
4
5
6
7

npm run normal 就是正常模式

npm run sages 就是贤者模式

现在,我们可以通过配置 package.json 来设置环境变量,就可以在代码中获取对应的值,所以我们就可以很轻松的换环境了。

别急,可以使用 cross-env (opens new window) 依赖包,支持跨平台配置环境变量。 安装依赖:

npm install --save-dev cross-env
1

配置环境

"scripts": {
  "normal": "cross-env girlfriend='woman' node friend.js"
}
1
2
3

这样是不是好很多了~

不对啊,贤者模式有很多种情况啊,比如时长、地点等等,一大串的拼在后面也很 low。

其实有多种方法来解决这个问题,比如写个 env 模块

// env.js
module.exports = {
  'normal': {
    // 正常模式下的
  },
  // 其他模式
}
1
2
3
4
5
6
7

然后不同命令加载不同对象,进而修改环境变量:

const envConfig = require('env.js')[process.env.NODE_ENV];
if (Object.prototype.toString.call(envConfig) === '[object Object]') {
  for (const k in envConfig) {
    process.env[k] = envConfig[k]
  }
}

1
2
3
4
5
6
7

当然还有很多种方式。

在这里,我推荐使用 .env文件 结合 dotenv 依赖包的方式来实现。

好了,我要正经起来了。

# 关于 dotenv

dotenv 是一个零依赖包,可将环境变量从 .env 文件加载到 process.env;其原理是将 .env 文件解析为 json 对象,并对其中的 key-value 对通过 process.env 将其赋值为环境变量,之后便可通过 process.env[key] 来使用。

基本使用流程如下

# 安装

# with npm
npm install dotenv

# or with Yarn
yarn add dotenv
1
2
3
4
5

# 使用

require('dotenv').config() // 默认是.env文件
1

它会把项目根目录下的 .env 文件内容加载到 process.env

我们也可以使用它的 Parse 方法,拿到一个 json 对象

const fs = require('fs')
const dotenv = require('dotenv')
const config = dotenv.parse(fs.readFileSync('.env')) // will return an object
console.log(typeof config, config) // object { girlfriend : 'woman' }
1
2
3
4

更多相关内容,请见:传送门 (opens new window)

# 分享一下我的方案

首先,不同环境对应不同 .env 文件,如:.env.beta.env.prod

.env 文件

NODE_ENV = 'development'
HOST = 'lacalhost'
PORT = 3000
MONGODB_HOST = '127.0.0.1'
MONGODB_USERNAME = 'root'
MONGODB_PASSWORD = '123'
1
2
3
4
5
6

.env.prod 文件

NODE_ENV = 'production'
HOST = '10.100.xx.xx'
PORT = 3000
MONGODB_HOST = '43.247.xx.xx'
MONGODB_USERNAME = 'root'
MONGODB_PASSWORD = 'xxxxxx'
1
2
3
4
5
6

之后在 scripts 命令中,使用 --mode 参数来区分不同环境,并与相关文件名对应;

package.json 文件

{
  ...,
  "scripts": {
    "dev": "cross-env nodemon server/index.js --watch",
    "prod": "cross-env node server/index.js --mode prod"
  },
  ...
}
1
2
3
4
5
6
7
8

然后在执行文件中,拿到命令参数 process.argv,并根据不同 mode 来加载不同 .env 文件,这样,我们就可以很轻松的换环境了;

server/index.js 文件

const dotenv = require('dotenv')
const argv = process.argv
const mode = argv[2] === '--mode' ? argv[3] : ''
const envFile = mode ? `.env.${mode}` : '.env'
dotenv.config({ path: envFile })
1
2
3
4
5

问题,声明命令参数必须统一,这样通过 process.argv 拿到的参数才能正确。比如:

// package.json
"scripts": {
  "dev": "cross-env nodemon server/index.js --watch --mode dev"
}

// server/index.js
console.log(process.argv)
// => ['xxx', 'xx', 'dev']
1
2
3
4
5
6
7
8

如果,要按照我这样的方式拿到参数,必须这样声明:

// package.json
"scripts": {
  "dev": "cross-env nodemon server/index.js --mode dev --watch"
}
1
2
3
4

关于 process.argv 方面的内容,我不是特别明白,若有错误请见谅。

# 最后

新的一年,新的开始,希望大家在新的一年都能找到自己 对象

本文完。