TypeScript 编译流程及内部类简介
最近在做一个 ts 语言编译的项目,了解了一下 ts 库提供的一些 api,以及编译流程。ts 自身提供了强大的 api,可以帮助我们进行 ts 语言编译器的开发。
依赖预处理
进行编译前首先要遍历代码根据 import 找到哪些文件是需要被引入到整个编译流程中的。
对单个文件处理,生成抽象语法树节点
parser 会根据输入的源文件,生成抽象语法树节点。在 ts 编译过程中,一个文件的抽象语法树是使用 SourceFile
结构来定义的。
1 | interface SourceFile extends Declaration { |
SourceFile
本身是基础抽象语法树结构Node
的一个扩展,他在抽象语法树的基础上增加了额外的接口,可以用来获取源文件代码,源文件地址,identifier 列表等功能。
在 parser 解析生成语法树后,会有一个 binder 遍历语法树来生成 binds Symbols,每一个 name 都会生成一个 Symbol。同时 binder 也会对命名空间进行处理,确保每个 Symbol 都在正确的命名空间中被创建。
Symbol
表示一个有名字的声明。用来链接定义同一个实体的不同声明节点,比方说一个 class 和它同样名称的 namespace 会有同样的 Symbol。
1 | interface Symbol { |
生成 SourceFile 以及各节点对应的 Symbol,可以通过 createSourceFile
API来实现。
多文件合并处理
由于要进行类型分析,分开处理单个文件是不行的,需要将多个文件中的声明合并在一起。因此下一个步骤是在一个整体视角上将所有依赖文件合并分析,构建一个 Program。
Program
指一个编译单元。它是由多个SourceFile
和一些编译选项组成的。Program
是类型系统和代码生成的总入口。
可以使用 createProgram
API 来生成 Program
。由于 Program
由多个 SourceFile
组成,事实上,createProgram
也包含了依赖预处理、生成抽象语法树节点过程。我们只需要将入口 ts 代码以及编译选项传给 createProgram
就可以得到入口文件及其依赖文件(包括代码和一些声明文件)生成的 SourceFile
。
1 | const program = ts.createProgram([filePath], { |
类型分析
在生成 Program
实例后,可以调用 .getTypeChecker()
方法得到 TypeChecker
实例。
1 | const typeChecker = program.getTypeChecker(); |
TypeChecker
是 ts 类型系统的核心。它负责计算不同文件中 Symbols 的关系,判断Symbol
的类型,生成语法诊断(也就是语法错误)。
TypeChecker
做的第一件事情是分析不同文件间的 Symbol
,合并相同的 Symbol
,得到一个统一的 Symbol Table。在初始化后,TypeChecker
就可以分析得到 Program
的各种状态,例如:
- 这个 Node 对应的 Symbol 是什么?
- 这个 Symbol 的类型是什么?
- 在这部分 AST 中,哪些 Symbol 是可见的?
- 这个文件有哪些 error ?
TypeChecker
是懒惰式进行分析的,在调用时才会计算必要的内容,不会去尝试计算整个整体。
生成代码
通过 Program
实例可以创建 Emitter
,Emitter
用来生成 SourceFile
对应的输出,例如 .js、.jsx、.d.ts、.js.map 等。