从高层次上讲,TypeScript 编译器是一个帮助我们分析并将 TypeScript 代码编译成 JavaScript(.js)的工具,同时生成一些类型定义文件(.d.ts)或源映射文件(*.js.map)。
如果源文件中存在问题,TypeScript 编译器还能够提供诊断信息,以便我们了解问题所在并如何修复。
编译过程
在内部,这是一个涉及多个不同部分的复杂过程,以下是该过程的概要:
编译过程始于调用 tsc
命令。运行时,TypeScript 编译器需要一个 tsconfig.json
文件,该文件主要定义了两部分:编译选项(Compiler Options)和输入文件(Input Files)。
{
"files": [
"src/*.ts"
],
"compilerOptions": {
...
}
}
编译上下文将被创建为一个 Program
对象,定义在 src/compiler/program.ts
文件中。在创建时,它会加载所有输入文件及其导入项,并调用解析器(定义在 src/compiler/parser.ts
)将每个文件解析成抽象语法树(AST)。
在底层,解析器创建了一个扫描器实例(定义在
src/compiler/scanner.ts
),扫描源代码并生成一系列 SyntaxKind
令牌。
解析过程没有在此停止,接着 AST 会被传递给绑定器(定义在 src/compiler/binder.ts
),以创建 AST 节点和符号之间的映射。
符号是用于存储每个节点类型信息的附加元数据。绑定器创建了一个符号表,在后续的类型检查阶段将被使用。
之后,通过 Program.emit
调用,将创建一个 Emit Worker
,它负责将 AST 转换为 JavaScript 源代码字符串及其他内容。存在两种类型的发射器:
- JavaScript 发射器:定义在
src/compiler/emitter.ts
,负责发射 JavaScript 源代码和源映射。 - 类型定义发射器:定义在
src/compiler/definitionEmitter.ts
,负责发射类型定义文件。
在发射器运行时,它将调用 getDiagnostics()
函数来创建一个类型检查器(定义在 src/compiler/checker.ts
)。然后,发射器会遍历 AST 来处理每个节点。
对于每个节点,它会使用符号表中的类型数据进行代码分析,如果一切正常,将生成最终的 JavaScript 源代码。
错误报告
在编译过程中,根据编译器发现错误的阶段,不同类型的错误可能会被返回。
enum BuildResultFlags {
None = 0,
Success = 1 << 0,
DeclarationOutputUnchanged = 1 << 1,
ConfigFileErrors = 1 << 2,
SyntaxErrors = 1 << 3,
TypeErrors = 1 << 4,
DeclarationEmitErrors = 1 << 5,
EmitErrors = 1 << 6,
AnyErrors = ConfigFileErrors | SyntaxErrors | TypeErrors | DeclarationEmitErrors | EmitErrors
}
例如,如果 tsconfig.json
文件中有错误,将返回 ConfigFileErrors
。
如果扫描器发现错误,则为 SyntaxErrors
。有时,代码语法正确但语义不正确,这通常是 TypeErrors
,可以通过解析器或类型检查器捕获。例如:
let a: number = "hello";
该代码语法正确,但语义不正确,因为不能将字符串值赋给数字变量。
总结
本文仅介绍了编译过程的概述以及各部分之间的关系,通过这些,你可以深入探索 TypeScript 源代码,了解具体实现方式。
建议阅读《TypeScript 编译器内部结构》文档,以获得比本文更深入的理解(包括各部分的代码以及它们的相互调用方式)。