アーキテクチャに関するメモ
貢献に興味がある場合は、コードベースの理解に役立つ一般的なアーキテクチャに関するメモを以下に示します。(特定の側面やトピックについて詳しく知りたい場合は、rushstack-websites ドキュメントモノレポで GitHib issue を作成してお知らせください。)
プロジェクトの構造
API Extractor のコードは、大まかな全体的な操作フローに配置できるサブシステムを反映したソースコードフォルダに分かれています。
src/cli - 処理を開始するコマンドラインインターフェース (CLI)
src/api - このフォルダには、
Extractor
やExtractorConfig
などのパブリック API が含まれています。CLI は、外部のコンシューマと同じ方法でこれらの API を呼び出します。特別な内部構造は使用しません。この段階で TypeScript コンパイラが設定され、以下で使用されるts.Program
オブジェクトが生成されます。src/collector -
Collector
は、以下の多くのステージを実行する中心的なオーケストレーターとして機能します。概念的には、すべての API 情報を、主にCollectorEntity
オブジェクトという zentrale Stelle に「収集」しています。このフォルダには、**api-extractor.json** の"messages"
テーブルに基づいてエラーと警告をルーティングするMessageRouter
クラスも含まれています。src/analyzer - TypeScript コンパイラの抽象構文木 (AST) をトラバースし、API Extractor で使用されるより高レベルの表現を生成するコアアナライザ。ここには4つの主要な技術があります
コンパイラの
ts.Symbol
およびts.Declaration
クラスをミラーリングするAstSymbol
およびAstDeclaration
クラス。違いは、ドキュメント Web サイトとその `api-extractor-model` 表現で「API アイテム」になる興味深いノード (例: クラス、列挙型、インターフェースなど) のサブセットに対してのみ `AstDeclaration` ノードが生成されることです。この縮約されたツリーは、すべての中間 `ts.Declaration` ノード (例: `extends` 句、`:` トークンなど) を省略します。AstSymbol.ts のコードコメントは、この非常に重要なデータ構造についてより詳細な情報を提供しています。TypeScript の
import
ステートメントのチェーンをトラバースし、中間シンボルエイリアスを除去して、.d.ts ロールアップで見られるようなフラット化されたビューを構築するExportAnalyzer
。問題は、コンパイラの API では、このトラバーサルが作業パッケージを離れるタイミング (例: `node_modules` フォルダまたはコンパイラのランタイムライブラリにホップするタイミング) を検出することが困難であることです。そのため、このファイルはインポート構文の種類ごとに特別な処理を行っています。`export * from` 構文は、群を抜いて最も複雑な形式です。認識する特定のノードタイプを除いて、その意味のほとんどを無視しながら TypeScript ソースコードを書き直すための、かなり平凡だがかなり効果的なユーティリティである
Span
クラス。API Extractor は、.d.ts ファイルを書き込むためにコンパイラのエミッターを使用しません。これは、これらの API が開始時に公開されていなかったためと、元の .d.ts 入力をより忠実に保持するためです。DtsRollupGenerator._modifySpan() 関数は、`Span` がどのように使用されるかの良い例です。AstReferenceResolver
: TSDoc 宣言参照が与えられると、これは `AstSymbolTable` をウォークして、それが参照するものを何でも見つけます。
src/enhancers -
Collector
がすべての API オブジェクトとそのメタデータを収集した後、`enhancers` と呼ばれる一連の追加の後処理ステージを実行します。現在のものは、`ValidationEnhancer` (いくつかの API 検証ルールを適用する) と `DocCommentEnhancer` (たとえば `@inheritDoc` 参照を展開するなど、TSDoc コメントを調整する) です。src/generators - このフォルダは、API Extractor の有名な3つの出力タイプを実装しています: `ApiReportGenerator`、`DtsRollupGenerator`、および `ApiModelGenerator`。
src/schemas - このフォルダには、`api-extractor init` テンプレートファイル、**api-extractor.json** の JSON スキーマ、および **api-extractor.json** 設定のデフォルト値を表す **api-extractor-defaults.json** が含まれています。
データフロー
API Extractor を理解する別の有用な方法は、宣言が各ステージでどのように変換されるかを調べることです。2つのオーバーロードを持つ単純な `function` 宣言を考えてみましょう
import { Report } from 'reporting-package';
/** Declaration 1 */
export declare function add(report: Report, amount: number): void;
/** Declaration 2 */
export declare function add(report: Report, title: string): void;
これがどのように処理されるかを以下に示します
**コンパイラステージ:** TypeScript コンパイラエンジンは、.d.ts ファイルを解析された構文を表す2つの `ts.Declaration` オブジェクト (各オーバーロードに1つ) に解析します。次に、コンパイラのアナライザは、関数の型を表す関連付けられた `ts.Symbol` を作成します。各 TypeScript 型は常に正確に1つのシンボルになり、この場合は2つの宣言 (2つのオーバーロード) が関連付けられます。また、このシンボルには多くの「エイリアス」が存在します。たとえば、`import { add } from "./math"` と記述すると、ここでの `add` という単語は、その宣言がその `import` ステートメントであるシンボルエイリアスになります。シンボルエイリアスのチェーンをたどると (おそらく多くのインポートとエクスポートを通して)、常に `add()` の元の実際の定義に対応する一意の「フォローされたシンボル」に到達します。
**アナライザステージ:** API Extractor は API エントリポイントから開始し、各エクスポートをフォローしてその「フォローされたシンボル」を見つけます。次に、`add()` の `AstSymbol` と2つの `AstDeclaration` を作成します。アナライザはまた、AST ツリーを上下にウォークしてコンテキストを入力します。たとえば、`AstSymbol` が `class` である場合、そのメンバーごとに子 `AstSymbol` を作成します。また、クラスが `namespace` に属している場合は、名前空間を表す親 `AstSymbol` が追加されます。
`import` ステートメントに従っている間、外部 NPM パッケージに到達すると、分析はそこで停止し、通常の `AstSymbol` ではなく `AstImport` を生成します。これは、API Extractor がパッケージの境界を理解しており、実際には各プロジェクトで個別に呼び出されるように設計されているためです。したがって、上記の例では、`Report` は `AstSymbol` ではなく `AstImport` になります。アナライザの全体的な仕事は、非常に詳細なコンパイラデータ構造を選別し、`AstSymbol` オブジェクトの簡略化されたツリーを生成することです。このアルゴリズムは API Extractor の最も複雑なステージであるため、分離して単一目的を維持するように努めています。
**コレクターステージ:** コレクターは、.d.ts ロールアップのトップレベルアイテムになるもののインベントリを作成します。これらを `CollectorEntity` オブジェクトと呼び、`add()` 関数に1つ、`Report` インポートに1つあります。したがって、`AstSymbol` と `AstImport` は `CollectorEntity` になることができます。ただし、`AstDeclaration` は `CollectorEntity` になることができず、`AstModule` (.d.ts ソースファイルのアナライザの表現) もできません。これを明確にするために、アナライザのオブジェクトは、`CollectorEntity` になることができる場合に限り、`AstEntity` 基底クラスから継承します。`CollectorEntity` は `AstEntity` をラップし、いくつかの追加のコレクターステージ情報を追加します
- エンティティが .d.ts ロールアップの `export` であるか、単なるローカル宣言であるか。
- .d.ts ロールアップのローカル名。ローカル宣言は、名前の競合を避けるために `DtsRollupGenerator._makeUniqueNames()` によって名前を変更する必要がある場合があるためです
- ローカル名とは異なる可能性のあるエクスポート名。例: `export { A as B, A as C }`。
エンハンサー段階: エンハンサーは主に、
DeclarationMetadata
、ApiItemMetadata
、およびSymbolMetadata
オブジェクトを操作します。これらのオブジェクトはAstSymbol
とAstDeclaration
に格納されますが、完全にコレクター段階によって所有されます。ApiReportGenerator と DtsRollupGenerator: これらのジェネレーターは基本的に
CollectorEntity
の項目を大きなテキストファイルにダンプするだけで、フォーマットが異なります。リリースの種類に応じて項目をトリミングする以外には、あまり処理を行いません。api-extractor-model 段階: **@microsoft/api-extractor-model** パッケージは完全に独立しており、上記の他の API Extractor の型には依存しません。これは、ポータブルな .api.json ファイル形式を定義します。
ApiItem
基底クラスから継承する独自の豊富な階層があります(実際にはミックスイン継承です):ApiClass
、ApiNamespace
、ApiParameter
など。この例では、add()
関数は、この表現ではApiFunction
項目になります。このモデルは、サードパーティが複雑なコンパイラのデータ構造を理解しなくてもドキュメントを簡単に生成できるように設計されています。したがって、ApiModelGenerator
はadd()
のCollectorEntity
を受け取り、.api.json にシリアライズされるApiFunction
に変換します。アナライザーは内部で
AstReferenceResolver
ヘルパーを使用して、TSDoc 宣言参照を検索し、ターゲットのAstDeclaration
を見つけたことを思い出してください。.api.json ファイルの場合、**@microsoft/api-extractor-model** は、ApiItem
ターゲットを検索する同様のModelReferenceResolver
ヘルパーを提供します。API Documenter 段階: 最後に、ここで最後の変換が行われます。これが最後です! :-) API Documenter が .api.json ファイルを読み込むと、それを直接 .md ファイルにレンダリングするわけではありません。最初に、
add()
の例関数のApiFunction
を TSDocDocNode
要素のツリーに変換します。通常、DocNode
はドキュメントコメントを表すために使用されます。しかし、それはたまたまリッチテキストを表すことができる完全な DOM のような構造になっています。add()
の TSDoc コメントはすでにこの種のリッチテキストであるため、API Documenter は巧妙にこの表現を再利用して Web ページ全体をモデル化します。この中間表現により、マークダウンエミッターをドキュメントエンジンから分離し、将来 HTML や React などの他の形式を出力することを容易にします。
要約すると、単純な add()
関数について、このパイプラインは多くの異なる表現を生成しました
- オーバーロード宣言の
AstDeclaration
- TypeScript 型の
AstSymbol
- .d.ts ファイルのエントリの
CollectorEntity
- シンボルと宣言に詳細情報を注釈するための
DeclarationMetadata
、ApiItemMetadata
、およびSymbolMetadata
- .api.json ファイルの
ApiFunction
- ドキュメント Web サイトの
DocNode
サブツリー