一、Typescript 编译器核心
- 语法分析器(Parser):从一系列原文件开始, 根据语言的语法, 生成抽象语法树(AST)
- 联合器(Binder):使用一个
Symbol
将相同结构的声明联合在一起(例如:同一个接口或模块的不同声明,或拥有相同名字的函数和模块)。这能帮助类型系统
推导出这些具名的声明 - 类型解析器与检查器(Type resolver / Checker):解析每种类型的构造,检查读写语义并生成适当的诊断信息
- 生成器(Emitter):从输入文件(.ts和.d.ts)生成输出结果,结果可以是以下形式之一:JavaScript(.js),声明(.d.ts),或者是source maps(.js.map)
- 预处理器(Pre-processor):“编译上下文”指的是某个“程序(
program
)”里涉及到的所有文件。上下文的是通过检查所有从命令行上传入编译器的文件,按顺序,然后再加入这些文件的直接或间接引用的文件(import 和 /// <reference path=... />
引入的文件)创建的。根据引用图,你会发现它是一个有序的源文件列表,他们组成了整个程序(program
)。当解析 import 导入的的时候,会优先选择 .ts 文件而不是 .d.ts 文件,以确保处理的是最新的文件。编译器选择根据模块解析策略进行导入模块的解析。导入解析失败不会报error,因为可能已经声明了外部模块
二、数据结构
Node
: AST 的基本构建单元块。通常来说,Node
代表了语法中的非终端节点。与非终端节点相对的终端节点,比如标识符、字面量等,也在 AST 中SourceFile
:对应源文件的 AST 。SourceFile
本身是一个Node
,它额外提供了一些接口,用于访问包括原始文本、文件包含的引用、标识符列表,以及字符位置映射Program
:编译单元的所有SourceFile
和编译选项的集合。它是类型系统和代码生成的主要入口Symbol
:已命名的声明,由类型联合器所生成。它连接了 AST 中的声明节点和其他地方的同名声明实体。它是语义系统的基本构建单元块Type
: 它是语义系统的另一部分,它可以是具名的(如类、接口),也可以是匿名的(如对象字面量)Signature
: TS 语言中包含三种类型签名:函数调用签名、构造函数签名和索引签名
三、编译过程详解
3.1、预处理器(preprocessing)处理
预处理器负责根据待编译文件
计算参与编译的文件,生成源文件
列表,构成编译上下文
和 Program
3.1.1、待编译文件
默认为项目目录下所有的 .ts、.tsx、.d.ts 为待编译文件
注意:
1. 默认 node_modules 是通过 exclude 排除在外的
2. 如果指定 allowJs 为 true 则 .js
和 .jsx
也会包含在内
3. 影响待编译文件配置有:files
、 include
、 exclude
3.1.2、需计算的文件
根据待编译文件中如下方式引入的文件:
/// <reference path=... />
标签引入的依赖声明文件import
表达式引入的文件
注意:
当解析 import 导入的的时候,会优先选择 .ts/.tsx文件而不是 .d.ts 文件,以确保处理的是最新的文件
3.1.3、默认包含的文件
所有可见的 @types
目录下的所有文件
如:node_modules/@types
、./node_modules/@types/
等等
3.2、语法分析器(parser)处理
语法分析器将预处理器
得到的源文件列表
中的文件解析生成包含抽象语法树(AST)Node 的 SourceFile
对象
SourceFile
对象 = 源文件 AST
+ 额外信息
(如文件名及文件信息等)
3.3、联合器(Binder)处理
联合器遍历并处理语法分析器
生成的 AST
,并将 AST 中的声明结合放到一个 Symbol
中。
一个 Symbol
会对应到一个命名实体。 这里有个一微妙的差别,几个声明节点可能会是名字相同的实体。 也就是说,有时候不同的Node具有相同的Symbol,并且每个Symbol保持跟踪它的声明节点。 比如,一个名字相同的class和namespace可以合并,并且拥有相同的Symbol。 联合器也会处理作用域,以确保每个Symbol都在正确的封闭作用域里创建
然后通过 createSourceFile
API 生成带有 Symbol
的 SourceFile
SourceFile对象
= 源文件 AST
+ Symbol
+ 额外信息
(如文件名及文件信息等)
此时的 Symobl 仅表示单个文件的声明信息
3.4、类型解析器与检查器(Type resolver / Checker)处理
3.4.1、生成 Program
通过调用 createProgramAPI
来创建 Program
Program
= All SourceFile
+ CompilerOptions
3.4.2、生成 TypeChecker
进行处理
通过 Program
实例创建 TypeChecker
TypeChecker是TypeScript类型系统的核心,它负责计算出不同文件里的Symbols之间的关系,将Type赋值给Symbol,并生成任何语义Diagnostic(比如:error)
处理内容:
TypeChecker
合并不同的SourceFile
里的Symbol
到一个单独的视图,创建单一的Symbol
表(囊括所有文件的全局Symbol视图 )- 类型检查
Symbol 合并到一张表后,TypeChecker就可以解决关于这个程序的任何问题了。 这些“问题”可以是:
1. 这个Node的Symbol是什么?
2. 这个Symbol的Type是什么?
3. 在AST的某个部分里有哪些Symbol是可见的?
4. 某个函数声明的Signature都有哪些?
5. 针对某个文件应该报哪些错误?
TypeChecker计算所有东西都是“懒惰的”;为了回答一个问题它仅“解决”必要的信息。 TypeChecker仅会检测和这个问题有关的Node,Symbol或Type,不会检测额外的实体。
3.5、生成器(Emitter)处理
通过 Program 创建一个 Emitter
Emitter 将给定的 SourceFile 生成编译后文件(.js
,.jsx
,.d.ts
和.js.map
)