https://docs.3rcd.com/nestjs/chapter17
这个章节看了好久,一直没有理解配置生产的逻辑,所以花时间好好梳理了一遍,以供参考。
1、代码入口
// src/main.ts
import { createApp, listened, startApp } from "./modules/core/helpers/app";
import { createOptions } from "./options";
startApp(createApp(createOptions), listened);
1、createOptions
createOptions
参数类型是接口CreateOptions
,主要有以下几个变量
属性 | 类型 | 必选 |
modules | (configure: Configure) => Promise<Required<ModuleMetadata[‘imports’]>> | ✅ |
builder | ContainerBuilder | ✅ |
globals | Object | ❌ |
providers | ModuleMetadata[‘providers’] | ❌ |
config | Object | ✅ |
CreateOptions.globals
属性 | 类型 | 必选 |
pipe | (configure: Configure) => PipeTransform<any> | null | ❌ |
interceptor | Type<any> | null | ❌ |
filter | Type<any> | null | ❌ |
CreateOptions.config
属性 | 类型 | 必选 |
factories | Record<string, ConfigureFactory<RecordAny>> | ✅ |
storage | ConfigStorageOption | ✅ |
// src/options.ts
export const createOptions: CreateOptions = {};
// src/modules/core/types.ts
export interface CreateOptions {
modules: (
configure: Configure,
) => Promise<Required<ModuleMetadata["imports"]>>;
builder: ContainerBuilder;
globals?: {
pipe?: (configure: Configure) => PipeTransform<any> | null;
interceptor?: Type<any> | null;
filter?: Type<any> | null;
};
providers?: ModuleMetadata["providers"];
config: {
factories: Record<string, ConfigureFactory<RecordAny>>;
storage: ConfigStorageOption;
};
}
export interface ModuleMetadata {
imports?: Array<
Type<any> | DynamicModule | Promise<DynamicModule> | ForwardReference
>;
controllers?: Type<any>[];
providers?: Provider[];
exports?: Array<
| DynamicModule
| Promise<DynamicModule>
| string
| symbol
| Provider
| ForwardReference
| Abstract<any>
| Function
>;
}
export interface ContainerBuilder {
(params: {
configure: Configure;
BootModule: Type<any>;
}): Promise<NestFastifyApplication>;
}
ConfigStorageOption
export interface ConfigStorageOption {
enable?: boolean;
filePath?: string;
}
属性 | 类型 | 必选 |
enable | boolean | ❌ |
filePath | string | ❌ |
ConfigureFactory
export interface ConfigureFactory<
T extends RecordAny,
P extends RecordAny = T,
> {
register: ConfigureRegister<RePartial<T>>;
defaultRegister?: ConfigureRegister<T>;
storage?: boolean;
hook?: (configure: Configure, value: T) => P | Promise<P>;
append?: boolean;
}
属性 | 类型 | 必选 |
register | ConfigureRegister<RePartial<T>> | ✅ |
defaultRegister | ConfigureRegister<T> | ❌ |
storage | boolean | ❌ |
hook | (configure: Configure, value: T) => P | Promise<P> | ❌ |
append | boolean | ❌ |
class Configure
是关键类
interface SetStorageOption {
enabled?: boolean;
change?: boolean;
}
export class Configure {
protected inited = false;
protected factories: Record<string, ConfigureFactory<RecordAny>> = {};
protected config: RecordAny = {};
protected _env: Env;
protected storage: ConfigStorage;
get env() {
return this._env;
}
all() {
return this.config;
}
has(key: string) {
return has(this.config, key);
}
async initialize(configs: RecordAny = {}, options: ConfigStorageOption = {}) {
if (this.inited) {
return this;
}
this._env = new Env();
await this._env.load();
const { enable, filePath } = options;
this.storage = new ConfigStorage(enable, filePath);
for (const key of Object.keys(configs)) {
this.add(key, configs[key]);
}
await this.sync();
this.inited = true;
return this;
}
async get<T>(key: string, defaultValue?: T): Promise<T> {
if (
!has(this.config, key) &&
defaultValue === undefined &&
has(this.factories, key)
) {
await this.syncFactory(key);
return this.get(key, defaultValue);
}
return get(this.config, key, defaultValue);
}
set<T>(
key: string,
value: T,
storage: SetStorageOption | boolean = false,
append = false,
) {
const storageEnable =
typeof storage === "boolean" ? storage : !!storage.enabled;
const storageChange =
typeof storage === "boolean" ? false : !!storage.change;
if (storageEnable && this.storage.enabled) {
this.changeStorageValue(key, value, storageChange, append);
} else {
set(this.config, key, value);
}
return this;
}
async sync(name?: string) {
if (isNil(name)) {
for (const key in this.factories) {
await this.syncFactory(key);
}
} else {
await this.syncFactory(name);
}
}
protected async syncFactory(key: string) {
if (has(this.config, key) || !has(this.factories, key)) {
return this;
}
const { register, defaultRegister, storage, hook, append } =
this.factories[key];
let defaultValue = {};
let value = isAsyncFunction(register)
? await register(this)
: register(this);
if (!isNil(defaultRegister)) {
defaultValue = isAsyncFunction(defaultRegister)
? await defaultRegister(this)
: defaultRegister(this);
value = deepMerge(defaultValue, value, "replace");
}
if (!isNil(hook)) {
value = isAsyncFunction(hook)
? await hook(this, value)
: hook(this, value);
}
if (this.storage.enabled) {
value = deepMerge(
value,
get(this.storage.config, key, isArray(value) ? [] : {}),
);
}
this.set(key, value, storage && isNil(await this.get(key, null)), append);
return this;
}
add<T extends RecordAny>(
key: string,
register: ConfigureFactory<T> | ConfigureRegister<T>,
) {
if (!isFunction(register) && "register" in register) {
this.factories[key] = register as any;
} else if (isFunction(register)) {
this.factories[key] = { register };
}
return this;
}
remove(key: string) {
if (this.storage.enabled && has(this.storage.config, key)) {
this.storage.remove(key);
this.config = deepMerge(this.config, this.storage.config, "replace");
} else if (has(this.config, key)) {
this.config = omit(this.config, [key]);
}
return this;
}
async store(key: string, change = false, append = false) {
if (!this.storage.enabled) {
throw new Error("Must enable storage first");
}
this.changeStorageValue(key, await this.get(key, null), change, append);
return this;
}
protected changeStorageValue<T>(
key: string,
value: T,
change = false,
append = false,
) {
if (change || !has(this.storage.config, key)) {
this.storage.set(key, value);
} else if (isObject(get(this.storage.config, key))) {
this.storage.set(
key,
deepMerge(
value,
get(this.storage.config, key),
append ? "merge" : "replace",
),
);
}
this.config = deepMerge(
this.config,
this.storage.config,
append ? "merge" : "replace",
);
}
}
createOptions
是一个CreateOptions
类型的变量,从config目录下获取所有的自定义的配置文件的信息
import * as configs from './config'
config: { factories: configs as any, storage: { enable: true } },
同时定义了modules
函数和builder
函数,modules
获取了所有的模块信息,builder
则用来创建一个NestFastifyApplication
modules: async (configure) => [
DatabaseModule.forRoot(configure),
MeiliModule.forRoot(configure),
ContentModule.forRoot(configure),
CoreModule.forRoot(configure),
],
builder: async ({ configure, BootModule }) => {
const container = await NestFactory.create<NestFastifyApplication>(
BootModule,
new FastifyAdapter(),
{
cors: true,
logger: ['error', 'warn'],
},
);
return container;
},
2、createApp
createApp
是一个函数,函数的入参是类型是CreateOptions
,返回结果是一个一步函数async (): Promise<App>
2.1 initialize
initialize
方法是整个配置系统的初始化入口。
async initialize(configs: RecordAny = {}, options: ConfigStorageOption = {}) {
if (this.inited) {
return this;
}
this._env = new Env();
await this._env.load();
const { enable, filePath } = options;
this.storage = new ConfigStorage(enable, filePath);
for (const key of Object.keys(configs)) {
this.add(key, configs[key]);
}
await this.sync();
this.inited = true;
return this;
}
2.1.1 防重复初始化检查
if (this.inited) {
return this;
}
- 检查
this.inited
标志
- 如果已经初始化过,直接返回,避免重复初始化
2.1.2 环境变量系统初始化
this._env = new Env();
await this._env.load();
2.1.3 配置存储系统初始化
const { enable, filePath } = options;
this.storage = new ConfigStorage(enable, filePath);
- 从
options
中解构存储配置
- 创建
ConfigStorage
实例
enable
: 是否启用配置存储
filePath
: 存储文件路径(可选)
- 读取到的配置项保存在
ConfigStorage._config
中
2.1.4 注册配置工厂
for (const key of Object.keys(configs)) {
this.add(key, configs[key]);
}
- 遍历传入的
configs
对象
- 为每个配置调用
add
方法注册到 this.factories
add<T extends RecordAny>(key: string, register: ConfigureFactory<T> | ConfigureRegister<T>) {
if (!isFunction(register) && 'register' in register) {
this.factories[key] = register as any;
} else if (isFunction(register)) {
this.factories[key] = { register };
}
return this;
}
- 如果是完整的
ConfigureFactory
对象,直接存储
- 如果只是函数,包装成
{ register: 函数 }
格式
2.1.5 同步所有配置工厂
await this.sync();
// ...
async sync(name?: string) {
if (isNil(name)) {
for (const key in this.factories) {
await this.syncFactory(key);
}
} else {
await this.syncFactory(name);
}
}
- 如果没有指定
name
,同步所有工厂
- 为每个工厂调用
syncFactory
方法
- 这会将配置工厂转换为实际配置值
syncFactory
将一个配置工厂(ConfigureFactory
)同步为实际的配置对象,并存储到 this.config
中。
if (has(this.config, key) || !has(this.factories, key)) {
return this;
}
- 如果配置已存在 (
this.config
中有该 key)
- 或者工厂不存在 (
this.factories
中没有该 key)
- 则直接返回,不需要同步
const { register, defaultRegister, storage, hook, append } = this.factories[key];
从配置工厂中提取:
register
: 主要配置注册函数
defaultRegister
: 默认配置注册函数
storage
: 是否启用存储
hook
: 配置处理钩子
append
: 是否追加模式
let value = isAsyncFunction(register) ? await register(this) : register(this);
- 检查
register
是否为异步函数
- 如果是异步,使用
await
调用
- 如果是同步,直接调用
- 传入
this
(Configure 实例)作为参数
if (!isNil(defaultRegister)) {
defaultValue = isAsyncFunction(defaultRegister)
? await defaultRegister(this)
: defaultRegister(this);
value = deepMerge(defaultValue, value, 'replace');
}
- 如果存在默认配置函数,执行它
- 将默认配置与用户配置深度合并
replace
模式:用户配置覆盖默认配置
if (!isNil(hook)) {
value = isAsyncFunction(hook) ? await hook(this, value) : hook(this, value);
}
- 如果存在钩子函数,执行它
- 钩子可以对合并后的配置进行后处理
- 传入 Configure 实例和当前配置值
if (this.storage.enabled) {
value = deepMerge(value, get(this.storage.config, key, isArray(value) ? [] : {}));
}
- 如果启用了配置存储
- 从存储中读取配置并合并
- 存储配置优先级最高
this.set(key, value, storage && isNil(await this.get(key, null)), append);
- 调用
set
方法保存配置
- 第三个参数:是否写入存储(当 storage 为 true 且当前没有该配置时)
- 第四个参数:是否追加模式
配置优先级(从低到高)
- 默认配置 (
defaultRegister
)
- 用户配置 (
register
)
- 钩子处理 (
hook
)
- 存储配置 (
this.storage.config
)
2.1.6 标记初始化完成
this.inited = true;
return this;
- 设置初始化标志为
true
- 返回
this
支持链式调用
—
initialize() 开始
│
├─> 检查是否已初始化 (this.inited)
│ └─> 如果已初始化,直接返回
│
├─> 创建并加载环境变量系统
│ ├─> new Env()
│ └─> await env.load() // 加载 .env 文件
│
├─> 初始化配置存储系统
│ └─> new ConfigStorage(enable, filePath)
│
├─> 注册所有配置工厂
│ ├─> 遍历 configs 对象
│ └─> 调用 add() 方法注册到 this.factories
│
├─> 同步所有配置工厂
│ ├─> 调用 sync() 方法
│ ├─> 遍历 this.factories
│ └─> 为每个工厂调用 syncFactory()
│ ├─> 执行 register 函数
│ ├─> 执行 defaultRegister 函数
│ ├─> 深度合并配置
│ ├─> 执行 hook 函数
│ ├─> 合并存储配置
│ └─> 保存到 this.config
│
└─> 标记初始化完成 (this.inited = true)
初始化后的状态
初始化完成后,Configure
实例包含:
this._env
: 环境变量管理器
this.storage
: 配置存储管理器
this.factories
: 所有注册的配置工厂
this.config
: 所有同步后的实际配置值
this.inited = true
: 初始化完成标志
2.2 createBootModule
const BootModule = await createBootModule(app.configure, options);
- 调用
createBootModule
函数创建引导模块
- 传入配置实例和选项
- 这个模块将作为应用的根模块
- 收集所有模块:
options.modules
、ConfigModule
、CoreModule
- 处理模块元数据和全局标记
- 添加全局管道、拦截器和过滤器
- 创建并返回
BootModule
2.3 构建应用容器
app.container = await builder({ configure: app.configure, BootModule });
- 调用
builder
函数创建 NestJS 应用容器
- 传入配置实例和引导模块
- 将创建的容器保存到
app.container
builder 实现
builder: async ({ configure, BootModule }) => {
const container = await NestFactory.create<NestFastifyApplication>(
BootModule,
new FastifyAdapter(),
{
cors: true,
logger: ['error', 'warn'],
},
);
return container;
},
- 使用 NestFactory.create 创建 Nest 应用
- 使用 FastifyAdapter 作为 HTTP 适配器
- 配置 CORS 和日志级别
2.4 设置全局前缀
if (app.configure.has('app.prefix')) {
app.container.setGlobalPrefix(await app.configure.get<string>('app.prefix'));
}
- 检查是否配置了
app.prefix
- 如果有,设置全局 API 前缀
- 例如:
/api/posts
而不是 /posts
2.5 配置依赖注入容器
useContainer(app.container.select(BootModule), { fallbackOnErrors: true });
- 配置 class-validator 使用 Nest 的依赖注入容器
- 这允许在验证器中注入服务
fallbackOnErrors: true
表示在错误时回退到默认容器
2.6 返回应用实例
- 返回
app
对象,包含:
-
container
: NestFastifyApplication 实例
-
configure
: Configure 实例
完整流程图
createApp(options) 开始
│
└─> 返回异步函数 async (): Promise<App> => {
│
├─> 解构 options: { config, builder }
│
├─> 初始化配置系统
│ └─> await app.configure.initialize(config.factories, config.storage)
│ ├─> 加载环境变量
│ ├─> 初始化配置存储
│ ├─> 注册配置工厂
│ └─> 同步所有配置
│
├─> 检查必要配置
│ └─> 如果 !app.configure.has('app'),抛出异常
│
├─> 创建引导模块
│ └─> await createBootModule(app.configure, options)
│ ├─> 收集所有模块
│ │ ├─> 用户模块
│ │ ├─> ConfigModule.forRoot()
│ │ └─> CoreModule.forRoot()
│ │
│ ├─> 处理模块元数据
│ │
│ ├─> 添加全局提供者
│ │ ├─> 全局管道 (APP_PIPE)
│ │ ├─> 全局拦截器 (APP_INTERCEPTOR)
│ │ └─> 全局过滤器 (APP_FILTER)
│ │
│ └─> 创建 BootModule
│
├─> 构建应用容器
│ └─> app.container = await builder({ configure, BootModule })
│ └─> NestFactory.create(BootModule, new FastifyAdapter(), options)
│
├─> 设置全局前缀
│ └─> 如果有 app.prefix,设置全局 API 前缀
│
├─> 配置依赖注入容器
│ └─> useContainer(app.container.select(BootModule), { fallbackOnErrors: true })
│
└─> 返回 app 对象
}