Das CLI-Plugin

Die empfohlene Lösung für webpack: true, automatisch, bei jedem Build, ohne separaten Schritt, den man sich merken muss.

Das ist derselbe Mechanismus, den auch das eigene CLI-Plugin von @nestjs/swagger nutzt, um unter webpack: true zu funktionieren. Deshalb ist @ApiProperty() selbst in einem Webpack-Build nicht auf jeder DTO-Eigenschaft nötig. Die Nest-CLI speist einen TypeScript-Compiler-Plugin-Hook (compilerOptions.plugins) identisch sowohl in tsc als auch in den Webpack-Builder (ts-loader) ein.

Das Plugin registrieren

Registrier nestjs-docfy in nest-cli.json:

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.

Automatische Erkennung

Es ist leicht zu übersehen, das Plugin zu registrieren, bis ein Build ohne Dokumentation ausgeliefert wird, deshalb prüft jeder generate-Lauf schon nest-cli.json und gibt eine Warnung aus, wenn webpack: true ohne das Plugin gesetzt ist. Übergib --register-plugin, damit das für dich behoben wird, statt die Datei von Hand zu bearbeiten:

bash
npx nestjs-docfy generate --register-plugin

Das ist bewusst Opt-in, da manche Teams absichtlich bei patch-spec statt einem Compiler-Plugin bleiben – generate bearbeitet nest-cli.json also nie, außer du bittest es explizit darum. Es hängt an ein bestehendes compilerOptions.plugins-Array an (erkennt sowohl String- als auch Objektform-Einträge als bereits registriert) und respektiert --dry-run.

Wie es funktioniert

Das Plugin von nestjs-docfy schreibt keine Decorator-Syntax um und fasst den AST überhaupt nicht an. Bei jeder Kompilierung führt es exakt dieselbe statische Analyse erneut aus, die generate/check/patch-spec bereits durchführen (über ts-morph, gegen den Quellbaum, nicht das Bundle), und schreibt den resultierenden Patch nach docfy-metadata.json neben die Build-Ausgabe.

Danach, direkt nach SwaggerModule.createDocument(), liest applyDocfyMetadata() diese Datei und führt sie zusammen. Es ist kein require.cache im Spiel und kein Problem mit gebündelter Klassenidentität, weil nie die laufende App gebraucht wird, um etwas zu berechnen.

vs. patch-spec

Manual, CI-driven alternative

Möchtest du lieber kein Compiler-Plugin hinzufügen (zum Beispiel bei einer Build-Pipeline, die nicht die Nest-CLI ist, oder einer strikteren Policy dazu, was während der Kompilierung läuft), berechnet patch-spec denselben Patch von Hand gegen ein bereits gebautes Dokument. Das war die einzige Option, bevor es das Plugin gab, und bleibt für einmaliges Patchen nützlich.

SWC-Builder

@nestjs/cli behandelt den SWC-Builder anders. Es ruft before() unter "builder": "swc" nie auf, sondern nur einen ReadonlyVisitor, und das auch nur, wenn "typeCheck": true zusätzlich gesetzt ist (sonst überspringt SWC die Typprüfung komplett von sich aus). Dieses Plugin exportiert beide Hooks, es reicht also, diese Einstellung zu aktivieren:

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

Eine Sache, die man wissen sollte: Das Plugin auf diese Weise zu registrieren schreibt bei jedem Build eine metadata.ts-Datei neben deine Source-Wurzel. Das ist dasselbe Artefakt, das der eigene SWC-Support von @nestjs/swagger dort schon erzeugt, leer und harmlos, wenn jenes Plugin nicht auch registriert ist.

Lässt du typeCheck weg, wird das Plugin stattdessen einfach still. Der Build läuft weiterhin durch, aber es wird keine docfy-metadata.json geschrieben, und applyDocfyMetadata() hat nichts zum Zusammenführen. generate erkennt genau diese Kombination und warnt davor.

Nichts davon berührt die Runtime-Discovery von DocfyModule. Sie funktioniert unter SWC genauso wie unter tsc, weil SWC weiterhin eine Datei pro Modul kompiliert und require.cache auf dieselbe Weise gefüllt wird. Ist typeCheck keine Option, bringen dich @WithDocs() zusammen mit DocfyModule.forRoot() trotzdem ans Ziel, ganz ohne Plugin.