nodejs中的config(node-config)组件应用

最近在使用Nodejs做一些后端服务的时候,由于业务不复杂,所以在连接RedisMysql的时候总是哪里遇到了写哪里,结果可想而知,在服务端部署的时候,那是“痛彻心扉”各种位置的配置参数修改,这样是在开始时候图省事反而造成的“恶果”

突然想起,在很早的时候用过的node-confg(config)模块,可以直接根据环境变量的设置,来读取不同的配置文件,从而实现运行时的动态切换,接下来对最新版的config组件进行下测试使用。

0x00 config模块的安装及初步使用

NPM地址:https://www.npmjs.com/package/config
仓库地址:https://github.com/node-config/node-config

1. 创建测试项目

首先,新建一个空的目录,在目录中创建nodejs项目

1
2
3
4
#任选一个自己常用的执行,然后一路回车直到创建完成。
$ npm init
$ yarn init
$ pnpm init

创建完成会自动生成package.json文件,接着安装config模块

1
2
3
4
#任选一个自己常用的执行
$ npm i config
$ yarn add config
$ pnpm i config

我当前安装的config模块版本是3.3.9,整体的package.json文件如下

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"name": "configtest",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
},
"author": "",
"license": "ISC",
"dependencies": {
"config": "^3.3.9"
}
}

2. 基础应用

在项目根创建config文件夹,并在文件夹中创建default.jsondevelopment.jsonproduction.json文件,内容如下

config/default.json
1
2
3
4
5
6
{
"server": {
"name": "dict-server",
"port": 8080
}
}
config/development.json
1
2
3
4
5
{
"server": {
"port": 8081
}
}
config/production.json
1
2
3
4
5
{
"server": {
"port": 8082
}
}

接着,在项目根目录创建src文件夹,并创建我们的测试脚本,比如app.js文件

src/app.js
1
2
3
4
5
6
7
const config = require('config'); 

const name = config.get('server.name');
const port = config.get('server.port');
console.log("当前环境:", process.env.NODE_ENV);
console.log("当前服务:", name);
console.log("启动端口:", port);

最后,在项目根目录执行node src/app.js,可以看到如下输出

输出结果
1
2
3
当前环境: undefined
当前服务: dict-server
启动端口: 8081

切换环境变量来进行配置切换,重新执行命令
(windows执行)set NODE_ENV=production&node src/app.js
(linux/mac执行)export NODE_ENV=production && node src/app.js

输出如下

输出结果
1
2
3
当前环境: production
当前服务: dict-server
启动端口: 8082

至此,一个简单的示例就完成了。

0x01 config模块介绍

通过第一步的示例,我们可以改变环境变量NODE_ENV的赋值,来切换程序读取不同配置。

我们总共在config目录中创建了三个配置文件,分别是

  1. default:
    基础配置文件,作为兜底配置,不管NODE_ENV设置什么,default也都会加载,但是会被相同的覆盖掉。
    就像示例中的server.name参数,在NODE_ENV=production时,production.json并没有配置server.name参数,但是也能正常获取到。

  2. development
    开发配置,默认配置,当NODE_ENV未配置时,会使用该配置。

  3. production
    生产配置,通过NODE_ENV=production来启用配置。

当然,我们也还可以定义其他的文件名,来作为环境配置,只要NODE_ENV的取值对应的文件是存在的,都可以被加载解析。

同时,config模块,还可以使用以下文件来作为配置文件:

支持的配置文件格式
1
2
3
4
5
6
7
8
9
10
11
.json
.json5
.hjson
.yaml or .yml
.coffee
.js
.cson
.properties
.toml
.ts
.xml

0x02 config模块代码及进阶使用

1. 配置文件默认加载顺序

通过阅读源码,我们可以看到loadFileConfigs函数,用来初始化并加载配置文件

