架构说明
如果您有兴趣贡献,这里有一些通用的架构说明,希望可以帮助您了解代码库。(顺便说一下,如果您想查看有关特定方面或主题的更多详细信息,请通过在 rushstack-websites 文档单体存储库中创建一个 GitHib 问题来告知我们。)
项目结构
API 提取器的代码被分成反映子系统的源代码文件夹,这些子系统可以按一个粗略的整体操作流程来排列。
src/cli - 用于启动操作的命令行界面 (CLI)
src/api - 此文件夹包含公共 API,例如
Extractor
和ExtractorConfig
。CLI 以外部使用者相同的方式调用这些 API;它不使用任何特殊的内部机制。TypeScript 编译器在此阶段进行配置,生成将在下面使用的ts.Program
对象。src/collector -
Collector
充当中央协调器,运行下面许多阶段。从概念上讲,它是在一个中央位置“收集”所有 API 信息,主要是CollectorEntity
对象。此文件夹还包含MessageRouter
类,该类根据 api-extractor.json 中的"messages"
表路由错误和警告。src/analyzer - 核心分析器,它遍历 TypeScript 编译器的抽象语法树 (AST) 并生成 API 提取器使用的更高级别的表示形式。这里有 4 个主要的技术部分
AstSymbol
和AstDeclaration
类,它们镜像编译器的ts.Symbol
和ts.Declaration
类。不同之处在于,仅针对将成为文档网站及其api-extractor-model
表示中的“API 项目”的特定节点子集(例如类、枚举、接口等)生成AstDeclaration
节点。此压缩树省略了所有中间ts.Declaration
节点(例如extends
子句、:
令牌等)。AstSymbol.ts 代码注释提供了有关此非常重要的数据结构的更多详细信息。ExportAnalyzer
,它遍历 TypeScriptimport
语句链,消除中间符号别名以构建在 .d.ts 卷积中看到的扁平化视图。问题是,编译器的 API 使检测何时此遍历离开工作包(例如跳入node_modules
文件夹或编译器的运行时库)变得很困难。这就是为什么此文件对每种导入语法都有特殊处理的原因。export * from
结构是迄今为止最复杂的形式。Span
类,它是一个相当简单但相当有效的实用程序,用于重写 TypeScript 源代码,同时忽略其大部分含义,除了我们识别的特定节点类型。API 提取器不使用编译器的发射器来写入 .d.ts 文件,部分原因是在我们开始时这些 API 不是公开的,而且因为它们更忠实地保留了原始的 .d.ts 输入。 DtsRollupGenerator._modifySpan() 函数很好地说明了如何使用Span
。AstReferenceResolver
:给定一个 TSDoc 声明引用,它会遍历AstSymbolTable
以找到它引用的任何内容。
src/enhancers - 在
Collector
收集完所有 API 对象及其元数据后,我们会运行一系列称为enhancers
的附加后处理阶段。当前阶段是ValidationEnhancer
(它应用一些 API 验证规则)和DocCommentEnhancer
,它会调整 TSDoc 注释,例如扩展@inheritDoc
引用。src/generators - 此文件夹实现了 API 提取器的著名 3 种输出类型:
ApiReportGenerator
、DtsRollupGenerator
和ApiModelGenerator
。src/schemas - 此文件夹包含
api-extractor init
模板文件、api-extractor.json 的 JSON 架构以及 api-extractor-defaults.json,它代表 api-extractor.json 设置的默认值。
数据流
理解 API 提取器的另一种有用方法是检查声明在被每个阶段转换时会发生什么。考虑一个具有两个重载的简单 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 文件解析为两个
ts.Declaration
对象(每个重载一个),它们代表解析的语法。编译器的分析器随后会生成一个关联的ts.Symbol
,它代表函数的类型。每个 TypeScript 类型始终只成为一个符号,在本例中,它与两个关联声明(两个重载)相关联。该符号还将有许多“别名”。例如,如果我们编写import { add } from "./math"
,此处的add
将成为一个符号别名,其声明是该import
语句。如果我们按照符号别名链(可能通过许多导入和导出),我们总是会到达与add()
的原始真实定义相对应的唯一“跟随符号”。分析器阶段:API 提取器从您的 API 入口点开始,并跟踪每个导出以找到其“跟随符号”。然后我们为
add()
生成一个AstSymbol
和两个AstDeclaration
。分析器还会遍历 AST 树以填写上下文。例如,如果AstSymbol
是一个class
,那么我们会为它的每个成员创建一个子AstSymbol
。如果该类属于一个namespace
,则会添加一个父AstSymbol
来代表该命名空间。在跟踪
import
语句时,如果我们到达一个外部 NPM 包,则分析会在那里停止并生成一个AstImport
而不是一个常规的AstSymbol
。这是因为 API 提取器了解包边界,并且实际上被设计为分别在每个项目上调用。因此,在上面的示例中,Report
将成为一个AstImport
而不是一个AstSymbol
。分析器的总体工作是筛选极其详细的编译器数据结构并生成一个简化的AstSymbol
对象树。此算法是 API 提取器中最复杂的阶段,因此我们尝试使其保持隔离和单一用途。收集器阶段:收集器构建了将最终成为 .d.ts 卷积中顶层项目的库存。我们称这些为
CollectorEntity
对象,对于我们的add()
函数有一个,对于Report
导入有一个。因此,AstSymbol
和AstImport
可以成为一个CollectorEntity
。但请注意,AstDeclaration
不能,AstModule
(分析器对 .d.ts 源文件的表示)也不能。为了保持一致性,如果分析器对象可以成为一个CollectorEntity
,那么它们就从AstEntity
基类继承。CollectorEntity
包装了AstEntity
并添加了一些额外的收集器阶段信息- 该实体是 .d.ts 卷积的
export
还是仅一个本地声明。 - .d.ts 卷积中的本地名称,因为本地声明可能需要由
DtsRollupGenerator._makeUniqueNames()
重命名以避免命名冲突 - 导出名称,可以与本地名称不同。例如:
export { A as B, A as C }
。
- 该实体是 .d.ts 卷积的
增强器阶段:增强器主要使用
DeclarationMetadata
、ApiItemMetadata
和SymbolMetadata
对象。这些对象存储在AstSymbol
和AstDeclaration
上,但它们完全由收集器阶段拥有。ApiReportGenerator 和 DtsRollupGenerator:这些生成器本质上只是将
CollectorEntity
项目转储到一个大型文本文件中,但格式不同。除了根据发布类型修剪项目之外,它们没有执行太多处理。api-extractor-model 阶段:@microsoft/api-extractor-model 包完全独立,不依赖于上面描述的任何其他 API 提取器类型。它定义了可移植的 .api.json 文件格式。它有自己的丰富层次结构,继承自
ApiItem
基类(实际上是混合继承):ApiClass
、ApiNamespace
、ApiParameter
等。在我们的示例中,add()
函数将成为此表示中的一个ApiFunction
项目。此模型旨在使第三方能够轻松生成文档,而无需了解棘手的编译器数据结构。因此,ApiModelGenerator
会获取我们的add()
的CollectorEntity
并将其转换为一个ApiFunction
,该函数将被序列化到 .api.json 中。回想一下,分析器在内部使用了
AstReferenceResolver
帮助程序来查找 TSDoc 声明引用并找到目标AstDeclaration
。对于 .api.json 文件,@microsoft/api-extractor-model 提供了类似的ModelReferenceResolver
帮助程序,它可以查找ApiItem
目标。API 文档生成器阶段: 好了,这里发生了最后一次转换。这是最后一个了!:-) 当 API 文档生成器加载 .api.json 文件时,它不会直接将其渲染成 .md 文件。首先,它会将我们
add()
示例函数的ApiFunction
转换为 TSDocDocNode
元素的树。通常DocNode
用于表示文档注释。但它恰好是一个完整的类似 DOM 的结构,可以表示富文本。由于add()
的 TSDoc 注释已经是这种类型的富文本,API 文档生成器巧妙地重用了这种表示来模拟整个网页。这种中间表示使 markdown 发射器能够与文档引擎分离,并且在将来可以轻松地输出其他格式,例如 HTML 或 React。
总而言之,对于简陋的 add()
函数,此管道生成了许多不同的表示
AstDeclaration
用于重载声明AstSymbol
用于 TypeScript 类型CollectorEntity
用于 .d.ts 文件中的条目DeclarationMetadata
、ApiItemMetadata
和SymbolMetadata
用于使用更多信息来注释符号和声明ApiFunction
用于 .api.json 文件DocNode
子树用于文档网站