CLI 插件

针对 webpack: true 推荐的修复方式,在每次构建时自动生效,不需要额外记住一个单独的步骤。

这正是 @nestjs/swagger 自身 CLI 插件在 webpack: true 下工作所用的机制。这也是为什么即便在 webpack 构建下,也不需要在每个 DTO 属性上都加 @ApiProperty()。Nest CLI 会把一个 TypeScript 编译器插件钩子(compilerOptions.plugins)同时喂给 tsc 和 webpack(ts-loader)构建器,两者行为完全一致。

注册插件

nest-cli.json 中注册 nestjs-docfy

nest-cli.json
{
  "compilerOptions": {
    "webpack": true,
    "plugins": ["nestjs-docfy"]
  }
}
main.ts
import { applyDocfyMetadata } from 'nestjs-docfy';

const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, applyDocfyMetadata(document));
OptionTypeDefaultDescription
metadataPathstringdocfy-metadata.json next to the running entry fileWhere to read the plugin-generated metadata from.
strictbooleanfalseThrow instead of warning when the metadata file is missing or invalid.

自动检测

忘记注册插件很容易,往往要等到构建出去了却没有文档才被发现,所以每次运行 generate 都会检查 nest-cli.json,在设置了 webpack: true 却没配插件时打印警告。传入 --register-plugin,让它替你修好这个问题,而不用手动编辑文件:

bash
npx nestjs-docfy generate --register-plugin

这是刻意做成 opt-in 的,因为有些团队会特意选择继续用 patch-spec,而不是加一个编译器插件,所以除非你明确要求,否则 generate 从不会修改 nest-cli.json。它会追加到任何已有的 compilerOptions.plugins 数组中(能识别字符串形式和对象形式两种已注册条目),并遵守 --dry-run

工作原理

nestjs-docfy 的插件不会重写任何装饰器语法,也完全不碰 AST。每次编译时,它都会重新跑一遍和 generate/check/patch-spec 已经在做的完全相同的静态分析(通过 ts-morph,针对源代码树,而不是 bundle),并把结果写进构建输出旁边的 docfy-metadata.json

然后,在 SwaggerModule.createDocument() 之后立刻,applyDocfyMetadata() 读取这个文件并把内容合并进去。这里没有涉及 require.cache,也没有 bundle 内类身份不一致的问题,因为它从来不需要运行中的应用来计算任何东西。

与 patch-spec 的对比

Manual, CI-driven alternative

如果你不想加编译器插件(比如构建管线不是 Nest CLI,或者对编译期间能运行什么有更严格的策略),patch-spec 可以针对一份已经构建好的文档手动计算出完全相同的补丁。在插件出现之前,这是唯一的选择,现在对一次性打补丁来说依然有用。

SWC builder

@nestjs/cli 对 SWC builder 的处理方式不一样。在 "builder": "swc" 下它从不调用 before(),只调用 ReadonlyVisitor,而且只有同时设置了 "typeCheck": true 才会调用(否则 SWC 自己就跳过类型检查)。这个插件把两个 hook 都导出了,所以打开这个设置就够了:

nest-cli.json
{
  "compilerOptions": {
    "builder": "swc",
    "typeCheck": true,
    "plugins": ["nestjs-docfy"]
  }
}
Requires typeCheck: true

有一点值得知道:用这种方式注册插件,每次构建都会在你的 source 根目录旁边写一个 metadata.ts 文件。这和 @nestjs/swagger 自己的 SWC 支持在那里已经产生的是同一个产物,如果那个插件没有一起注册,它就是空的、无害的。

不设置 typeCheck,插件就会安静下来。构建仍然会成功,但不会写入任何 docfy-metadata.jsonapplyDocfyMetadata() 也没有东西可以合并。generate 会捕获正好这种组合并发出警告。

这些都不会影响 DocfyModule 的运行时发现机制。它在 SWC 下的表现和在 tsc 下完全一样,因为 SWC 仍然是一个模块编译成一个文件,require.cache 最终会以同样的方式被填充。如果设置 typeCheck 不是一个选项,@WithDocs() 加上 DocfyModule.forRoot() 不用插件也能让你达到同样效果。