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 等。