延迟加载模块 Lazy loading modules

默认情况下,模块是主动加载的,这意味着只要应用程序加载,所有模块也会加载,无论它们是否立即需要。虽然这对于大多数应用程序来说都没问题,但它可能会成为在**无服务器环境**中运行的应用程序/工作器的瓶颈,因为启动延迟(`冷启动`)至关重要。

延迟加载可以通过仅加载特定无服务器函数调用所需的模块来帮助减少引导时间。此外,您还可以在无服务器函数热身后异步加载其他模块,以进一步加快后续调用的引导时间(延迟模块注册)。

提示

如果您熟悉**Angular框架,您可能之前见过[延迟加载模块](https://angular.dev/guide/ngmodules/lazy-loading#lazy-loading-basics)这个术语。请注意,此技术在 Nest 中功能不同**,因此请将其视为具有类似命名约定的完全不同的功能。

警告

请注意,生命周期钩子方法 不会在延迟加载的模块和服务中调用。

入门

为了按需加载模块,Nest 提供了 LazyModuleLoader 类,可以以正常方式将其注入到类中:

cats.service
ts
@Injectable()
export class CatsService {
  constructor(private lazyModuleLoader: LazyModuleLoader) {}
}
js
@Injectable()
@Dependencies(LazyModuleLoader)
export class CatsService {
  constructor(lazyModuleLoader) {
    this.lazyModuleLoader = lazyModuleLoader;
  }
}
提示

LazyModuleLoader 类从 @nestjs/core 包导入。

或者,您可以从应用程序引导文件(main.ts)中获取对 LazyModuleLoader 提供程序的引用,如下所示:

ts
// "app" represents a Nest application instance
const lazyModuleLoader = app.get(LazyModuleLoader)

有了这个,您现在可以使用以下结构加载任何模块:

ts
const { LazyModule } = await import('./lazy.module')
const moduleRef = await this.lazyModuleLoader.load(() => LazyModule)
提示

延迟加载模块在第一次调用 LazyModuleLoader#load 方法时被缓存。这意味着,每次连续尝试加载 LazyModule 都会非常快,并且会返回一个缓存实例,而不是再次加载模块。

bash
Load "LazyModule" attempt: 1
time: 2.379ms
Load "LazyModule" attempt: 2
time: 0.294ms
Load "LazyModule" attempt: 3
time: 0.303ms

此外,延迟加载模块与应用程序引导程序中急切加载的模块以及应用程序中稍后注册的任何其他延迟模块共享相同的模块图。

其中 lazy.module.ts 是一个 TypeScript 文件,它导出一个 常规 Nest 模块(无需额外更改)。

LazyModuleLoader#load 方法返回 模块引用LazyModule 的),它允许您浏览内部提供程序列表并使用其注入令牌作为查找键获取对任何提供程序的引用。

例如,假设我们有一个具有以下定义的 LazyModule

ts
@Module({
  providers: [LazyService],
  exports: [LazyService],
})
export class LazyModule {}
提示

延迟加载的模块不能注册为全局模块,因为这根本没有意义(因为它们是延迟注册的,当所有静态注册的模块都已实例化时,按需注册)。同样,注册的全局增强器(保护器/拦截器/等)也无法正常工作

这样,我们可以获得对LazyService提供程序的引用,如下所示:

ts
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并添加compilerOptions.moduleResolution属性,并将node作为值:

json
{
  "compilerOptions": {
    "module": "esnext",
    "moduleResolution": "node"
  }
}

设置这些选项后,您将能够利用 代码拆分 功能。

延迟加载控制器、网关和解析器

由于 Nest 中的控制器(或 GraphQL 应用程序中的解析器)代表路由/路径/主题(或查询/变异)的集合,因此您无法使用 LazyModuleLoader 类延迟加载它们

警告

在延迟加载模块内注册的控制器、解析器网关 将不会按预期运行。同样,您无法按需注册中间件函数(通过实现 MiddlewareConsumer 接口)。

例如,假设您正在构建一个 REST API(HTTP 应用程序),并在底层使用 Fastify 驱动程序(使用 @nestjs/platform-fastify 包)。在应用程序准备就绪/成功监听消息后,Fastify 不允许您注册路由。这意味着,即使我们分析了模块控制器中注册的路由映射,所有延迟加载的路由也无法访问,因为无法在运行时注册它们。

同样,我们作为 @nestjs/microservices 包的一部分提供的一些传输策略(包括 Kafka、gRPC 或 RabbitMQ)需要在建立连接之前订阅/监听特定主题/频道。一旦您的应用程序开始监听消息,框架将无法订阅/监听新主题。

最后,启用代码优先方法的 @nestjs/graphql 包会根据元数据自动动态生成 GraphQL 模式。这意味着,它需要事先加载所有类。否则,就无法创建适当、有效的模式。

常见用例

最常见的是,当您的工作程序/cron 作业/lambda 和无服务器函数/webhook 必须根据输入参数(路由路径/日期/查询参数等)触发不同的服务(不同的逻辑)时,您会看到延迟加载模块。另一方面,对于启动时间无关紧要的单片应用程序来说,延迟加载模块可能没有太大意义。