架构说明
如果您有兴趣贡献,这里有一些通用的架构说明,希望可以帮助您了解代码库。(顺便说一下,如果您想查看有关特定方面或主题的更多详细信息,请通过在 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子树用于文档网站