多环境配置
配置是我们常用的功能,而且在不同的环境,经常会使用不同的配置信息。
本篇我们来介绍 Midway 如何加载不同环境的业务配置。
配置文件
最为简单的就是使用框架提供的业务配置文件能力。
该能力可以在所有业务代码和组件中使用,贯穿整个 Midway 生命周期。
配置文件可以以两种格式导出,对象形式 和 函数形式。
经过我们的实践,对象形式 会更加的简单友好,可以规避许多错误用法。
大部分文 档中我们都将以此形式进行展示。
配置文件目录
我们可以自定义一个目录,在其中放入配置文件。
比如 src/config 目录。
➜  my_midway_app tree
.
├── src
│   ├── config
│   │   ├── config.default.ts
│   │   ├── config.prod.ts
│   │   ├── config.unittest.ts
│   │   └── config.local.ts
│   ├── interface.ts
│   └── service
├── test
├── package.json
└── tsconfig.json
配置文件的名字有一些特定的约定。
config.default.ts 为默认的配置文件,所有环境都会加载这个配置文件。
其余的文件名,使用 config.环境 作为文件名,具体环境的概念请查看 运行环境。
配置不是 必选项,请酌情添加自己需要的环境配置。
对象形式
配置文件导出的格式为 object,比如:
// src/config/config.default.ts
import { MidwayConfig } from '@midwayjs/core';
export default {
  keys: '1639994056460_8009',
  koa: {
    port: 7001,
  },
} as MidwayConfig;
函数形式
配置文件为一个带有 appInfo 参数的函数。这个函数在框架初始化时会被自动执行,将返回值合并进完整的配置对象。
// src/config/config.default.ts
import { MidwayAppInfo, MidwayConfig } from '@midwayjs/core';
export default (appInfo: MidwayAppInfo): MidwayConfig => {
  return {
    keys: '1639994056460_8009',
    koa: {
      port: 7001,
    },
    view: {
      root: path.join(appInfo.appDir, 'view'),
    },
  };
}
这个函数的参数为 MidwayAppInfo 类型,值为以下内容。
| appInfo | 说明 | 
|---|---|
| pkg | package.json | 
| name | 应用名,同 pkg.name | 
| baseDir | 应用代码的 src (本地开发)或者 dist (上线后)目录 | 
| appDir | 应用代码的目录 | 
| HOME | 用户目录,如 admin 账户为 /home/admin | 
| root | 应用根目录,只有在 local 和 unittest 环境下为 baseDir,其他都为 HOME。 | 
配置文件定义
Midway 提供了 MidwayConfig 作为统一的配置项定义,所有的组件都会将定义合并到此配置项定义中。每当一个组件被开启(在 configuration.ts 中被 imports ),MidwayConfig 就会自动包含该组件的配置定义。
为此,请尽可能使用文档推荐的格式,以达到最佳的使用效果。
每当启用一个新组件时,配置定义都会自动加入该组件的配置项,通过这个行为,也可以变相的检查是否启用了某个组件。
比如,我们启用了 view 组件的效果。