node_modules/config/lib/config.js
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
/**
* Load the individual file configurations.
*
* <p>
* This method builds a map of filename to the configuration object defined
* by the file. The search order is:
* </p>
*
* <!-- 这里可以看到搜索配置文件及加载顺序 -->
* <pre>
* default.EXT
* (deployment).EXT
* (hostname).EXT
* (hostname)-(deployment).EXT
* local.EXT
* local-(deployment).EXT
* runtime.json
* </pre>
*
* <p>
* EXT can be yml, yaml, coffee, iced, json, cson or js signifying the file type.
* yaml (and yml) is in YAML format, coffee is a coffee-script, iced is iced-coffee-script,
* json is in JSON format, cson is in CSON format, properties is in .properties format
* (http://en.wikipedia.org/wiki/.properties), and js is a javascript executable file that is
* require()'d with module.exports being the config object.
* </p>
*
* <p>
* hostname is the $HOST environment variable (or --HOST command line parameter)
* if set, otherwise the $HOSTNAME environment variable (or --HOSTNAME command
* line parameter) if set, otherwise the hostname found from
* require('os').hostname().
* </p>
*
* <p>
* Once a hostname is found, everything from the first period ('.') onwards
* is removed. For example, abc.example.com becomes abc
* </p>
*
* <p>
* (deployment) is the deployment type, found in the $NODE_ENV environment
* variable (which can be overridden by using $NODE_CONFIG_ENV
* environment variable). Defaults to 'development'.
* </p>
*
* <p>
* The runtime.json file contains configuration changes made at runtime either
* manually, or by the application setting a configuration value.
* </p>
*
* <p>
* If the $NODE_APP_INSTANCE environment variable (or --NODE_APP_INSTANCE
* command line parameter) is set, then files with this appendage will be loaded.
* See the Multiple Application Instances section of the main documentation page
* for more information.
* </p>
*
* @protected
* @method loadFileConfigs
* @param configDir { string | null } the path to the directory containing the configurations to load
* @param options { object | undefined } parsing options. Current supported option: skipConfigSources: true|false
* @return config {Object} The configuration object
*/
util.loadFileConfigs = function(configDir, options) {

// Initialize
var t = this,
config = {};

// Specify variables that can be used to define the environment
var node_env_var_names = ['NODE_CONFIG_ENV', 'NODE_ENV'];

// Loop through the variables to try and set environment
for (const node_env_var_name of node_env_var_names) {
NODE_ENV = util.initParam(node_env_var_name);
if (!!NODE_ENV) {
NODE_ENV_VAR_NAME = node_env_var_name;
break;
}
}

// If we haven't successfully set the environment using the variables, we'll default it
if (!NODE_ENV) {
//当未配置的时候,默认使用
NODE_ENV = 'development';
}

node_env_var_names.forEach(node_env_var_name => {
env[node_env_var_name] = NODE_ENV;
});

// Split files name, for loading multiple files.
// 可以看到NODE_ENV按照,进行了拆分,则设置NODE_ENV时可以指定多个配置文件,比如NODE_ENV=prod,swagger
NODE_ENV = NODE_ENV.split(',');

//在源码中,configDir参数未设置,所以,会使用 process.cwd()/config 来作为配置目录
var dir = configDir || util.initParam('NODE_CONFIG_DIR', Path.join( process.cwd(), 'config') );
dir = _toAbsolutePath(dir);

(1). 未指定时,默认加载development配置。
(2). 环境变量可以指定(用,隔开的)多个配置值,比如同时加载prod.json、swagger.json配置文件。
(3). 加载的是进程根目录的config目录下的配置文件。
(4). 会初始化配置文件加载顺序,后加载的配置项会覆盖前面的,加载顺序如下所示
文件加载列表

2. 支持的配置文件的使用

parser.js源码中,可以看到支持的配置文件列表

node_modules/config/parser.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var order = ['js', 'cjs', 'ts', 'json', 'json5', 'hjson', 'toml', 'coffee', 'iced', 'yaml', 'yml', 
'cson', 'properties', 'xml', 'boolean', 'number'];
var definitions = {
cjs: Parser.jsParser,
coffee: Parser.coffeeParser,
cson: Parser.csonParser,
hjson: Parser.hjsonParser,
iced: Parser.icedParser,
js: Parser.jsParser,
json: Parser.jsonParser,
json5: Parser.json5Parser,
properties: Parser.propertiesParser,
toml: Parser.tomlParser,
ts: Parser.tsParser,
xml: Parser.xmlParser,
yaml: Parser.yamlParser,
yml: Parser.yamlParser,
boolean: Parser.booleanParser,
number: Parser.numberParser
};

我们随便选个常用的,比如yml配置文件的加载,看下

node_modules/config/parser.js@Parser.yamlParser
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Parser.yamlParser = function(filename, content) {
if (!Yaml && !VisionmediaYaml) {
// Lazy loading
try {
// Try to load the better js-yaml module
Yaml = require(JS_YAML_DEP);
}
catch (e) {
try {
// If it doesn't exist, load the fallback visionmedia yaml module.
VisionmediaYaml = require(YAML_DEP);
}
catch (e) { }
}
}
if (Yaml) {
return Yaml.load(content);
}
…………
}

在函数中,可以看到会通过Yaml = require(JS_YAML_DEP);来完成解析器加载,而JS_YAML_DEP的值定义为js-yaml

node_modules/config/parser.js@变量定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var Yaml = null,
VisionmediaYaml = null,
Coffee = null,
Iced = null,
CSON = null,
PPARSER = null,
JSON5 = null,
TOML = null,
HJSON = null,
XML = null;

// Define soft dependencies so transpilers don't include everything
var COFFEE_2_DEP = 'coffeescript',
COFFEE_DEP = 'coffee-script',
ICED_DEP = 'iced-coffee-script',
JS_YAML_DEP = 'js-yaml',
YAML_DEP = 'yaml',
JSON5_DEP = 'json5',
HJSON_DEP = 'hjson',
TOML_DEP = 'toml',
CSON_DEP = 'cson',
PPARSER_DEP = 'properties',
XML_DEP = 'x2js',
TS_DEP = 'ts-node';

在变量的定义中看到JS_YAML_DEP = 'js-yaml',,则想通过config加载XXXX.yml配置文件,则必须也要安装js-yaml依赖

安装js-yaml依赖,用来读取yml配置文件
1
2
3
4
#任选一个自己常用的执行
$ npm i js-yaml
$ yarn add js-yaml
$ pnpm i js-yaml

这样,就可以在配置文件中使用default.yml来进行参数配置了

config/default.yml
1
2
3
#服务配置
server:
port: 8080