Nest.js 延迟加载模块
默认情况下,模块是实时加载的,这意味着一旦应用程序加载,所有模块也会加载,无论它们是否立即需要。 虽然这对大多数应用程序来说都没有什么问题,但它可能成为在无服务器环境中运行的应用程序的瓶颈,其中启动延迟(“冷启动”)就显得至关重要。
延迟加载可以通过仅加载特定无服务器函数调用所需的模块来帮助减少启动时间。 此外,我们还可以在无服务器功能启动后异步加载其他模块,从而进一步加快后续调用的引导时间(延迟模块注册)。
提示
:如果我们熟悉 Angular 框架,可能以前见过“延迟加载模块”这个术语。 请注意,此技术在 Nest 中的功能有些不一样,因此请将此视为具有相似命名约定的完全不同的功能。
开始
为了按需加载模块,Nest 提供了 LazyModuleLoader 类,可以以正常方式注入到类中:
@Injectable()
export class CatsService {
constructor(private lazyModuleLoader: LazyModuleLoader) {}
}
提示
: LazyModuleLoader 类是从 @nestjs/core 包中导入的。
或者,我们可以从应用程序引导文件 (main.ts
) 中获取对 LazyModuleLoader 提供程序的引用,如下所示:
// "app" 表示一个 Nest 应用程序实例
const lazyModuleLoader = app.get(LazyModuleLoader);
有了这个,我们现在可以使用以下结构加载任何模块:
const { LazyModule } = await import('./lazy.module');
const moduleRef = await this.lazyModuleLoader.load(() => LazyModule);
提示
: “延迟加载”模块在第一次 LazyModuleLoader#load 方法调用时被缓存。 这意味着,每次连续尝试加载 LazyModule 都会非常快,并且会返回一个缓存的实例,而不是再次加载模块。Load "LazyModule" attempt: 1 time: 2.379ms Load "LazyModule" attempt: 2 time: 0.294ms Load "LazyModule" attempt: 3 time: 0.303ms
此外,“延迟加载”模块与那些在应用程序引导程序上及时加载的模块以及稍后在我们的应用程序中注册的任何其他延迟模块共享相同的模块图。
其中 lazy.module.ts
是导出常规 Nest 模块的 TypeScript 文件(无需额外更改)。
LazyModuleLoader#load
方法返回模块引用(LazyModule 的),它允许我们导航提供程序的内部列表并使用其注入令牌作为查找键获取对任何提供程序的引用。
例如,假设我们有一个具有以下定义的 LazyModule:
@Module({
providers: [LazyService],
exports: [LazyService],
})
export class LazyModule {}
提示
:延迟加载的模块不能注册为全局模块,因为它根本没有意义(因为它们是延迟注册的,当所有静态注册的模块都已经实例化时按需注册)。 同样,注册的全局增强器(守卫/拦截器/等)也无法正常工作。
有了这个,我们可以获得对 LazyService 提供者的引用,如下所示:
const { LazyModule } = await import('./lazy.module');
const moduleRef = await this.lazyModuleLoader.load(() => LazyModule);
const { LazyService } = await import('./lazy.service');
const lazyService = moduleRef.get(LazyService);
警告
: 如果使用 Webpack,请确保更新 tsconfig.json 文件 - 将 compilerOptions.module 设置为“esnext”并添加带有“node”作为值的 compilerOptions.moduleResolution 属性:{ "compilerOptions": { "module": "esnext", "moduleResolution": "node", ... } }
设置这些选项后,我们将能够利用代码拆分功能。
延迟加载控制器、网关和解析器
由于 Nest 中的控制器(或 GraphQL 应用程序中的解析器)表示一组路由/路径/主题(或查询/突变),因此您不能使用 LazyModuleLoader 类延迟加载它们。
例如,假设我们正在构建一个带有 Fastify 驱动程序的 REST API(HTTP 应用程序)(使用 @nestjs/platform-fastify 包)。 Fastify 不允许我们在应用程序准备好/成功监听消息后注册路由。这意味着即使我们分析了在模块控制器中注册的路由映射,所有延迟加载的路由都无法访问,因为无法在运行时注册它们。
同样,我们作为 @nestjs/microservices
包(包括 Kafka、gRPC 或 RabbitMQ)的一部分提供的一些传输策略需要在建立连接之前订阅/侦听特定主题/通道。一旦应用程序开始接收消息,框架将无法订阅/收听新主题。
最后,启用了代码优先方法的 @nestjs/graphql
包会根据元数据自动动态生成 GraphQL 模式。这意味着,它需要预先加载所有类。否则,将无法创建适当的有效模式。
常见用例
最常见的情况是,当我们的 worker/cron
job/lambda
和无服务器函数/webhook 必须根据输入参数(路由路径/日期/查询参数等)触发不同的服务(不同的逻辑)时,我们会看到延迟加载的模块。 另一方面,延迟加载模块对于单体应用程序可能没有太大意义,在这些应用程序中,启动时间相当无关紧要。