为什么不使用普通 key 导出形式而使用对象?
1、用户在不了解配置项的情况下,依旧需要查看文档了解每项含义,除了第一层有一定的提示作用外,后面的层级提示没有很明显的效率提升
2、key 导出的形式在过深的结构下展示没有优势
3、key 导出可能会出现重复,但是代码层面不会有警告或者报错,难以排查,这一点对象形式较为友好
对象形式加载配置文件
框架提供了加载不同环境的配置文件的功能,需要在 src/configuration.ts 文件中开启。
配置加载有两种方式,对象形式 和 指定目录形式 加载。
从 Midway v3 开始,我们将以 对象形式 作为主推的配置加载形式。
在单文件构建、ESM 等场景下,只支持这种标准的模块加载方式来加载配置。
每个环境的配置文件 必须显式指定添加,后续框架会根据实际的环境进行合并。
// src/configuration.ts
import { Configuration } from '@midwayjs/core';
import * as DefaultConfig from './config/config.default';
import * as LocalConfig from './config/config.local';
@Configuration({
  importConfigs: [
    {
      default: DefaultConfig,
      local: LocalConfig
    }
  ]
})
export class MainConfiguration {
}
importConfigs 中的数组中传递配置对象,每个对象的 key 为环境,值为环境对应的配置值,midway 在启动中会根据环境来加载对应的配置。
指定目录、文件加载配置
指定加载一个目录,目录里所有的 config.*.ts 都会被扫描加载。
ESM,单文件部署 等方式不支持目录配置加载。
importConfigs  这里只是指定需要加载的文件,实际运行时会自动选择当前的环境来找对应的文件后缀。
配置文件的规则为:
- 1、可以指定一个目录,推荐传统的 src/config目录,也可以指定一个文件
- 2、文件指定无需 ts 后缀
- 3、配置文件 必须显式指定添加
示例:指定目录
// src/configuration.ts
import { Configuration } from '@midwayjs/core';
import { join } from 'path';
@Configuration({
  importConfigs: [
    join(__dirname, './config/'),
  ]
})
export class MainConfiguration {
}
示例:指定特定文件
手动指定一批文件时,这个时候如果文件不存在,则会报错。
// src/configuration.ts
import { Configuration } from '@midwayjs/core';
import { join } from 'path';
@Configuration({
  importConfigs: [
    join(__dirname, './config/config.default'),
    join(__dirname, './config/config.local'),
    join(__dirname, './config/custom.local')		// 可以使用自定义的命名,只要中间部分带环境就行
  ]
})
export class MainConfiguration {
}
也可以使用项目外的配置,但是请使用绝对路径,以及 *.js  后缀。
比如目录结构如下(注意 customConfig.default.js 文件):
 base-app
 ├── package.json
 ├── customConfig.default.js
 └── src
     ├── configuration.ts
     └── config
         └── config.default.ts
// src/configuration.ts
import { Configuration } from '@midwayjs/core';
import { join } from 'path';
@Configuration({
  importConfigs: [
    join(__dirname, './config/'),
    join(__dirname, '../customConfig.default'),
  ]
})
export class MainConfiguration {
}
配置加载顺序
配置存在优先级(应用代码 > 组件),相对于此运行环境的优先级会更高。
比如在 prod 环境加载一个配置的加载顺序如下,后加载的会覆盖前面的同名配置。
-> 组件 config.default.ts
-> 应用 config.default.ts
-> 组件 config.prod.ts
-> 应用 config.prod.ts
配置合并规则
默认会加载 **/config.defaut.ts  的文件以及 **/config.{环境}.ts  文件。
比如,下面的代码在 local 环境会查找 config.default.* 和 config.local.* 文件,如果在其他环境,则只会查找 config.default.* 和 config.{当前环境}.* ,如果文件不存在,则不会加载,也不会报错。
// src/configuration.ts
import { Configuration } from '@midwayjs/core';
import { join } from 'path';
@Configuration({
  importConfigs: [
    join(__dirname, './config/'),
  ]
})
export class MainConfiguration {
}
为了向前兼容,我们对某些特殊环境的配置读取做了一些处理。这里环境的值指的是根据 NODE_ENV 和 MIDWAY_SERVER_ENV 的值综合得出的 结果。
| 环境的值 | 读取的配置文件 | 
|---|---|
| prod | *.default.ts + *.prod.ts | 
| production | *.default.ts + *.production.ts + *.prod.ts | 
| unittest | *.default.ts + *.unittest.ts | 
| test | *.default.ts + *.test.ts + *.unittest.ts | 
除了上述表格外,其余都是 *.default.ts + *.{当前环境}.ts 的值。
此外,配置的合并使用 extend2 模块进行深度拷贝,extend2 fork 自 extend,处理数组时会存在差异。
const a = {
  arr: [ 1, 2 ],
};
const b = {
  arr: [ 3 ],
};
extend(true, a, b);
// => { arr: [ 3 ] }
根据上面的例子,框架直接覆盖数组而不是进行合并。
获取配置
Midway 会将配置都保存在内部的配置服务中,整个结构是一个对象,在 Midway 业务代码使用时,使用 @Config 装饰器注入。